mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-18 22:33:08 +00:00
Fix orphaned features when deleting worktrees (#820)
* Changes from fix/orphaned-features * fix: Handle feature migration failures and improve UI accessibility * feat: Add event emission for worktree deletion and feature migration * fix: Handle OpenCode model errors and prevent duplicate model IDs * feat: Add summary dialog and async verify with loading state * fix: Add type attributes to buttons and improve OpenCode model selection * fix: Add null checks for onVerify callback and opencode model selection
This commit is contained in:
@@ -311,6 +311,7 @@ const initialState: AppState = {
|
||||
keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS,
|
||||
muteDoneSound: false,
|
||||
disableSplashScreen: false,
|
||||
defaultSortNewestCardOnTop: false,
|
||||
serverLogLevel: 'info',
|
||||
enableRequestLogging: true,
|
||||
showQueryDevtools: true,
|
||||
@@ -333,6 +334,7 @@ const initialState: AppState = {
|
||||
opencodeDefaultModel: DEFAULT_OPENCODE_MODEL,
|
||||
dynamicOpencodeModels: [],
|
||||
enabledDynamicModelIds: [],
|
||||
knownDynamicModelIds: [],
|
||||
cachedOpencodeProviders: [],
|
||||
opencodeModelsLoading: false,
|
||||
opencodeModelsError: null,
|
||||
@@ -1233,6 +1235,9 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
// Splash Screen actions
|
||||
setDisableSplashScreen: (disabled) => set({ disableSplashScreen: disabled }),
|
||||
|
||||
// Board Card Sorting (global default) actions
|
||||
setDefaultSortNewestCardOnTop: (enabled) => set({ defaultSortNewestCardOnTop: enabled }),
|
||||
|
||||
// Server Log Level actions
|
||||
setServerLogLevel: (level) => set({ serverLogLevel: level }),
|
||||
setEnableRequestLogging: (enabled) => set({ enableRequestLogging: enabled }),
|
||||
@@ -1355,21 +1360,52 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
|
||||
// OpenCode CLI Settings actions
|
||||
setEnabledOpencodeModels: (models) => set({ enabledOpencodeModels: models }),
|
||||
setOpencodeDefaultModel: (model) => set({ opencodeDefaultModel: model }),
|
||||
toggleOpencodeModel: (model, enabled) =>
|
||||
setOpencodeDefaultModel: async (model) => {
|
||||
set({ opencodeDefaultModel: model });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.settings.updateGlobal({ opencodeDefaultModel: model });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync opencodeDefaultModel:', error);
|
||||
}
|
||||
},
|
||||
toggleOpencodeModel: async (model, enabled) => {
|
||||
set((state) => ({
|
||||
enabledOpencodeModels: enabled
|
||||
? [...state.enabledOpencodeModels, model]
|
||||
? [...new Set([...state.enabledOpencodeModels, model])]
|
||||
: state.enabledOpencodeModels.filter((m) => m !== model),
|
||||
})),
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.settings.updateGlobal({ enabledOpencodeModels: get().enabledOpencodeModels });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync enabledOpencodeModels:', error);
|
||||
}
|
||||
},
|
||||
setDynamicOpencodeModels: (models) => set({ dynamicOpencodeModels: models }),
|
||||
setEnabledDynamicModelIds: (ids) => set({ enabledDynamicModelIds: ids }),
|
||||
toggleDynamicModel: (modelId, enabled) =>
|
||||
setEnabledDynamicModelIds: async (ids) => {
|
||||
const deduped = Array.from(new Set(ids));
|
||||
set({ enabledDynamicModelIds: deduped });
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.settings.updateGlobal({ enabledDynamicModelIds: deduped });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync enabledDynamicModelIds:', error);
|
||||
}
|
||||
},
|
||||
toggleDynamicModel: async (modelId, enabled) => {
|
||||
set((state) => ({
|
||||
enabledDynamicModelIds: enabled
|
||||
? [...state.enabledDynamicModelIds, modelId]
|
||||
? [...new Set([...state.enabledDynamicModelIds, modelId])]
|
||||
: state.enabledDynamicModelIds.filter((id) => id !== modelId),
|
||||
})),
|
||||
}));
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.settings.updateGlobal({ enabledDynamicModelIds: get().enabledDynamicModelIds });
|
||||
} catch (error) {
|
||||
logger.error('Failed to sync enabledDynamicModelIds:', error);
|
||||
}
|
||||
},
|
||||
setCachedOpencodeProviders: (providers) => set({ cachedOpencodeProviders: providers }),
|
||||
|
||||
// Gemini CLI Settings actions
|
||||
@@ -2877,13 +2913,43 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
(m) => !m.id.startsWith(OPENCODE_BEDROCK_MODEL_PREFIX)
|
||||
);
|
||||
|
||||
// Auto-enable only models that are genuinely new (never seen before).
|
||||
// Models that existed previously and were explicitly deselected by the user
|
||||
// should NOT be re-enabled on subsequent fetches.
|
||||
const currentEnabledIds = get().enabledDynamicModelIds;
|
||||
const currentKnownIds = get().knownDynamicModelIds;
|
||||
const allFetchedIds = filteredModels.map((m) => m.id);
|
||||
// Only auto-enable models that have NEVER been seen before (not in knownDynamicModelIds)
|
||||
const trulyNewModelIds = allFetchedIds.filter((id) => !currentKnownIds.includes(id));
|
||||
const updatedEnabledIds =
|
||||
trulyNewModelIds.length > 0
|
||||
? [...new Set([...currentEnabledIds, ...trulyNewModelIds])]
|
||||
: currentEnabledIds;
|
||||
// Track all discovered model IDs (union of known + newly fetched)
|
||||
const updatedKnownIds = [...new Set([...currentKnownIds, ...allFetchedIds])];
|
||||
|
||||
set({
|
||||
dynamicOpencodeModels: filteredModels,
|
||||
enabledDynamicModelIds: updatedEnabledIds,
|
||||
knownDynamicModelIds: updatedKnownIds,
|
||||
cachedOpencodeProviders: data.providers ?? [],
|
||||
opencodeModelsLoading: false,
|
||||
opencodeModelsLastFetched: now,
|
||||
opencodeModelsError: null,
|
||||
});
|
||||
|
||||
// Persist newly enabled model IDs and known model IDs to server settings
|
||||
if (trulyNewModelIds.length > 0) {
|
||||
try {
|
||||
const httpApi = getHttpApiClient();
|
||||
await httpApi.settings.updateGlobal({
|
||||
enabledDynamicModelIds: updatedEnabledIds,
|
||||
knownDynamicModelIds: updatedKnownIds,
|
||||
});
|
||||
} catch (syncError) {
|
||||
logger.error('Failed to sync enabledDynamicModelIds after auto-enable:', syncError);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
set({
|
||||
opencodeModelsLoading: false,
|
||||
|
||||
@@ -168,6 +168,9 @@ export interface AppState {
|
||||
// Splash Screen Settings
|
||||
disableSplashScreen: boolean; // When true, skip showing the splash screen overlay on startup
|
||||
|
||||
// Board Card Sorting (global default)
|
||||
defaultSortNewestCardOnTop: boolean; // Global default: sort latest card on top in board columns and list view
|
||||
|
||||
// Server Log Level Settings
|
||||
serverLogLevel: ServerLogLevel; // Log level for the API server (error, warn, info, debug)
|
||||
enableRequestLogging: boolean; // Enable HTTP request logging (Morgan)
|
||||
@@ -215,6 +218,7 @@ export interface AppState {
|
||||
// from `opencode models` CLI and depend on current provider authentication state
|
||||
dynamicOpencodeModels: ModelDefinition[]; // Dynamically discovered models from OpenCode CLI
|
||||
enabledDynamicModelIds: string[]; // Which dynamic models are enabled
|
||||
knownDynamicModelIds: string[]; // All dynamic model IDs ever seen (used to avoid re-enabling explicitly deselected models)
|
||||
cachedOpencodeProviders: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -574,6 +578,9 @@ export interface AppActions {
|
||||
// Splash Screen actions
|
||||
setDisableSplashScreen: (disabled: boolean) => void;
|
||||
|
||||
// Board Card Sorting (global default) actions
|
||||
setDefaultSortNewestCardOnTop: (enabled: boolean) => void;
|
||||
|
||||
// Server Log Level actions
|
||||
setServerLogLevel: (level: ServerLogLevel) => void;
|
||||
setEnableRequestLogging: (enabled: boolean) => void;
|
||||
@@ -616,12 +623,16 @@ export interface AppActions {
|
||||
setCodexEnableImages: (enabled: boolean) => Promise<void>;
|
||||
|
||||
// OpenCode CLI Settings actions
|
||||
// Note: setOpencodeDefaultModel, toggleOpencodeModel, setEnabledDynamicModelIds, and
|
||||
// toggleDynamicModel return Promise<void> because they persist state to the server.
|
||||
// TODO: harmonize other provider action types (e.g., setCursorDefaultModel, toggleCursorModel,
|
||||
// setGeminiDefaultModel) to also return Promise<void> for consistent async persistence.
|
||||
setEnabledOpencodeModels: (models: OpencodeModelId[]) => void;
|
||||
setOpencodeDefaultModel: (model: OpencodeModelId) => void;
|
||||
toggleOpencodeModel: (model: OpencodeModelId, enabled: boolean) => void;
|
||||
setOpencodeDefaultModel: (model: OpencodeModelId) => Promise<void>;
|
||||
toggleOpencodeModel: (model: OpencodeModelId, enabled: boolean) => Promise<void>;
|
||||
setDynamicOpencodeModels: (models: ModelDefinition[]) => void;
|
||||
setEnabledDynamicModelIds: (ids: string[]) => void;
|
||||
toggleDynamicModel: (modelId: string, enabled: boolean) => void;
|
||||
setEnabledDynamicModelIds: (ids: string[]) => Promise<void>;
|
||||
toggleDynamicModel: (modelId: string, enabled: boolean) => Promise<void>;
|
||||
setCachedOpencodeProviders: (
|
||||
providers: Array<{ id: string; name: string; authenticated: boolean; authMethod?: string }>
|
||||
) => void;
|
||||
|
||||
Reference in New Issue
Block a user