feat: Add settingsService integration for feature defaults and improve worktree handling

This commit is contained in:
gsxdsm
2026-03-02 03:28:37 -08:00
parent 34161ccc08
commit 33a2e04bf0
6 changed files with 105 additions and 32 deletions

View File

@@ -833,18 +833,11 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
)
)
: current.agentModelBySession,
// Sanitize currentWorktreeByProject: only restore entries where path is null
// (main branch). Non-null paths point to worktree directories that may have
// been deleted while the app was closed. Restoring a stale path causes the
// board to render an invalid worktree selection, triggering a crash loop
// (error boundary reloads → restores same bad path → crash again).
// The use-worktrees validation effect will re-discover valid worktrees
// from the server once they load.
currentWorktreeByProject: Object.fromEntries(
Object.entries(sanitizeWorktreeByProject(settings.currentWorktreeByProject)).filter(
([, worktree]) => worktree.path === null
)
),
// Restore all valid worktree selections (both main branch and feature worktrees).
// The validation effect in use-worktrees.ts handles deleted worktrees gracefully
// by resetting to main branch when the worktree list loads and the cached
// worktree no longer exists.
currentWorktreeByProject: sanitizeWorktreeByProject(settings.currentWorktreeByProject),
// UI State
worktreePanelCollapsed: settings.worktreePanelCollapsed ?? false,
lastProjectDir: settings.lastProjectDir ?? '',

View File

@@ -864,8 +864,8 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
)
)
: currentAppState.agentModelBySession,
// Sanitize: only restore entries with path === null (main branch).
// Non-null paths may reference deleted worktrees, causing crash loops.
// Restore all valid worktree selections (both main branch and feature worktrees).
// The validation effect in use-worktrees.ts handles deleted worktrees gracefully.
currentWorktreeByProject: sanitizeWorktreeByProject(
serverSettings.currentWorktreeByProject ?? currentAppState.currentWorktreeByProject
),

View File

@@ -2512,9 +2512,33 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
setSpecCreatingForProject: (projectPath) => set({ specCreatingForProject: projectPath }),
isSpecCreatingForProject: (projectPath) => get().specCreatingForProject === projectPath,
setDefaultPlanningMode: (mode) => set({ defaultPlanningMode: mode }),
setDefaultRequirePlanApproval: (require) => set({ defaultRequirePlanApproval: require }),
setDefaultFeatureModel: (entry) => set({ defaultFeatureModel: entry }),
setDefaultPlanningMode: async (mode) => {
set({ defaultPlanningMode: mode });
try {
const httpApi = getHttpApiClient();
await httpApi.settings.updateGlobal({ defaultPlanningMode: mode });
} catch (error) {
logger.error('Failed to sync defaultPlanningMode:', error);
}
},
setDefaultRequirePlanApproval: async (require) => {
set({ defaultRequirePlanApproval: require });
try {
const httpApi = getHttpApiClient();
await httpApi.settings.updateGlobal({ defaultRequirePlanApproval: require });
} catch (error) {
logger.error('Failed to sync defaultRequirePlanApproval:', error);
}
},
setDefaultFeatureModel: async (entry) => {
set({ defaultFeatureModel: entry });
try {
const httpApi = getHttpApiClient();
await httpApi.settings.updateGlobal({ defaultFeatureModel: entry });
} catch (error) {
logger.error('Failed to sync defaultFeatureModel:', error);
}
},
setDefaultThinkingLevel: async (level) => {
const currentModel = get().defaultFeatureModel;
@@ -2523,20 +2547,30 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
// Also update defaultFeatureModel's thinkingLevel if compatible
if (availableLevels.includes(level)) {
const updatedFeatureModel = { ...currentModel, thinkingLevel: level };
set({
defaultThinkingLevel: level,
defaultFeatureModel: { ...currentModel, thinkingLevel: level },
defaultFeatureModel: updatedFeatureModel,
});
// Sync to server - include defaultFeatureModel since thinkingLevel is embedded there too
try {
const httpApi = getHttpApiClient();
await httpApi.settings.updateGlobal({
defaultThinkingLevel: level,
defaultFeatureModel: updatedFeatureModel,
});
} catch (error) {
logger.error('Failed to sync defaultThinkingLevel:', error);
}
} else {
set({ defaultThinkingLevel: level });
}
// Sync to server
try {
const httpApi = getHttpApiClient();
await httpApi.settings.updateGlobal({ defaultThinkingLevel: level });
} catch (error) {
logger.error('Failed to sync defaultThinkingLevel:', error);
// Sync to server
try {
const httpApi = getHttpApiClient();
await httpApi.settings.updateGlobal({ defaultThinkingLevel: level });
} catch (error) {
logger.error('Failed to sync defaultThinkingLevel:', error);
}
}
},