Skip to content

Show Theme Picker during On Boarding#186

Merged
webdevcody merged 4 commits intomainfrom
theme-on-boarding
Dec 20, 2025
Merged

Show Theme Picker during On Boarding#186
webdevcody merged 4 commits intomainfrom
theme-on-boarding

Conversation

@webdevcody
Copy link
Collaborator

@webdevcody webdevcody commented Dec 20, 2025

better UX, makes it feel personal from the start

Summary by CodeRabbit

  • New Features

    • Persistent file-based settings with secure credential handling and migration from legacy storage
    • Startup splash screen with animated introduction
    • Theme selection added to setup flow; settings migrate/sync for desktop apps
  • Style

    • Added many new visual themes and bundled theme imports for dynamic loading
  • Chores

    • Server/client settings API and background sync for Electron builds

✏️ Tip: You can customize this high-level summary in your review settings.

- Add SettingsService to handle reading/writing global and project settings.
- Introduce API routes for managing settings, including global settings, credentials, and project-specific settings.
- Implement migration functionality to transfer settings from localStorage to file-based storage.
- Create common utilities for settings routes and integrate logging for error handling.
- Update server entry point to include new settings routes.
- Consolidate destructuring of useSetupStore into a single line for cleaner code.
- Remove unnecessary blank line at the beginning of the file.
- Update the link in the README for the Agentic Jumpstart course to include a GitHub-specific query parameter.
- Ensure consistent userData path across development and production environments in the Electron app, with error handling for path setting.
- Improve the isElectron function to check for Electron context more robustly.
@coderabbitai
Copy link

coderabbitai bot commented Dec 20, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds file-based settings persistence and migration: a new SettingsService, API routes under /api/settings, UI migration/sync hooks, a splash screen and theme selection step, plus many new theme CSS files and related client/server wiring.

Changes

Cohort / File(s) Summary
Server: Settings service & paths
apps/server/src/services/settings-service.ts, apps/server/src/lib/automaker-paths.ts
New SettingsService class providing atomic JSON read/write, deep-merge updates, project-scoped settings, credentials handling, migration from localStorage, and path/dir helpers (getGlobalSettingsPath, getCredentialsPath, getProjectSettingsPath, ensureDataDir).
Server: Settings routes & common
apps/server/src/routes/settings/index.ts, apps/server/src/routes/settings/common.ts, apps/server/src/routes/settings/routes/*
New router factory createSettingsRoutes and route handler factories for status, global, credentials, project, migrate endpoints plus logging/error helpers; handlers call the provided SettingsService.
Server: Integration & tests
apps/server/src/index.ts, apps/server/tests/unit/services/settings-service.test.ts, apps/server/tests/unit/lib/automaker-paths.test.ts, other updated unit tests
Instantiates SettingsService, mounts /api/settings; adds comprehensive unit tests for settings service and path utilities and several other expanded tests.
Types
apps/server/src/types/settings.ts
New TypeScript types, interfaces, version constants, and default constants for GlobalSettings, ProjectSettings, Credentials, keyboard shortcuts, AI profiles, and related defaults.
UI: Migration hooks & API client
apps/ui/src/hooks/use-settings-migration.ts, apps/ui/src/lib/http-api-client.ts
New hook useSettingsMigration that queries status, migrates localStorage to server, and exposes sync helpers (syncSettingsToServer, syncCredentialsToServer, syncProjectSettingsToServer); HttpApiClient gains settings methods mapping to /api/settings endpoints.
UI: App lifecycle & store integration
apps/ui/src/App.tsx, apps/ui/src/main.ts, apps/ui/src/lib/electron.ts, apps/ui/src/routes/__root.tsx, apps/ui/src/store/app-store.ts, apps/ui/src/store/setup-store.ts
Adds splash/migration startup flow, normalizes Electron userData path, improves isElectron detection, adds setup hydration guard and routing, debounced sync scheduling for global and per-project state, and extends setup steps with "theme".
UI: Splash & setup theme step
apps/ui/src/components/splash-screen.tsx, apps/ui/src/components/views/setup-view.tsx, apps/ui/src/components/views/setup-view/steps/theme-step.tsx, apps/ui/src/components/views/setup-view/steps/index.ts
New SplashScreen component and ThemeStep UI; setup flow updated to include theme selection and integrate splash completion.
UI: Theme styles
apps/ui/src/styles/themes/*.css (16 new theme files), apps/ui/src/styles/theme-imports.ts
Adds 16 standalone theme CSS files (dark, light, retro, dracula, nord, monokai, tokyonight, solarized, gruvbox, catppuccin, onedark, synthwave, red, cream, sunset, gray) and an imports aggregator to prevent tree-shaking.
UI: Global styles
apps/ui/src/styles/global.css
Removes many previous per-theme rules, consolidating global.css to baseline styles (theme-specific rules moved into theme files).
Docs
README.md
Minor README link query parameter change in a Markdown hyperlink.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant API
    participant FS

    User->>UI: Open app
    UI->>API: GET /api/settings/status
    API->>FS: Check settings files
    FS-->>API: files/status
    API-->>UI: { needsMigration, hasGlobalSettings, hasCredentials, dataDir }
    alt needsMigration
        UI->>UI: Collect localStorage keys
        UI->>API: POST /api/settings/migrate (data)
        API->>API: validate & parse migration payload
        API->>FS: write global settings, credentials, project files (atomic writes)
        FS-->>API: write results
        API-->>UI: migration result (migratedGlobalSettings, migratedCredentials, migratedProjectCount, errors)
        UI->>UI: clear migrated localStorage keys (keep necessary)
    end
    UI->>UI: Show SplashScreen / Setup (Theme selection)
    UI->>API: PUT /api/settings/global (on theme select)
    API->>FS: update global settings file (atomic)
    FS-->>API: updated settings
    API-->>UI: { success, settings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Review focus:
    • apps/server/src/services/settings-service.ts: atomic write, deep-merge semantics, version handling, migration error accumulation.
    • apps/server/src/routes/settings/routes/migrate.ts and route handlers: input validation and consistent error responses.
    • UI store sync logic (apps/ui/src/store/app-store.ts) and debounce/timer cleanup.
    • Theme CSS moves and global.css removals to ensure no leftover references break styles.
    • Tests exercising file I/O and migration flows for flaky filesystem or environment-dependent failures.

Suggested labels

Refactor, Tests

Poem

🐰
I hopped through files and planted keys,
Moved little settings from browsers with ease.
Splash lights shimmer, themes take flight,
Server keeps secrets safe at night.
Hooray — settings tucked in burrowed peace.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a theme picker to the onboarding flow, which aligns with the comprehensive file changes introducing theme selection UI and settings management infrastructure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch theme-on-boarding

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @webdevcody, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the initial user experience by integrating a theme selection step into the onboarding flow. It also includes a major architectural improvement by migrating user settings from ephemeral browser localStorage to persistent file-based storage on the server, ensuring data integrity and better management. A new splash screen adds a professional touch to the application's startup.

Highlights

  • Onboarding Theme Picker: A new 'Theme' step has been added to the onboarding process, allowing users to select their preferred theme early on for a personalized experience from the start.
  • Settings Persistence Refactor: The application's settings management has been significantly refactored to move from browser-based localStorage to persistent file-based storage on the server. This ensures settings are more robust and survive application updates.
  • New Settings Service & API: A new SettingsService has been introduced on the server, along with a comprehensive set of API routes (/api/settings) to handle global, credentials, and project-specific settings, including migration from old localStorage data.
  • Animated Splash Screen: An animated splash screen is now displayed on application startup, providing a more polished and engaging initial user experience.
  • Theme CSS Refactor: All theme-specific CSS styles have been extracted from global.css into individual CSS files for each theme, improving modularity and maintainability.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant and well-architected feature: a file-based settings persistence system, replacing the previous localStorage-only approach. This includes a robust migration path for existing users. The changes are extensive, touching both the backend server to add new API endpoints and services, and the frontend to handle migration, settings synchronization, and UI updates.

Key improvements include:

  • Persistent Settings: Global, project, and credential settings are now stored in JSON files, ensuring they persist across sessions and app updates.
  • Data Migration: A migration flow is implemented to seamlessly move user settings from localStorage to the new file-based system.
  • Enhanced Onboarding: The setup flow is improved with a new theme picker, providing a better user experience from the start.
  • Code Refactoring: The CSS for themes has been modularized into separate files, improving maintainability.

The implementation is solid, with good practices like atomic file writes on the server to prevent data corruption. My review includes a few suggestions to improve the maintainability of the settings synchronization logic on the frontend. Overall, this is an excellent contribution that greatly improves the application's robustness and user experience.

const parsed = JSON.parse(localStorageData["automaker-storage"]);
appState = parsed.state || parsed;
} catch (e) {
errors.push(`Failed to parse automaker-storage: ${e}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better error diagnostics during migration, it's a good practice to log the error message specifically, rather than the whole error object which can be verbose and less clear when stringified. This will make debugging migration issues easier.

Suggested change
errors.push(`Failed to parse automaker-storage: ${e}`);
errors.push(`Failed to parse automaker-storage: ${(e as Error).message}`);

Comment on lines +175 to +197
const updates = {
theme: state.theme,
sidebarOpen: state.sidebarOpen,
chatHistoryOpen: state.chatHistoryOpen,
kanbanCardDetailLevel: state.kanbanCardDetailLevel,
maxConcurrency: state.maxConcurrency,
defaultSkipTests: state.defaultSkipTests,
enableDependencyBlocking: state.enableDependencyBlocking,
useWorktrees: state.useWorktrees,
showProfilesOnly: state.showProfilesOnly,
defaultPlanningMode: state.defaultPlanningMode,
defaultRequirePlanApproval: state.defaultRequirePlanApproval,
defaultAIProfileId: state.defaultAIProfileId,
muteDoneSound: state.muteDoneSound,
enhancementModel: state.enhancementModel,
keyboardShortcuts: state.keyboardShortcuts,
aiProfiles: state.aiProfiles,
projects: state.projects,
trashedProjects: state.trashedProjects,
projectHistory: state.projectHistory,
projectHistoryIndex: state.projectHistoryIndex,
lastSelectedSessionByProject: state.lastSelectedSessionByProject,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The updates object is constructed by manually listing properties from the state. This approach is prone to errors and difficult to maintain, as any new setting added to the global state must also be manually added here to be synced. Consider refactoring the Zustand store to have a dedicated globalSettings slice that mirrors the GlobalSettings type from the backend. This would allow you to sync the entire slice without manually listing properties, making the code more robust and easier to maintain.

Comment on lines +2477 to +2492
const importantSettingsChanged =
state.theme !== previousState.theme ||
state.projects !== previousState.projects ||
state.trashedProjects !== previousState.trashedProjects ||
state.keyboardShortcuts !== previousState.keyboardShortcuts ||
state.aiProfiles !== previousState.aiProfiles ||
state.maxConcurrency !== previousState.maxConcurrency ||
state.defaultSkipTests !== previousState.defaultSkipTests ||
state.enableDependencyBlocking !== previousState.enableDependencyBlocking ||
state.useWorktrees !== previousState.useWorktrees ||
state.showProfilesOnly !== previousState.showProfilesOnly ||
state.muteDoneSound !== previousState.muteDoneSound ||
state.enhancementModel !== previousState.enhancementModel ||
state.defaultPlanningMode !== previousState.defaultPlanningMode ||
state.defaultRequirePlanApproval !== previousState.defaultRequirePlanApproval ||
state.defaultAIProfileId !== previousState.defaultAIProfileId;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current method of checking for changes in important settings involves a long chain of manual comparisons. This is fragile and can easily become outdated as new settings are added or state is refactored. A more maintainable approach would be to either group these settings into a single settings object within the store for simpler deep comparison, or use a utility function for deep object comparison to detect changes automatically.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🧹 Nitpick comments (10)
apps/ui/src/styles/themes/cream.css (1)

1-116: LGTM!

Well-structured theme with consistent use of the oklch color space for perceptual uniformity. All required theme variables are defined with appropriate warm earth tones.

Note: The scrollbar styling uses WebKit-specific pseudo-elements. Firefox users won't see styled scrollbars. Consider adding Firefox scrollbar properties if cross-browser consistency is desired:

.cream {
  scrollbar-color: oklch(0.7 0.03 60) oklch(0.9 0.015 70);
  scrollbar-width: thin;
}
apps/ui/src/components/views/setup-view/steps/theme-step.tsx (1)

44-69: Consider accessibility improvements for screen readers.

The theme selection buttons work well visually but could benefit from accessibility enhancements.

🔎 Suggested improvements
             <button
               key={option.value}
               data-testid={option.testId}
+              aria-label={`Select ${option.label} theme${isSelected ? ' (currently selected)' : ''}`}
+              aria-pressed={isSelected}
               onMouseEnter={() => handleThemeHover(option.value)}
               onMouseLeave={handleThemeLeave}
               onClick={() => handleThemeClick(option.value)}
apps/ui/src/App.tsx (1)

18-22: Move migration logging into useEffect.

The migration success logging runs on every render when migrated is true. This should be wrapped in a useEffect to log only once when migration completes.

🔎 Proposed fix
  // Run settings migration on startup (localStorage -> file storage)
  const migrationState = useSettingsMigration();
-  if (migrationState.migrated) {
-    console.log("[App] Settings migrated to file storage");
-  }
+
+  useEffect(() => {
+    if (migrationState.migrated) {
+      console.log("[App] Settings migrated to file storage");
+    }
+  }, [migrationState.migrated]);
apps/ui/src/styles/themes/monokai.css (1)

66-69: Consider adding status color variables for consistency.

Other themes in this PR (sunset.css, gray.css) define status color variables (--status-success, --status-warning, --status-error, --status-info, --status-backlog, --status-in-progress, --status-waiting). Adding these to the Monokai theme would ensure consistent behavior across all themes.

apps/server/src/routes/settings/routes/update-global.ts (1)

10-34: Consider adding schema validation for settings updates.

The handler performs basic object type checking but relies on type assertions without runtime validation. While the SettingsService may handle this, adding explicit schema validation (e.g., using Zod or similar) would provide stronger guarantees against malformed or malicious input.

Example with Zod:

import { z } from 'zod';

const GlobalSettingsUpdateSchema = z.object({
  theme: z.enum(['light', 'dark', 'system']).optional(),
  maxConcurrency: z.number().int().min(1).max(10).optional(),
  // ... other fields
}).partial();

// In handler:
const parseResult = GlobalSettingsUpdateSchema.safeParse(req.body);
if (!parseResult.success) {
  return res.status(400).json({
    success: false,
    error: 'Invalid settings format',
    details: parseResult.error.issues
  });
}
const updates = parseResult.data;
apps/ui/src/components/splash-screen.tsx (3)

45-59: Consider accessibility: respect user's motion preferences.

The splash screen uses intensive animations but doesn't check for prefers-reduced-motion. Users with vestibular disorders or motion sensitivity should have animations disabled.

🔎 Proposed enhancement
 export function SplashScreen({ onComplete }: { onComplete: () => void }) {
+  const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+  
   const [phase, setPhase] = useState<"enter" | "hold" | "exit" | "done">(
     "enter"
   );

   const particles = useMemo(() => generateParticles(50), []);

   useEffect(() => {
+    // Skip animations if user prefers reduced motion
+    if (prefersReducedMotion) {
+      const timer = setTimeout(() => {
+        setPhase("done");
+        onComplete();
+      }, 100);
+      return () => clearTimeout(timer);
+    }
+
     const timers: NodeJS.Timeout[] = [];
     // ... rest of animation logic

3-6: Extract timing constants to a config object.

While the constants are defined at the module level, grouping them into a configuration object would improve maintainability and make it easier to adjust the animation timing as a cohesive unit.

🔎 Proposed refactor
-const TOTAL_DURATION = 2300; // Total animation duration in ms (tightened from 4000)
-const LOGO_ENTER_DURATION = 500; // Tightened from 1200
-const PARTICLES_ENTER_DELAY = 100; // Tightened from 400
-const EXIT_START = 1800; // Adjusted for shorter duration
+const ANIMATION_TIMING = {
+  TOTAL_DURATION: 2300,
+  LOGO_ENTER_DURATION: 500,
+  PARTICLES_ENTER_DELAY: 100,
+  EXIT_START: 1800,
+} as const;

 // Usage:
-timers.push(setTimeout(() => setPhase("hold"), LOGO_ENTER_DURATION));
+timers.push(setTimeout(() => setPhase("hold"), ANIMATION_TIMING.LOGO_ENTER_DURATION));

75-88: Consider extracting inline styles to a separate CSS file.

The inline <style> tag with keyframes works, but extracting these animations to a dedicated CSS module or the global styles would improve separation of concerns and enable better CSS tooling support.

apps/ui/src/hooks/use-settings-migration.ts (1)

55-151: Consider centralizing error handling for the migration flow.

The migration logic is solid overall. A few observations:

  1. Line 137: result.errors.join(", ") assumes errors is always an array. If the server returns undefined or null, this will throw.
  2. The hook silently sets state on error but doesn't provide a way for callers to retry the migration.
🔎 Suggested defensive check
         } else {
           console.warn(
             "[Settings Migration] Migration had errors:",
             result.errors
           );
           setState({
             checked: true,
             migrated: false,
-            error: result.errors.join(", "),
+            error: Array.isArray(result.errors) ? result.errors.join(", ") : "Migration failed",
           });
         }
apps/ui/src/styles/themes/dark.css (1)

94-112: Minor: Inconsistent indentation and consider reducing !important usage.

Line 96-98 has extra indentation compared to other rules. The !important declarations on lines 105-111 may indicate specificity conflicts that could be resolved by adjusting selector specificity instead.

🔎 Indentation fix
 /* Theme-specific overrides */

-  .dark .content-bg {
+.dark .content-bg {
     background: linear-gradient(135deg, oklch(0.04 0 0), oklch(0.08 0 0), oklch(0.04 0 0));
-  }
+}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fcc6cb and ace736c.

📒 Files selected for processing (45)
  • README.md (1 hunks)
  • apps/server/src/index.ts (3 hunks)
  • apps/server/src/lib/automaker-paths.ts (1 hunks)
  • apps/server/src/routes/settings/common.ts (1 hunks)
  • apps/server/src/routes/settings/index.ts (1 hunks)
  • apps/server/src/routes/settings/routes/get-credentials.ts (1 hunks)
  • apps/server/src/routes/settings/routes/get-global.ts (1 hunks)
  • apps/server/src/routes/settings/routes/get-project.ts (1 hunks)
  • apps/server/src/routes/settings/routes/migrate.ts (1 hunks)
  • apps/server/src/routes/settings/routes/status.ts (1 hunks)
  • apps/server/src/routes/settings/routes/update-credentials.ts (1 hunks)
  • apps/server/src/routes/settings/routes/update-global.ts (1 hunks)
  • apps/server/src/routes/settings/routes/update-project.ts (1 hunks)
  • apps/server/src/services/settings-service.ts (1 hunks)
  • apps/server/src/types/settings.ts (1 hunks)
  • apps/ui/src/App.tsx (1 hunks)
  • apps/ui/src/components/splash-screen.tsx (1 hunks)
  • apps/ui/src/components/views/setup-view.tsx (5 hunks)
  • apps/ui/src/components/views/setup-view/steps/index.ts (1 hunks)
  • apps/ui/src/components/views/setup-view/steps/theme-step.tsx (1 hunks)
  • apps/ui/src/hooks/use-settings-migration.ts (1 hunks)
  • apps/ui/src/lib/electron.ts (1 hunks)
  • apps/ui/src/lib/http-api-client.ts (1 hunks)
  • apps/ui/src/main.ts (1 hunks)
  • apps/ui/src/routes/__root.tsx (3 hunks)
  • apps/ui/src/store/app-store.ts (1 hunks)
  • apps/ui/src/store/setup-store.ts (1 hunks)
  • apps/ui/src/styles/global.css (0 hunks)
  • apps/ui/src/styles/theme-imports.ts (1 hunks)
  • apps/ui/src/styles/themes/catppuccin.css (1 hunks)
  • apps/ui/src/styles/themes/cream.css (1 hunks)
  • apps/ui/src/styles/themes/dark.css (1 hunks)
  • apps/ui/src/styles/themes/dracula.css (1 hunks)
  • apps/ui/src/styles/themes/gray.css (1 hunks)
  • apps/ui/src/styles/themes/gruvbox.css (1 hunks)
  • apps/ui/src/styles/themes/light.css (1 hunks)
  • apps/ui/src/styles/themes/monokai.css (1 hunks)
  • apps/ui/src/styles/themes/nord.css (1 hunks)
  • apps/ui/src/styles/themes/onedark.css (1 hunks)
  • apps/ui/src/styles/themes/red.css (1 hunks)
  • apps/ui/src/styles/themes/retro.css (1 hunks)
  • apps/ui/src/styles/themes/solarized.css (1 hunks)
  • apps/ui/src/styles/themes/sunset.css (1 hunks)
  • apps/ui/src/styles/themes/synthwave.css (1 hunks)
  • apps/ui/src/styles/themes/tokyonight.css (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/ui/src/styles/global.css
🧰 Additional context used
🧬 Code graph analysis (15)
apps/server/src/routes/settings/index.ts (8)
apps/server/src/services/settings-service.ts (1)
  • SettingsService (91-542)
apps/server/src/routes/settings/routes/status.ts (1)
  • createStatusHandler (10-28)
apps/server/src/routes/settings/routes/get-global.ts (1)
  • createGetGlobalHandler (9-23)
apps/server/src/routes/settings/routes/update-global.ts (1)
  • createUpdateGlobalHandler (10-34)
apps/server/src/routes/settings/routes/get-credentials.ts (1)
  • createGetCredentialsHandler (9-23)
apps/server/src/routes/settings/routes/update-credentials.ts (1)
  • createUpdateCredentialsHandler (10-39)
apps/server/src/routes/settings/routes/get-project.ts (1)
  • createGetProjectHandler (10-34)
apps/server/src/routes/settings/routes/migrate.ts (1)
  • createMigrateHandler (9-54)
apps/server/src/routes/settings/routes/update-project.ts (3)
apps/server/src/types/settings.ts (1)
  • ProjectSettings (184-200)
apps/server/src/routes/settings/common.ts (1)
  • logError (15-15)
apps/server/src/routes/common.ts (1)
  • getErrorMessage (370-372)
apps/server/src/routes/settings/routes/get-credentials.ts (2)
apps/server/src/routes/settings/common.ts (1)
  • logError (15-15)
apps/server/src/routes/common.ts (1)
  • getErrorMessage (370-372)
apps/server/src/routes/settings/routes/update-credentials.ts (3)
apps/server/src/types/settings.ts (1)
  • Credentials (146-153)
apps/server/src/routes/settings/common.ts (1)
  • logError (15-15)
apps/server/src/routes/common.ts (1)
  • getErrorMessage (370-372)
apps/ui/src/App.tsx (3)
apps/ui/src/hooks/use-settings-migration.ts (1)
  • useSettingsMigration (47-154)
apps/ui/src/utils/router.ts (1)
  • router (11-15)
apps/ui/src/components/splash-screen.tsx (1)
  • SplashScreen (38-309)
apps/server/src/routes/settings/routes/update-global.ts (3)
apps/server/src/types/settings.ts (1)
  • GlobalSettings (95-141)
apps/server/src/routes/settings/common.ts (1)
  • logError (15-15)
apps/server/src/routes/common.ts (1)
  • getErrorMessage (370-372)
apps/server/src/routes/settings/routes/status.ts (3)
apps/server/src/services/settings-service.ts (2)
  • hasGlobalSettings (156-159)
  • hasCredentials (249-252)
apps/server/src/routes/settings/common.ts (1)
  • logError (15-15)
apps/server/src/routes/common.ts (1)
  • getErrorMessage (370-372)
apps/server/src/routes/settings/routes/get-project.ts (2)
apps/server/src/routes/settings/common.ts (1)
  • logError (15-15)
apps/server/src/routes/common.ts (1)
  • getErrorMessage (370-372)
apps/server/src/types/settings.ts (1)
apps/ui/src/store/app-store.ts (9)
  • ThemeMode (19-36)
  • KanbanCardDetailLevel (38-38)
  • AgentModel (258-258)
  • PlanningMode (267-267)
  • ThinkingLevel (264-264)
  • ModelProvider (261-261)
  • KeyboardShortcuts (151-179)
  • AIProfile (270-279)
  • DEFAULT_KEYBOARD_SHORTCUTS (182-213)
apps/ui/src/routes/__root.tsx (2)
apps/ui/src/store/setup-store.ts (1)
  • useSetupStore (125-183)
apps/ui/tests/utils/project/setup.ts (1)
  • setupComplete (620-635)
apps/ui/src/components/views/setup-view.tsx (2)
apps/ui/src/store/setup-store.ts (1)
  • useSetupStore (125-183)
apps/ui/src/components/views/setup-view/steps/theme-step.tsx (1)
  • ThemeStep (12-90)
apps/server/src/routes/settings/routes/get-global.ts (3)
apps/server/src/services/settings-service.ts (1)
  • SettingsService (91-542)
apps/server/src/routes/settings/common.ts (1)
  • logError (15-15)
apps/server/src/routes/common.ts (1)
  • getErrorMessage (370-372)
apps/ui/src/store/app-store.ts (1)
apps/ui/src/hooks/use-settings-migration.ts (2)
  • syncSettingsToServer (160-205)
  • syncProjectSettingsToServer (232-261)
apps/ui/src/hooks/use-settings-migration.ts (2)
apps/ui/src/lib/electron.ts (1)
  • isElectron (510-520)
apps/ui/src/lib/http-api-client.ts (1)
  • getHttpApiClient (1023-1028)
apps/server/src/services/settings-service.ts (2)
apps/server/src/types/settings.ts (15)
  • GlobalSettings (95-141)
  • DEFAULT_GLOBAL_SETTINGS (226-252)
  • SETTINGS_VERSION (267-267)
  • Credentials (146-153)
  • DEFAULT_CREDENTIALS (254-261)
  • CREDENTIALS_VERSION (268-268)
  • ProjectSettings (184-200)
  • DEFAULT_PROJECT_SETTINGS (263-265)
  • PROJECT_SETTINGS_VERSION (269-269)
  • KeyboardShortcuts (32-53)
  • AIProfile (56-65)
  • ProjectRef (68-74)
  • TrashedProjectRef (77-80)
  • BoardBackgroundSettings (158-168)
  • WorktreeInfo (173-179)
apps/server/src/lib/automaker-paths.ts (5)
  • getGlobalSettingsPath (102-104)
  • ensureDataDir (123-126)
  • getCredentialsPath (109-111)
  • getProjectSettingsPath (116-118)
  • ensureAutomakerDir (87-91)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (45)
README.md (1)

11-11: LGTM!

Adding UTM tracking parameter for analytics is a standard practice for tracking referral sources.

apps/ui/src/lib/electron.ts (1)

510-520: LGTM!

The enhanced isElectron() detection is more robust:

  1. SSR-safe with typeof window === "undefined" check
  2. Multiple detection paths for reliability
  3. Optional chaining prevents runtime errors
apps/ui/src/components/views/setup-view/steps/theme-step.tsx (1)

12-89: Well-implemented theme selection step.

The component correctly:

  • Integrates with the app store for theme state management
  • Implements hover preview with proper cleanup
  • Uses responsive grid layout
  • Shows visual selection feedback

Note: The hover preview won't work on touch devices, but tap-to-select functions correctly, which is acceptable UX.

apps/server/src/lib/automaker-paths.ts (1)

92-126: LGTM!

Clean additions that follow the existing patterns in this file:

  • Consistent naming with other path utilities
  • Good separation of credentials from general settings for security
  • ensureDataDir uses recursive: true for safe idempotent directory creation
apps/ui/src/styles/themes/retro.css (1)

134-137: The retro-glow animation is already defined in apps/ui/src/styles/global.css at line 657 and is available to this theme file. No issue here.

Likely an incorrect or invalid review comment.

apps/ui/src/components/views/setup-view/steps/index.ts (1)

3-3: LGTM! Clean barrel export addition.

The ThemeStep export is properly added alongside existing setup step exports.

apps/ui/src/store/setup-store.ts (1)

54-60: LGTM! Proper type extension.

The "theme" step is correctly added to the SetupStep union, positioned logically in the setup flow sequence.

apps/ui/src/main.ts (1)

307-319: LGTM! Robust path normalization.

The userData path initialization is well-implemented with:

  • Conditional update (only if path differs)
  • Try/catch for error handling
  • Informative logging
apps/ui/src/styles/theme-imports.ts (1)

1-22: LGTM! Effective tree-shaking prevention.

This aggregation module correctly ensures all theme CSS files are bundled for dynamic loading.

apps/ui/src/App.tsx (2)

10-16: LGTM! Clean session-based splash gating.

The sessionStorage approach correctly ensures the splash screen displays only once per session.


24-27: LGTM! Proper callback memoization.

The handleSplashComplete callback is correctly memoized with useCallback and manages both sessionStorage persistence and state updates.

apps/ui/src/routes/__root.tsx (2)

69-96: LGTM! Robust hydration-aware routing.

The implementation correctly:

  • Waits for setup store hydration before enforcing routing rules
  • Subscribes to onFinishHydration callback when needed
  • Properly manages subscription cleanup
  • Prevents race conditions during store rehydration

The routing logic is sound and the dependencies are correct.


24-26: LGTM! Safe hydration state initialization.

The optional chaining ensures safe access to the persist API, with proper fallback to false.

apps/server/src/routes/settings/routes/update-project.ts (1)

10-48: LGTM! Solid route handler implementation.

The handler properly:

  • Validates required parameters with appropriate error responses
  • Uses the settings service to perform updates
  • Implements consistent error handling and logging
  • Returns well-structured responses
apps/ui/src/styles/themes/red.css (1)

1-69: Theme looks good overall.

The Red theme defines a comprehensive set of CSS variables using consistent OKLCh color values. The color palette is cohesive with the crimson/red aesthetic.

For full parity with other themes (sunset.css, gray.css), consider adding status color variables and scrollbar/component overrides in a follow-up.

apps/ui/src/styles/themes/sunset.css (1)

1-111: LGTM!

The Sunset theme is well-structured with a complete set of CSS variables including status colors and scrollbar overrides. This serves as a good reference implementation for other themes.

apps/server/src/routes/settings/routes/get-global.ts (1)

9-23: LGTM!

The handler follows a clean factory pattern with proper dependency injection, making it testable. Error handling is consistent with the shared utilities, and the async/await pattern is correctly implemented.

apps/server/src/routes/settings/common.ts (1)

1-15: LGTM!

Clean utility module that centralizes logging and error handling for settings routes. The scoped logger ("Settings") will aid in log filtering and debugging.

apps/ui/src/styles/themes/gray.css (1)

1-110: LGTM!

The Gray theme is comprehensive with a complete variable set including status colors and scrollbar overrides. The low-chroma OKLCh values create a clean, minimal aesthetic appropriate for the theme.

apps/ui/src/styles/themes/light.css (1)

63-102: XML syntax highlighting rules look good.

The light theme XML highlighting uses appropriate contrast levels for readability on light backgrounds.

apps/server/src/routes/settings/routes/status.ts (1)

10-28: LGTM! Well-structured status endpoint.

The handler correctly computes migration status and follows consistent error handling patterns. The logic is clean and the response structure aligns with other settings routes.

apps/server/src/routes/settings/routes/get-credentials.ts (1)

9-23: LGTM! Secure credential retrieval.

The handler correctly fetches masked credentials for safe UI display, preventing exposure of sensitive API keys. Error handling and response structure are consistent with the API conventions.

apps/server/src/routes/settings/routes/get-project.ts (1)

10-34: LGTM! Proper validation and error handling.

The handler correctly validates the required projectPath parameter and follows consistent patterns. The use of POST for this endpoint is well-justified by the comment regarding special characters in paths.

apps/server/src/index.ts (1)

40-40: LGTM! Clean integration of settings service.

The SettingsService is properly instantiated and wired into the Express app. The settings API is correctly mounted after authentication middleware, ensuring protected access to configuration endpoints.

Also applies to: 45-45, 113-113, 143-143

apps/server/src/routes/settings/routes/update-credentials.ts (1)

10-39: LGTM! Good security practices.

The handler properly validates input, updates credentials through the service layer, and returns only masked credentials for confirmation. This prevents accidental exposure of sensitive API keys.

apps/ui/src/store/app-store.ts (1)

2476-2516: Consider deep equality checks for object references.

The change detection uses reference comparison (!==) for objects like projects, trashedProjects, keyboardShortcuts, and aiProfiles. Since these are managed by Zustand with immutable updates, this should work correctly. However, if any action accidentally mutates these objects in place, syncs would be missed.

This is acceptable given Zustand's immutable patterns, but consider documenting this assumption or adding runtime checks in development mode.

Additionally, verify that all store actions that modify these objects follow immutable update patterns. For example, ensure setKeyboardShortcuts, addAIProfile, etc. create new object references rather than mutating existing ones.

apps/server/src/routes/settings/routes/migrate.ts (1)

9-54: LGTM! Well-structured migration handler.

The handler properly validates input, provides detailed logging, and returns comprehensive migration results including success status, migration counts, and any errors encountered. Good separation of concerns with the migration logic delegated to the SettingsService.

apps/ui/src/components/views/setup-view.tsx (2)

18-27: LGTM! Theme step properly integrated.

The theme step is well-integrated into the setup flow between "welcome" and "claude" steps. The step name mapping logic correctly handles all transitions.


119-124: LGTM! Theme step rendering implemented correctly.

The ThemeStep component is properly rendered with appropriate onNext and onBack handlers that maintain the flow consistency.

apps/ui/src/styles/themes/synthwave.css (1)

75-78: The synthwave-glow animation is properly defined in apps/ui/src/styles/global.css at line 703 and will be applied correctly when both stylesheets are loaded. No action needed.

Likely an incorrect or invalid review comment.

apps/ui/src/hooks/use-settings-migration.ts (2)

1-52: LGTM on hook structure and constants.

The hook documentation is clear, the ref-based guard against double execution is correct for React StrictMode, and the localStorage key constants are well-organized.


232-261: LGTM on project settings sync utility.

The function correctly guards for Electron mode and handles errors gracefully.

apps/ui/src/styles/themes/dark.css (2)

1-92: LGTM on the core theme variables.

The comprehensive set of CSS custom properties using oklch color space is well-structured. The semantic grouping (backgrounds, foregrounds, brand colors, status indicators, etc.) makes the theme maintainable.


127-166: LGTM on XML syntax highlighting tokens.

The syntax highlighting color choices provide good contrast and follow common conventions for code editors.

apps/server/src/services/settings-service.ts (4)

45-61: Atomic write implementation looks correct.

The temp-file-then-rename pattern ensures atomicity. The cleanup in the catch block is appropriate.

One minor consideration: Date.now() could theoretically collide under extreme concurrency. If this becomes an issue, consider adding a random suffix.


66-77: Non-ENOENT errors are swallowed silently.

When a file exists but contains invalid JSON (corruption), this returns the default value and only logs an error. The caller has no way to know the file was corrupted. Consider whether this is the desired behavior or if corruption should propagate differently.


102-121: LGTM on getGlobalSettings with backwards compatibility.

The spread pattern correctly applies defaults while preserving existing values, including nested keyboardShortcuts.


477-510: LGTM on per-project migration loop.

The error handling per-project is good - it collects errors without failing the entire migration. The approach of aggregating project paths from multiple sources before iterating is clean.

apps/server/src/types/settings.ts (4)

31-65: LGTM on interface definitions.

The KeyboardShortcuts and AIProfile interfaces align well with the UI counterparts shown in the relevant code snippets.


92-141: LGTM on GlobalSettings interface.

The interface is comprehensive and well-documented with inline comments explaining each section.


202-269: LGTM on default values and version constants.

The defaults are sensible, and versioning the settings formats will help with future migrations.


5-29: No issues found. The AgentModel type is properly aligned between server and UI, both defining identical values: "opus" | "sonnet" | "haiku".

apps/ui/src/lib/http-api-client.ts (3)

840-888: LGTM on settings status and global settings API.

The API structure follows the established patterns in this client and provides appropriate type annotations for the response shapes.


913-951: Using POST for getProject is unconventional but acceptable.

Line 942 uses POST to fetch project settings, likely to avoid URL-encoding the projectPath. This is a reasonable trade-off, though documenting this deviation from REST conventions would help future maintainers.


953-967: No action required—client and server payload structure are correctly aligned.

The server handler explicitly expects the payload wrapped as { data: ... } (line 12 of migrate.ts), and the client sends exactly that structure. The migration will work as intended.

Likely an incorrect or invalid review comment.

Comment on lines +30 to +32
// Project settings
router.post("/project", createGetProjectHandler(settingsService));
router.put("/project", createUpdateProjectHandler(settingsService));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use GET method for retrieving project settings.

Line 31 uses POST for retrieving project settings, which violates REST conventions. GET requests should be used for read operations.

Consider either:

  1. Change to router.get("/project") and accept projectPath as a query parameter
  2. Use a path parameter: router.get("/project/:projectPath") (requires URL encoding for paths)
🔎 Recommended refactor

Option 1 - Query parameter approach:

  // Project settings
- router.post("/project", createGetProjectHandler(settingsService));
+ router.get("/project", createGetProjectHandler(settingsService));
  router.put("/project", createUpdateProjectHandler(settingsService));

Then update the handler in apps/server/src/routes/settings/routes/get-project.ts to read from query params:

const { projectPath } = req.query as { projectPath?: string };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +261 to +272
async getProjectSettings(projectPath: string): Promise<ProjectSettings> {
const settingsPath = getProjectSettingsPath(projectPath);
const settings = await readJsonFile<ProjectSettings>(
settingsPath,
DEFAULT_PROJECT_SETTINGS
);

return {
...DEFAULT_PROJECT_SETTINGS,
...settings,
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the getProjectSettingsPath function implementation
rg -n "getProjectSettingsPath" --type ts -A 10 -B 2

# Also check for path validation utilities
rg -n "path.*traversal|normalize|resolve" --type ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 7076


🏁 Script executed:

#!/bin/bash
# Find the getAutomakerDir function
rg -n "function getAutomakerDir|export function getAutomakerDir" --type ts -A 5

# Check for any path validation in automaker-paths.ts
rg -n "resolve|normalize|traversal" apps/server/src/lib/automaker-paths.ts

Repository: AutoMaker-Org/automaker

Length of output: 479


🏁 Script executed:

#!/bin/bash
# Find callers of getProjectSettings
rg -n "\.getProjectSettings|getProjectSettings\(" --type ts -B 2 -A 2

# Also check if there's any validation on projectPath in the settings service
rg -n "projectPath" apps/server/src/services/settings-service.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 2547


🏁 Script executed:

#!/bin/bash
# Examine the get-project.ts route file
cat -n apps/server/src/routes/settings/routes/get-project.ts

Repository: AutoMaker-Org/automaker

Length of output: 1351


🏁 Script executed:

#!/bin/bash
# Find readJsonFile implementation
rg -n "function readJsonFile|export.*readJsonFile" --type ts -A 10

# Find ensureAutomakerDir implementation
rg -n "export.*ensureAutomakerDir" --type ts -A 10

Repository: AutoMaker-Org/automaker

Length of output: 1891


🏁 Script executed:

#!/bin/bash
# Look for any validation middleware or path validation utilities
rg -n "projectPath|validatePath|sanitize" apps/server/src/routes/settings/ --type ts

# Check if there's a validation layer or middleware
rg -n "middleware|validator|validate" apps/server/src/routes/ --type ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 3190


🏁 Script executed:

#!/bin/bash
# Examine the validatePath function in security.js
rg -n "validatePath" apps/server/src/lib/security.ts -A 20

Repository: AutoMaker-Org/automaker

Length of output: 325


🏁 Script executed:

#!/bin/bash
# Check more context around validatePath and allowedPaths
cat -n apps/server/src/lib/security.ts | head -70

Repository: AutoMaker-Org/automaker

Length of output: 2015


Add path traversal validation for projectPath parameter.

The projectPath parameter from user input lacks validation for path traversal patterns. Apply path validation similar to the fs routes that use validatePath, or at minimum verify that the resolved path remains within the intended project directory using path.resolve() combined with a containment check (e.g., startsWith(baseDir)).

  async getProjectSettings(projectPath: string): Promise<ProjectSettings> {
    const settingsPath = getProjectSettingsPath(projectPath);
    const settings = await readJsonFile<ProjectSettings>(
      settingsPath,
      DEFAULT_PROJECT_SETTINGS
    );

    return {
      ...DEFAULT_PROJECT_SETTINGS,
      ...settings,
    };
  }
🤖 Prompt for AI Agents
In apps/server/src/services/settings-service.ts around lines 261 to 272, the
projectPath parameter is used without path-traversal validation; update the
function to validate the incoming projectPath (reuse the existing validatePath
utility if available) or resolve the settingsPath and the allowed base project
directory with path.resolve(...) and ensure the resolved settingsPath is
contained within the baseDir (e.g., startsWith(baseDir) or using path.relative
to confirm it doesn't escape), and throw an error or reject the request if the
containment check fails before calling readJsonFile.

Comment on lines +351 to +396
// Extract global settings
const globalSettings: Partial<GlobalSettings> = {
theme: (appState.theme as GlobalSettings["theme"]) || "dark",
sidebarOpen:
appState.sidebarOpen !== undefined
? (appState.sidebarOpen as boolean)
: true,
chatHistoryOpen: (appState.chatHistoryOpen as boolean) || false,
kanbanCardDetailLevel:
(appState.kanbanCardDetailLevel as GlobalSettings["kanbanCardDetailLevel"]) ||
"standard",
maxConcurrency: (appState.maxConcurrency as number) || 3,
defaultSkipTests:
appState.defaultSkipTests !== undefined
? (appState.defaultSkipTests as boolean)
: true,
enableDependencyBlocking:
appState.enableDependencyBlocking !== undefined
? (appState.enableDependencyBlocking as boolean)
: true,
useWorktrees: (appState.useWorktrees as boolean) || false,
showProfilesOnly: (appState.showProfilesOnly as boolean) || false,
defaultPlanningMode:
(appState.defaultPlanningMode as GlobalSettings["defaultPlanningMode"]) ||
"skip",
defaultRequirePlanApproval:
(appState.defaultRequirePlanApproval as boolean) || false,
defaultAIProfileId:
(appState.defaultAIProfileId as string | null) || null,
muteDoneSound: (appState.muteDoneSound as boolean) || false,
enhancementModel:
(appState.enhancementModel as GlobalSettings["enhancementModel"]) ||
"sonnet",
keyboardShortcuts:
(appState.keyboardShortcuts as KeyboardShortcuts) ||
DEFAULT_GLOBAL_SETTINGS.keyboardShortcuts,
aiProfiles: (appState.aiProfiles as AIProfile[]) || [],
projects: (appState.projects as ProjectRef[]) || [],
trashedProjects:
(appState.trashedProjects as TrashedProjectRef[]) || [],
projectHistory: (appState.projectHistory as string[]) || [],
projectHistoryIndex: (appState.projectHistoryIndex as number) || -1,
lastSelectedSessionByProject:
(appState.lastSelectedSessionByProject as Record<string, string>) ||
{},
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use nullish coalescing (??) instead of logical OR (||) for falsy-but-valid values.

Several settings use || defaultValue which will incorrectly override valid falsy values like 0, false, or "". For example:

  • Line 362: maxConcurrency of 0 would become 3
  • Line 392: projectHistoryIndex of 0 would become -1
🔎 Partial fix for numeric fields
-        maxConcurrency: (appState.maxConcurrency as number) || 3,
+        maxConcurrency: (appState.maxConcurrency as number) ?? 3,
...
-        projectHistoryIndex: (appState.projectHistoryIndex as number) || -1,
+        projectHistoryIndex: (appState.projectHistoryIndex as number) ?? -1,
🤖 Prompt for AI Agents
In apps/server/src/services/settings-service.ts around lines 351–396 the code
uses the logical OR (||) to apply defaults which will overwrite valid falsy
values (0, false, "", etc.); change those usages to nullish coalescing (??) so
only null or undefined trigger the default — specifically replace occurrences
like (appState.maxConcurrency as number) || 3, (appState.chatHistoryOpen as
boolean) || false, (appState.useWorktrees as boolean) || false,
(appState.showProfilesOnly as boolean) || false,
(appState.defaultRequirePlanApproval as boolean) || false,
(appState.defaultAIProfileId as string | null) || null, (appState.muteDoneSound
as boolean) || false, (appState.enhancementModel as
GlobalSettings["enrichmentModel"]) || "sonnet", (appState.keyboardShortcuts as
KeyboardShortcuts) || DEFAULT_GLOBAL_SETTINGS.keyboardShortcuts,
(appState.aiProfiles as AIProfile[]) || [], (appState.projects as ProjectRef[])
|| [], (appState.trashedProjects as TrashedProjectRef[]) || [],
(appState.projectHistory as string[]) || [], and (appState.projectHistoryIndex
as number) || -1 to use ?? with the same default values (keep the existing !==
undefined ternaries as they are); after making the changes, run TypeScript
build/tests to ensure types and behavior are correct.

Comment on lines +143 to +153
/**
* Credentials - stored in {DATA_DIR}/credentials.json
*/
export interface Credentials {
version: number;
apiKeys: {
anthropic: string;
google: string;
openai: string;
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for credentials, writeFile, writeFileSync, chmod references in the TypeScript/JavaScript files
echo "=== Searching for credentials file handling ==="
rg -n "credentials|writeFile|chmod|0o[0-9]|mode:" --type ts --type js apps/server/src/ | head -50
echo ""
echo "=== Searching for DATA_DIR references ==="
rg -n "DATA_DIR" --type ts --type js apps/server/src/ | head -20
echo ""
echo "=== Checking for credentials-related files ==="
find apps/server/src/ -type f -name "*credential*" -o -name "*secret*" -o -name "*key*" 2>/dev/null | head -20

Repository: AutoMaker-Org/automaker

Length of output: 6421


🏁 Script executed:

#!/bin/bash
# Check the actual implementation of credentials writing in settings-service.ts
echo "=== Settings Service - around line 209 (atomicWriteJson call) ==="
sed -n '200,220p' apps/server/src/services/settings-service.ts
echo ""
echo "=== Settings Service - around line 50 (fs.writeFile call) ==="
sed -n '40,60p' apps/server/src/services/settings-service.ts
echo ""
echo "=== Finding atomicWriteJson definition ==="
rg -n "atomicWriteJson" --type ts apps/server/src/
echo ""
echo "=== Checking for mode parameter in writeFile calls ==="
rg -B2 -A2 "writeFile.*credentials" --type ts apps/server/src/

Repository: AutoMaker-Org/automaker

Length of output: 1810


🏁 Script executed:

#!/bin/bash
# Check if there are any chmod calls for credentials file
echo "=== Checking for chmod calls ==="
rg -n "chmod" --type ts --type js apps/server/src/

echo ""
echo "=== Check full atomicWriteJson function ==="
sed -n '45,65p' apps/server/src/services/settings-service.ts

echo ""
echo "=== Check if credentials.json path has any security comments ==="
sed -n '100,115p' apps/server/src/lib/automaker-paths.ts

echo ""
echo "=== Check types/settings.ts header for security docs ==="
sed -n '140,155p' apps/server/src/types/settings.ts

Repository: AutoMaker-Org/automaker

Length of output: 1511


Add restrictive file permissions and security documentation for credentials file.

The Credentials interface stores API keys as plain strings, and the atomicWriteJson function writes them to disk without setting restrictive file permissions. Currently, fs.writeFile() is called without a mode: 0o600 option, and there's no subsequent chmod() call to restrict access.

Update the file write to include { mode: 0o600 } to ensure only the owner can read/write the credentials file:

await fs.writeFile(tempPath, content, { mode: 0o600, encoding: "utf-8" });

Additionally, document the security implications in the Credentials interface header comment to clarify that this file should never be committed or shared.

🤖 Prompt for AI Agents
In apps/server/src/types/settings.ts around lines 143 to 153, the Credentials
interface stores sensitive API keys but lacks documentation about secure
handling; update the header comment above the Credentials interface to state
this file contains secrets (never commit, do not share, restrict file
permissions) and mention it is written to {DATA_DIR}/credentials.json. Also
locate the atomicWriteJson or the code that writes the credentials file and
ensure the write sets restrictive permissions by passing { mode: 0o600 } to
fs.writeFile (and/or call fs.chmod(tempPath, 0o600) after write) so only the
owner can read/write the credentials file.

Comment on lines +160 to +205
export async function syncSettingsToServer(): Promise<boolean> {
if (!isElectron()) return false;

try {
const api = getHttpApiClient();
const automakerStorage = localStorage.getItem("automaker-storage");

if (!automakerStorage) {
return false;
}

const parsed = JSON.parse(automakerStorage);
const state = parsed.state || parsed;

// Extract settings to sync
const updates = {
theme: state.theme,
sidebarOpen: state.sidebarOpen,
chatHistoryOpen: state.chatHistoryOpen,
kanbanCardDetailLevel: state.kanbanCardDetailLevel,
maxConcurrency: state.maxConcurrency,
defaultSkipTests: state.defaultSkipTests,
enableDependencyBlocking: state.enableDependencyBlocking,
useWorktrees: state.useWorktrees,
showProfilesOnly: state.showProfilesOnly,
defaultPlanningMode: state.defaultPlanningMode,
defaultRequirePlanApproval: state.defaultRequirePlanApproval,
defaultAIProfileId: state.defaultAIProfileId,
muteDoneSound: state.muteDoneSound,
enhancementModel: state.enhancementModel,
keyboardShortcuts: state.keyboardShortcuts,
aiProfiles: state.aiProfiles,
projects: state.projects,
trashedProjects: state.trashedProjects,
projectHistory: state.projectHistory,
projectHistoryIndex: state.projectHistoryIndex,
lastSelectedSessionByProject: state.lastSelectedSessionByProject,
};

const result = await api.settings.updateGlobal(updates);
return result.success;
} catch (error) {
console.error("[Settings Sync] Failed to sync settings:", error);
return false;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

JSON.parse can throw on malformed localStorage data.

Line 171 parses automakerStorage without error handling. If the localStorage data is corrupted, this will throw an unhandled exception and return false silently.

🔎 Proposed fix
   try {
     const api = getHttpApiClient();
     const automakerStorage = localStorage.getItem("automaker-storage");

     if (!automakerStorage) {
       return false;
     }

-    const parsed = JSON.parse(automakerStorage);
+    let parsed;
+    try {
+      parsed = JSON.parse(automakerStorage);
+    } catch {
+      console.error("[Settings Sync] Failed to parse automaker-storage");
+      return false;
+    }
     const state = parsed.state || parsed;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function syncSettingsToServer(): Promise<boolean> {
if (!isElectron()) return false;
try {
const api = getHttpApiClient();
const automakerStorage = localStorage.getItem("automaker-storage");
if (!automakerStorage) {
return false;
}
const parsed = JSON.parse(automakerStorage);
const state = parsed.state || parsed;
// Extract settings to sync
const updates = {
theme: state.theme,
sidebarOpen: state.sidebarOpen,
chatHistoryOpen: state.chatHistoryOpen,
kanbanCardDetailLevel: state.kanbanCardDetailLevel,
maxConcurrency: state.maxConcurrency,
defaultSkipTests: state.defaultSkipTests,
enableDependencyBlocking: state.enableDependencyBlocking,
useWorktrees: state.useWorktrees,
showProfilesOnly: state.showProfilesOnly,
defaultPlanningMode: state.defaultPlanningMode,
defaultRequirePlanApproval: state.defaultRequirePlanApproval,
defaultAIProfileId: state.defaultAIProfileId,
muteDoneSound: state.muteDoneSound,
enhancementModel: state.enhancementModel,
keyboardShortcuts: state.keyboardShortcuts,
aiProfiles: state.aiProfiles,
projects: state.projects,
trashedProjects: state.trashedProjects,
projectHistory: state.projectHistory,
projectHistoryIndex: state.projectHistoryIndex,
lastSelectedSessionByProject: state.lastSelectedSessionByProject,
};
const result = await api.settings.updateGlobal(updates);
return result.success;
} catch (error) {
console.error("[Settings Sync] Failed to sync settings:", error);
return false;
}
}
export async function syncSettingsToServer(): Promise<boolean> {
if (!isElectron()) return false;
try {
const api = getHttpApiClient();
const automakerStorage = localStorage.getItem("automaker-storage");
if (!automakerStorage) {
return false;
}
let parsed;
try {
parsed = JSON.parse(automakerStorage);
} catch {
console.error("[Settings Sync] Failed to parse automaker-storage");
return false;
}
const state = parsed.state || parsed;
// Extract settings to sync
const updates = {
theme: state.theme,
sidebarOpen: state.sidebarOpen,
chatHistoryOpen: state.chatHistoryOpen,
kanbanCardDetailLevel: state.kanbanCardDetailLevel,
maxConcurrency: state.maxConcurrency,
defaultSkipTests: state.defaultSkipTests,
enableDependencyBlocking: state.enableDependencyBlocking,
useWorktrees: state.useWorktrees,
showProfilesOnly: state.showProfilesOnly,
defaultPlanningMode: state.defaultPlanningMode,
defaultRequirePlanApproval: state.defaultRequirePlanApproval,
defaultAIProfileId: state.defaultAIProfileId,
muteDoneSound: state.muteDoneSound,
enhancementModel: state.enhancementModel,
keyboardShortcuts: state.keyboardShortcuts,
aiProfiles: state.aiProfiles,
projects: state.projects,
trashedProjects: state.trashedProjects,
projectHistory: state.projectHistory,
projectHistoryIndex: state.projectHistoryIndex,
lastSelectedSessionByProject: state.lastSelectedSessionByProject,
};
const result = await api.settings.updateGlobal(updates);
return result.success;
} catch (error) {
console.error("[Settings Sync] Failed to sync settings:", error);
return false;
}
}
🤖 Prompt for AI Agents
In apps/ui/src/hooks/use-settings-migration.ts around lines 160 to 205,
JSON.parse(automakerStorage) can throw if localStorage contains malformed data;
wrap the parse in its own try/catch (or use a safeParse helper) so parse errors
are caught, log the parsing error with context, return false (or clear/backup
the corrupted localStorage key) instead of letting an exception bubble, and only
proceed to build and send the updates when parsing succeeds.

Comment on lines +71 to +74
/* ========================================
SYNTHWAVE '84 THEME
Neon dreams of the 80s
======================================== */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix incorrect theme comment.

The comment block references "SYNTHWAVE '84 THEME" but this file is onedark.css. This appears to be a copy-paste error.

🔎 Proposed fix
 /* ========================================
-   SYNTHWAVE '84 THEME
-   Neon dreams of the 80s
+   ONE DARK THEME OVERRIDES
+   Component-specific customizations
    ======================================== */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* ========================================
SYNTHWAVE '84 THEME
Neon dreams of the 80s
======================================== */
/* ========================================
ONE DARK THEME OVERRIDES
Component-specific customizations
======================================== */
🤖 Prompt for AI Agents
In apps/ui/src/styles/themes/onedark.css around lines 71 to 74, the block
comment incorrectly labels the file as "SYNTHWAVE '84 THEME"; replace that
comment with a correct One Dark theme header (e.g., "ONE DARK THEME" and a short
accurate descriptor like "A balanced dark theme") so the file comment matches
the actual theme and remove any leftover synthwave/neon wording.

Comment on lines +82 to +85
/* ========================================
DRACULA THEME
Inspired by the popular Dracula VS Code theme
======================================== */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incorrect comment block - copy-paste artifact.

The comment mentions "DRACULA THEME" but this file defines the Retro theme.

🔎 Suggested fix
-/* ========================================
-   DRACULA THEME
-   Inspired by the popular Dracula VS Code theme
-   ======================================== */
+/* ========================================
+   RETRO / CYBERPUNK THEME
+   Neon green terminal aesthetic
+   ======================================== */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* ========================================
DRACULA THEME
Inspired by the popular Dracula VS Code theme
======================================== */
/* ========================================
RETRO / CYBERPUNK THEME
Neon green terminal aesthetic
======================================== */
🤖 Prompt for AI Agents
In apps/ui/src/styles/themes/retro.css around lines 82 to 85, the comment block
incorrectly says "DRACULA THEME" (a copy-paste artifact) while this file defines
the Retro theme; update the comment to accurately describe the Retro theme
(e.g., change heading and description to "RETRO THEME" and a short line like
"Inspired by classic retro color palettes") so the header matches the file
purpose and remove any leftover references to Dracula.

Comment on lines +71 to +74
/* ========================================
GRUVBOX THEME
Retro groove color scheme
======================================== */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix incorrect theme comment.

The comment block references "GRUVBOX THEME" but this file is solarized.css. This appears to be a copy-paste error.

🔎 Proposed fix
 /* ========================================
-   GRUVBOX THEME
-   Retro groove color scheme
+   SOLARIZED THEME OVERRIDES
+   Component-specific customizations
    ======================================== */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* ========================================
GRUVBOX THEME
Retro groove color scheme
======================================== */
/* ========================================
SOLARIZED THEME OVERRIDES
Component-specific customizations
======================================== */
🤖 Prompt for AI Agents
In apps/ui/src/styles/themes/solarized.css around lines 71 to 74, the comment
block incorrectly labels the section "GRUVBOX THEME" (a copy-paste error);
update the comment to correctly reference the Solarized theme and an appropriate
short description (e.g., "SOLARIZED THEME — Calm, low-contrast color scheme") so
the header matches the file and intent.

Comment on lines +71 to +72
/* Red Theme - Bold crimson/red aesthetic */

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove or correct the misleading comment.

The comment references "Red Theme - Bold crimson/red aesthetic" in a Synthwave theme file. This is a copy-paste error.

🔎 Suggested fix
-/* Red Theme - Bold crimson/red aesthetic */
-
+/* Theme-specific overrides for Synthwave */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* Red Theme - Bold crimson/red aesthetic */
/* Theme-specific overrides for Synthwave */
🤖 Prompt for AI Agents
In apps/ui/src/styles/themes/synthwave.css around lines 71 to 72, remove or
update the misleading comment "Red Theme - Bold crimson/red aesthetic" which is
a copy-paste error; replace it with an accurate comment relevant to the
Synthwave theme (e.g., "Synthwave Theme - neon/purple aesthetic") or delete the
line entirely so the file comments correctly reflect the theme.

Comment on lines +71 to +74
/* ========================================
SOLARIZED DARK THEME
The classic color scheme by Ethan Schoonover
======================================== */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incorrect comment block - copy-paste artifact.

The comment mentions "SOLARIZED DARK THEME" but this file defines the Tokyonight theme. This appears to be a copy-paste error from a template.

🔎 Suggested fix
-/* ========================================
-   SOLARIZED DARK THEME
-   The classic color scheme by Ethan Schoonover
-   ======================================== */
+/* ========================================
+   TOKYONIGHT THEME
+   Inspired by the Tokyo Night VS Code theme
+   ======================================== */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* ========================================
SOLARIZED DARK THEME
The classic color scheme by Ethan Schoonover
======================================== */
/* ========================================
TOKYONIGHT THEME
Inspired by the Tokyo Night VS Code theme
======================================== */
🤖 Prompt for AI Agents
In apps/ui/src/styles/themes/tokyonight.css around lines 71 to 74, the comment
header incorrectly reads "SOLARIZED DARK THEME" (a copy-paste artifact); update
the comment to correctly identify the Tokyonight theme (e.g., "TOKYONIGHT THEME"
or a brief Tokyonight description) so the header matches the file contents and
remove any leftover references to Solarized.

- Add comprehensive unit tests for SettingsService, covering global and project settings management, including creation, updates, and merging with defaults.
- Implement tests for handling credentials, ensuring proper masking and merging of API keys.
- Introduce tests for migration from localStorage, validating successful data transfer and error handling.
- Enhance error handling in subprocess management tests, ensuring robust timeout and output reading scenarios.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/server/tests/unit/lib/sdk-options.test.ts (1)

148-180: LGTM! Tests correctly verify optional parameter propagation.

The new test cases for createSuggestionsOptions properly verify that systemPrompt, abortController, and outputFormat are included in the returned options when provided. The tests follow the existing pattern established in this file and align with the implementation shown in the relevant code snippets.

Optional: Consider adding negative test cases

For completeness, you could add a test verifying that these optional fields are NOT present when NOT provided:

it("should not include optional fields when not provided", async () => {
  const { createSuggestionsOptions } = await import("@/lib/sdk-options.js");

  const options = createSuggestionsOptions({ cwd: "/test/path" });

  expect(options.systemPrompt).toBeUndefined();
  expect(options.abortController).toBeUndefined();
  expect(options.outputFormat).toBeUndefined();
});

This verifies the conditional spread operators work correctly in both directions.

apps/server/tests/unit/lib/worktree-metadata.test.ts (1)

70-94: Consider aligning test pattern with existing tests for consistency.

The new test cases use a different pattern than the existing tests in this suite. Specifically, the branch parameter passed to writeWorktreeMetadata and readWorktreeMetadata doesn't match the metadata.branch property:

  • Line 71: branch = "" but line 73: branch: "branch" (literal string)
  • Line 85: branch = "///" but line 87: branch: "branch" (literal string)

In contrast, existing tests (lines 34-44, 46-56, 58-68) follow the pattern where the branch parameter matches the metadata.branch property (e.g., const branch = "feature/test-branch" and metadata: { branch, ... }).

While these tests verify round-trip consistency, they don't reflect realistic usage where the metadata about a branch would contain the actual branch name used as the lookup key.

🔎 Suggested refactor to align with established pattern
     it("should handle empty branch name", async () => {
       const branch = "";
       const metadata: WorktreeMetadata = {
-        branch: "branch",
+        branch,
         createdAt: new Date().toISOString(),
       };
 
       // Empty branch name should be sanitized to "_branch"
       await writeWorktreeMetadata(testProjectPath, branch, metadata);
       const result = await readWorktreeMetadata(testProjectPath, branch);
       expect(result).toEqual(metadata);
     });
 
     it("should handle branch name that becomes empty after sanitization", async () => {
       // Test branch that would become empty after removing invalid chars
       const branch = "///";
       const metadata: WorktreeMetadata = {
-        branch: "branch",
+        branch,
         createdAt: new Date().toISOString(),
       };
 
       await writeWorktreeMetadata(testProjectPath, branch, metadata);
       const result = await readWorktreeMetadata(testProjectPath, branch);
       expect(result).toEqual(metadata);
     });
apps/server/tests/unit/lib/subprocess-manager.test.ts (1)

271-299: Test doesn't verify that timeout is reset on each output.

The test advances time by 150ms with a 200ms timeout and checks the process isn't killed. However, this doesn't prove the timeout resets on each output—it only verifies the process isn't killed before the initial timeout expires.

To properly verify timeout reset behavior, the test should simulate outputs spaced over time intervals where each interval is less than the timeout, but the total duration exceeds it. For example, with a 200ms timeout, emit outputs at 150ms, 300ms, and 450ms. If the timeout resets correctly, the process completes successfully despite taking 450ms total.

🔎 Suggested approach to verify timeout reset behavior
-    it("should reset timeout when output is received", async () => {
-      vi.useFakeTimers();
-      const mockProcess = createMockProcess({
-        stdoutLines: [
-          '{"type":"first"}',
-          '{"type":"second"}',
-          '{"type":"third"}',
-        ],
-        exitCode: 0,
-        delayMs: 50,
-      });
-
-      vi.mocked(cp.spawn).mockReturnValue(mockProcess);
-
-      const generator = spawnJSONLProcess({
-        ...baseOptions,
-        timeout: 200,
-      });
-
-      const promise = collectAsyncGenerator(generator);
-
-      // Advance time but not enough to trigger timeout
-      await vi.advanceTimersByTimeAsync(150);
-      // Process should not be killed yet
-      expect(mockProcess.kill).not.toHaveBeenCalled();
-
-      vi.useRealTimers();
-      await promise;
-    });
+    it("should reset timeout when output is received", async () => {
+      vi.useFakeTimers();
+      const mockProcess = new EventEmitter() as any;
+      const stdout = new Readable({ read() {} });
+      const stderr = new Readable({ read() {} });
+      mockProcess.stdout = stdout;
+      mockProcess.stderr = stderr;
+      mockProcess.kill = vi.fn();
+
+      vi.mocked(cp.spawn).mockReturnValue(mockProcess);
+
+      const generator = spawnJSONLProcess({
+        ...baseOptions,
+        timeout: 200, // 200ms timeout
+      });
+
+      const promise = collectAsyncGenerator(generator);
+
+      // Emit outputs at intervals that would exceed timeout without reset
+      // Each output is within timeout, but total time exceeds it
+      process.nextTick(() => {
+        setTimeout(() => stdout.push('{"type":"first"}\n'), 150);   // at 150ms
+        setTimeout(() => stdout.push('{"type":"second"}\n'), 300);  // at 300ms (would timeout without reset)
+        setTimeout(() => stdout.push('{"type":"third"}\n'), 450);   // at 450ms (would timeout without reset)
+        setTimeout(() => {
+          stdout.push(null);
+          mockProcess.emit("exit", 0);
+        }, 500);
+      });
+
+      // Advance through all the timeouts
+      await vi.advanceTimersByTimeAsync(500);
+
+      vi.useRealTimers();
+      const results = await promise;
+
+      // Process should complete successfully with all outputs
+      expect(mockProcess.kill).not.toHaveBeenCalled();
+      expect(results).toHaveLength(3);
+    });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ace736c and c76ba69.

📒 Files selected for processing (8)
  • apps/server/tests/unit/lib/automaker-paths.test.ts (2 hunks)
  • apps/server/tests/unit/lib/error-handler.test.ts (3 hunks)
  • apps/server/tests/unit/lib/sdk-options.test.ts (3 hunks)
  • apps/server/tests/unit/lib/security.test.ts (2 hunks)
  • apps/server/tests/unit/lib/subprocess-manager.test.ts (1 hunks)
  • apps/server/tests/unit/lib/worktree-metadata.test.ts (1 hunks)
  • apps/server/tests/unit/providers/claude-provider.test.ts (1 hunks)
  • apps/server/tests/unit/services/settings-service.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
apps/server/tests/unit/lib/security.test.ts (1)
apps/server/src/lib/security.ts (2)
  • initAllowedPaths (15-35)
  • getAllowedPaths (61-63)
apps/server/tests/unit/lib/error-handler.test.ts (1)
apps/server/src/lib/error-handler.ts (2)
  • isCancellationError (30-38)
  • classifyError (78-105)
apps/server/tests/unit/lib/subprocess-manager.test.ts (2)
apps/server/src/lib/subprocess-manager.ts (1)
  • spawnJSONLProcess (26-153)
apps/server/tests/utils/helpers.ts (1)
  • collectAsyncGenerator (8-14)
apps/server/tests/unit/providers/claude-provider.test.ts (1)
apps/server/tests/utils/helpers.ts (1)
  • collectAsyncGenerator (8-14)
apps/server/tests/unit/lib/automaker-paths.test.ts (1)
apps/server/src/lib/automaker-paths.ts (4)
  • getGlobalSettingsPath (102-104)
  • getCredentialsPath (109-111)
  • getProjectSettingsPath (116-118)
  • ensureDataDir (123-126)
apps/server/tests/unit/lib/sdk-options.test.ts (1)
apps/server/src/lib/sdk-options.ts (3)
  • createSuggestionsOptions (213-226)
  • createAutoModeOptions (265-279)
  • createCustomOptions (286-305)
🪛 Biome (2.1.2)
apps/server/tests/unit/providers/claude-provider.test.ts

[error] 243-245: This generator function doesn't contain yield.

(lint/correctness/useYield)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (19)
apps/server/tests/unit/lib/sdk-options.test.ts (2)

243-264: LGTM! Tests correctly verify optional parameter propagation.

The new test cases for createAutoModeOptions properly verify that systemPrompt and abortController are included when provided. The implementation is consistent with the pattern used throughout this test file.


295-330: LGTM! Tests correctly verify optional parameter propagation.

The new test cases for createCustomOptions properly verify that sandbox, systemPrompt, and abortController are included when provided. The sandbox test appropriately verifies the complete object structure with both properties.

apps/server/tests/unit/lib/subprocess-manager.test.ts (1)

301-326: Good test for stdout error handling.

This test properly verifies the error path when reading stdout fails. It correctly expects both error propagation (rejection) and logging behavior.

apps/server/tests/unit/providers/claude-provider.test.ts (1)

237-260: LGTM! Comprehensive error handling test.

The test correctly verifies that errors thrown by the SDK's async generator are propagated and logged. The async generator intentionally throws without yielding to simulate SDK failures.

Note: The static analysis warning about the generator not containing yield is a false positive—the generator is designed to throw immediately to test error handling.

apps/server/tests/unit/lib/security.test.ts (2)

56-68: LGTM! Proper test coverage for WORKSPACE_DIR handling.

The test correctly verifies that WORKSPACE_DIR is included in allowed paths when set, aligning with the implementation in security.ts.


73-73: Good practice: Test isolation maintained.

Explicitly deleting WORKSPACE_DIR in these tests ensures they aren't affected by environment state from other tests.

Also applies to: 88-88

apps/server/tests/unit/lib/error-handler.test.ts (2)

5-5: LGTM! Comprehensive coverage for cancellation error detection.

The tests thoroughly validate isCancellationError behavior, including all supported keywords ('cancelled', 'canceled', 'stopped', 'aborted'), case insensitivity, and negative cases.

Also applies to: 36-62


123-157: LGTM! Correct precedence validation.

The tests properly verify that classifyError prioritizes error types as: authentication > abort > cancellation, which matches the implementation in error-handler.ts.

apps/server/tests/unit/lib/automaker-paths.test.ts (1)

144-229: LGTM! Thorough coverage of new path utilities.

The tests comprehensively validate:

  • Path construction for global settings, credentials, and project settings
  • Edge cases like trailing slashes
  • Directory creation with ensureDataDir, including idempotency and nested paths
  • Proper setup/teardown with temp directories
apps/server/tests/unit/services/settings-service.test.ts (10)

1-38: LGTM! Excellent test structure and setup.

Proper test isolation with unique temp directories per test run and thorough cleanup in afterEach.


40-93: LGTM! Comprehensive global settings tests.

Tests thoroughly validate:

  • Default settings when file doesn't exist
  • Reading existing settings
  • Merging with defaults for partial settings
  • Deep merging of nested objects (keyboardShortcuts)

95-160: LGTM! Update and creation logic well-tested.

Tests verify creation, merging, deep merging of keyboard shortcuts, and automatic data directory creation. The verification includes both in-memory results and persisted file content.


175-212: LGTM! Credentials handling properly validated.

Tests cover defaults, reading existing credentials, and merging with defaults for missing API keys, ensuring backward compatibility.


214-283: LGTM! Credentials update and deep merge tested.

Tests verify creation, merging, and deep merging of API keys, ensuring individual keys are preserved during partial updates.


285-325: LGTM! Masking logic thoroughly tested.

Tests cover empty keys, correct masking format, and edge cases like short keys. The masking pattern (e.g., "sk-a...cdef") is validated.


342-378: LGTM! Project settings read and merge tested.

Tests verify default settings, reading existing project settings, and merging with defaults for partial settings.


380-464: LGTM! Project updates and directory creation validated.

Tests verify creation, merging, deep merging of board background settings, and automatic .automaker directory creation.


480-616: LGTM! Comprehensive migration testing.

Excellent coverage of localStorage migration scenarios:

  • Global settings, credentials, and project settings migration
  • Direct localStorage values (lastProjectDir, recentFolders, worktreePanelCollapsed)
  • Invalid JSON handling
  • Error handling for read-only directories

The tests ensure robust migration with proper error recovery.


625-641: LGTM! Atomic write error handling tested.

Tests verify cleanup behavior when writes fail due to permission errors, using a read-only directory simulation.

@webdevcody webdevcody merged commit ee40f27 into main Dec 20, 2025
4 checks passed
@webdevcody webdevcody deleted the theme-on-boarding branch December 20, 2025 14:18
webdevcody pushed a commit that referenced this pull request Dec 20, 2025
…rage)

This commit addresses CodeRabbit feedback from PR #186 by adding detailed
documentation to all public APIs in the settings module:

**Server-side documentation:**
- SettingsService class: 12 public methods with parameter and return types
- Settings types (settings.ts): All type aliases, interfaces, and constants
  documented with usage context
- Route handlers (8 endpoints): Complete endpoint documentation with request/response
  schemas
- Automaker paths utilities: All 13 path resolution functions fully documented

**Client-side documentation:**
- useSettingsMigration hook: Migration flow and state documented
- Sync functions: Three sync helpers (settings, credentials, project) with usage guidelines
- localStorage constants: Clear documentation of migration keys and cleanup strategy

All docstrings follow JSDoc format with:
- Purpose and behavior description
- Parameter documentation with types
- Return value documentation
- Usage examples where applicable
- Cross-references between related functions

This improves code maintainability, IDE autocomplete, and developer onboarding.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This was referenced Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant