Merge pull request #583 from stefandevo/fix/initial-theme

fix: prevent new projects from overriding global theme setting
This commit is contained in:
Web Dev Cody
2026-01-18 17:17:20 -05:00
committed by GitHub
7 changed files with 39 additions and 121 deletions

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { createLogger } from '@automaker/utils/logger'; import { createLogger } from '@automaker/utils/logger';
import { useNavigate } from '@tanstack/react-router'; import { useNavigate } from '@tanstack/react-router';
import { useAppStore, type ThemeMode } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { useOSDetection } from '@/hooks/use-os-detection'; import { useOSDetection } from '@/hooks/use-os-detection';
import { getElectronAPI, isElectron } from '@/lib/electron'; import { getElectronAPI, isElectron } from '@/lib/electron';
import { initializeProject } from '@/lib/project-init'; import { initializeProject } from '@/lib/project-init';
@@ -76,14 +76,11 @@ export function DashboardView() {
const { const {
projects, projects,
trashedProjects,
currentProject,
upsertAndSetCurrentProject, upsertAndSetCurrentProject,
addProject, addProject,
setCurrentProject, setCurrentProject,
toggleProjectFavorite, toggleProjectFavorite,
moveProjectToTrash, moveProjectToTrash,
theme: globalTheme,
} = useAppStore(); } = useAppStore();
const [showNewProjectModal, setShowNewProjectModal] = useState(false); const [showNewProjectModal, setShowNewProjectModal] = useState(false);
@@ -143,12 +140,8 @@ export function DashboardView() {
return; return;
} }
const trashedProject = trashedProjects.find((p) => p.path === path); // Theme handling (trashed project recovery or undefined for global) is done by the store
const effectiveTheme = upsertAndSetCurrentProject(path, name);
(trashedProject?.theme as ThemeMode | undefined) ||
(currentProject?.theme as ThemeMode | undefined) ||
globalTheme;
upsertAndSetCurrentProject(path, name, effectiveTheme);
toast.success('Project opened', { toast.success('Project opened', {
description: `Opened ${name}`, description: `Opened ${name}`,
@@ -164,15 +157,7 @@ export function DashboardView() {
setIsOpening(false); setIsOpening(false);
} }
}, },
[ [projects, upsertAndSetCurrentProject, navigate, moveProjectToTrash]
projects,
trashedProjects,
currentProject,
globalTheme,
upsertAndSetCurrentProject,
navigate,
moveProjectToTrash,
]
); );
const handleOpenProject = useCallback(async () => { const handleOpenProject = useCallback(async () => {

View File

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

View File

@@ -9,7 +9,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { useAppStore, type ThemeMode } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { initializeProject } from '@/lib/project-init'; import { initializeProject } from '@/lib/project-init';
import { import {
@@ -38,15 +38,7 @@ import { useNavigate } from '@tanstack/react-router';
const logger = createLogger('WelcomeView'); const logger = createLogger('WelcomeView');
export function WelcomeView() { export function WelcomeView() {
const { const { projects, upsertAndSetCurrentProject, addProject, setCurrentProject } = useAppStore();
projects,
trashedProjects,
currentProject,
upsertAndSetCurrentProject,
addProject,
setCurrentProject,
theme: globalTheme,
} = useAppStore();
const navigate = useNavigate(); const navigate = useNavigate();
const [showNewProjectModal, setShowNewProjectModal] = useState(false); const [showNewProjectModal, setShowNewProjectModal] = useState(false);
const [isCreating, setIsCreating] = 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) // Upsert project and set as current (handles both create and update cases)
// Theme preservation is handled by the store action // Theme handling (trashed project recovery or undefined for global) is done by the store
const trashedProject = trashedProjects.find((p) => p.path === path); upsertAndSetCurrentProject(path, name);
const effectiveTheme =
(trashedProject?.theme as ThemeMode | undefined) ||
(currentProject?.theme as ThemeMode | undefined) ||
globalTheme;
upsertAndSetCurrentProject(path, name, effectiveTheme);
// Show initialization dialog if files were created // Show initialization dialog if files were created
if (initResult.createdFiles && initResult.createdFiles.length > 0) { if (initResult.createdFiles && initResult.createdFiles.length > 0) {
@@ -150,14 +137,7 @@ export function WelcomeView() {
setIsOpening(false); setIsOpening(false);
} }
}, },
[ [upsertAndSetCurrentProject, analyzeProject, navigate]
trashedProjects,
currentProject,
globalTheme,
upsertAndSetCurrentProject,
analyzeProject,
navigate,
]
); );
const handleOpenProject = useCallback(async () => { 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)); const updatedProjects = projects.map((p) => (p.id === existingProject.id ? project : p));
set({ projects: updatedProjects }); set({ projects: updatedProjects });
} else { } else {
// Create new project - check for trashed project with same path first (preserves theme if deleted/recreated) // Create new project - only set theme if explicitly provided or recovering from trash
// Then fall back to provided theme, then current project theme, then global theme // Otherwise leave undefined so project uses global theme ("Use Global Theme" checked)
const trashedProject = trashedProjects.find((p) => p.path === path); 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 = { project = {
id: `project-${Date.now()}`, id: `project-${Date.now()}`,
name, name,
path, path,
lastOpened: new Date().toISOString(), lastOpened: new Date().toISOString(),
theme: effectiveTheme, theme: projectTheme, // May be undefined - intentional!
}; };
// Add the new project to the store // Add the new project to the store
set({ set({