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,