diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 4d7e4043..b1bf9953 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -93,9 +93,10 @@ The Tauri v2 permission model is defined in `src-tauri/capabilities/desktop-main ## Configuration files -- `~/.leanspec/desktop-config.json` — Desktop app configuration and project registry +- `~/.lean-spec/desktop.json` — Desktop app configuration (window size, shortcuts, theme, etc.) +- `~/.lean-spec/projects.json` — Project registry -The config file is automatically created on first launch and updated as you add/remove projects. +These config files are automatically created on first launch and updated as you add/remove projects. ## Features diff --git a/packages/desktop/src-tauri/src/config.rs b/packages/desktop/src-tauri/src/config.rs index 8a6c6c91..8142b33d 100644 --- a/packages/desktop/src-tauri/src/config.rs +++ b/packages/desktop/src-tauri/src/config.rs @@ -8,7 +8,8 @@ use serde::{Deserialize, Serialize}; static CONFIG: Lazy> = Lazy::new(|| RwLock::new(DesktopConfig::load_or_default())); const CONFIG_DIR: &str = ".lean-spec"; -const CONFIG_FILE: &str = "desktop.yaml"; +const CONFIG_FILE: &str = "desktop.json"; +const LEGACY_CONFIG_FILE: &str = "desktop.yaml"; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -98,26 +99,39 @@ impl Default for DesktopConfig { impl DesktopConfig { fn load_or_default() -> Self { let path = config_file_path(); + + // Try JSON first match fs::read_to_string(&path) { - Ok(raw) => match serde_yaml::from_str::(&raw) { + Ok(raw) => match serde_json::from_str::(&raw) { Ok(mut config) => { normalize_config(&mut config); - config + return config; } Err(error) => { - eprintln!("Failed to parse desktop config: {error}"); - Self::default() + eprintln!("Failed to parse desktop config as JSON: {error}"); + eprintln!("Will attempt migration from legacy YAML format"); } }, - Err(_) => Self::default(), + Err(_) => {} } + + // Migration: Try legacy YAML + if let Some(legacy_config) = load_legacy_yaml() { + eprintln!("Migrating desktop config from YAML to JSON format"); + legacy_config.persist(); // Save as JSON + backup_legacy_yaml(); + eprintln!("Migration complete: desktop.yaml → desktop.json"); + return legacy_config; + } + + Self::default() } fn persist(&self) { if let Some(dir) = config_dir() { if fs::create_dir_all(&dir).is_ok() { let file = dir.join(CONFIG_FILE); - if let Ok(serialized) = serde_yaml::to_string(self) { + if let Ok(serialized) = serde_json::to_string_pretty(self) { if let Err(error) = fs::write(file, serialized) { eprintln!("Unable to write desktop config: {error}"); } @@ -137,6 +151,26 @@ fn normalize_config(config: &mut DesktopConfig) { } } +fn load_legacy_yaml() -> Option { + let dir = config_dir()?; + let path = dir.join(LEGACY_CONFIG_FILE); + let raw = fs::read_to_string(path).ok()?; + let mut config: DesktopConfig = serde_yaml::from_str(&raw).ok()?; + normalize_config(&mut config); + Some(config) +} + +fn backup_legacy_yaml() { + if let Some(dir) = config_dir() { + let legacy = dir.join(LEGACY_CONFIG_FILE); + let backup = dir.join("desktop.yaml.bak"); + match fs::rename(&legacy, &backup) { + Ok(_) => eprintln!("Legacy config backed up: desktop.yaml.bak"), + Err(error) => eprintln!("Failed to backup legacy config: {error}"), + } + } +} + pub fn config_dir() -> Option { home_dir().map(|home| home.join(CONFIG_DIR)) } diff --git a/specs/148-leanspec-desktop-app/README.md b/specs/148-leanspec-desktop-app/README.md index e527c3ce..fc124ecf 100644 --- a/specs/148-leanspec-desktop-app/README.md +++ b/specs/148-leanspec-desktop-app/README.md @@ -51,10 +51,10 @@ The current web-based UI (`lean-spec ui`) requires: ## Implementation Summary (Dec 10, 2025) - **New package:** `packages/desktop` ships a Vite-powered chrome plus a Rust/Tauri backend. The shell embeds the existing Next.js UI by launching its standalone server in the background (dev uses `pnpm --filter @leanspec/ui dev`, production bundles `.next/standalone`). -- **Windowing:** Frameless window with custom title bar + native controls, backed by `tauri-plugin-window-state` for automatic persistence and close-to-tray behavior (configurable via `desktop.yaml`). +- **Windowing:** Frameless window with custom title bar + native controls, backed by `tauri-plugin-window-state` for automatic persistence and close-to-tray behavior (configurable via `desktop.json`). - **Project registry:** Rust port of the project registry keeps `~/.lean-spec/projects.json` in sync, validates folders, and exposes commands for refresh/add/switch. Config-driven active project switches restart the embedded UI with the right `SPECS_DIR`. - **Tray + shortcuts:** Dedicated modules (`tray.rs`, `shortcuts.rs`) manage recent-project menus, quick actions (open, add, refresh, check for updates), and global shortcuts (`Cmd/Ctrl+Shift+L/K/N`). Frontend listeners open the project switcher or project picker when shortcuts fire. -- **Notifications + updater:** Desktop emits OS notifications on project changes and wires a `desktop_check_updates` command to the Tauri updater so tray actions can trigger update checks. Auto-update channels (`stable`/`beta`) live in `desktop.yaml`. +- **Notifications + updater:** Desktop emits OS notifications on project changes and wires a `desktop_check_updates` command to the Tauri updater so tray actions can trigger update checks. Auto-update channels (`stable`/`beta`) live in `desktop.json`. - **Documentation:** Root `README.md` and `packages/desktop/README.md` describe the desktop workflow. A helper script (`pnpm prepare:ui`) copies the Next standalone build so `pnpm build:desktop` produces platform bundles. ### Developer Workflow @@ -237,33 +237,35 @@ The desktop app wraps `@leanspec/ui` with minimal changes: ### Configuration -**Desktop-Specific Config (~/.lean-spec/desktop.yaml):** -```yaml -window: - width: 1400 - height: 900 - x: 100 - y: 100 - maximized: false - -behavior: - startMinimized: false - minimizeToTray: true - launchAtLogin: false - -shortcuts: - global: - toggleWindow: "CommandOrControl+Shift+L" - quickSwitcher: "CommandOrControl+Shift+K" - newSpec: "CommandOrControl+Shift+N" - -updates: - autoCheck: true - autoInstall: false - channel: "stable" # or "beta" - -appearance: - theme: "system" # "light", "dark", or "system" +**Desktop-Specific Config (~/.lean-spec/desktop.json):** +```json +{ + "window": { + "width": 1400, + "height": 900, + "x": 100, + "y": 100, + "maximized": false + }, + "behavior": { + "startMinimized": false, + "minimizeToTray": true, + "launchAtLogin": false + }, + "shortcuts": { + "toggleWindow": "CommandOrControl+Shift+L", + "quickSwitcher": "CommandOrControl+Shift+K", + "newSpec": "CommandOrControl+Shift+N" + }, + "updates": { + "autoCheck": true, + "autoInstall": false, + "channel": "stable" + }, + "appearance": { + "theme": "system" + } +} ``` ### Distribution @@ -325,7 +327,7 @@ appearance: **Day 10: Notifications** - [x] Implement native OS notifications (project add/switch events) -- [ ] Add notification preferences _(desktop.yaml toggle TBD)_ +- [ ] Add notification preferences _(desktop.json toggle TBD)_ - [ ] Test on all platforms ### Phase 3: Polish & Distribution (Week 3) @@ -334,7 +336,7 @@ appearance: - [x] Configure Tauri updater (endpoints + channel config) - [ ] Set up update server (GitHub Releases) - [x] Implement update UI hooks (tray action + command) -- [x] Add update channel selection (stable/beta via `desktop.yaml`) +- [x] Add update channel selection (stable/beta via `desktop.json`) - [ ] Test update flow end-to-end _(requires release infra)_ **Day 13-14: Build & Release** diff --git a/specs/162-desktop-config-json-migration/README.md b/specs/162-desktop-config-json-migration/README.md index 2f4d8480..6aafcd08 100644 --- a/specs/162-desktop-config-json-migration/README.md +++ b/specs/162-desktop-config-json-migration/README.md @@ -1,5 +1,5 @@ --- -status: planned +status: complete created: '2025-12-10' tags: - desktop @@ -12,12 +12,17 @@ created_at: '2025-12-10T08:49:08.237Z' depends_on: - 147-json-config-format - 148-leanspec-desktop-app -updated_at: '2025-12-10T08:49:08.287Z' +updated_at: '2025-12-18T09:56:48.390Z' +completed_at: '2025-12-18T09:56:48.390Z' +completed: '2025-12-18' +transitions: + - status: complete + at: '2025-12-18T09:56:48.390Z' --- # Migrate Desktop Config from YAML to JSON -> **Status**: 🗓️ Planned · **Priority**: High · **Created**: 2025-12-10 · **Tags**: desktop, config, migration, breaking-change, consistency +> **Status**: ✅ Complete · **Priority**: High · **Created**: 2025-12-10 · **Tags**: desktop, config, migration, breaking-change, consistency ## Overview diff --git a/specs/168-leanspec-orchestration-platform/DESIGN.md b/specs/168-leanspec-orchestration-platform/DESIGN.md index 11968155..b2144d98 100644 --- a/specs/168-leanspec-orchestration-platform/DESIGN.md +++ b/specs/168-leanspec-orchestration-platform/DESIGN.md @@ -520,82 +520,84 @@ Continue to testing? [Y/n]: y ## Configuration -### Desktop Config (~/.lean-spec/desktop.yaml) - -```yaml -orchestration: - defaultAgent: claude - guidedMode: true # Pause between phases - autoValidate: true # Run validation after implementation - autoComplete: false # Require manual completion - -agents: - claude: - enabled: true - priority: 1 - models: - default: claude-3-5-sonnet-20241022 - fast: claude-3-5-haiku-20241022 - copilot: - enabled: true - priority: 2 - cursor: - enabled: false - aider: - enabled: true - priority: 3 - -agentRelay: - endpoint: "http://localhost:8080" - apiKey: "${AGENT_RELAY_API_KEY}" - timeout: 300000 # 5 minutes - retryAttempts: 3 - retryDelay: 5000 # 5 seconds - -devlog: - endpoint: "http://localhost:9090" - apiKey: "${DEVLOG_API_KEY}" - enabled: true - batchSize: 10 # Batch telemetry events - flushInterval: 5000 # Flush every 5 seconds - -validation: - autoRun: true - runTests: true - runLinters: true - runTypeCheck: true - aiReview: true - - # Test runner - testCommand: "npm test" - testCoverage: true - - # Linter - linterCommand: "npm run lint" - linterIgnore: ["*.test.ts", "*.spec.ts"] - - # Type checker - typeCheckCommand: "tsc --noEmit" - -notifications: - phaseComplete: true - sessionComplete: true - validationFailed: true - validationPassed: false # Only notify on failure - -shortcuts: - implement: "CommandOrControl+Shift+I" - validate: "CommandOrControl+Shift+V" - newSpec: "CommandOrControl+Shift+N" - quickSwitcher: "CommandOrControl+Shift+K" - -ui: - theme: "system" # light, dark, or system - outputFontSize: 13 - outputFontFamily: "Menlo, Monaco, 'Courier New', monospace" - animatePhaseProgress: true - showTokenCount: true - showDuration: true +### Desktop Config (~/.lean-spec/desktop.json) + +```json +{ + "orchestration": { + "defaultAgent": "claude", + "guidedMode": true, + "autoValidate": true, + "autoComplete": false + }, + "agents": { + "claude": { + "enabled": true, + "priority": 1, + "models": { + "default": "claude-3-5-sonnet-20241022", + "fast": "claude-3-5-haiku-20241022" + } + }, + "copilot": { + "enabled": true, + "priority": 2 + }, + "cursor": { + "enabled": false + }, + "aider": { + "enabled": true, + "priority": 3 + } + }, + "agentRelay": { + "endpoint": "http://localhost:8080", + "apiKey": "${AGENT_RELAY_API_KEY}", + "timeout": 300000, + "retryAttempts": 3, + "retryDelay": 5000 + }, + "devlog": { + "endpoint": "http://localhost:9090", + "apiKey": "${DEVLOG_API_KEY}", + "enabled": true, + "batchSize": 10, + "flushInterval": 5000 + }, + "validation": { + "autoRun": true, + "runTests": true, + "runLinters": true, + "runTypeCheck": true, + "aiReview": true, + "testCommand": "npm test", + "testCoverage": true, + "linterCommand": "npm run lint", + "linterIgnore": ["*.test.ts", "*.spec.ts"], + "typeCheckCommand": "tsc --noEmit" + }, + "notifications": { + "phaseComplete": true, + "sessionComplete": true, + "validationFailed": true, + "validationPassed": false + }, + "shortcuts": { + "implement": "CommandOrControl+Shift+I", + "validate": "CommandOrControl+Shift+V", + "newSpec": "CommandOrControl+Shift+N", + "quickSwitcher": "CommandOrControl+Shift+K" + }, + "ui": { + "theme": "system", + "outputFontSize": 13, + "outputFontFamily": "Menlo, Monaco, 'Courier New', monospace", + "animatePhaseProgress": true, + "showTokenCount": true, + "showDuration": true + } +} ``` ### Project-Specific Config (.leanspec/config.yaml)