jettypod 4.4.120 → 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 (208) hide show
  1. package/.env +2 -1
  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 +54 -49
  8. package/apps/dashboard/app/demo/gates/page.tsx +3 -5
  9. package/apps/dashboard/app/design-system/page.tsx +1 -1
  10. package/apps/dashboard/app/globals.css +74 -2
  11. package/apps/dashboard/app/install-claude/page.tsx +3 -5
  12. package/apps/dashboard/app/login/page.tsx +17 -20
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +60 -12
  15. package/apps/dashboard/app/signup/page.tsx +14 -17
  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 +12 -15
  19. package/apps/dashboard/app/work/[id]/page.tsx +90 -75
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +70 -61
  22. package/apps/dashboard/components/CardMenu.tsx +0 -1
  23. package/apps/dashboard/components/ClaudePanel.tsx +541 -283
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
  26. package/apps/dashboard/components/CopyableId.tsx +1 -2
  27. package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
  28. package/apps/dashboard/components/DragContext.tsx +132 -62
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +5 -6
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +0 -1
  34. package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
  35. package/apps/dashboard/components/EpicGroup.tsx +100 -70
  36. package/apps/dashboard/components/GateCard.tsx +0 -1
  37. package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
  39. package/apps/dashboard/components/JettyLoader.tsx +0 -1
  40. package/apps/dashboard/components/KanbanBoard.tsx +319 -173
  41. package/apps/dashboard/components/KanbanCard.tsx +341 -107
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
  44. package/apps/dashboard/components/MainNav.tsx +24 -25
  45. package/apps/dashboard/components/MessageBlock.tsx +93 -16
  46. package/apps/dashboard/components/ModeStartCard.tsx +0 -1
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
  48. package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
  53. package/apps/dashboard/components/ReviewFooter.tsx +12 -14
  54. package/apps/dashboard/components/SessionList.tsx +0 -1
  55. package/apps/dashboard/components/SubscribeContent.tsx +40 -11
  56. package/apps/dashboard/components/TestTree.tsx +1 -2
  57. package/apps/dashboard/components/TipCard.tsx +2 -4
  58. package/apps/dashboard/components/Toast.tsx +0 -1
  59. package/apps/dashboard/components/TypeIcon.tsx +7 -8
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
  62. package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
  63. package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
  64. package/apps/dashboard/components/WorkItemTree.tsx +2 -4
  65. package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
  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 +20 -73
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
  72. package/apps/dashboard/components/ui/Button.tsx +1 -1
  73. package/apps/dashboard/components/ui/Input.tsx +1 -1
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
  77. package/apps/dashboard/contexts/UsageContext.tsx +62 -31
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  81. package/apps/dashboard/index.html +73 -0
  82. package/apps/dashboard/lib/data-bridge.ts +722 -0
  83. package/apps/dashboard/lib/db.ts +69 -1302
  84. package/apps/dashboard/lib/environment-config.ts +173 -0
  85. package/apps/dashboard/lib/environment-verification.ts +119 -0
  86. package/apps/dashboard/lib/kanban-utils.ts +226 -26
  87. package/apps/dashboard/lib/proof-run.ts +495 -0
  88. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  89. package/apps/dashboard/lib/service-recovery.ts +326 -0
  90. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  91. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  92. package/apps/dashboard/lib/session-stream-manager.ts +253 -122
  93. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  94. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  95. package/apps/dashboard/lib/tauri.ts +106 -0
  96. package/apps/dashboard/lib/utils.ts +3 -3
  97. package/apps/dashboard/next-env.d.ts +1 -1
  98. package/apps/dashboard/package.json +21 -33
  99. package/apps/dashboard/public/bug-icon.png +0 -0
  100. package/apps/dashboard/public/buoy-icon.png +0 -0
  101. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  102. package/apps/dashboard/public/pier-icon.png +0 -0
  103. package/apps/dashboard/public/star-icon.png +0 -0
  104. package/apps/dashboard/public/wrench-icon.png +0 -0
  105. package/apps/dashboard/scripts/tauri-build.js +228 -0
  106. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  107. package/apps/dashboard/src/main.tsx +12 -0
  108. package/apps/dashboard/src/router.tsx +107 -0
  109. package/apps/dashboard/src/vite-env.d.ts +1 -0
  110. package/apps/dashboard/tsconfig.json +7 -12
  111. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  112. package/apps/dashboard/vite.config.ts +33 -0
  113. package/apps/update-server/src/index.ts +167 -30
  114. package/claude-hooks/global-guardrails.js +14 -13
  115. package/crates/jettypod-cli/Cargo.toml +19 -0
  116. package/crates/jettypod-cli/src/commands.rs +1249 -0
  117. package/crates/jettypod-cli/src/main.rs +595 -0
  118. package/crates/jettypod-core/Cargo.toml +26 -0
  119. package/crates/jettypod-core/build.rs +98 -0
  120. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  121. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  122. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  123. package/crates/jettypod-core/src/auth.rs +294 -0
  124. package/crates/jettypod-core/src/config.rs +397 -0
  125. package/crates/jettypod-core/src/db/mod.rs +507 -0
  126. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  127. package/crates/jettypod-core/src/db/startup.rs +101 -0
  128. package/crates/jettypod-core/src/db/validate.rs +149 -0
  129. package/crates/jettypod-core/src/error.rs +76 -0
  130. package/crates/jettypod-core/src/git.rs +458 -0
  131. package/crates/jettypod-core/src/lib.rs +20 -0
  132. package/crates/jettypod-core/src/sessions.rs +625 -0
  133. package/crates/jettypod-core/src/skills.rs +556 -0
  134. package/crates/jettypod-core/src/work.rs +1086 -0
  135. package/crates/jettypod-core/src/worktree.rs +628 -0
  136. package/crates/jettypod-core/src/ws.rs +767 -0
  137. package/cucumber-test.cjs +6 -0
  138. package/jettypod.js +96 -4
  139. package/lib/bdd-preflight.js +96 -0
  140. package/lib/merge-lock.js +111 -253
  141. package/lib/migrations/030-rejection-round-columns.js +54 -0
  142. package/lib/migrations/031-session-isolation-index.js +17 -0
  143. package/lib/work-commands/index.js +58 -16
  144. package/lib/work-tracking/index.js +108 -8
  145. package/package.json +1 -1
  146. package/skills-templates/bug-mode/SKILL.md +43 -1
  147. package/skills-templates/chore-mode/SKILL.md +40 -1
  148. package/skills-templates/design-system-selection/SKILL.md +273 -0
  149. package/skills-templates/epic-planning/SKILL.md +14 -0
  150. package/skills-templates/feature-planning/SKILL.md +90 -1
  151. package/skills-templates/production-mode/SKILL.md +20 -0
  152. package/skills-templates/simple-improvement/SKILL.md +39 -2
  153. package/skills-templates/speed-mode/SKILL.md +10 -15
  154. package/skills-templates/stable-mode/SKILL.md +47 -0
  155. package/apps/dashboard/README.md +0 -36
  156. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
  157. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  158. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
  159. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  160. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -525
  161. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  162. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  163. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  164. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  165. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  166. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  167. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  168. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  169. package/apps/dashboard/app/api/tests/route.ts +0 -9
  170. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  171. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  172. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  173. package/apps/dashboard/app/api/usage/route.ts +0 -17
  174. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  175. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  176. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  177. package/apps/dashboard/app/api/work/[id]/route.ts +0 -35
  178. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
  179. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  180. package/apps/dashboard/app/layout.tsx +0 -55
  181. package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
  182. package/apps/dashboard/electron/ipc-handlers.js +0 -1026
  183. package/apps/dashboard/electron/main.js +0 -2306
  184. package/apps/dashboard/electron/preload.js +0 -125
  185. package/apps/dashboard/electron/session-manager.js +0 -163
  186. package/apps/dashboard/electron-builder.config.js +0 -357
  187. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  188. package/apps/dashboard/lib/backlog-parser.ts +0 -50
  189. package/apps/dashboard/lib/claude-process-manager.ts +0 -529
  190. package/apps/dashboard/lib/db-bridge.ts +0 -283
  191. package/apps/dashboard/lib/prototypes.ts +0 -202
  192. package/apps/dashboard/lib/test-results-db.ts +0 -307
  193. package/apps/dashboard/lib/tests.ts +0 -282
  194. package/apps/dashboard/next.config.js +0 -66
  195. package/apps/dashboard/postcss.config.mjs +0 -7
  196. package/apps/dashboard/public/bug-icon.svg +0 -9
  197. package/apps/dashboard/public/buoy-icon.svg +0 -9
  198. package/apps/dashboard/public/file.svg +0 -1
  199. package/apps/dashboard/public/globe.svg +0 -1
  200. package/apps/dashboard/public/in-flight-seagull.svg +0 -9
  201. package/apps/dashboard/public/next.svg +0 -1
  202. package/apps/dashboard/public/pier-icon.svg +0 -14
  203. package/apps/dashboard/public/star-icon.svg +0 -9
  204. package/apps/dashboard/public/vercel.svg +0 -1
  205. package/apps/dashboard/public/window.svg +0 -1
  206. package/apps/dashboard/public/wrench-icon.svg +0 -9
  207. package/apps/dashboard/scripts/download-node.js +0 -104
  208. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
@@ -0,0 +1,102 @@
1
+ // Tauri IPC bridge for auth, project, Claude CLI, and shell operations.
2
+ // Database operations are in data-bridge.ts.
3
+ // Low-level primitives (invoke, isTauri, openShell, openDialog) live in lib/tauri.ts.
4
+
5
+ import { invoke, isTauri, openShell, openDialog as tauriOpenDialog } from './tauri';
6
+
7
+ export { isTauri };
8
+
9
+ // Auth types
10
+ export interface AuthUser {
11
+ email: string;
12
+ plan: string;
13
+ name?: string;
14
+ }
15
+
16
+ export interface AuthStatus {
17
+ authenticated: boolean;
18
+ user: AuthUser | null;
19
+ }
20
+
21
+ // Auth namespace
22
+ export const auth = {
23
+ getStatus: () => invoke<AuthStatus>('auth_get_status'),
24
+ getToken: () => invoke<string | null>('auth_get_token'),
25
+ saveToken: (token: string, user: AuthUser) => invoke<void>('auth_save_token', { token, user }),
26
+ logout: () => invoke<void>('auth_logout'),
27
+ hasLoggedInBefore: () => invoke<boolean>('auth_has_logged_in_before'),
28
+ getPostLoginPath: () => invoke<string | null>('auth_get_post_login_path'),
29
+ setPostLoginPath: (path: string | null) => invoke<void>('auth_set_post_login_path', { path }),
30
+ loginWithGoogle: () => invoke<void>('auth_login_with_google'),
31
+ };
32
+
33
+ // Project types
34
+ export interface RecentProject {
35
+ name: string;
36
+ path: string;
37
+ }
38
+
39
+ // Project namespace
40
+ export const project = {
41
+ getRecent: () => invoke<RecentProject[]>('project_get_recent'),
42
+ addRecent: (name: string, path: string) => invoke<void>('project_add_recent', { name, path }),
43
+ async openDialog(): Promise<{ success: boolean; canceled?: boolean; error?: string }> {
44
+ let selected: string | string[] | null;
45
+ try {
46
+ selected = await tauriOpenDialog({ directory: true, title: 'Open JettyPod Project' });
47
+ } catch (err) {
48
+ const msg = err instanceof Error ? err.message : String(err);
49
+ console.error('Dialog open failed:', msg);
50
+ return { success: false, error: msg };
51
+ }
52
+ if (!selected || (Array.isArray(selected) && selected.length === 0)) {
53
+ return { success: false, canceled: true };
54
+ }
55
+ const projectPath = Array.isArray(selected) ? selected[0] : selected;
56
+ try {
57
+ await invoke('db_init', { projectPath });
58
+ const name = projectPath.split('/').pop() || projectPath;
59
+ await invoke('project_add_recent', { name, path: projectPath });
60
+ return { success: true };
61
+ } catch (err) {
62
+ const msg = err instanceof Error ? err.message : String(err);
63
+ console.error('Project init failed:', msg);
64
+ return { success: false, error: msg };
65
+ }
66
+ },
67
+ async newProject(): Promise<{ success: boolean; canceled?: boolean }> {
68
+ // Same as openDialog — user picks folder, we initialize it
69
+ return project.openDialog();
70
+ },
71
+ async openRecent(path: string): Promise<{ success: boolean }> {
72
+ try {
73
+ await invoke('db_init', { projectPath: path });
74
+ const name = path.split('/').pop() || path;
75
+ await invoke('project_add_recent', { name, path });
76
+ return { success: true };
77
+ } catch {
78
+ return { success: false };
79
+ }
80
+ },
81
+ };
82
+
83
+ // Claude CLI types
84
+ export interface CliResult {
85
+ success: boolean;
86
+ error: string | null;
87
+ output: string | null;
88
+ }
89
+
90
+ // Claude CLI namespace
91
+ export const claudeCode = {
92
+ isAuthenticated: () => invoke<boolean>('claude_code_is_authenticated'),
93
+ login: () => invoke<CliResult>('claude_code_login'),
94
+ install: () => invoke<CliResult>('claude_code_install'),
95
+ update: () => invoke<CliResult>('claude_code_update'),
96
+ };
97
+
98
+ // Shell namespace
99
+ export const shell = {
100
+ openPath: (path: string) => openShell(path),
101
+ openUrl: (url: string) => openShell(url),
102
+ };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Canonical Tauri IPC bridge — single source of truth for all __TAURI__ access.
3
+ *
4
+ * Every module that needs Tauri globals (invoke, listen, shell, dialog)
5
+ * should import from this file instead of touching window.__TAURI__ directly.
6
+ */
7
+
8
+ interface TauriGlobal {
9
+ core: {
10
+ invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
11
+ };
12
+ shell: {
13
+ open: (path: string) => Promise<void>;
14
+ };
15
+ dialog: {
16
+ open: (options?: {
17
+ directory?: boolean;
18
+ multiple?: boolean;
19
+ title?: string;
20
+ }) => Promise<string | string[] | null>;
21
+ };
22
+ event: {
23
+ listen: <T>(
24
+ event: string,
25
+ handler: (event: { payload: T }) => void
26
+ ) => Promise<() => void>;
27
+ };
28
+ }
29
+
30
+ function getTauri(): TauriGlobal {
31
+ return (window as any).__TAURI__;
32
+ }
33
+
34
+ /** Returns true when running inside a Tauri webview. */
35
+ export function isTauri(): boolean {
36
+ return !!(window as any).__TAURI__;
37
+ }
38
+
39
+ /** Invoke a Tauri IPC command. */
40
+ export async function invoke<T>(
41
+ cmd: string,
42
+ args?: Record<string, unknown>
43
+ ): Promise<T> {
44
+ return getTauri().core.invoke(cmd, args);
45
+ }
46
+
47
+ /** Listen for a Tauri event. Returns an unlisten function. */
48
+ export async function listen<T>(
49
+ event: string,
50
+ handler: (event: { payload: T }) => void
51
+ ): Promise<() => void> {
52
+ return getTauri().event.listen(event, handler);
53
+ }
54
+
55
+ /** Invoke a Tauri IPC command with a timeout. Rejects if the call doesn't resolve within timeoutMs. */
56
+ export async function invokeWithTimeout<T>(
57
+ cmd: string,
58
+ args?: Record<string, unknown>,
59
+ timeoutMs: number = 10000
60
+ ): Promise<T> {
61
+ return Promise.race([
62
+ invoke<T>(cmd, args),
63
+ new Promise<never>((_, reject) =>
64
+ setTimeout(() => reject(new Error(`IPC timeout after ${timeoutMs}ms: ${cmd}`)), timeoutMs)
65
+ ),
66
+ ]);
67
+ }
68
+
69
+ // ─── Shell command result type ──────────────────────────────
70
+
71
+ export interface ShellCommandResult {
72
+ stdout: string;
73
+ stderr: string;
74
+ exit_code: number | null;
75
+ }
76
+
77
+ /**
78
+ * Run a shell command and return its output.
79
+ * Used by service recovery for pre-flight checks and remedies.
80
+ */
81
+ export async function runShellCommand(
82
+ command: string,
83
+ args: string[],
84
+ options?: { cwd?: string; timeoutMs?: number }
85
+ ): Promise<ShellCommandResult> {
86
+ return invoke<ShellCommandResult>('run_shell_command', {
87
+ command,
88
+ args,
89
+ cwd: options?.cwd ?? null,
90
+ timeoutMs: options?.timeoutMs ?? null,
91
+ });
92
+ }
93
+
94
+ /** Open a path or URL via the system shell. */
95
+ export function openShell(path: string): Promise<void> {
96
+ return getTauri().shell.open(path);
97
+ }
98
+
99
+ /** Open a native file/directory dialog. */
100
+ export function openDialog(options?: {
101
+ directory?: boolean;
102
+ multiple?: boolean;
103
+ title?: string;
104
+ }): Promise<string | string[] | null> {
105
+ return getTauri().dialog.open(options);
106
+ }
@@ -5,8 +5,8 @@ export function cn(...inputs: ClassValue[]) {
5
5
  return twMerge(clsx(inputs))
6
6
  }
7
7
 
8
+ export const WS_PORT = 47808;
9
+
8
10
  export function getWebSocketUrl(): string {
9
- return typeof window !== 'undefined'
10
- ? `ws://${window.location.hostname}:47808`
11
- : 'ws://localhost:47808';
11
+ return `ws://${window.location.hostname}:${WS_PORT}`;
12
12
  }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/types/routes.d.ts";
3
+ import "./.next/dev/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -1,59 +1,47 @@
1
1
  {
2
2
  "name": "dashboard",
3
- "version": "4.4.120",
3
+ "version": "4.4.121",
4
4
  "private": true,
5
- "main": "electron/main.js",
6
5
  "scripts": {
7
- "dev": "concurrently -n next,ws -c blue,green \"next dev --webpack\" \"node scripts/ws-server.js\"",
8
- "build": "next build --webpack",
9
- "start": "next start",
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview",
10
9
  "lint": "eslint",
11
- "preelectron:dev": "npm rebuild better-sqlite3 --silent",
12
- "electron:dev": "electron .",
13
- "prebuild:node": "node scripts/download-node.js",
14
- "electron:build": "npm run prebuild:node && npm run build && npx electron-builder --config electron-builder.config.js",
15
- "electron:build:mac": "npm run prebuild:node && npm run build && npx electron-builder --config electron-builder.config.js --mac",
16
- "electron:build:mac:arm64": "npm run build && npx electron-builder --config electron-builder.config.js --mac --arm64",
17
- "electron:build:mac:x64": "npm run build && npx electron-builder --config electron-builder.config.js --mac --x64",
18
- "electron:build:mac:universal": "npm run build && npx electron-builder --config electron-builder.config.js --mac --universal",
19
- "electron:build:win": "npm run build && npx electron-builder --config electron-builder.config.js --win",
20
- "electron:build:linux": "npm run build && npx electron-builder --config electron-builder.config.js --linux",
21
- "electron:pack": "npm run build && npx electron-builder --config electron-builder.config.js --dir",
22
- "upload:r2": "node scripts/upload-to-r2.js",
23
- "electron:release": "npm run electron:build:mac && npm run upload:r2"
10
+ "tauri:dev": "cargo tauri dev",
11
+ "tauri:build": "node scripts/tauri-build.js",
12
+ "tauri:build:release": "node scripts/tauri-build.js --release",
13
+ "tauri:build:arm64": "node scripts/tauri-build.js --release --target=arm64",
14
+ "tauri:build:x64": "node scripts/tauri-build.js --release --target=x64",
15
+ "tauri:build:universal": "node scripts/tauri-build.js --release --target=universal",
16
+ "tauri:release": "node scripts/tauri-build.js --release --upload",
17
+ "upload:tauri:r2": "node scripts/upload-tauri-to-r2.js"
24
18
  },
25
19
  "dependencies": {
26
20
  "@dnd-kit/core": "^6.3.1",
27
21
  "@dnd-kit/sortable": "^10.0.0",
28
- "@dnd-kit/utilities": "^3.2.2",
29
- "@tailwindcss/typography": "^0.5.19",
30
- "better-sqlite3": "^12.5.0",
22
+ "@fontsource-variable/geist-mono": "^5.2.7",
23
+ "@tanstack/react-virtual": "^3.13.19",
31
24
  "class-variance-authority": "^0.7.1",
32
25
  "clsx": "^2.1.1",
33
- "electron-updater": "^6.3.9",
26
+ "eventemitter3": "^5.0.4",
34
27
  "framer-motion": "^11.15.0",
35
- "lucide-react": "^0.555.0",
36
- "next": "16.0.6",
37
28
  "react": "19.2.0",
38
29
  "react-dom": "19.2.0",
39
30
  "react-markdown": "^10.1.0",
31
+ "react-router-dom": "^7.13.0",
40
32
  "remark-gfm": "^4.0.1",
41
- "tailwind-merge": "^3.4.0",
42
- "ws": "^8.19.0"
33
+ "tailwind-merge": "^3.4.0"
43
34
  },
44
35
  "devDependencies": {
45
- "@tailwindcss/postcss": "^4",
46
- "@types/better-sqlite3": "^7.6.13",
36
+ "@tailwindcss/vite": "^4.2.0",
47
37
  "@types/node": "^20",
48
38
  "@types/react": "^19",
49
39
  "@types/react-dom": "^19",
50
- "concurrently": "^9.2.1",
51
- "electron": "^32.0.0",
52
- "electron-builder": "^26.4.0",
40
+ "@vitejs/plugin-react-swc": "^4.2.3",
53
41
  "eslint": "^9",
54
- "eslint-config-next": "16.0.6",
55
42
  "tailwindcss": "^4",
56
43
  "tw-animate-css": "^1.4.0",
57
- "typescript": "5.9.3"
44
+ "typescript": "5.9.3",
45
+ "vite": "^7.3.1"
58
46
  }
59
47
  }
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Tauri Build Script
5
+ *
6
+ * Orchestrates the Tauri release build:
7
+ * 1. Validates prerequisites (cargo-tauri, signing keys)
8
+ * 2. Builds the Vite frontend
9
+ * 3. Runs `cargo tauri build` with code signing
10
+ * 4. Optionally uploads to R2
11
+ *
12
+ * Usage:
13
+ * node scripts/tauri-build.js [--release] [--upload] [--target arm64|x64|universal]
14
+ *
15
+ * Environment variables:
16
+ * APPLE_SIGNING_IDENTITY - Code signing identity (default: "-" for ad-hoc)
17
+ * APPLE_TEAM_ID - Apple Developer Team ID (enables notarization)
18
+ * APPLE_ID - Apple ID email (for notarization)
19
+ * APPLE_PASSWORD - App-specific password (for notarization)
20
+ * TAURI_SIGNING_PRIVATE_KEY - Ed25519 private key for update signing
21
+ * TAURI_SIGNING_PRIVATE_KEY_PASSWORD - Password for the signing key
22
+ */
23
+
24
+ const { execSync, spawnSync } = require('child_process');
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+
28
+ const ROOT = path.resolve(__dirname, '..');
29
+ const TAURI_DIR = path.join(ROOT, 'src-tauri');
30
+ const TAURI_CONF = path.join(TAURI_DIR, 'tauri.conf.json');
31
+ const TAURI_PROD_CONF = path.join(TAURI_DIR, 'tauri.prod.conf.json');
32
+ // Cargo uses the workspace-level target/ directory (repo root), not src-tauri/target/
33
+ const WORKSPACE_ROOT = path.resolve(ROOT, '..', '..');
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Argument parsing
37
+ // ---------------------------------------------------------------------------
38
+
39
+ const args = process.argv.slice(2);
40
+ const isRelease = args.includes('--release');
41
+ const doUpload = args.includes('--upload');
42
+ const targetArg = args.find(a => a.startsWith('--target='));
43
+ const target = targetArg ? targetArg.split('=')[1] : null;
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Validation
47
+ // ---------------------------------------------------------------------------
48
+
49
+ function check(label, fn) {
50
+ process.stdout.write(` ${label}... `);
51
+ try {
52
+ const result = fn();
53
+ console.log('āœ…');
54
+ return result;
55
+ } catch (e) {
56
+ console.log('āŒ');
57
+ console.error(` ${e.message}`);
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ function validate() {
63
+ console.log('\nšŸ” Validating prerequisites:\n');
64
+
65
+ check('cargo-tauri CLI', () => {
66
+ try {
67
+ execSync('cargo tauri --version 2>/dev/null', { stdio: 'pipe', env: { ...process.env, PATH: `${process.env.HOME}/.cargo/bin:${process.env.PATH}` } });
68
+ } catch {
69
+ throw new Error('cargo-tauri not found. Install with: cargo install tauri-cli');
70
+ }
71
+ });
72
+
73
+ check('Rust toolchain', () => {
74
+ try {
75
+ execSync('rustc --version 2>/dev/null', { stdio: 'pipe', env: { ...process.env, PATH: `${process.env.HOME}/.cargo/bin:${process.env.PATH}` } });
76
+ } catch {
77
+ throw new Error('Rust not found. Install from https://rustup.rs');
78
+ }
79
+ });
80
+
81
+ check('tauri.conf.json', () => {
82
+ if (!fs.existsSync(TAURI_CONF)) {
83
+ throw new Error(`Missing ${TAURI_CONF}`);
84
+ }
85
+ });
86
+
87
+ if (isRelease) {
88
+ check('Signing identity', () => {
89
+ const identity = process.env.APPLE_SIGNING_IDENTITY;
90
+ if (!identity || identity === '-') {
91
+ console.log('\n āš ļø Using ad-hoc signing. Set APPLE_SIGNING_IDENTITY for production.');
92
+ }
93
+ });
94
+
95
+ if (process.env.APPLE_TEAM_ID) {
96
+ check('Notarization credentials', () => {
97
+ if (!process.env.APPLE_ID) throw new Error('APPLE_ID required for notarization');
98
+ if (!process.env.APPLE_PASSWORD) throw new Error('APPLE_PASSWORD required for notarization');
99
+ });
100
+ }
101
+
102
+ check('Update signing key', () => {
103
+ if (!process.env.TAURI_SIGNING_PRIVATE_KEY) {
104
+ console.log('\n āš ļø No TAURI_SIGNING_PRIVATE_KEY set. Updates won\'t be signed.');
105
+ }
106
+ });
107
+ }
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Build steps
112
+ // ---------------------------------------------------------------------------
113
+
114
+ function runCommand(label, cmd, opts = {}) {
115
+ console.log(`\nšŸ“¦ ${label}...`);
116
+ const env = {
117
+ ...process.env,
118
+ PATH: `${process.env.HOME}/.cargo/bin:${process.env.PATH}`,
119
+ };
120
+
121
+ const result = spawnSync('sh', ['-c', cmd], {
122
+ stdio: 'inherit',
123
+ cwd: opts.cwd || ROOT,
124
+ env,
125
+ });
126
+
127
+ if (result.status !== 0) {
128
+ console.error(`\nāŒ ${label} failed (exit code ${result.status})`);
129
+ process.exit(1);
130
+ }
131
+ }
132
+
133
+ function updateSigningIdentity() {
134
+ // Pass signing identity via APPLE_SIGNING_IDENTITY env var.
135
+ // Tauri reads this automatically — no need to mutate tauri.conf.json.
136
+ const identity = process.env.APPLE_SIGNING_IDENTITY;
137
+ if (identity && identity !== '-') {
138
+ console.log(` Using signing identity: ${identity}`);
139
+ } else {
140
+ console.log(' Using ad-hoc signing (no APPLE_SIGNING_IDENTITY set)');
141
+ }
142
+ }
143
+
144
+ function buildTauri() {
145
+ let buildCmd = 'cargo tauri build';
146
+
147
+ if (target === 'universal') {
148
+ buildCmd += ' --target universal-apple-darwin';
149
+ } else if (target === 'arm64') {
150
+ buildCmd += ' --target aarch64-apple-darwin';
151
+ } else if (target === 'x64') {
152
+ buildCmd += ' --target x86_64-apple-darwin';
153
+ }
154
+
155
+ // Apply production config overlay for release builds
156
+ if (isRelease && fs.existsSync(TAURI_PROD_CONF)) {
157
+ buildCmd += ` --config "${TAURI_PROD_CONF}"`;
158
+ console.log(' Applying production config overlay (JettyPod name + clean icon)');
159
+ }
160
+
161
+ // Pass signing key via environment
162
+ const extraEnv = [];
163
+ if (process.env.TAURI_SIGNING_PRIVATE_KEY) {
164
+ extraEnv.push('TAURI_SIGNING_PRIVATE_KEY');
165
+ }
166
+ if (process.env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD) {
167
+ extraEnv.push('TAURI_SIGNING_PRIVATE_KEY_PASSWORD');
168
+ }
169
+
170
+ runCommand('Building Tauri app', buildCmd, { cwd: TAURI_DIR });
171
+ }
172
+
173
+ function showArtifacts() {
174
+ const targetDir = path.join(WORKSPACE_ROOT, 'target');
175
+ // When cross-compiling, bundles go under target/{triple}/release/bundle/
176
+ const candidates = [
177
+ path.join(targetDir, 'release', 'bundle'),
178
+ path.join(targetDir, 'x86_64-apple-darwin', 'release', 'bundle'),
179
+ path.join(targetDir, 'aarch64-apple-darwin', 'release', 'bundle'),
180
+ path.join(targetDir, 'universal-apple-darwin', 'release', 'bundle'),
181
+ ];
182
+ const bundleDir = candidates.find(d => fs.existsSync(d)) || path.join(targetDir, 'release', 'bundle');
183
+
184
+ console.log('\nāœ… Build complete! Artifacts:');
185
+
186
+ // Check for DMG files
187
+ const dmgDir = path.join(bundleDir, 'dmg');
188
+ if (fs.existsSync(dmgDir)) {
189
+ const dmgs = fs.readdirSync(dmgDir).filter(f => f.endsWith('.dmg'));
190
+ dmgs.forEach(f => {
191
+ const size = (fs.statSync(path.join(dmgDir, f)).size / 1024 / 1024).toFixed(1);
192
+ console.log(` šŸ“€ ${f} (${size} MB)`);
193
+ });
194
+ }
195
+
196
+ // Check for app bundle
197
+ const macosDir = path.join(bundleDir, 'macos');
198
+ if (fs.existsSync(macosDir)) {
199
+ const apps = fs.readdirSync(macosDir).filter(f => f.endsWith('.app'));
200
+ apps.forEach(f => {
201
+ console.log(` šŸ“± ${f}`);
202
+ });
203
+ }
204
+ }
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // Main
208
+ // ---------------------------------------------------------------------------
209
+
210
+ console.log('šŸš€ JettyPod Tauri Build');
211
+ console.log(` Mode: ${isRelease ? 'RELEASE' : 'DEBUG'}`);
212
+ console.log(` Target: ${target || 'native'}`);
213
+ console.log(` Upload: ${doUpload ? 'yes' : 'no'}`);
214
+
215
+ validate();
216
+
217
+ if (isRelease) {
218
+ updateSigningIdentity();
219
+ }
220
+
221
+ buildTauri();
222
+ showArtifacts();
223
+
224
+ if (doUpload) {
225
+ runCommand('Uploading to R2', 'node scripts/upload-tauri-to-r2.js');
226
+ }
227
+
228
+ console.log('\nšŸŽ‰ Done!\n');
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Upload Tauri release artifacts to Cloudflare R2.
5
+ * Run after tauri build: node scripts/upload-tauri-to-r2.js
6
+ *
7
+ * Requires wrangler login or CLOUDFLARE_API_TOKEN env var.
8
+ *
9
+ * Uploads DMG and update signature files to R2 under the
10
+ * tauri/ prefix, organized by target/arch for the updater endpoint.
11
+ */
12
+
13
+ const { execSync } = require('child_process');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const BUCKET_NAME = 'jettypod-releases';
18
+ // Cargo uses the workspace-level target/ directory (repo root), not src-tauri/target/
19
+ const WORKSPACE_ROOT = path.resolve(__dirname, '..', '..', '..');
20
+ const TARGET_DIR = path.join(WORKSPACE_ROOT, 'target');
21
+
22
+ // Tauri puts release bundles under target/release/bundle/
23
+ // or target/{triple}/release/bundle/ for cross-compilation
24
+ function findBundleDir() {
25
+ const candidates = [
26
+ path.join(TARGET_DIR, 'release', 'bundle'),
27
+ path.join(TARGET_DIR, 'aarch64-apple-darwin', 'release', 'bundle'),
28
+ path.join(TARGET_DIR, 'x86_64-apple-darwin', 'release', 'bundle'),
29
+ path.join(TARGET_DIR, 'universal-apple-darwin', 'release', 'bundle'),
30
+ ];
31
+
32
+ for (const dir of candidates) {
33
+ if (fs.existsSync(dir)) return dir;
34
+ }
35
+
36
+ return null;
37
+ }
38
+
39
+ function findArtifacts(bundleDir) {
40
+ const artifacts = [];
41
+
42
+ // DMG files
43
+ const dmgDir = path.join(bundleDir, 'dmg');
44
+ if (fs.existsSync(dmgDir)) {
45
+ for (const f of fs.readdirSync(dmgDir)) {
46
+ if (f.endsWith('.dmg')) {
47
+ artifacts.push({ name: f, path: path.join(dmgDir, f), type: 'dmg' });
48
+ }
49
+ }
50
+ }
51
+
52
+ // Update signature files (.tar.gz and .sig for Tauri updater)
53
+ const macosDir = path.join(bundleDir, 'macos');
54
+ if (fs.existsSync(macosDir)) {
55
+ for (const f of fs.readdirSync(macosDir)) {
56
+ if (f.endsWith('.tar.gz') || f.endsWith('.tar.gz.sig')) {
57
+ artifacts.push({ name: f, path: path.join(macosDir, f), type: 'update' });
58
+ }
59
+ }
60
+ }
61
+
62
+ return artifacts;
63
+ }
64
+
65
+ function uploadFile(artifact, r2Key) {
66
+ const stats = fs.statSync(artifact.path);
67
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(1);
68
+
69
+ process.stdout.write(` ${r2Key} (${sizeMB} MB)... `);
70
+
71
+ try {
72
+ execSync(
73
+ `npx wrangler r2 object put "${BUCKET_NAME}/${r2Key}" --file="${artifact.path}" --remote`,
74
+ { stdio: 'pipe' }
75
+ );
76
+ console.log('āœ…');
77
+ return true;
78
+ } catch (error) {
79
+ console.log('āŒ');
80
+ console.error(` ${error.message}`);
81
+ return false;
82
+ }
83
+ }
84
+
85
+ function main() {
86
+ console.log('šŸš€ Uploading Tauri release artifacts to R2...\n');
87
+
88
+ const bundleDir = findBundleDir();
89
+ if (!bundleDir) {
90
+ console.error('āŒ No Tauri bundle directory found.');
91
+ console.error('Run `cargo tauri build` first.');
92
+ process.exit(1);
93
+ }
94
+
95
+ console.log(`Bundle dir: ${bundleDir}\n`);
96
+
97
+ const artifacts = findArtifacts(bundleDir);
98
+ if (artifacts.length === 0) {
99
+ console.error('āŒ No release artifacts found.');
100
+ process.exit(1);
101
+ }
102
+
103
+ console.log(`Found ${artifacts.length} artifact(s):\n`);
104
+
105
+ let success = 0;
106
+ let failed = 0;
107
+
108
+ for (const artifact of artifacts) {
109
+ // Upload under tauri/ prefix for organization
110
+ const r2Key = `tauri/${artifact.name}`;
111
+ if (uploadFile(artifact, r2Key)) {
112
+ success++;
113
+ } else {
114
+ failed++;
115
+ }
116
+ }
117
+
118
+ console.log(`\nšŸ“¦ Upload complete: ${success} uploaded, ${failed} failed`);
119
+
120
+ if (failed > 0) {
121
+ process.exit(1);
122
+ }
123
+ }
124
+
125
+ main();