diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx
index 7b55cb60..2624514a 100644
--- a/apps/ui/src/components/views/board-view.tsx
+++ b/apps/ui/src/components/views/board-view.tsx
@@ -636,10 +636,8 @@ export function BoardView() {
const result = await api.features.bulkUpdate(currentProject.path, featureIds, finalUpdates);
if (result.success) {
- // Update local state
- featureIds.forEach((featureId) => {
- updateFeature(featureId, finalUpdates);
- });
+ // Invalidate React Query cache to refetch features with server-updated values
+ loadFeatures();
toast.success(`Updated ${result.updatedCount} features`);
exitSelectionMode();
} else {
@@ -655,7 +653,7 @@ export function BoardView() {
[
currentProject,
selectedFeatureIds,
- updateFeature,
+ loadFeatures,
exitSelectionMode,
getPrimaryWorktreeBranch,
addAndSelectWorktree,
@@ -783,10 +781,8 @@ export function BoardView() {
const result = await api.features.bulkUpdate(currentProject.path, featureIds, updates);
if (result.success) {
- // Update local state for all features
- featureIds.forEach((featureId) => {
- updateFeature(featureId, updates);
- });
+ // Invalidate React Query cache to refetch features with server-updated values
+ loadFeatures();
toast.success(`Verified ${result.updatedCount} features`);
exitSelectionMode();
} else {
@@ -798,7 +794,7 @@ export function BoardView() {
logger.error('Bulk verify failed:', error);
toast.error('Failed to verify features');
}
- }, [currentProject, selectedFeatureIds, updateFeature, exitSelectionMode]);
+ }, [currentProject, selectedFeatureIds, loadFeatures, exitSelectionMode]);
// Handler for addressing PR comments - creates a feature and starts it automatically
const handleAddressPRComments = useCallback(
diff --git a/apps/ui/src/components/views/board-view/dialogs/mass-edit-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/mass-edit-dialog.tsx
index f98908f9..99612433 100644
--- a/apps/ui/src/components/views/board-view/dialogs/mass-edit-dialog.tsx
+++ b/apps/ui/src/components/views/board-view/dialogs/mass-edit-dialog.tsx
@@ -128,6 +128,7 @@ export function MassEditDialog({
// Field values
const [model, setModel] = useState('claude-sonnet');
const [thinkingLevel, setThinkingLevel] = useState('none');
+ const [providerId, setProviderId] = useState(undefined);
const [planningMode, setPlanningMode] = useState('skip');
const [requirePlanApproval, setRequirePlanApproval] = useState(false);
const [priority, setPriority] = useState(2);
@@ -162,6 +163,7 @@ export function MassEditDialog({
});
setModel(getInitialValue(selectedFeatures, 'model', 'claude-sonnet') as ModelAlias);
setThinkingLevel(getInitialValue(selectedFeatures, 'thinkingLevel', 'none') as ThinkingLevel);
+ setProviderId(undefined); // Features don't store providerId, but we track it after selection
setPlanningMode(getInitialValue(selectedFeatures, 'planningMode', 'skip') as PlanningMode);
setRequirePlanApproval(getInitialValue(selectedFeatures, 'requirePlanApproval', false));
setPriority(getInitialValue(selectedFeatures, 'priority', 2));
@@ -226,10 +228,11 @@ export function MassEditDialog({
Select a specific model configuration
{
setModel(entry.model as ModelAlias);
setThinkingLevel(entry.thinkingLevel || 'none');
+ setProviderId(entry.providerId);
// Auto-enable model and thinking level for apply state
setApplyState((prev) => ({
...prev,
diff --git a/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx b/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
index 0a7fcd70..364d435f 100644
--- a/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
+++ b/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
@@ -415,6 +415,44 @@ export function PhaseModelSelector({
}
}
+ // Fallback: Check ClaudeCompatibleProvider models by model ID only (when providerId is not set)
+ // This handles cases where features store model ID but not providerId
+ for (const provider of enabledProviders) {
+ const providerModel = provider.models?.find((m) => m.id === selectedModel);
+ if (providerModel) {
+ // Count providers of same type to determine if we need provider name suffix
+ const sameTypeCount = enabledProviders.filter(
+ (p) => p.providerType === provider.providerType
+ ).length;
+ const suffix = sameTypeCount > 1 ? ` (${provider.name})` : '';
+ // Add thinking level to label if not 'none'
+ const thinkingLabel =
+ selectedThinkingLevel !== 'none'
+ ? ` (${THINKING_LEVEL_LABELS[selectedThinkingLevel]} Thinking)`
+ : '';
+ // Get icon based on provider type
+ const getIconForProviderType = () => {
+ switch (provider.providerType) {
+ case 'glm':
+ return GlmIcon;
+ case 'minimax':
+ return MiniMaxIcon;
+ case 'openrouter':
+ return OpenRouterIcon;
+ default:
+ return getProviderIconForModel(providerModel.id) || OpenRouterIcon;
+ }
+ };
+ return {
+ id: selectedModel,
+ label: `${providerModel.displayName}${suffix}${thinkingLabel}`,
+ description: provider.name,
+ provider: 'claude-compatible' as const,
+ icon: getIconForProviderType(),
+ };
+ }
+ }
+
return null;
}, [
selectedModel,