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,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();
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Standalone WebSocket server for browser dev mode.
|
|
5
|
+
*
|
|
6
|
+
* Polls the JettyPod SQLite database and cucumber-results.json for changes
|
|
7
|
+
* and broadcasts notifications to connected clients. This is the same
|
|
8
|
+
* behaviour embedded in electron/main.js, extracted so `npm run dev` can
|
|
9
|
+
* provide live updates without Electron running.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/ws-server.js [project-path]
|
|
13
|
+
*
|
|
14
|
+
* project-path defaults to the repository root (two levels up from this script).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { WebSocketServer } = require('ws');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
const WS_PORT = 47808;
|
|
22
|
+
const DB_POLL_MS = 500;
|
|
23
|
+
|
|
24
|
+
const projectRoot = process.argv[2] || path.resolve(__dirname, '..', '..', '..');
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// State
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
const clients = new Set();
|
|
30
|
+
let dbPollInterval = null;
|
|
31
|
+
let testResultsPollInterval = null;
|
|
32
|
+
let lastDbMtimes = { db: null, wal: null };
|
|
33
|
+
let lastTestResultsMtime = null;
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Helpers
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
function log(msg) {
|
|
39
|
+
console.log(`[ws-server] ${msg}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function broadcast(message) {
|
|
43
|
+
const payload = JSON.stringify(message);
|
|
44
|
+
for (const client of clients) {
|
|
45
|
+
if (client.readyState === 1) { // WebSocket.OPEN
|
|
46
|
+
client.send(payload);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Database polling (WAL-aware)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
function startDatabasePolling() {
|
|
55
|
+
const dbPath = path.join(projectRoot, '.jettypod', 'work.db');
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(dbPath)) {
|
|
58
|
+
log(`Database not found at ${dbPath}, retrying in 5s...`);
|
|
59
|
+
setTimeout(startDatabasePolling, 5000);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const dbStats = fs.statSync(dbPath);
|
|
65
|
+
lastDbMtimes.db = dbStats.mtimeMs;
|
|
66
|
+
|
|
67
|
+
const walPath = dbPath + '-wal';
|
|
68
|
+
if (fs.existsSync(walPath)) {
|
|
69
|
+
lastDbMtimes.wal = fs.statSync(walPath).mtimeMs;
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
log(`Failed to get initial db stats: ${err.message}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
log(`Polling database for changes (WAL-aware)...`);
|
|
77
|
+
|
|
78
|
+
dbPollInterval = setInterval(() => {
|
|
79
|
+
try {
|
|
80
|
+
let changed = false;
|
|
81
|
+
|
|
82
|
+
const dbStats = fs.statSync(dbPath);
|
|
83
|
+
if (dbStats.mtimeMs !== lastDbMtimes.db) {
|
|
84
|
+
lastDbMtimes.db = dbStats.mtimeMs;
|
|
85
|
+
changed = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const walPath = dbPath + '-wal';
|
|
89
|
+
if (fs.existsSync(walPath)) {
|
|
90
|
+
const walStats = fs.statSync(walPath);
|
|
91
|
+
if (walStats.mtimeMs !== lastDbMtimes.wal) {
|
|
92
|
+
lastDbMtimes.wal = walStats.mtimeMs;
|
|
93
|
+
changed = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (changed) {
|
|
98
|
+
broadcast({ type: 'db_change', timestamp: Date.now() });
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// File might be temporarily locked during writes — ignore
|
|
102
|
+
}
|
|
103
|
+
}, DB_POLL_MS);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Test results polling
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
function startTestResultsPolling() {
|
|
110
|
+
const testResultsPath = path.join(projectRoot, 'cucumber-results.json');
|
|
111
|
+
|
|
112
|
+
if (fs.existsSync(testResultsPath)) {
|
|
113
|
+
try {
|
|
114
|
+
lastTestResultsMtime = fs.statSync(testResultsPath).mtimeMs;
|
|
115
|
+
log(`Watching test results at ${testResultsPath}`);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
log(`Failed to get initial test results stats: ${err.message}`);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
log(`Test results file not found at ${testResultsPath}, will watch for creation`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
testResultsPollInterval = setInterval(() => {
|
|
124
|
+
try {
|
|
125
|
+
if (!fs.existsSync(testResultsPath)) {
|
|
126
|
+
if (lastTestResultsMtime !== null) {
|
|
127
|
+
lastTestResultsMtime = null;
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const stats = fs.statSync(testResultsPath);
|
|
133
|
+
if (stats.mtimeMs !== lastTestResultsMtime) {
|
|
134
|
+
lastTestResultsMtime = stats.mtimeMs;
|
|
135
|
+
broadcast({ type: 'test_change', timestamp: Date.now() });
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// ignore
|
|
139
|
+
}
|
|
140
|
+
}, DB_POLL_MS);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Server
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
const wss = new WebSocketServer({ port: WS_PORT });
|
|
147
|
+
|
|
148
|
+
wss.on('connection', (ws) => {
|
|
149
|
+
clients.add(ws);
|
|
150
|
+
log(`Client connected. Total: ${clients.size}`);
|
|
151
|
+
ws.send(JSON.stringify({ type: 'connected', timestamp: Date.now() }));
|
|
152
|
+
|
|
153
|
+
ws.on('close', () => {
|
|
154
|
+
clients.delete(ws);
|
|
155
|
+
log(`Client disconnected. Total: ${clients.size}`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
ws.on('error', (err) => {
|
|
159
|
+
log(`Client error: ${err.message}`);
|
|
160
|
+
ws.close();
|
|
161
|
+
clients.delete(ws);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
wss.on('error', (err) => {
|
|
166
|
+
if (err.code === 'EADDRINUSE') {
|
|
167
|
+
log(`Port ${WS_PORT} already in use (Electron running?). Exiting.`);
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
log(`Server error: ${err.message}`);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
wss.on('listening', () => {
|
|
174
|
+
log(`WebSocket server running on ws://localhost:${WS_PORT}`);
|
|
175
|
+
log(`Watching project: ${projectRoot}`);
|
|
176
|
+
startDatabasePolling();
|
|
177
|
+
startTestResultsPolling();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Graceful shutdown
|
|
181
|
+
function shutdown() {
|
|
182
|
+
log('Shutting down...');
|
|
183
|
+
if (dbPollInterval) clearInterval(dbPollInterval);
|
|
184
|
+
if (testResultsPollInterval) clearInterval(testResultsPollInterval);
|
|
185
|
+
for (const client of clients) client.close();
|
|
186
|
+
clients.clear();
|
|
187
|
+
wss.close(() => process.exit(0));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
process.on('SIGINT', shutdown);
|
|
191
|
+
process.on('SIGTERM', shutdown);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { RouterProvider } from 'react-router-dom'
|
|
4
|
+
import { router } from './router'
|
|
5
|
+
import '@fontsource-variable/geist-mono'
|
|
6
|
+
import '../app/globals.css'
|
|
7
|
+
|
|
8
|
+
createRoot(document.getElementById('root')!).render(
|
|
9
|
+
<StrictMode>
|
|
10
|
+
<RouterProvider router={router} />
|
|
11
|
+
</StrictMode>
|
|
12
|
+
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { lazy, Suspense, useState, useEffect } from 'react'
|
|
2
|
+
import { createBrowserRouter, Outlet } from 'react-router-dom'
|
|
3
|
+
import { AppShell } from '../components/AppShell'
|
|
4
|
+
import { JettyLoader } from '../components/JettyLoader'
|
|
5
|
+
import { dataBridge } from '../lib/data-bridge'
|
|
6
|
+
|
|
7
|
+
// Lazy-load all pages — prefetch nav targets on app load so first navigation
|
|
8
|
+
// never shows a spinner. It's a desktop app; the extra chunks cost nothing.
|
|
9
|
+
const dashboardImport = () => import('../app/page')
|
|
10
|
+
const Dashboard = lazy(dashboardImport)
|
|
11
|
+
const workDetailImport = () => import('../app/work/[id]/page')
|
|
12
|
+
const WorkDetail = lazy(workDetailImport)
|
|
13
|
+
const proofDashboardImport = () => import('../app/work/[id]/proof/page')
|
|
14
|
+
const ProofDashboard = lazy(proofDashboardImport)
|
|
15
|
+
const decisionDetailImport = () => import('../app/decision/[id]/page')
|
|
16
|
+
const DecisionDetail = lazy(decisionDetailImport)
|
|
17
|
+
const testsImport = () => import('../app/tests/page')
|
|
18
|
+
const Tests = lazy(testsImport)
|
|
19
|
+
const settingsImport = () => import('../app/settings/page')
|
|
20
|
+
const Settings = lazy(settingsImport)
|
|
21
|
+
const prototypesImport = () => import('../app/prototypes/page')
|
|
22
|
+
const Prototypes = lazy(prototypesImport)
|
|
23
|
+
|
|
24
|
+
// Kick off chunk prefetches immediately
|
|
25
|
+
dashboardImport()
|
|
26
|
+
workDetailImport()
|
|
27
|
+
proofDashboardImport()
|
|
28
|
+
decisionDetailImport()
|
|
29
|
+
testsImport()
|
|
30
|
+
settingsImport()
|
|
31
|
+
prototypesImport()
|
|
32
|
+
|
|
33
|
+
const Welcome = lazy(() => import('../app/welcome/page'))
|
|
34
|
+
const Login = lazy(() => import('../app/login/page'))
|
|
35
|
+
const Signup = lazy(() => import('../app/signup/page'))
|
|
36
|
+
const Subscribe = lazy(() => import('../app/subscribe/page'))
|
|
37
|
+
const InstallClaude = lazy(() => import('../app/install-claude/page'))
|
|
38
|
+
const ConnectClaude = lazy(() => import('../app/connect-claude/page'))
|
|
39
|
+
// Dev-only pages — excluded from production to reduce bundle
|
|
40
|
+
const DesignSystem = import.meta.env.DEV ? lazy(() => import('../app/design-system/page')) : () => null
|
|
41
|
+
const DemoGates = import.meta.env.DEV ? lazy(() => import('../app/demo/gates/page')) : () => null
|
|
42
|
+
const DevSessionPrototype = import.meta.env.DEV ? lazy(() => import('../app/prototypes/dev-session/page')) : () => null
|
|
43
|
+
const ProofDashboardPrototype = import.meta.env.DEV ? lazy(() => import('../app/prototypes/proof-dashboard/page')) : () => null
|
|
44
|
+
const OnboardingPrototype = import.meta.env.DEV ? lazy(() => import('../app/prototypes/onboarding/page')) : () => null
|
|
45
|
+
const OnboardingMadlibPrototype = import.meta.env.DEV ? lazy(() => import('../app/prototypes/onboarding-madlib/page')) : () => null
|
|
46
|
+
|
|
47
|
+
function PageLoader() {
|
|
48
|
+
return (
|
|
49
|
+
<div className="h-full flex items-center justify-center bg-background">
|
|
50
|
+
<JettyLoader size={80} />
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function RootLayout() {
|
|
56
|
+
const [projectName, setProjectName] = useState('JettyPod')
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
dataBridge.getProjectRoot().then((root) => {
|
|
60
|
+
if (root) {
|
|
61
|
+
setProjectName(root.split('/').pop() || 'JettyPod')
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}, [])
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<AppShell projectName={projectName}>
|
|
68
|
+
<Outlet />
|
|
69
|
+
</AppShell>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const router = createBrowserRouter([
|
|
74
|
+
{
|
|
75
|
+
path: '/',
|
|
76
|
+
element: <RootLayout />,
|
|
77
|
+
children: [
|
|
78
|
+
{
|
|
79
|
+
// Dashboard layout route — kanban stays mounted while viewing work items.
|
|
80
|
+
// Navigating to /work/:id hides the kanban (display:none) instead of
|
|
81
|
+
// unmounting it, so returning to / is instant with no refetch.
|
|
82
|
+
element: <Suspense fallback={<PageLoader />}><Dashboard /></Suspense>,
|
|
83
|
+
children: [
|
|
84
|
+
{ index: true, element: null },
|
|
85
|
+
{ path: 'work/:id', element: <Suspense fallback={<PageLoader />}><WorkDetail /></Suspense> },
|
|
86
|
+
{ path: 'work/:id/proof', element: <Suspense fallback={<PageLoader />}><ProofDashboard /></Suspense> },
|
|
87
|
+
{ path: 'decision/:id', element: <Suspense fallback={<PageLoader />}><DecisionDetail /></Suspense> },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{ path: 'tests', element: <Suspense fallback={<PageLoader />}><Tests /></Suspense> },
|
|
91
|
+
{ path: 'settings', element: <Suspense fallback={<PageLoader />}><Settings /></Suspense> },
|
|
92
|
+
{ path: 'prototypes', element: <Suspense fallback={<PageLoader />}><Prototypes /></Suspense> },
|
|
93
|
+
{ path: 'welcome', element: <Suspense fallback={<PageLoader />}><Welcome /></Suspense> },
|
|
94
|
+
{ path: 'login', element: <Suspense fallback={<PageLoader />}><Login /></Suspense> },
|
|
95
|
+
{ path: 'signup', element: <Suspense fallback={<PageLoader />}><Signup /></Suspense> },
|
|
96
|
+
{ path: 'subscribe', element: <Suspense fallback={<PageLoader />}><Subscribe /></Suspense> },
|
|
97
|
+
{ path: 'install-claude', element: <Suspense fallback={<PageLoader />}><InstallClaude /></Suspense> },
|
|
98
|
+
{ path: 'connect-claude', element: <Suspense fallback={<PageLoader />}><ConnectClaude /></Suspense> },
|
|
99
|
+
{ path: 'design-system', element: <Suspense fallback={<PageLoader />}><DesignSystem /></Suspense> },
|
|
100
|
+
{ path: 'demo/gates', element: <Suspense fallback={<PageLoader />}><DemoGates /></Suspense> },
|
|
101
|
+
{ path: 'prototypes/dev-session', element: <Suspense fallback={<PageLoader />}><DevSessionPrototype /></Suspense> },
|
|
102
|
+
{ path: 'prototypes/proof-dashboard', element: <Suspense fallback={<PageLoader />}><ProofDashboardPrototype /></Suspense> },
|
|
103
|
+
{ path: 'prototypes/onboarding', element: <Suspense fallback={<PageLoader />}><OnboardingPrototype /></Suspense> },
|
|
104
|
+
{ path: 'prototypes/onboarding-madlib', element: <Suspense fallback={<PageLoader />}><OnboardingMadlibPrototype /></Suspense> },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|