fix: prevent new projects from overriding global theme setting

When creating new projects, the theme was always explicitly set even when
matching the global theme. This caused "Use Global Theme" to be unchecked,
preventing global theme changes from affecting the project.

Now theme is only set on new projects when explicitly provided or when
recovering a trashed project's theme preference.
This commit is contained in:
Stefan de Vogelaere
2026-01-18 16:12:32 +01:00
parent 96202d4bc2
commit 75fe579e93
6 changed files with 35 additions and 102 deletions

View File

@@ -2,7 +2,7 @@ import { useState, useCallback, useEffect } from 'react';
import { Plus, Bug, FolderOpen, BookOpen } from 'lucide-react';
import { useNavigate, useLocation } from '@tanstack/react-router';
import { cn } from '@/lib/utils';
import { useAppStore, type ThemeMode } from '@/store/app-store';
import { useAppStore } from '@/store/app-store';
import { useOSDetection } from '@/hooks/use-os-detection';
import { ProjectSwitcherItem } from './components/project-switcher-item';
import { ProjectContextMenu } from './components/project-context-menu';
@@ -10,7 +10,7 @@ import { EditProjectDialog } from './components/edit-project-dialog';
import { NotificationBell } from './components/notification-bell';
import { NewProjectModal } from '@/components/dialogs/new-project-modal';
import { OnboardingDialog } from '@/components/layout/sidebar/dialogs';
import { useProjectCreation, useProjectTheme } from '@/components/layout/sidebar/hooks';
import { useProjectCreation } from '@/components/layout/sidebar/hooks';
import { SIDEBAR_FEATURE_FLAGS } from '@/components/layout/sidebar/constants';
import type { Project } from '@/lib/electron';
import { getElectronAPI } from '@/lib/electron';
@@ -41,7 +41,6 @@ export function ProjectSwitcher() {
projects,
currentProject,
setCurrentProject,
trashedProjects,
upsertAndSetCurrentProject,
specCreatingForProject,
setSpecCreatingForProject,
@@ -69,9 +68,6 @@ export function ProjectSwitcher() {
const appMode = import.meta.env.VITE_APP_MODE || '?';
const versionSuffix = `${getOSAbbreviation(os)}${appMode}`;
// Get global theme for project creation
const { globalTheme } = useProjectTheme();
// Project creation state and handlers
const {
showNewProjectModal,
@@ -84,9 +80,6 @@ export function ProjectSwitcher() {
handleCreateFromTemplate,
handleCreateFromCustomUrl,
} = useProjectCreation({
trashedProjects,
currentProject,
globalTheme,
upsertAndSetCurrentProject,
});
@@ -161,13 +154,8 @@ export function ProjectSwitcher() {
}
// Upsert project and set as current (handles both create and update cases)
// Theme preservation is handled by the store action
const trashedProject = trashedProjects.find((p) => p.path === path);
const effectiveTheme =
(trashedProject?.theme as ThemeMode | undefined) ||
(currentProject?.theme as ThemeMode | undefined) ||
globalTheme;
upsertAndSetCurrentProject(path, name, effectiveTheme);
// Theme handling (trashed project recovery or undefined for global) is done by the store
upsertAndSetCurrentProject(path, name);
// Check if app_spec.txt exists
const specExists = await hasAppSpec(path);
@@ -198,7 +186,7 @@ export function ProjectSwitcher() {
});
}
}
}, [trashedProjects, upsertAndSetCurrentProject, currentProject, globalTheme, navigate]);
}, [upsertAndSetCurrentProject, navigate]);
// Handler for creating initial spec from the setup dialog
const handleCreateInitialSpec = useCallback(async () => {

View File

@@ -4,7 +4,7 @@ import { useNavigate, useLocation } from '@tanstack/react-router';
const logger = createLogger('Sidebar');
import { cn } from '@/lib/utils';
import { useAppStore, type ThemeMode } from '@/store/app-store';
import { useAppStore } from '@/store/app-store';
import { useNotificationsStore } from '@/store/notifications-store';
import { useKeyboardShortcuts, useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts';
import { getElectronAPI } from '@/lib/electron';
@@ -34,7 +34,6 @@ import {
useProjectCreation,
useSetupDialog,
useTrashOperations,
useProjectTheme,
useUnviewedValidations,
} from './sidebar/hooks';
@@ -79,9 +78,6 @@ export function Sidebar() {
// State for trash dialog
const [showTrashDialog, setShowTrashDialog] = useState(false);
// Project theme management (must come before useProjectCreation which uses globalTheme)
const { globalTheme } = useProjectTheme();
// Project creation state and handlers
const {
showNewProjectModal,
@@ -97,9 +93,6 @@ export function Sidebar() {
handleCreateFromTemplate,
handleCreateFromCustomUrl,
} = useProjectCreation({
trashedProjects,
currentProject,
globalTheme,
upsertAndSetCurrentProject,
});
@@ -198,13 +191,8 @@ export function Sidebar() {
}
// Upsert project and set as current (handles both create and update cases)
// Theme preservation is handled by the store action
const trashedProject = trashedProjects.find((p) => p.path === path);
const effectiveTheme =
(trashedProject?.theme as ThemeMode | undefined) ||
(currentProject?.theme as ThemeMode | undefined) ||
globalTheme;
upsertAndSetCurrentProject(path, name, effectiveTheme);
// Theme handling (trashed project recovery or undefined for global) is done by the store
upsertAndSetCurrentProject(path, name);
// Check if app_spec.txt exists
const specExists = await hasAppSpec(path);
@@ -232,7 +220,7 @@ export function Sidebar() {
});
}
}
}, [trashedProjects, upsertAndSetCurrentProject, currentProject, globalTheme]);
}, [upsertAndSetCurrentProject]);
// Navigation sections and keyboard shortcuts (defined after handlers)
const { navSections, navigationShortcuts } = useNavigation({

View File

@@ -6,20 +6,13 @@ const logger = createLogger('ProjectCreation');
import { initializeProject } from '@/lib/project-init';
import { toast } from 'sonner';
import type { StarterTemplate } from '@/lib/templates';
import type { ThemeMode } from '@/store/app-store';
import type { TrashedProject, Project } from '@/lib/electron';
import type { Project } from '@/lib/electron';
interface UseProjectCreationProps {
trashedProjects: TrashedProject[];
currentProject: Project | null;
globalTheme: ThemeMode;
upsertAndSetCurrentProject: (path: string, name: string, theme: ThemeMode) => Project;
upsertAndSetCurrentProject: (path: string, name: string) => Project;
}
export function useProjectCreation({
trashedProjects,
currentProject,
globalTheme,
upsertAndSetCurrentProject,
}: UseProjectCreationProps) {
// Modal state
@@ -67,14 +60,8 @@ export function useProjectCreation({
</project_specification>`
);
// Determine theme: try trashed project theme, then current project theme, then global
const trashedProject = trashedProjects.find((p) => p.path === projectPath);
const effectiveTheme =
(trashedProject?.theme as ThemeMode | undefined) ||
(currentProject?.theme as ThemeMode | undefined) ||
globalTheme;
upsertAndSetCurrentProject(projectPath, projectName, effectiveTheme);
// Let the store handle theme (trashed project recovery or undefined for global)
upsertAndSetCurrentProject(projectPath, projectName);
setShowNewProjectModal(false);
@@ -92,7 +79,7 @@ export function useProjectCreation({
throw error;
}
},
[trashedProjects, currentProject, globalTheme, upsertAndSetCurrentProject]
[upsertAndSetCurrentProject]
);
/**
@@ -169,14 +156,8 @@ export function useProjectCreation({
</project_specification>`
);
// Determine theme
const trashedProject = trashedProjects.find((p) => p.path === projectPath);
const effectiveTheme =
(trashedProject?.theme as ThemeMode | undefined) ||
(currentProject?.theme as ThemeMode | undefined) ||
globalTheme;
upsertAndSetCurrentProject(projectPath, projectName, effectiveTheme);
// Let the store handle theme (trashed project recovery or undefined for global)
upsertAndSetCurrentProject(projectPath, projectName);
setShowNewProjectModal(false);
setNewProjectName(projectName);
setNewProjectPath(projectPath);
@@ -194,7 +175,7 @@ export function useProjectCreation({
setIsCreatingProject(false);
}
},
[trashedProjects, currentProject, globalTheme, upsertAndSetCurrentProject]
[upsertAndSetCurrentProject]
);
/**
@@ -244,14 +225,8 @@ export function useProjectCreation({
</project_specification>`
);
// Determine theme
const trashedProject = trashedProjects.find((p) => p.path === projectPath);
const effectiveTheme =
(trashedProject?.theme as ThemeMode | undefined) ||
(currentProject?.theme as ThemeMode | undefined) ||
globalTheme;
upsertAndSetCurrentProject(projectPath, projectName, effectiveTheme);
// Let the store handle theme (trashed project recovery or undefined for global)
upsertAndSetCurrentProject(projectPath, projectName);
setShowNewProjectModal(false);
setNewProjectName(projectName);
setNewProjectPath(projectPath);
@@ -269,7 +244,7 @@ export function useProjectCreation({
setIsCreatingProject(false);
}
},
[trashedProjects, currentProject, globalTheme, upsertAndSetCurrentProject]
[upsertAndSetCurrentProject]
);
return {

View File

@@ -24,10 +24,10 @@ export function ThemeStep({ onNext, onBack }: ThemeStepProps) {
const handleThemeClick = (themeValue: string) => {
setTheme(themeValue as typeof theme);
// Also update the current project's theme if one exists
// This ensures the selected theme is visible since getEffectiveTheme() prioritizes project theme
if (currentProject) {
setProjectTheme(currentProject.id, themeValue as typeof theme);
// Clear the current project's theme so it uses the global theme
// This ensures "Use Global Theme" is checked and the project inherits the global theme
if (currentProject && currentProject.theme !== undefined) {
setProjectTheme(currentProject.id, null);
}
setPreviewTheme(null);
};

View File

@@ -9,7 +9,7 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { useAppStore, type ThemeMode } from '@/store/app-store';
import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
import { initializeProject } from '@/lib/project-init';
import {
@@ -38,15 +38,7 @@ import { useNavigate } from '@tanstack/react-router';
const logger = createLogger('WelcomeView');
export function WelcomeView() {
const {
projects,
trashedProjects,
currentProject,
upsertAndSetCurrentProject,
addProject,
setCurrentProject,
theme: globalTheme,
} = useAppStore();
const { projects, upsertAndSetCurrentProject, addProject, setCurrentProject } = useAppStore();
const navigate = useNavigate();
const [showNewProjectModal, setShowNewProjectModal] = useState(false);
const [isCreating, setIsCreating] = useState(false);
@@ -109,13 +101,8 @@ export function WelcomeView() {
}
// Upsert project and set as current (handles both create and update cases)
// Theme preservation is handled by the store action
const trashedProject = trashedProjects.find((p) => p.path === path);
const effectiveTheme =
(trashedProject?.theme as ThemeMode | undefined) ||
(currentProject?.theme as ThemeMode | undefined) ||
globalTheme;
upsertAndSetCurrentProject(path, name, effectiveTheme);
// Theme handling (trashed project recovery or undefined for global) is done by the store
upsertAndSetCurrentProject(path, name);
// Show initialization dialog if files were created
if (initResult.createdFiles && initResult.createdFiles.length > 0) {
@@ -150,14 +137,7 @@ export function WelcomeView() {
setIsOpening(false);
}
},
[
trashedProjects,
currentProject,
globalTheme,
upsertAndSetCurrentProject,
analyzeProject,
navigate,
]
[upsertAndSetCurrentProject, analyzeProject, navigate]
);
const handleOpenProject = useCallback(async () => {

View File

@@ -1627,16 +1627,18 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
const updatedProjects = projects.map((p) => (p.id === existingProject.id ? project : p));
set({ projects: updatedProjects });
} else {
// Create new project - check for trashed project with same path first (preserves theme if deleted/recreated)
// Then fall back to provided theme, then current project theme, then global theme
// Create new project - only set theme if explicitly provided or recovering from trash
// Otherwise leave undefined so project uses global theme ("Use Global Theme" checked)
const trashedProject = trashedProjects.find((p) => p.path === path);
const effectiveTheme = theme || trashedProject?.theme || currentProject?.theme || globalTheme;
const projectTheme =
theme !== undefined ? theme : (trashedProject?.theme as ThemeMode | undefined);
project = {
id: `project-${Date.now()}`,
name,
path,
lastOpened: new Date().toISOString(),
theme: effectiveTheme,
theme: projectTheme, // May be undefined - intentional!
};
// Add the new project to the store
set({