splash screen configurable in global settings

This commit is contained in:
webdevcody
2026-01-23 12:55:01 -05:00
parent 0b92349890
commit bc3e3dad1c
7 changed files with 211 additions and 94 deletions

View File

@@ -233,6 +233,7 @@ interface RunningFeature {
abortController: AbortController; abortController: AbortController;
isAutoMode: boolean; isAutoMode: boolean;
startTime: number; startTime: number;
leaseCount: number;
model?: string; model?: string;
provider?: ModelProvider; provider?: ModelProvider;
} }
@@ -334,6 +335,54 @@ export class AutoModeService {
this.settingsService = settingsService ?? null; this.settingsService = settingsService ?? null;
} }
private acquireRunningFeature(params: {
featureId: string;
projectPath: string;
isAutoMode: boolean;
allowReuse?: boolean;
abortController?: AbortController;
}): RunningFeature {
const existing = this.runningFeatures.get(params.featureId);
if (existing) {
if (!params.allowReuse) {
throw new Error('already running');
}
existing.leaseCount = (existing.leaseCount ?? 1) + 1;
return existing;
}
const abortController = params.abortController ?? new AbortController();
const entry: RunningFeature = {
featureId: params.featureId,
projectPath: params.projectPath,
worktreePath: null,
branchName: null,
abortController,
isAutoMode: params.isAutoMode,
startTime: Date.now(),
leaseCount: 1,
};
this.runningFeatures.set(params.featureId, entry);
return entry;
}
private releaseRunningFeature(featureId: string, options?: { force?: boolean }): void {
const entry = this.runningFeatures.get(featureId);
if (!entry) {
return;
}
if (options?.force) {
this.runningFeatures.delete(featureId);
return;
}
entry.leaseCount = (entry.leaseCount ?? 1) - 1;
if (entry.leaseCount <= 0) {
this.runningFeatures.delete(featureId);
}
}
/** /**
* Track a failure and check if we should pause due to consecutive failures. * Track a failure and check if we should pause due to consecutive failures.
* This handles cases where the SDK doesn't return useful error messages. * This handles cases where the SDK doesn't return useful error messages.
@@ -1076,24 +1125,17 @@ export class AutoModeService {
providedWorktreePath?: string, providedWorktreePath?: string,
options?: { options?: {
continuationPrompt?: string; continuationPrompt?: string;
/** Internal flag: set to true when called from a method that already tracks the feature */
_calledInternally?: boolean;
} }
): Promise<void> { ): Promise<void> {
if (this.runningFeatures.has(featureId)) { const tempRunningFeature = this.acquireRunningFeature({
throw new Error('already running');
}
// Add to running features immediately to prevent race conditions
const abortController = new AbortController();
const tempRunningFeature: RunningFeature = {
featureId, featureId,
projectPath, projectPath,
worktreePath: null,
branchName: null,
abortController,
isAutoMode, isAutoMode,
startTime: Date.now(), allowReuse: options?._calledInternally,
}; });
this.runningFeatures.set(featureId, tempRunningFeature); const abortController = tempRunningFeature.abortController;
// Save execution state when feature starts // Save execution state when feature starts
if (isAutoMode) { if (isAutoMode) {
@@ -1130,9 +1172,8 @@ export class AutoModeService {
continuationPrompt = continuationPrompt.replace(/\{\{approvedPlan\}\}/g, planContent); continuationPrompt = continuationPrompt.replace(/\{\{approvedPlan\}\}/g, planContent);
// Recursively call executeFeature with the continuation prompt // Recursively call executeFeature with the continuation prompt
// Remove from running features temporarily, it will be added back // Feature is already tracked, the recursive call will reuse the entry
this.runningFeatures.delete(featureId); return await this.executeFeature(
return this.executeFeature(
projectPath, projectPath,
featureId, featureId,
useWorktrees, useWorktrees,
@@ -1140,6 +1181,7 @@ export class AutoModeService {
providedWorktreePath, providedWorktreePath,
{ {
continuationPrompt, continuationPrompt,
_calledInternally: true,
} }
); );
} }
@@ -1149,9 +1191,8 @@ export class AutoModeService {
logger.info( logger.info(
`Feature ${featureId} has existing context, resuming instead of starting fresh` `Feature ${featureId} has existing context, resuming instead of starting fresh`
); );
// Remove from running features temporarily, resumeFeature will add it back // Feature is already tracked, resumeFeature will reuse the entry
this.runningFeatures.delete(featureId); return await this.resumeFeature(projectPath, featureId, useWorktrees, true);
return this.resumeFeature(projectPath, featureId, useWorktrees);
} }
} }
@@ -1401,7 +1442,7 @@ export class AutoModeService {
logger.info( logger.info(
`Pending approvals at cleanup: ${Array.from(this.pendingApprovals.keys()).join(', ') || 'none'}` `Pending approvals at cleanup: ${Array.from(this.pendingApprovals.keys()).join(', ') || 'none'}`
); );
this.runningFeatures.delete(featureId); this.releaseRunningFeature(featureId);
// Update execution state after feature completes // Update execution state after feature completes
if (this.autoLoopRunning && projectPath) { if (this.autoLoopRunning && projectPath) {
@@ -1581,7 +1622,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
// Remove from running features immediately to allow resume // Remove from running features immediately to allow resume
// The abort signal will still propagate to stop any ongoing execution // The abort signal will still propagate to stop any ongoing execution
this.runningFeatures.delete(featureId); this.releaseRunningFeature(featureId, { force: true });
return true; return true;
} }
@@ -1589,50 +1630,67 @@ Complete the pipeline step instructions above. Review the previous work and appl
/** /**
* Resume a feature (continues from saved context) * Resume a feature (continues from saved context)
*/ */
async resumeFeature(projectPath: string, featureId: string, useWorktrees = false): Promise<void> { async resumeFeature(
if (this.runningFeatures.has(featureId)) { projectPath: string,
throw new Error('already running'); featureId: string,
} useWorktrees = false,
/** Internal flag: set to true when called from a method that already tracks the feature */
// Load feature to check status _calledInternally = false
const feature = await this.loadFeature(projectPath, featureId); ): Promise<void> {
if (!feature) { this.acquireRunningFeature({
throw new Error(`Feature ${featureId} not found`);
}
// Check if feature is stuck in a pipeline step
const pipelineInfo = await this.detectPipelineStatus(
projectPath,
featureId, featureId,
(feature.status || '') as FeatureStatusWithPipeline projectPath,
); isAutoMode: false,
allowReuse: _calledInternally,
});
if (pipelineInfo.isPipeline) {
// Feature stuck in pipeline - use pipeline resume
return this.resumePipelineFeature(projectPath, feature, useWorktrees, pipelineInfo);
}
// Normal resume flow for non-pipeline features
// Check if context exists in .automaker directory
const featureDir = getFeatureDir(projectPath, featureId);
const contextPath = path.join(featureDir, 'agent-output.md');
let hasContext = false;
try { try {
await secureFs.access(contextPath); // Load feature to check status
hasContext = true; const feature = await this.loadFeature(projectPath, featureId);
} catch { if (!feature) {
// No context throw new Error(`Feature ${featureId} not found`);
} }
if (hasContext) { // Check if feature is stuck in a pipeline step
// Load previous context and continue const pipelineInfo = await this.detectPipelineStatus(
const context = (await secureFs.readFile(contextPath, 'utf-8')) as string; projectPath,
return this.executeFeatureWithContext(projectPath, featureId, context, useWorktrees); featureId,
} (feature.status || '') as FeatureStatusWithPipeline
);
// No context, start fresh - executeFeature will handle adding to runningFeatures if (pipelineInfo.isPipeline) {
return this.executeFeature(projectPath, featureId, useWorktrees, false); // Feature stuck in pipeline - use pipeline resume
// Pass _alreadyTracked to prevent double-tracking
return await this.resumePipelineFeature(projectPath, feature, useWorktrees, pipelineInfo);
}
// Normal resume flow for non-pipeline features
// Check if context exists in .automaker directory
const featureDir = getFeatureDir(projectPath, featureId);
const contextPath = path.join(featureDir, 'agent-output.md');
let hasContext = false;
try {
await secureFs.access(contextPath);
hasContext = true;
} catch {
// No context
}
if (hasContext) {
// Load previous context and continue
// executeFeatureWithContext -> executeFeature will see feature is already tracked
const context = (await secureFs.readFile(contextPath, 'utf-8')) as string;
return await this.executeFeatureWithContext(projectPath, featureId, context, useWorktrees);
}
// No context, start fresh - executeFeature will see feature is already tracked
return await this.executeFeature(projectPath, featureId, useWorktrees, false, undefined, {
_calledInternally: true,
});
} finally {
this.releaseRunningFeature(featureId);
}
} }
/** /**
@@ -1682,7 +1740,9 @@ Complete the pipeline step instructions above. Review the previous work and appl
// Reset status to in_progress and start fresh // Reset status to in_progress and start fresh
await this.updateFeatureStatus(projectPath, featureId, 'in_progress'); await this.updateFeatureStatus(projectPath, featureId, 'in_progress');
return this.executeFeature(projectPath, featureId, useWorktrees, false); return this.executeFeature(projectPath, featureId, useWorktrees, false, undefined, {
_calledInternally: true,
});
} }
// Edge Case 2: Step no longer exists in pipeline config // Edge Case 2: Step no longer exists in pipeline config
@@ -1828,17 +1888,14 @@ Complete the pipeline step instructions above. Review the previous work and appl
`[AutoMode] Resuming pipeline for feature ${featureId} from step ${startFromStepIndex + 1}/${sortedSteps.length}` `[AutoMode] Resuming pipeline for feature ${featureId} from step ${startFromStepIndex + 1}/${sortedSteps.length}`
); );
// Add to running features immediately const runningEntry = this.acquireRunningFeature({
const abortController = new AbortController();
this.runningFeatures.set(featureId, {
featureId, featureId,
projectPath, projectPath,
worktreePath: null, // Will be set below
branchName: feature.branchName ?? null,
abortController,
isAutoMode: false, isAutoMode: false,
startTime: Date.now(), allowReuse: true,
}); });
const abortController = runningEntry.abortController;
runningEntry.branchName = feature.branchName ?? null;
try { try {
// Validate project path // Validate project path
@@ -1863,11 +1920,8 @@ Complete the pipeline step instructions above. Review the previous work and appl
validateWorkingDirectory(workDir); validateWorkingDirectory(workDir);
// Update running feature with worktree info // Update running feature with worktree info
const runningFeature = this.runningFeatures.get(featureId); runningEntry.worktreePath = worktreePath;
if (runningFeature) { runningEntry.branchName = branchName ?? null;
runningFeature.worktreePath = worktreePath;
runningFeature.branchName = branchName ?? null;
}
// Emit resume event // Emit resume event
this.emitAutoModeEvent('auto_mode_feature_start', { this.emitAutoModeEvent('auto_mode_feature_start', {
@@ -1945,7 +1999,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
}); });
} }
} finally { } finally {
this.runningFeatures.delete(featureId); this.releaseRunningFeature(featureId);
} }
} }
@@ -1962,11 +2016,12 @@ Complete the pipeline step instructions above. Review the previous work and appl
// Validate project path early for fast failure // Validate project path early for fast failure
validateWorkingDirectory(projectPath); validateWorkingDirectory(projectPath);
if (this.runningFeatures.has(featureId)) { const runningEntry = this.acquireRunningFeature({
throw new Error(`Feature ${featureId} is already running`); featureId,
} projectPath,
isAutoMode: false,
const abortController = new AbortController(); });
const abortController = runningEntry.abortController;
// Load feature info for context FIRST to get branchName // Load feature info for context FIRST to get branchName
const feature = await this.loadFeature(projectPath, featureId); const feature = await this.loadFeature(projectPath, featureId);
@@ -2048,17 +2103,10 @@ Address the follow-up instructions above. Review the previous work and make the
const provider = ProviderFactory.getProviderNameForModel(model); const provider = ProviderFactory.getProviderNameForModel(model);
logger.info(`Follow-up for feature ${featureId} using model: ${model}, provider: ${provider}`); logger.info(`Follow-up for feature ${featureId} using model: ${model}, provider: ${provider}`);
this.runningFeatures.set(featureId, { runningEntry.worktreePath = worktreePath;
featureId, runningEntry.branchName = branchName;
projectPath, runningEntry.model = model;
worktreePath, runningEntry.provider = provider;
branchName,
abortController,
isAutoMode: false,
startTime: Date.now(),
model,
provider,
});
try { try {
// Update feature status to in_progress BEFORE emitting event // Update feature status to in_progress BEFORE emitting event
@@ -2206,7 +2254,7 @@ Address the follow-up instructions above. Review the previous work and make the
} }
} }
} finally { } finally {
this.runningFeatures.delete(featureId); this.releaseRunningFeature(featureId);
} }
} }
@@ -4225,6 +4273,7 @@ After generating the revised spec, output:
return this.executeFeature(projectPath, featureId, useWorktrees, false, undefined, { return this.executeFeature(projectPath, featureId, useWorktrees, false, undefined, {
continuationPrompt: prompt, continuationPrompt: prompt,
_calledInternally: true,
}); });
} }

View File

@@ -6,14 +6,25 @@ import { SplashScreen } from './components/splash-screen';
import { useSettingsSync } from './hooks/use-settings-sync'; import { useSettingsSync } from './hooks/use-settings-sync';
import { useCursorStatusInit } from './hooks/use-cursor-status-init'; import { useCursorStatusInit } from './hooks/use-cursor-status-init';
import { useProviderAuthInit } from './hooks/use-provider-auth-init'; import { useProviderAuthInit } from './hooks/use-provider-auth-init';
import { useAppStore } from './store/app-store';
import './styles/global.css'; import './styles/global.css';
import './styles/theme-imports'; import './styles/theme-imports';
import './styles/font-imports'; import './styles/font-imports';
const logger = createLogger('App'); const logger = createLogger('App');
// Key for localStorage to persist splash screen preference
const DISABLE_SPLASH_KEY = 'automaker-disable-splash';
export default function App() { export default function App() {
const disableSplashScreen = useAppStore((state) => state.disableSplashScreen);
const [showSplash, setShowSplash] = useState(() => { const [showSplash, setShowSplash] = useState(() => {
// Check localStorage for user preference (available synchronously)
const savedPreference = localStorage.getItem(DISABLE_SPLASH_KEY);
if (savedPreference === 'true') {
return false;
}
// Only show splash once per session // Only show splash once per session
if (sessionStorage.getItem('automaker-splash-shown')) { if (sessionStorage.getItem('automaker-splash-shown')) {
return false; return false;
@@ -21,6 +32,11 @@ export default function App() {
return true; return true;
}); });
// Sync the disableSplashScreen setting to localStorage for fast access on next startup
useEffect(() => {
localStorage.setItem(DISABLE_SPLASH_KEY, String(disableSplashScreen));
}, [disableSplashScreen]);
// Clear accumulated PerformanceMeasure entries to prevent memory leak in dev mode // Clear accumulated PerformanceMeasure entries to prevent memory leak in dev mode
// React's internal scheduler creates performance marks/measures that accumulate without cleanup // React's internal scheduler creates performance marks/measures that accumulate without cleanup
useEffect(() => { useEffect(() => {
@@ -61,7 +77,7 @@ export default function App() {
return ( return (
<> <>
<RouterProvider router={router} /> <RouterProvider router={router} />
{showSplash && <SplashScreen onComplete={handleSplashComplete} />} {showSplash && !disableSplashScreen && <SplashScreen onComplete={handleSplashComplete} />}
</> </>
); );
} }

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Palette, Moon, Sun, Type } from 'lucide-react'; import { Switch } from '@/components/ui/switch';
import { Palette, Moon, Sun, Type, Sparkles } from 'lucide-react';
import { darkThemes, lightThemes } from '@/config/theme-options'; import { darkThemes, lightThemes } from '@/config/theme-options';
import { import {
UI_SANS_FONT_OPTIONS, UI_SANS_FONT_OPTIONS,
@@ -18,7 +19,14 @@ interface AppearanceSectionProps {
} }
export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) { export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) {
const { fontFamilySans, fontFamilyMono, setFontSans, setFontMono } = useAppStore(); const {
fontFamilySans,
fontFamilyMono,
setFontSans,
setFontMono,
disableSplashScreen,
setDisableSplashScreen,
} = useAppStore();
// Determine if current theme is light or dark // Determine if current theme is light or dark
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme); const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
@@ -189,6 +197,30 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
</div> </div>
</div> </div>
</div> </div>
{/* Splash Screen Section */}
<div className="space-y-4 pt-6 border-t border-border/50">
<div className="flex items-center gap-2 mb-4">
<Sparkles className="w-4 h-4 text-muted-foreground" />
<Label className="text-foreground font-medium">Startup</Label>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="disable-splash-screen" className="text-sm">
Disable Splash Screen
</Label>
<p className="text-xs text-muted-foreground">
Skip the animated splash screen when the app starts
</p>
</div>
<Switch
id="disable-splash-screen"
checked={disableSplashScreen}
onCheckedChange={setDisableSplashScreen}
/>
</div>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -181,6 +181,7 @@ export function parseLocalStorageSettings(): Partial<GlobalSettings> | null {
defaultPlanningMode: state.defaultPlanningMode as GlobalSettings['defaultPlanningMode'], defaultPlanningMode: state.defaultPlanningMode as GlobalSettings['defaultPlanningMode'],
defaultRequirePlanApproval: state.defaultRequirePlanApproval as boolean, defaultRequirePlanApproval: state.defaultRequirePlanApproval as boolean,
muteDoneSound: state.muteDoneSound as boolean, muteDoneSound: state.muteDoneSound as boolean,
disableSplashScreen: state.disableSplashScreen as boolean,
enhancementModel: state.enhancementModel as GlobalSettings['enhancementModel'], enhancementModel: state.enhancementModel as GlobalSettings['enhancementModel'],
validationModel: state.validationModel as GlobalSettings['validationModel'], validationModel: state.validationModel as GlobalSettings['validationModel'],
phaseModels: state.phaseModels as GlobalSettings['phaseModels'], phaseModels: state.phaseModels as GlobalSettings['phaseModels'],
@@ -711,6 +712,7 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
model: 'claude-opus', model: 'claude-opus',
}, },
muteDoneSound: settings.muteDoneSound ?? false, muteDoneSound: settings.muteDoneSound ?? false,
disableSplashScreen: settings.disableSplashScreen ?? false,
serverLogLevel: settings.serverLogLevel ?? 'info', serverLogLevel: settings.serverLogLevel ?? 'info',
enableRequestLogging: settings.enableRequestLogging ?? true, enableRequestLogging: settings.enableRequestLogging ?? true,
showQueryDevtools: settings.showQueryDevtools ?? true, showQueryDevtools: settings.showQueryDevtools ?? true,
@@ -798,6 +800,7 @@ function buildSettingsUpdateFromStore(): Record<string, unknown> {
defaultPlanningMode: state.defaultPlanningMode, defaultPlanningMode: state.defaultPlanningMode,
defaultRequirePlanApproval: state.defaultRequirePlanApproval, defaultRequirePlanApproval: state.defaultRequirePlanApproval,
muteDoneSound: state.muteDoneSound, muteDoneSound: state.muteDoneSound,
disableSplashScreen: state.disableSplashScreen,
serverLogLevel: state.serverLogLevel, serverLogLevel: state.serverLogLevel,
enableRequestLogging: state.enableRequestLogging, enableRequestLogging: state.enableRequestLogging,
enhancementModel: state.enhancementModel, enhancementModel: state.enhancementModel,

View File

@@ -64,6 +64,7 @@ const SETTINGS_FIELDS_TO_SYNC = [
'defaultRequirePlanApproval', 'defaultRequirePlanApproval',
'defaultFeatureModel', 'defaultFeatureModel',
'muteDoneSound', 'muteDoneSound',
'disableSplashScreen',
'serverLogLevel', 'serverLogLevel',
'enableRequestLogging', 'enableRequestLogging',
'showQueryDevtools', 'showQueryDevtools',
@@ -710,6 +711,7 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
? migratePhaseModelEntry(serverSettings.defaultFeatureModel) ? migratePhaseModelEntry(serverSettings.defaultFeatureModel)
: { model: 'claude-opus' }, : { model: 'claude-opus' },
muteDoneSound: serverSettings.muteDoneSound, muteDoneSound: serverSettings.muteDoneSound,
disableSplashScreen: serverSettings.disableSplashScreen ?? false,
serverLogLevel: serverSettings.serverLogLevel ?? 'info', serverLogLevel: serverSettings.serverLogLevel ?? 'info',
enableRequestLogging: serverSettings.enableRequestLogging ?? true, enableRequestLogging: serverSettings.enableRequestLogging ?? true,
enhancementModel: serverSettings.enhancementModel, enhancementModel: serverSettings.enhancementModel,

View File

@@ -686,6 +686,9 @@ export interface AppState {
// Audio Settings // Audio Settings
muteDoneSound: boolean; // When true, mute the notification sound when agents complete (default: false) muteDoneSound: boolean; // When true, mute the notification sound when agents complete (default: false)
// Splash Screen Settings
disableSplashScreen: boolean; // When true, skip showing the splash screen overlay on startup
// Server Log Level Settings // Server Log Level Settings
serverLogLevel: ServerLogLevel; // Log level for the API server (error, warn, info, debug) serverLogLevel: ServerLogLevel; // Log level for the API server (error, warn, info, debug)
enableRequestLogging: boolean; // Enable HTTP request logging (Morgan) enableRequestLogging: boolean; // Enable HTTP request logging (Morgan)
@@ -1183,6 +1186,9 @@ export interface AppActions {
// Audio Settings actions // Audio Settings actions
setMuteDoneSound: (muted: boolean) => void; setMuteDoneSound: (muted: boolean) => void;
// Splash Screen actions
setDisableSplashScreen: (disabled: boolean) => void;
// Server Log Level actions // Server Log Level actions
setServerLogLevel: (level: ServerLogLevel) => void; setServerLogLevel: (level: ServerLogLevel) => void;
setEnableRequestLogging: (enabled: boolean) => void; setEnableRequestLogging: (enabled: boolean) => void;
@@ -1502,6 +1508,7 @@ const initialState: AppState = {
worktreesByProject: {}, worktreesByProject: {},
keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS, // Default keyboard shortcuts keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS, // Default keyboard shortcuts
muteDoneSound: false, // Default to sound enabled (not muted) muteDoneSound: false, // Default to sound enabled (not muted)
disableSplashScreen: false, // Default to showing splash screen
serverLogLevel: 'info', // Default to info level for server logs serverLogLevel: 'info', // Default to info level for server logs
enableRequestLogging: true, // Default to enabled for HTTP request logging enableRequestLogging: true, // Default to enabled for HTTP request logging
showQueryDevtools: true, // Default to enabled (only shown in dev mode anyway) showQueryDevtools: true, // Default to enabled (only shown in dev mode anyway)
@@ -2626,6 +2633,9 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
// Audio Settings actions // Audio Settings actions
setMuteDoneSound: (muted) => set({ muteDoneSound: muted }), setMuteDoneSound: (muted) => set({ muteDoneSound: muted }),
// Splash Screen actions
setDisableSplashScreen: (disabled) => set({ disableSplashScreen: disabled }),
// Server Log Level actions // Server Log Level actions
setServerLogLevel: (level) => set({ serverLogLevel: level }), setServerLogLevel: (level) => set({ serverLogLevel: level }),
setEnableRequestLogging: (enabled) => set({ enableRequestLogging: enabled }), setEnableRequestLogging: (enabled) => set({ enableRequestLogging: enabled }),

View File

@@ -861,6 +861,10 @@ export interface GlobalSettings {
/** Mute completion notification sound */ /** Mute completion notification sound */
muteDoneSound: boolean; muteDoneSound: boolean;
// Splash Screen
/** Disable the splash screen overlay on app startup */
disableSplashScreen: boolean;
// Server Logging Preferences // Server Logging Preferences
/** Log level for the API server (error, warn, info, debug). Default: info */ /** Log level for the API server (error, warn, info, debug). Default: info */
serverLogLevel?: ServerLogLevel; serverLogLevel?: ServerLogLevel;
@@ -1320,6 +1324,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
defaultRequirePlanApproval: false, defaultRequirePlanApproval: false,
defaultFeatureModel: { model: 'claude-opus' }, // Use canonical ID defaultFeatureModel: { model: 'claude-opus' }, // Use canonical ID
muteDoneSound: false, muteDoneSound: false,
disableSplashScreen: false,
serverLogLevel: 'info', serverLogLevel: 'info',
enableRequestLogging: true, enableRequestLogging: true,
showQueryDevtools: true, showQueryDevtools: true,