mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 06:42:03 +00:00
feat: implement API-first settings management and description history tracking
- Migrated settings persistence from localStorage to an API-first approach, ensuring consistency between Electron and web modes. - Introduced `useSettingsSync` hook for automatic synchronization of settings to the server with debouncing. - Enhanced feature update logic to track description changes with a history, allowing for better management of feature descriptions. - Updated various components and services to utilize the new settings structure and description history functionality. - Removed persist middleware from Zustand store, streamlining state management and improving performance.
This commit is contained in:
219
docs/settings-api-migration.md
Normal file
219
docs/settings-api-migration.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Settings API-First Migration
|
||||
|
||||
## Overview
|
||||
|
||||
This document summarizes the migration from localStorage-based settings persistence to an API-first approach. The goal was to ensure settings are consistent between Electron and web modes by using the server's `settings.json` as the single source of truth.
|
||||
|
||||
## Problem
|
||||
|
||||
Previously, settings were stored in two places:
|
||||
|
||||
1. **Browser localStorage** (via Zustand persist middleware) - isolated per browser/Electron instance
|
||||
2. **Server files** (`{DATA_DIR}/settings.json`)
|
||||
|
||||
This caused settings drift between Electron and web modes since each had its own localStorage.
|
||||
|
||||
## Solution
|
||||
|
||||
All settings are now:
|
||||
|
||||
1. **Fetched from the server API** on app startup
|
||||
2. **Synced back to the server API** when changed (with debouncing)
|
||||
3. **No longer cached in localStorage** (persist middleware removed)
|
||||
|
||||
## Files Changed
|
||||
|
||||
### New Files
|
||||
|
||||
#### `apps/ui/src/hooks/use-settings-sync.ts`
|
||||
|
||||
New hook that:
|
||||
|
||||
- Waits for migration to complete before starting
|
||||
- Subscribes to Zustand store changes
|
||||
- Debounces sync to server (1000ms delay)
|
||||
- Handles special case for `currentProjectId` (extracted from `currentProject` object)
|
||||
|
||||
### Modified Files
|
||||
|
||||
#### `apps/ui/src/store/app-store.ts`
|
||||
|
||||
- Removed `persist` middleware from Zustand store
|
||||
- Added new state fields:
|
||||
- `worktreePanelCollapsed: boolean`
|
||||
- `lastProjectDir: string`
|
||||
- `recentFolders: string[]`
|
||||
- Added corresponding setter actions
|
||||
|
||||
#### `apps/ui/src/store/setup-store.ts`
|
||||
|
||||
- Removed `persist` middleware from Zustand store
|
||||
|
||||
#### `apps/ui/src/hooks/use-settings-migration.ts`
|
||||
|
||||
Complete rewrite to:
|
||||
|
||||
- Run in both Electron and web modes (not just Electron)
|
||||
- Parse localStorage data and merge with server data
|
||||
- Prefer server data, but use localStorage for missing arrays (projects, profiles, etc.)
|
||||
- Export `waitForMigrationComplete()` for coordination with sync hook
|
||||
- Handle `currentProjectId` to restore the currently open project
|
||||
|
||||
#### `apps/ui/src/App.tsx`
|
||||
|
||||
- Added `useSettingsSync` hook
|
||||
- Wait for migration to complete before rendering router (prevents race condition)
|
||||
- Show loading state while settings are being fetched
|
||||
|
||||
#### `apps/ui/src/routes/__root.tsx`
|
||||
|
||||
- Removed persist middleware hydration checks (no longer needed)
|
||||
- Set `setupHydrated` to `true` by default
|
||||
|
||||
#### `apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx`
|
||||
|
||||
- Changed from localStorage to app store for `worktreePanelCollapsed`
|
||||
|
||||
#### `apps/ui/src/components/dialogs/file-browser-dialog.tsx`
|
||||
|
||||
- Changed from localStorage to app store for `recentFolders`
|
||||
|
||||
#### `apps/ui/src/lib/workspace-config.ts`
|
||||
|
||||
- Changed from localStorage to app store for `lastProjectDir`
|
||||
|
||||
#### `libs/types/src/settings.ts`
|
||||
|
||||
- Added `currentProjectId: string | null` to `GlobalSettings` interface
|
||||
- Added to `DEFAULT_GLOBAL_SETTINGS`
|
||||
|
||||
## Settings Synced to Server
|
||||
|
||||
The following fields are synced to the server when they change:
|
||||
|
||||
```typescript
|
||||
const SETTINGS_FIELDS_TO_SYNC = [
|
||||
'theme',
|
||||
'sidebarOpen',
|
||||
'chatHistoryOpen',
|
||||
'kanbanCardDetailLevel',
|
||||
'maxConcurrency',
|
||||
'defaultSkipTests',
|
||||
'enableDependencyBlocking',
|
||||
'skipVerificationInAutoMode',
|
||||
'useWorktrees',
|
||||
'showProfilesOnly',
|
||||
'defaultPlanningMode',
|
||||
'defaultRequirePlanApproval',
|
||||
'defaultAIProfileId',
|
||||
'muteDoneSound',
|
||||
'enhancementModel',
|
||||
'validationModel',
|
||||
'phaseModels',
|
||||
'enabledCursorModels',
|
||||
'cursorDefaultModel',
|
||||
'autoLoadClaudeMd',
|
||||
'keyboardShortcuts',
|
||||
'aiProfiles',
|
||||
'mcpServers',
|
||||
'promptCustomization',
|
||||
'projects',
|
||||
'trashedProjects',
|
||||
'currentProjectId',
|
||||
'projectHistory',
|
||||
'projectHistoryIndex',
|
||||
'lastSelectedSessionByProject',
|
||||
'worktreePanelCollapsed',
|
||||
'lastProjectDir',
|
||||
'recentFolders',
|
||||
];
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### On App Startup
|
||||
|
||||
```
|
||||
1. App mounts
|
||||
└── Shows "Loading settings..." screen
|
||||
|
||||
2. useSettingsMigration runs
|
||||
├── Waits for API key initialization
|
||||
├── Reads localStorage data (if any)
|
||||
├── Fetches settings from server API
|
||||
├── Merges data (prefers server, uses localStorage for missing arrays)
|
||||
├── Hydrates Zustand store (including currentProject from currentProjectId)
|
||||
├── Syncs merged data back to server (if needed)
|
||||
└── Signals completion via waitForMigrationComplete()
|
||||
|
||||
3. useSettingsSync initializes
|
||||
├── Waits for migration to complete
|
||||
├── Stores initial state hash
|
||||
└── Starts subscribing to store changes
|
||||
|
||||
4. Router renders
|
||||
├── Root layout reads currentProject (now properly set)
|
||||
└── Navigates to /board if project was open
|
||||
```
|
||||
|
||||
### On Settings Change
|
||||
|
||||
```
|
||||
1. User changes a setting
|
||||
└── Zustand store updates
|
||||
|
||||
2. useSettingsSync detects change
|
||||
├── Debounces for 1000ms
|
||||
└── Syncs to server via API
|
||||
|
||||
3. Server writes to settings.json
|
||||
```
|
||||
|
||||
## Migration Logic
|
||||
|
||||
When merging localStorage with server data:
|
||||
|
||||
1. **Server has data** → Use server data as base
|
||||
2. **Server missing arrays** (projects, aiProfiles, etc.) → Use localStorage arrays
|
||||
3. **Server missing objects** (lastSelectedSessionByProject) → Use localStorage objects
|
||||
4. **Simple values** (lastProjectDir, currentProjectId) → Use localStorage if server is empty
|
||||
|
||||
## Exported Functions
|
||||
|
||||
### `useSettingsMigration()`
|
||||
|
||||
Hook that handles initial settings hydration. Returns:
|
||||
|
||||
- `checked: boolean` - Whether hydration is complete
|
||||
- `migrated: boolean` - Whether data was migrated from localStorage
|
||||
- `error: string | null` - Error message if failed
|
||||
|
||||
### `useSettingsSync()`
|
||||
|
||||
Hook that handles ongoing sync. Returns:
|
||||
|
||||
- `loaded: boolean` - Whether sync is initialized
|
||||
- `syncing: boolean` - Whether currently syncing
|
||||
- `error: string | null` - Error message if failed
|
||||
|
||||
### `waitForMigrationComplete()`
|
||||
|
||||
Returns a Promise that resolves when migration is complete. Used for coordination.
|
||||
|
||||
### `forceSyncSettingsToServer()`
|
||||
|
||||
Manually triggers an immediate sync to server.
|
||||
|
||||
### `refreshSettingsFromServer()`
|
||||
|
||||
Fetches latest settings from server and updates store.
|
||||
|
||||
## Testing
|
||||
|
||||
All 1001 server tests pass after these changes.
|
||||
|
||||
## Notes
|
||||
|
||||
- **sessionStorage** is still used for session-specific state (splash screen shown, auto-mode state)
|
||||
- **Terminal layouts** are stored in the app store per-project (not synced to API - considered transient UI state)
|
||||
- The server's `{DATA_DIR}/settings.json` is the single source of truth
|
||||
Reference in New Issue
Block a user