jettypod 4.4.118 → 4.4.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/.env +4 -3
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -0,0 +1,840 @@
1
+ # JettyPod Stack Migration: Electron/Next.js → Tauri/Vite/Rust
2
+
3
+ ## Executive Summary
4
+
5
+ Migrate JettyPod from Electron + Next.js + Node.js to Tauri + Vite + React + Rust. The goal is dramatically lower resource usage (RAM, disk, startup time), cross-platform readiness (especially Windows), and a faster CLI — while preserving all current functionality.
6
+
7
+ **Current stack:** Electron 32 / Next.js 16 / React 19 / Node.js CLI / SQLite (two drivers) / Cloudflare Worker
8
+ **Target stack:** Tauri 2 / Vite / React 19 / Rust CLI + core / SQLite (rusqlite) / Cloudflare Worker (unchanged)
9
+
10
+ **Estimated total effort:** 5-6 months calendar, ~500-600 hours active development
11
+
12
+ ---
13
+
14
+ ## Current Architecture Snapshot
15
+
16
+ ### What exists today
17
+
18
+ | Layer | Technology | Size |
19
+ |-------|-----------|------|
20
+ | Desktop shell | Electron 32 (Chromium + Node.js) | 5 files, 3,849 lines |
21
+ | Frontend | Next.js 16 + React 19 + Tailwind 4 | 50 components, 40 pages/routes |
22
+ | CLI | Node.js (jettypod.js + lib/) | 3,473 + 2,639 lines + 106 modules |
23
+ | Data | SQLite via sqlite3 (async) + better-sqlite3 (sync) | 30 migrations, WAL mode |
24
+ | Update server | Cloudflare Worker + D1 + R2 + KV | 1,085 lines |
25
+ | Tests | Jest + Cucumber.js | 56 unit, 238 BDD features |
26
+ | Auth | Google OAuth + Email OTP → JWT → local file | jettypod:// protocol handler |
27
+ | Real-time | WebSocket server on port 47808 | Polls SQLite mtime every 500ms |
28
+
29
+ ### Key IPC surface (what Tauri must replace)
30
+
31
+ The Electron main process exposes **52 IPC channels** across these domains:
32
+
33
+ - **Database/Kanban** (18 channels): CRUD for work items, sessions, decisions, env vars
34
+ - **Claude subprocess** (3 channels): spawn, write stdin, kill process tree
35
+ - **Dev server** (4 channels): spawn/kill/status/list for app preview servers
36
+ - **Project management** (5 channels): dialog pickers, recent projects
37
+ - **Claude Code CLI** (5 channels): install, update, auth check, login
38
+ - **Subscription/billing** (4 channels): Stripe checkout, activation, portal
39
+ - **Auth** (7 channels): Google OAuth, token save/get/logout, status checks
40
+ - **Shell** (1 channel): open external URLs
41
+ - **Settings** (3 channels): project name, env vars
42
+ - **Process lifecycle** (2 channels): app version, dev mode detection
43
+
44
+ ### System-level dependencies in Electron
45
+
46
+ These are the things that make migration non-trivial:
47
+
48
+ 1. **Custom protocol handler** (jettypod://) — OAuth callback routing
49
+ 2. **Child process management** — Claude CLI + dev servers with process tree kill
50
+ 3. **Auto-updater** — electron-updater with JWT-authenticated manifest endpoint
51
+ 4. **File system access** — auth.json, subscription.json, config.json, SQLite databases
52
+ 5. **Shell integration** — PATH symlink creation via osascript sudo
53
+ 6. **WebSocket server** — embedded in main process for real-time DB change notifications
54
+ 7. **Native module rebuilds** — better-sqlite3 rebuilt for Electron's Node version
55
+ 8. **Skills sync** — copies bundled skills to ~/.claude/skills/ on launch
56
+ 9. **Menu bar** — native macOS menu with keyboard shortcuts
57
+
58
+ ---
59
+
60
+ ## Target Architecture
61
+
62
+ ```
63
+ ┌─────────────────────────────────────────────────────┐
64
+ │ Tauri Shell │
65
+ │ ┌──────────────┐ ┌─────────────────────────────┐ │
66
+ │ │ Rust Backend │ │ Webview (OS native) │ │
67
+ │ │ │ │ ┌───────────────────────┐ │ │
68
+ │ │ jettypod- │ │ │ Vite + React 19 │ │ │
69
+ │ │ core crate │◄─┤ │ (existing components) │ │ │
70
+ │ │ │ │ │ IPC via @tauri-apps/ │ │ │
71
+ │ │ - SQLite │ │ │ api invoke() │ │ │
72
+ │ │ - Git ops │ │ └───────────────────────┘ │ │
73
+ │ │ - Auth │ │ │ │
74
+ │ │ - Sessions │ └─────────────────────────────┘ │
75
+ │ │ - WS server │ │
76
+ │ └──────┬───────┘ │
77
+ │ │ │
78
+ └─────────┼────────────────────────────────────────────┘
79
+ │ shared crate
80
+ ┌─────────┴───────┐
81
+ │ jettypod-cli │
82
+ │ (Rust binary) │
83
+ │ same core lib │
84
+ └─────────────────┘
85
+ ```
86
+
87
+ ### Rust workspace layout
88
+
89
+ ```
90
+ jettypod-rs/
91
+ Cargo.toml # workspace root
92
+ crates/
93
+ jettypod-core/ # shared library
94
+ src/
95
+ lib.rs
96
+ db/ # SQLite schema, migrations, queries
97
+ git/ # worktree, branch, merge operations
98
+ auth/ # JWT decode, auth.json read/write
99
+ work/ # work item CRUD, status transitions
100
+ sessions/ # Claude session management
101
+ config/ # project config, env vars
102
+ jettypod-cli/ # CLI binary
103
+ src/
104
+ main.rs # clap argument parsing
105
+ commands/ # work, backlog, init, etc.
106
+ jettypod-tauri/ # Tauri app
107
+ src/
108
+ main.rs # Tauri setup, window management
109
+ commands/ # IPC command handlers
110
+ protocol.rs # jettypod:// deep link handler
111
+ updater.rs # auto-update configuration
112
+ processes.rs # Claude CLI + dev server subprocess management
113
+ websocket.rs # real-time DB change notifications
114
+ tauri.conf.json
115
+ frontend/ # Vite + React (moved from apps/dashboard/)
116
+ src/
117
+ components/ # existing 50 components (migrated)
118
+ pages/ # existing routes
119
+ lib/ # utilities
120
+ ipc.ts # Tauri invoke() wrapper (replaces window.electronAPI)
121
+ vite.config.ts
122
+ index.html
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Migration Phases
128
+
129
+ ### Phase 0: Proof of Concept (1 week)
130
+
131
+ **Goal:** Derisk the three hardest problems before committing to the full migration. Build a throwaway Tauri app that validates the critical paths.
132
+
133
+ **Duration:** 1 week
134
+
135
+ This phase exists because the biggest risks — auth deep links, subprocess management, and IPC reliability — are all Tauri-specific unknowns. Spending 1 week now saves potentially months of rework later.
136
+
137
+ #### What to build
138
+
139
+ A minimal Tauri 2 app (no real UI) that proves:
140
+
141
+ 1. **Auth flow end-to-end**
142
+ - Register `jettypod-test://` as a custom protocol
143
+ - Test cold start: app not running → browser opens `jettypod-test://auth/callback?token=xyz` → app launches → receives URL
144
+ - Test warm start: app running → browser opens URL → app receives it
145
+ - Verify URL parsing (hostname vs pathname gotcha)
146
+ - Test single-instance behavior (what happens on double-click login?)
147
+ - Implement a launch queue that buffers protocol events arriving before handlers initialize
148
+
149
+ 2. **Subprocess management**
150
+ - Spawn a long-running child process (simulate Claude CLI)
151
+ - Stream its stdout/stderr to the webview via Tauri events
152
+ - Kill the process and ALL its children reliably
153
+ - Test: spawn a process that itself spawns children → kill parent → verify children are dead
154
+ - On macOS: use process groups (`setsid` + `killpg`)
155
+ - Document what the Windows equivalent will need (`CREATE_NEW_PROCESS_GROUP` + `taskkill /T /F`)
156
+
157
+ 3. **One complex IPC round-trip**
158
+ - Open a real `.jettypod/work.db` with rusqlite
159
+ - Implement `get_kanban_data` command that queries and returns structured data
160
+ - Call it from the webview, verify serialization/deserialization
161
+ - Measure latency vs the Electron equivalent
162
+ - Test error propagation (what happens when the DB is locked? corrupt? missing?)
163
+
164
+ #### Acceptance criteria
165
+
166
+ - [ ] Deep link works on cold start (app not running)
167
+ - [ ] Deep link works on warm start (app already running)
168
+ - [ ] Double-activation doesn't create zombie windows
169
+ - [ ] Protocol URL parsed correctly (hostname=auth, path=/callback)
170
+ - [ ] Child process stdout/stderr streams to webview
171
+ - [ ] Process tree kill works (parent + all children)
172
+ - [ ] SQLite query returns correct data through Tauri IPC
173
+ - [ ] IPC error handling works (locked DB, missing file)
174
+
175
+ #### Go/no-go decision
176
+
177
+ If any of these three areas has a fundamental blocker in Tauri 2, we learn it now at 1 week cost instead of at month 3. If all three work, proceed with confidence.
178
+
179
+ ---
180
+
181
+ ### Phase 1: Tauri Shell with Node.js Backend (3-4 weeks)
182
+
183
+ **Goal:** Replace Electron with Tauri while keeping the existing Node.js backend as a sidecar process. This derisks the desktop shell migration without requiring Rust business logic yet.
184
+
185
+ **Duration:** 3-4 weeks
186
+
187
+ **Why this order:** The original plan had "Rust CLI first, then Tauri shell." That's backwards. The desktop shell is where all the risk lives (auth, subprocesses, auto-update, IPC). Port the shell first with a proven backend, then swap the backend to Rust when the shell is stable. This way you're never debugging Tauri quirks AND Rust bugs simultaneously.
188
+
189
+ #### Architecture (transitional)
190
+
191
+ ```
192
+ ┌─────────────────────────────────────────────────────┐
193
+ │ Tauri Shell │
194
+ │ ┌──────────────┐ ┌─────────────────────────────┐ │
195
+ │ │ Thin Rust │ │ Webview (OS native) │ │
196
+ │ │ layer │ │ ┌───────────────────────┐ │ │
197
+ │ │ │ │ │ Existing React/Next │ │ │
198
+ │ │ - Deep links │◄─┤ │ frontend (unchanged) │ │ │
199
+ │ │ - Menus │ │ │ │ │ │
200
+ │ │ - Updater │ │ └───────────────────────┘ │ │
201
+ │ │ - Process │ │ │ │
202
+ │ │ mgmt │ └─────────────────────────────┘ │
203
+ │ └──────┬───────┘ │
204
+ │ │ spawns │
205
+ │ ┌──────┴──────────┐ │
206
+ │ │ Node.js sidecar │ (existing ipc-handlers.js) │
207
+ │ │ - SQLite │ │
208
+ │ │ - Git ops │ │
209
+ │ │ - WebSocket │ │
210
+ │ └─────────────────┘ │
211
+ └─────────────────────────────────────────────────────┘
212
+ ```
213
+
214
+ The Rust layer handles only Tauri-specific concerns (window, menus, deep links, updater, process management). All business logic stays in Node.js as a sidecar process, communicating via local HTTP or stdin/stdout JSON-RPC.
215
+
216
+ #### What to port to Rust
217
+
218
+ Only the Electron-specific parts:
219
+ - Window creation and management
220
+ - Native menu bar
221
+ - Deep link handler (`jettypod://`) with launch queue from Phase 0
222
+ - Auto-updater (tauri-plugin-updater)
223
+ - File dialogs (tauri-plugin-dialog)
224
+ - Shell open (tauri-plugin-shell)
225
+ - Single-instance enforcement
226
+ - Process management for Claude CLI + dev servers (with platform-specific kill from Phase 0)
227
+
228
+ #### What stays in Node.js (for now)
229
+
230
+ - All 18 database IPC handlers → Node.js sidecar
231
+ - Claude session management
232
+ - WebSocket server (port 47808)
233
+ - Work tracking logic
234
+ - Git operations
235
+ - Skills sync
236
+ - Auth file I/O (auth.json, subscription.json)
237
+
238
+ #### IPC bridge approach
239
+
240
+ Two options for Tauri ↔ Node.js sidecar communication:
241
+
242
+ **Option A: Tauri sidecar + JSON-RPC over stdin/stdout**
243
+ - Tauri spawns Node.js script as managed sidecar
244
+ - Frontend calls Tauri command → Rust forwards to Node.js via stdin → response via stdout
245
+ - Pro: clean lifecycle management, Tauri restarts sidecar if it crashes
246
+ - Con: serialization overhead, adds latency
247
+
248
+ **Option B: Node.js HTTP server on localhost**
249
+ - Node.js runs existing logic as local HTTP server (already has API routes)
250
+ - Frontend calls Node.js directly via fetch() for data, Tauri commands for native features
251
+ - Pro: minimal changes to existing code, can reuse Next.js API routes directly
252
+ - Con: port management, CORS, two communication channels
253
+
254
+ **Recommendation:** Option B. The frontend already has API routes that do the exact same thing as the IPC handlers. In this transitional phase, just have the frontend hit localhost for data and Tauri for native features. This minimizes changes.
255
+
256
+ #### Frontend changes
257
+
258
+ Minimal in this phase:
259
+ - Replace `window.electronAPI` calls for native features (dialog, shell, auth protocol) → Tauri `invoke()`
260
+ - Data fetching calls stay as-is (hitting Next.js API routes on localhost)
261
+ - Add Tauri event listeners for deep link callbacks, updater status
262
+
263
+ #### Process management: platform-specific implementation
264
+
265
+ ```rust
266
+ // processes.rs
267
+
268
+ #[cfg(unix)]
269
+ pub fn kill_process_tree(pid: u32) -> Result<()> {
270
+ // Send SIGTERM to the process group
271
+ unsafe { libc::killpg(pid as i32, libc::SIGTERM) };
272
+ // Wait, then SIGKILL if still alive
273
+ std::thread::sleep(Duration::from_secs(2));
274
+ unsafe { libc::killpg(pid as i32, libc::SIGKILL) };
275
+ Ok(())
276
+ }
277
+
278
+ #[cfg(windows)]
279
+ pub fn kill_process_tree(pid: u32) -> Result<()> {
280
+ // taskkill /T terminates process tree, /F forces
281
+ Command::new("taskkill")
282
+ .args(["/PID", &pid.to_string(), "/T", "/F"])
283
+ .output()?;
284
+ Ok(())
285
+ }
286
+ ```
287
+
288
+ **Critical:** When spawning Claude CLI and dev servers, always create a new process group:
289
+ - Unix: `pre_exec(|| { libc::setsid(); Ok(()) })`
290
+ - Windows: `CREATE_NEW_PROCESS_GROUP` flag via `.creation_flags()`
291
+
292
+ #### Acceptance criteria
293
+
294
+ - [ ] App launches with Tauri shell + Node.js sidecar
295
+ - [ ] All pages render (Next.js served from localhost)
296
+ - [ ] Google OAuth flow completes end-to-end
297
+ - [ ] Deep link works: cold start AND warm start
298
+ - [ ] Claude subprocess spawns, streams, killable (including children)
299
+ - [ ] Dev server spawns, streams, killable
300
+ - [ ] Auto-updater checks for updates (new Tauri endpoint)
301
+ - [ ] File dialogs work
302
+ - [ ] Native menu bar with keyboard shortcuts
303
+ - [ ] WebSocket real-time updates work (via Node.js sidecar)
304
+ - [ ] Memory < 200MB idle (Node.js sidecar adds some overhead vs final state)
305
+ - [ ] Code signing + notarization work on macOS
306
+ - [ ] Single-instance enforcement works
307
+
308
+ ---
309
+
310
+ ### Phase 2: Rust Core + CLI (6-8 weeks)
311
+
312
+ **Goal:** Port all Node.js business logic to a Rust jettypod-core crate. Replace the Node.js sidecar in Tauri and ship a standalone Rust CLI.
313
+
314
+ **Duration:** 6-8 weeks (realistic for learning Rust while building)
315
+
316
+ **Prerequisite:** Phase 1 complete (Tauri shell is stable and proven)
317
+
318
+ Now that the desktop shell works and all the scary Tauri integration points are validated, this phase is "just" porting business logic to Rust. It's a lot of code, but the risk is low — if any piece of the Rust port isn't ready, the Node.js sidecar keeps working.
319
+
320
+ #### What to port
321
+
322
+ | Module | Current (Node.js) | Target (Rust) | Complexity | Hours |
323
+ |--------|-------------------|---------------|------------|-------|
324
+ | lib/database.js | sqlite3 async callbacks | rusqlite sync | Medium | 20h |
325
+ | lib/schema.js + lib/migrations/ | 30 migration files | rusqlite + refinery | Medium | 15h |
326
+ | lib/work-tracking/index.js | 2,639 lines CRUD | jettypod-core::work | High | 50h |
327
+ | lib/work-commands/ | Git worktree mgmt | jettypod-core::git via shell | Medium | 25h |
328
+ | lib/config.js | JSON config r/w | serde_json | Low | 5h |
329
+ | jettypod.js | 3,473 lines routing | clap + command modules | High | 40h |
330
+ | WebSocket broadcast | ws, mtime polling | tokio-tungstenite | Low | 10h |
331
+ | Auth file I/O | auth.json r/w | serde + fs | Low | 5h |
332
+ | Session management | SQLite + JSON | jettypod-core::sessions | Medium | 20h |
333
+ | Skills sync | fs.copy | fs::copy | Low | 5h |
334
+
335
+ **Estimated: ~200 hours** (includes Rust learning overhead — fighting the borrow checker, rewriting things that don't compile, etc.)
336
+
337
+ #### Key Rust crates
338
+
339
+ | Crate | Purpose | Replaces |
340
+ |-------|---------|----------|
341
+ | rusqlite | SQLite with bundled libsqlite3 | sqlite3 + better-sqlite3 |
342
+ | clap | CLI argument parsing | manual switch/case |
343
+ | serde + serde_json | JSON serialization | JSON.parse/stringify |
344
+ | jsonwebtoken | JWT decode | manual base64 decode |
345
+ | tokio | Async runtime (WS, subprocesses) | Node.js event loop |
346
+ | tokio-tungstenite | WebSocket server | ws |
347
+ | colored | Terminal colors | chalk |
348
+ | anyhow / thiserror | Error handling | throw/catch |
349
+ | refinery | SQLite migrations | manual _meta versioning |
350
+
351
+ #### Migration strategy within this phase
352
+
353
+ Port module by module, swapping each Tauri command from "forward to Node.js sidecar" to "call jettypod-core directly." This allows incremental validation:
354
+
355
+ 1. **Week 1-2:** Database layer + schema + migrations. Verify against real work.db files.
356
+ 2. **Week 2-4:** Work tracking CRUD. This is the largest piece. Port function by function, test each one.
357
+ 3. **Week 4-5:** Git operations (shell out to git), config, auth file I/O.
358
+ 4. **Week 5-6:** Session management, WebSocket server, skills sync.
359
+ 5. **Week 6-7:** CLI binary (clap routing, wire up all commands).
360
+ 6. **Week 7-8:** Integration testing, edge cases, polish.
361
+
362
+ At each step, the Tauri app can use the Rust implementation for ported modules and the Node.js sidecar for the rest. Once all modules are ported, kill the sidecar.
363
+
364
+ #### Decision: git2 vs shelling out
365
+
366
+ **Decision:** Shell out to git via std::process::Command. Worktrees are central to JettyPod and git2's worktree support is incomplete. Every user already has git installed.
367
+
368
+ #### SQLite concurrent access during transition
369
+
370
+ During this phase, the Rust code (rusqlite) and Node.js sidecar (better-sqlite3) may both access the same work.db. This is dangerous because:
371
+
372
+ - Different bundled SQLite versions can have WAL format incompatibilities
373
+ - Concurrent writers with different busy timeout behaviors can corrupt data
374
+
375
+ **Mitigation:** Module-by-module cutover means we never have both drivers writing to the same tables simultaneously. When a module is ported to Rust, its Node.js equivalent is disabled. The sidecar's responsibility shrinks with each ported module until it's removed entirely.
376
+
377
+ #### Testing strategy
378
+
379
+ - **Rust unit tests:** `cargo test` for every module in jettypod-core. Test against snapshot copies of real work.db files.
380
+ - **CLI integration tests:** Run new Rust CLI against existing .jettypod projects. Compare output with Node.js CLI.
381
+ - **IPC integration:** For each ported Tauri command, verify frontend behavior is unchanged.
382
+ - **Regression suite:** Run existing Jest + Cucumber tests against the Node.js sidecar throughout to catch breakage.
383
+
384
+ #### Acceptance criteria
385
+
386
+ - [ ] All jettypod work subcommands work identically in Rust CLI
387
+ - [ ] jettypod backlog output matches Node.js version
388
+ - [ ] Existing .jettypod/work.db databases read/write correctly
389
+ - [ ] All 30 migrations apply cleanly on existing databases
390
+ - [ ] All 52 IPC channels respond correctly via Rust (no more Node.js sidecar)
391
+ - [ ] WebSocket broadcasts trigger dashboard refreshes
392
+ - [ ] CLI cold start < 10ms (vs current ~200ms)
393
+ - [ ] Existing git hooks continue to work
394
+ - [ ] Node.js sidecar fully removed
395
+ - [ ] `cargo test` passes with >90% coverage on jettypod-core
396
+
397
+ ---
398
+
399
+ ### Phase 3: Drop Next.js for Vite (3-4 weeks)
400
+
401
+ **Goal:** Remove Next.js entirely. Frontend becomes a static Vite + React SPA loaded by Tauri's webview.
402
+
403
+ **Duration:** 3-4 weeks (not 1-2 — data fetching patterns, state hydration, and edge cases add up)
404
+
405
+ **Prerequisite:** Phase 2 complete (Node.js sidecar removed, all data via Tauri IPC)
406
+
407
+ #### Why
408
+
409
+ Next.js currently provides:
410
+ - File-based routing → replaceable with react-router
411
+ - API routes → no longer needed (all data via Tauri IPC since Phase 2)
412
+ - SSR → unused in a desktop app
413
+ - ~15MB bundle size overhead
414
+ - Webpack build pipeline → caused the Tailwind v4 class stripping bug
415
+
416
+ #### What's harder than it looks
417
+
418
+ - **Data fetching patterns change completely.** Next.js server components, `getServerSideProps`, and API route calls all become Tauri `invoke()` calls. This isn't just a find-and-replace — the timing and error handling are different.
419
+ - **State hydration disappears.** Next.js hydrates server-rendered HTML. A Vite SPA renders from scratch. Components that assumed server-rendered initial state will flash or break.
420
+ - **Loading states.** Next.js handled loading between page transitions. With react-router, you need explicit loading UI or Suspense boundaries.
421
+ - **Build-time optimizations gone.** Next.js was doing code splitting, image optimization, and chunk prioritization. Vite does some of this but differently.
422
+
423
+ #### Replacement map
424
+
425
+ | Next.js | Replacement |
426
+ |---------|-------------|
427
+ | app/ routing | react-router-dom v7 |
428
+ | next/link | Link from react-router |
429
+ | next/router | useNavigate() / useParams() |
430
+ | next/image | img tag |
431
+ | API routes (22 files) | Delete — all data via Tauri IPC |
432
+ | next.config.js | vite.config.ts |
433
+ | @tailwindcss/postcss | @tailwindcss/vite |
434
+ | getServerSideProps | useEffect + invoke() |
435
+ | Server components | Client components with loading states |
436
+
437
+ #### Routes
438
+
439
+ ```typescript
440
+ export const router = createBrowserRouter([
441
+ { path: '/', element: <Dashboard /> },
442
+ { path: '/work/:id', element: <WorkItemDetail /> },
443
+ { path: '/decision/:id', element: <DecisionDetail /> },
444
+ { path: '/tests', element: <Tests /> },
445
+ { path: '/settings', element: <Settings /> },
446
+ { path: '/prototypes', element: <Prototypes /> },
447
+ { path: '/welcome', element: <Welcome /> },
448
+ { path: '/login', element: <Login /> },
449
+ { path: '/signup', element: <Signup /> },
450
+ { path: '/subscribe', element: <Subscribe /> },
451
+ { path: '/install-claude', element: <InstallClaude /> },
452
+ { path: '/connect-claude', element: <ConnectClaude /> },
453
+ { path: '/design-system', element: <DesignSystem /> },
454
+ ]);
455
+ ```
456
+
457
+ #### Testing
458
+
459
+ - Component-level tests need updating (no more Next.js test utilities)
460
+ - E2E test framework: move to Playwright or WebdriverIO with Tauri driver
461
+ - Visual regression: screenshot comparison before/after for each page
462
+
463
+ #### Acceptance criteria
464
+
465
+ - [ ] All 14 pages render correctly
466
+ - [ ] Navigation works (history, back/forward)
467
+ - [ ] No Next.js imports remain
468
+ - [ ] No loading state regressions (pages don't flash empty)
469
+ - [ ] Vite HMR < 100ms
470
+ - [ ] Production build < 10 seconds
471
+ - [ ] Tailwind v4 custom classes work (no more stripping)
472
+ - [ ] Bundle < 2MB
473
+ - [ ] E2E test suite passes
474
+
475
+ ---
476
+
477
+ ### Phase 4: SQLite Formalization (1 week, overlaps Phase 2)
478
+
479
+ **Goal:** Single SQLite driver (rusqlite), typed schema, clean migrations.
480
+
481
+ **Duration:** 1 week (happens naturally as part of Phase 2's database porting)
482
+
483
+ #### Problems solved
484
+
485
+ 1. Two drivers (sqlite3 async + better-sqlite3 sync) → single rusqlite
486
+ 2. String literal queries → typed Rust structs with compile-time checking
487
+ 3. Fragile manual _meta versioning → refinery crate with embedded migrations
488
+ 4. Inconsistent WAL/busy-timeout config → single connection setup
489
+
490
+ No schema changes. Same tables, same data.
491
+
492
+ #### Acceptance criteria
493
+
494
+ - [ ] Existing work.db files work with rusqlite
495
+ - [ ] All 30 migrations apply cleanly
496
+ - [ ] CLI and Tauri both access DB without corruption
497
+ - [ ] WAL mode enforced on every connection
498
+ - [ ] Busy timeout set consistently (5000ms)
499
+ - [ ] Foreign keys enforced
500
+
501
+ ---
502
+
503
+ ### Phase 5: Update Server + Release Pipeline (2-3 weeks)
504
+
505
+ **Goal:** Cloudflare Worker serves Tauri update manifests. CI builds for macOS (arm64 + x64) and Linux. Dual update channels during transition.
506
+
507
+ **Duration:** 2-3 weeks
508
+
509
+ #### New update server route
510
+
511
+ ```typescript
512
+ router.get('/updates/tauri/:target/:arch/:current_version', async (req) => {
513
+ const latest = await getLatestVersion(mapPlatform(target, arch));
514
+ if (semver.lte(latest.version, current_version)) return new Response('', { status: 204 });
515
+ return Response.json({ version, url, signature, notes, pub_date });
516
+ });
517
+ ```
518
+
519
+ Keep existing Electron update routes. Track requests by user-agent for deprecation metrics.
520
+
521
+ #### Ed25519 signing
522
+
523
+ ```bash
524
+ # One-time key generation
525
+ tauri signer generate -w ~/.tauri/jettypod.key
526
+ # Public key → tauri.conf.json
527
+ # Private key → TAURI_SIGNING_PRIVATE_KEY in CI secrets
528
+ ```
529
+
530
+ #### CI (GitHub Actions)
531
+
532
+ ```yaml
533
+ name: Release
534
+ on:
535
+ push:
536
+ tags: ['v*']
537
+
538
+ jobs:
539
+ build:
540
+ strategy:
541
+ matrix:
542
+ include:
543
+ - os: macos-latest
544
+ target: aarch64-apple-darwin
545
+ - os: macos-13
546
+ target: x86_64-apple-darwin
547
+ - os: ubuntu-latest
548
+ target: x86_64-unknown-linux-gnu
549
+ runs-on: ${{ matrix.os }}
550
+ steps:
551
+ - uses: actions/checkout@v4
552
+ - uses: dtolnay/rust-toolchain@stable
553
+ - run: pnpm install --frozen-lockfile
554
+ working-directory: frontend
555
+ - uses: tauri-apps/tauri-action@v0
556
+ env:
557
+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
558
+ APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
559
+ APPLE_SIGNING_IDENTITY: "ERIK JOSEPH,BARYN SPANGENBERG (45W2KB54VS)"
560
+ ```
561
+
562
+ Note: Windows is Phase 6. Not in this CI matrix yet.
563
+
564
+ #### Artifacts
565
+
566
+ - macOS: .dmg + .app.tar.gz + .sig
567
+ - Linux: .AppImage + .tar.gz + .sig
568
+
569
+ #### Transition period (run for 6+ months)
570
+
571
+ - Electron users keep getting Electron updates
572
+ - Tauri users get Tauri updates
573
+ - Version telemetry tracks user count per platform
574
+ - After 90%+ on Tauri: ship final Electron update that shows "please download new version"
575
+
576
+ #### Acceptance criteria
577
+
578
+ - [ ] Tauri update endpoint returns correct JSON per platform
579
+ - [ ] Ed25519 signatures verify
580
+ - [ ] Auto-update end-to-end works
581
+ - [ ] macOS signed + notarized
582
+ - [ ] Linux AppImage works on Ubuntu 22.04+
583
+ - [ ] Download page serves Tauri artifacts
584
+ - [ ] Electron update channel still works in parallel
585
+ - [ ] Version telemetry tracks platform distribution
586
+
587
+ ---
588
+
589
+ ### Phase 6: Windows (separate project, ~6 months after macOS stable)
590
+
591
+ **Goal:** Ship Windows desktop app using the same Tauri codebase.
592
+
593
+ **Duration:** 4-6 weeks of focused work, but don't start until macOS + Linux are stable for 3+ months.
594
+
595
+ **Why this is its own phase and not "1-2 months later":**
596
+
597
+ Windows is not "the same code on a different OS." It's a different world:
598
+
599
+ - **Protocol handlers** require Windows Registry entries. Tauri's deep-link plugin handles this, but testing the install/uninstall/upgrade cycle for registry entries is its own project.
600
+ - **Process management** has no process groups. `taskkill /T /F` is the workaround but behaves differently from Unix `killpg`. Claude CLI spawning subprocesses on Windows needs dedicated testing.
601
+ - **File paths** — backslashes, UNC paths, `%APPDATA%` vs `~/Library/Application Support/`. Every hardcoded path assumption needs auditing.
602
+ - **Code signing** — completely different from macOS. You need an EV certificate (expensive, hardware token required for some CAs) or Azure Trusted Signing.
603
+ - **Auto-updater permissions** — Windows UAC prompts during update. NSIS installer behavior. Silent update vs interactive.
604
+ - **WebView2** — pre-installed on Windows 10/11 but the bootstrapper install flow needs testing on older/enterprise machines.
605
+
606
+ #### What to do
607
+
608
+ 1. Add Windows to CI matrix (windows-latest, x86_64-pc-windows-msvc)
609
+ 2. Audit all file path handling for Windows compatibility
610
+ 3. Test deep link registration/deregistration across install/update/uninstall
611
+ 4. Test process tree kill for Claude CLI + dev servers on Windows
612
+ 5. Obtain Windows code signing certificate
613
+ 6. Test auto-update flow including UAC
614
+ 7. Beta test with real Windows users before GA
615
+
616
+ #### Acceptance criteria
617
+
618
+ - [ ] Windows NSIS installer works
619
+ - [ ] Deep links work on Windows
620
+ - [ ] Process tree kill works on Windows
621
+ - [ ] Auto-update works (including UAC)
622
+ - [ ] Code signed with valid certificate
623
+ - [ ] WebView2 bootstrapper installs if missing
624
+ - [ ] All file paths use platform-appropriate separators
625
+
626
+ ---
627
+
628
+ ## Risk Register
629
+
630
+ ### Critical
631
+
632
+ | # | Risk | Likelihood | Impact | Mitigation |
633
+ |---|------|-----------|--------|------------|
634
+ | 1 | **Auth deep link race condition** — protocol URL arrives before Tauri handlers initialize on cold start | High | High | Phase 0 proves this works. Implement launch queue that buffers early events. Test cold/warm start explicitly. |
635
+ | 2 | **Rust learning curve blows timeline** — async, borrow checker, lifetimes | High | High | Phase 1 uses minimal Rust (thin shell). Learning happens gradually during Phase 2 with a working fallback (Node.js sidecar). |
636
+ | 3 | **Auto-update regression** — users stuck on old version permanently | Medium | Critical | Dual update channels for 6+ months. Version telemetry. Never deprecate Electron channel until >90% migrated. |
637
+ | 4 | **Process tree kill fails silently** — zombie Claude/dev-server processes | High | Medium | Phase 0 validates. Platform-specific implementations with process group isolation. Add monitoring that detects orphaned processes. |
638
+
639
+ ### Moderate
640
+
641
+ | # | Risk | Likelihood | Impact | Mitigation |
642
+ |---|------|-----------|--------|------------|
643
+ | 5 | **SQLite version mismatch during transition** — rusqlite and better-sqlite3 bundle different SQLite versions, causing WAL incompatibilities | Medium | High | Module-by-module cutover: never have both drivers writing same tables. Verify SQLite versions match or test WAL compatibility explicitly. |
644
+ | 6 | **Next.js removal harder than expected** — data fetching, hydration, loading states all change | High | Medium | Budget 3-4 weeks not 1-2. Port page by page, test each one. Don't rush this. |
645
+ | 7 | **Feature freeze during Phase 1** — desktop shell in flux | Medium | High | Phase 1 uses Node.js sidecar, so most features still work. Only native-feature changes are frozen. |
646
+ | 8 | **Two apps in the wild for extended period** — support burden, bug duplication | Medium | Medium | Accept this. Plan for 6+ months of dual support. Final Electron version redirects to download page. |
647
+ | 9 | **E2E test gap** — Electron spectron tests don't apply, new framework needed | High | Medium | Add to Phase 3. Choose Playwright or WebdriverIO with Tauri driver early. |
648
+
649
+ ---
650
+
651
+ ## Decision Log
652
+
653
+ ### D1: Git operations
654
+ **Decision:** Shell out to git CLI. git2 has incomplete worktree support.
655
+
656
+ ### D2: Async runtime
657
+ **Decision:** Hybrid. rusqlite sync for DB. std::process::Command sync for git. Tokio (bundled by Tauri) for WebSocket + subprocess streaming.
658
+
659
+ ### D3: Package manager
660
+ **Decision:** Switch to pnpm. Faster installs, stricter hoisting. Rebuilding pipeline anyway.
661
+
662
+ ### D4: Transition strategy
663
+ **Decision:** Gradual with extended overlap. Tauri as new download. Electron updates for 6+ months. Final Electron version redirects users after >90% migrated.
664
+
665
+ ### D5: Windows timeline
666
+ **Decision:** Separate phase (Phase 6). Ship macOS + Linux first. Windows 6+ months after macOS Tauri is stable. Protocol handlers, process management, and code signing are fundamentally different.
667
+
668
+ ### D6: Phase ordering
669
+ **Decision:** Tauri shell first (with Node.js sidecar), then Rust core. Derisks the desktop shell before investing in Rust business logic. If Tauri has a deal-breaker, we find out in week 2 instead of month 3.
670
+
671
+ ### D7: Sidecar communication (Phase 1)
672
+ **Decision:** Node.js runs as localhost HTTP server (reuses existing Next.js API routes). Frontend calls localhost for data, Tauri invoke() for native features. Minimal changes to existing code.
673
+
674
+ ---
675
+
676
+ ## Auth Flow in Tauri (Critical Path)
677
+
678
+ ```
679
+ User Browser Update Server macOS Tauri App
680
+ │ │ │ │ │
681
+ │ click login │ │ │ │
682
+ │──────────────►│ GET /auth/google │ │
683
+ │ │───────────────►│ │ │
684
+ │ │ 302 → Google │ │ │
685
+ │ │◄───────────────│ │ │
686
+ │ consent │ │ │ │
687
+ │──────────────►│ callback │ │ │
688
+ │ │───────────────►│ exchange + JWT │ │
689
+ │ │ redirect to │ │ │
690
+ │ │ jettypod://auth/callback?token= │ │
691
+ │ │◄───────────────│ │ │
692
+ │ │ open jettypod://... │ │
693
+ │ │─────────────────────────────────►│ │
694
+ │ │ │ │ deep_link event│
695
+ │ │ │ │───────────────►│
696
+ │ │ │ │ │
697
+ │ │ │ ┌────────┤ IF COLD START:│
698
+ │ │ │ │ launch │ buffer in │
699
+ │ │ │ │ queue │ launch queue, │
700
+ │ │ │ │ │ process after │
701
+ │ │ │ └────────┤ init complete │
702
+ │ │ │ │ │
703
+ │ │ │ │ parse URL │
704
+ │ │ │ │ hostname=auth │
705
+ │ │ │ │ path=/callback│
706
+ │ │ │ │ save auth.json│
707
+ │ dashboard │ │ │ emit navigate │
708
+ │◄─────────────────────────────────────────────────────────────────│
709
+ ```
710
+
711
+ **Gotcha:** new URL('jettypod://auth/callback?token=xyz') parses hostname as 'auth', pathname as '/callback'. NOT pathname '/auth/callback'. Parse carefully.
712
+
713
+ **Launch queue implementation:**
714
+
715
+ ```rust
716
+ use std::sync::Mutex;
717
+ use once_cell::sync::Lazy;
718
+
719
+ static LAUNCH_QUEUE: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
720
+ static APP_READY: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
721
+
722
+ pub fn queue_or_handle_url(url: String, app: Option<&AppHandle>) {
723
+ let ready = APP_READY.lock().unwrap();
724
+ if *ready {
725
+ if let Some(app) = app {
726
+ handle_deep_link(app, &url);
727
+ }
728
+ } else {
729
+ LAUNCH_QUEUE.lock().unwrap().push(url);
730
+ }
731
+ }
732
+
733
+ pub fn flush_launch_queue(app: &AppHandle) {
734
+ *APP_READY.lock().unwrap() = true;
735
+ let urls: Vec<String> = LAUNCH_QUEUE.lock().unwrap().drain(..).collect();
736
+ for url in urls {
737
+ handle_deep_link(app, &url);
738
+ }
739
+ }
740
+ ```
741
+
742
+ ---
743
+
744
+ ## Timeline
745
+
746
+ ```
747
+ Week: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
748
+ ├P0─┤
749
+ ├────Phase 1: Tauri Shell + Node.js Sidecar────┤
750
+ ├──────────Phase 2: Rust Core + CLI──────────────────┤
751
+ ├P4┤ ├──Phase 3: Vite──────┤
752
+ ├───Phase 5: Pipeline────┤
753
+
754
+ ... 6 months gap ...
755
+
756
+ ├──Phase 6: Windows──┤
757
+ ```
758
+
759
+ | Phase | Calendar | Hours | Blocks |
760
+ |-------|----------|-------|--------|
761
+ | 0: POC | 1 wk | ~30h | Everything (go/no-go) |
762
+ | 1: Tauri shell | 3-4 wks | ~100h | Phase 3 |
763
+ | 2: Rust core + CLI | 6-8 wks | ~200h | Phase 3 (sidecar removal) |
764
+ | 3: Drop Next.js | 3-4 wks | ~80h | — |
765
+ | 4: SQLite | 1 wk | ~20h | — (overlaps Phase 2) |
766
+ | 5: Pipeline | 2-3 wks | ~50h | Shipping |
767
+ | 6: Windows | 4-6 wks | ~100h | — (months later) |
768
+
769
+ **Phases 0-5 total: ~480 hours, 16-20 weeks calendar (~4-5 months)**
770
+ **Including Phase 6: ~580 hours, shipping Windows ~10-12 months from start**
771
+
772
+ ---
773
+
774
+ ## Rollback Strategy
775
+
776
+ | Phase | Rollback |
777
+ |-------|----------|
778
+ | 0: POC | It's throwaway code. Delete it. |
779
+ | 1: Tauri shell | Users stay on Electron. Tauri is a separate download. |
780
+ | 2: Rust core | Keep Node.js sidecar running. Swap failed Rust modules back to Node.js. |
781
+ | 3: Drop Next.js | Git revert. |
782
+ | 4: SQLite | Schema unchanged. Keep old drivers. |
783
+ | 5: Pipeline | Electron pipeline still works in parallel. |
784
+ | 6: Windows | Don't ship it. macOS + Linux are unaffected. |
785
+
786
+ **Full abort at any phase:** Electron + Node.js CLI continue working. No data migration occurred (schema unchanged throughout). Delete jettypod-rs/. Only cost is time.
787
+
788
+ The key advantage of the revised phase ordering: Phase 1's Node.js sidecar means you always have a working fallback. You're never in a state where both the desktop shell AND the business logic are half-ported.
789
+
790
+ ---
791
+
792
+ ## Testing Strategy
793
+
794
+ ### Phase 0
795
+ - Manual testing of POC scenarios (auth, subprocess, IPC)
796
+ - Automated test for deep link cold/warm start
797
+
798
+ ### Phase 1
799
+ - Existing Jest + Cucumber tests continue running against Node.js sidecar
800
+ - Manual testing of native features (menus, dialogs, deep links)
801
+ - Subprocess kill verification script (spawn → kill → check children)
802
+
803
+ ### Phase 2
804
+ - `cargo test` for every jettypod-core module
805
+ - Snapshot testing: compare Rust CLI output against Node.js CLI for every command
806
+ - Integration tests: run Rust CLI against real .jettypod projects
807
+ - Regression: keep running Jest/Cucumber against Node.js until fully ported
808
+
809
+ ### Phase 3
810
+ - Component tests updated for Vite (remove Next.js test utilities)
811
+ - E2E framework: Playwright with Tauri WebDriver
812
+ - Visual regression: screenshot comparison for each page before/after
813
+ - Loading state audit: verify no pages flash empty
814
+
815
+ ### Phase 5
816
+ - Update server integration tests (mock Tauri client checks for updates)
817
+ - Signing verification tests
818
+ - CI smoke tests on each platform
819
+
820
+ ### Phase 6
821
+ - Windows-specific test suite:
822
+ - Protocol handler registration/deregistration
823
+ - Process tree kill
824
+ - UAC behavior during update
825
+ - File path handling (backslash, UNC, long paths)
826
+
827
+ ---
828
+
829
+ ## Post-Migration Wins
830
+
831
+ 1. **Windows desktop app** — Tauri cross-compiles (Phase 6).
832
+ 2. **Linux desktop app** — AppImage, same pipeline.
833
+ 3. **Sub-second startup** — No Chromium cold start.
834
+ 4. **~50MB install** — vs ~250MB Electron.
835
+ 5. **~80-120MB RAM idle** — vs ~400-600MB Electron.
836
+ 6. **< 10ms CLI** — vs ~200ms Node.js.
837
+ 7. **Single SQLite driver** — no dual-driver bugs.
838
+ 8. **Vite HMR < 100ms** — vs 1-3s Next.js webpack.
839
+ 9. **Tailwind v4 works** — Vite plugin, no PostCSS stripping.
840
+ 10. **Shared core library** — CLI + desktop identical business logic.