mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
splash screen configurable in global settings
This commit is contained in:
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 }),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user