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.
- package/.env +4 -3
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
- package/apps/dashboard/app/demo/gates/page.tsx +43 -45
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +80 -4
- package/apps/dashboard/app/install-claude/page.tsx +4 -6
- package/apps/dashboard/app/login/page.tsx +72 -54
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +61 -13
- package/apps/dashboard/app/signup/page.tsx +242 -0
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +13 -16
- package/apps/dashboard/app/work/[id]/page.tsx +117 -118
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +92 -85
- package/apps/dashboard/components/CardMenu.tsx +45 -12
- package/apps/dashboard/components/ClaudePanel.tsx +771 -850
- package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
- package/apps/dashboard/components/CopyableId.tsx +3 -4
- package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
- package/apps/dashboard/components/DragContext.tsx +134 -63
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +6 -7
- package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +26 -7
- package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
- package/apps/dashboard/components/EpicGroup.tsx +359 -0
- package/apps/dashboard/components/GateCard.tsx +79 -17
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
- package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
- package/apps/dashboard/components/JettyLoader.tsx +37 -0
- package/apps/dashboard/components/KanbanBoard.tsx +368 -958
- package/apps/dashboard/components/KanbanCard.tsx +740 -0
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
- package/apps/dashboard/components/MainNav.tsx +38 -73
- package/apps/dashboard/components/MessageBlock.tsx +468 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -16
- package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
- package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
- package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
- package/apps/dashboard/components/ReviewFooter.tsx +139 -0
- package/apps/dashboard/components/SessionList.tsx +19 -19
- package/apps/dashboard/components/SubscribeContent.tsx +91 -47
- package/apps/dashboard/components/TestTree.tsx +16 -16
- package/apps/dashboard/components/TipCard.tsx +16 -17
- package/apps/dashboard/components/Toast.tsx +5 -6
- package/apps/dashboard/components/TypeIcon.tsx +55 -0
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
- package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
- package/apps/dashboard/components/WorkItemTree.tsx +11 -32
- package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
- package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
- package/apps/dashboard/contexts/UsageContext.tsx +87 -32
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1265
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +270 -0
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +308 -134
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -32
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
- package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
- package/apps/dashboard/public/in-flight-seagull.png +0 -0
- package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
- package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
- package/apps/dashboard/public/jettypod_logo.png +0 -0
- package/apps/dashboard/public/pier-icon.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +228 -80
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -0
- package/cucumber.js +9 -3
- package/docs/COMMAND_REFERENCE.md +34 -0
- package/hooks/post-checkout +32 -75
- package/hooks/post-merge +111 -10
- package/jest.setup.js +1 -0
- package/jettypod.js +145 -116
- package/lib/bdd-preflight.js +96 -0
- package/lib/chore-taxonomy.js +33 -10
- package/lib/database.js +36 -16
- package/lib/db-watcher.js +1 -1
- package/lib/git-hooks/pre-commit +1 -1
- package/lib/jettypod-backup.js +27 -4
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/027-plan-at-creation-column.js +3 -1
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +10 -5
- package/lib/seed-onboarding.js +1 -1
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +144 -19
- package/lib/work-tracking/index.js +148 -27
- package/lib/worktree-diagnostics.js +16 -16
- package/lib/worktree-facade.js +1 -1
- package/lib/worktree-manager.js +8 -8
- package/lib/worktree-reconciler.js +5 -5
- package/package.json +9 -2
- package/scripts/ndjson-to-cucumber-json.js +152 -0
- package/scripts/postinstall.js +25 -0
- package/skills-templates/bug-mode/SKILL.md +79 -20
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +171 -69
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +82 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +173 -74
- package/skills-templates/production-mode/SKILL.md +69 -49
- package/skills-templates/request-routing/SKILL.md +4 -4
- package/skills-templates/simple-improvement/SKILL.md +74 -29
- package/skills-templates/speed-mode/SKILL.md +217 -141
- package/skills-templates/stable-mode/SKILL.md +148 -89
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -43
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
- package/apps/dashboard/electron/ipc-handlers.js +0 -1028
- package/apps/dashboard/electron/main.js +0 -2124
- package/apps/dashboard/electron/preload.js +0 -123
- package/apps/dashboard/electron/session-manager.js +0 -141
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/claude-process-manager.ts +0 -492
- package/apps/dashboard/lib/db-bridge.ts +0 -282
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -50
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
- 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.
|