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.
- package/.env +2 -1
- 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 +54 -49
- package/apps/dashboard/app/demo/gates/page.tsx +3 -5
- package/apps/dashboard/app/design-system/page.tsx +1 -1
- package/apps/dashboard/app/globals.css +74 -2
- package/apps/dashboard/app/install-claude/page.tsx +3 -5
- package/apps/dashboard/app/login/page.tsx +17 -20
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +60 -12
- package/apps/dashboard/app/signup/page.tsx +14 -17
- 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 +12 -15
- package/apps/dashboard/app/work/[id]/page.tsx +90 -75
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +70 -61
- package/apps/dashboard/components/CardMenu.tsx +0 -1
- package/apps/dashboard/components/ClaudePanel.tsx +541 -283
- package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
- package/apps/dashboard/components/CopyableId.tsx +1 -2
- package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
- package/apps/dashboard/components/DragContext.tsx +132 -62
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +5 -6
- package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +0 -1
- package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
- package/apps/dashboard/components/EpicGroup.tsx +100 -70
- package/apps/dashboard/components/GateCard.tsx +0 -1
- package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
- package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
- package/apps/dashboard/components/JettyLoader.tsx +0 -1
- package/apps/dashboard/components/KanbanBoard.tsx +319 -173
- package/apps/dashboard/components/KanbanCard.tsx +341 -107
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
- package/apps/dashboard/components/MainNav.tsx +24 -25
- package/apps/dashboard/components/MessageBlock.tsx +93 -16
- package/apps/dashboard/components/ModeStartCard.tsx +0 -1
- package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
- package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
- package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
- package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
- package/apps/dashboard/components/ReviewFooter.tsx +12 -14
- package/apps/dashboard/components/SessionList.tsx +0 -1
- package/apps/dashboard/components/SubscribeContent.tsx +40 -11
- package/apps/dashboard/components/TestTree.tsx +1 -2
- package/apps/dashboard/components/TipCard.tsx +2 -4
- package/apps/dashboard/components/Toast.tsx +0 -1
- package/apps/dashboard/components/TypeIcon.tsx +7 -8
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
- package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
- package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
- package/apps/dashboard/components/WorkItemTree.tsx +2 -4
- package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
- 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 +20 -73
- package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
- package/apps/dashboard/components/ui/Button.tsx +1 -1
- package/apps/dashboard/components/ui/Input.tsx +1 -1
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
- package/apps/dashboard/contexts/UsageContext.tsx +62 -31
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1302
- 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 +226 -26
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- 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 +253 -122
- 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 +3 -3
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -33
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/in-flight-seagull.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/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 +167 -30
- 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/jettypod.js +96 -4
- package/lib/bdd-preflight.js +96 -0
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/work-commands/index.js +58 -16
- package/lib/work-tracking/index.js +108 -8
- package/package.json +1 -1
- package/skills-templates/bug-mode/SKILL.md +43 -1
- package/skills-templates/chore-mode/SKILL.md +40 -1
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +14 -0
- package/skills-templates/feature-planning/SKILL.md +90 -1
- package/skills-templates/production-mode/SKILL.md +20 -0
- package/skills-templates/simple-improvement/SKILL.md +39 -2
- package/skills-templates/speed-mode/SKILL.md +10 -15
- package/skills-templates/stable-mode/SKILL.md +47 -0
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
- 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 -525
- 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]/route.ts +0 -35
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -55
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
- package/apps/dashboard/electron/ipc-handlers.js +0 -1026
- package/apps/dashboard/electron/main.js +0 -2306
- package/apps/dashboard/electron/preload.js +0 -125
- package/apps/dashboard/electron/session-manager.js +0 -163
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/backlog-parser.ts +0 -50
- package/apps/dashboard/lib/claude-process-manager.ts +0 -529
- package/apps/dashboard/lib/db-bridge.ts +0 -283
- 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 -66
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/bug-icon.svg +0 -9
- package/apps/dashboard/public/buoy-icon.svg +0 -9
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/in-flight-seagull.svg +0 -9
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/pier-icon.svg +0 -14
- package/apps/dashboard/public/star-icon.svg +0 -9
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/public/wrench-icon.svg +0 -9
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import { ConnectClaudeScreen } from '@/components/ConnectClaudeScreen';
|
|
2
|
+
import { isTauri, claudeCode } from '@/lib/tauri-bridge';
|
|
4
3
|
|
|
5
4
|
export default function ConnectClaudePage() {
|
|
6
5
|
const handleConnect = async () => {
|
|
7
|
-
if (!
|
|
6
|
+
if (!isTauri()) {
|
|
8
7
|
return { success: false, error: 'Only available in the desktop app.' };
|
|
9
8
|
}
|
|
10
|
-
return await
|
|
9
|
+
return await claudeCode.login();
|
|
11
10
|
};
|
|
12
11
|
|
|
13
12
|
const handleCheckAuth = async () => {
|
|
14
|
-
if (!
|
|
15
|
-
return await
|
|
13
|
+
if (!isTauri()) return false;
|
|
14
|
+
return await claudeCode.isAuthenticated();
|
|
16
15
|
};
|
|
17
16
|
|
|
18
17
|
return (
|
|
@@ -1,41 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import Link from '
|
|
3
|
-
import {
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useParams, Link } from 'react-router-dom';
|
|
3
|
+
import { dataBridge } from '@/lib/data-bridge';
|
|
4
|
+
import type { DecisionData } from '@/lib/data-bridge';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
export default function DecisionPage() {
|
|
7
|
+
const { id } = useParams<{ id: string }>();
|
|
8
|
+
const [decision, setDecision] = useState<DecisionData | null>(null);
|
|
9
|
+
const [loading, setLoading] = useState(true);
|
|
10
|
+
const [notFound, setNotFound] = useState(false);
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
async function loadData() {
|
|
14
|
+
const decisionId = parseInt(id || '', 10);
|
|
15
|
+
if (isNaN(decisionId)) {
|
|
16
|
+
setNotFound(true);
|
|
17
|
+
setLoading(false);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
try {
|
|
22
|
+
const data = await dataBridge.getDecision(decisionId);
|
|
23
|
+
if (!data) {
|
|
24
|
+
setNotFound(true);
|
|
25
|
+
} else {
|
|
26
|
+
setDecision(data);
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
setNotFound(true);
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
loadData();
|
|
35
|
+
}, [id]);
|
|
16
36
|
|
|
17
|
-
|
|
37
|
+
if (loading) return null;
|
|
18
38
|
|
|
19
|
-
if (!decision) {
|
|
20
|
-
|
|
39
|
+
if (notFound || !decision) {
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex-1 flex items-center justify-center">
|
|
42
|
+
<div className="text-center">
|
|
43
|
+
<h1 className="text-2xl font-bold text-zinc-900 dark:text-zinc-100 mb-2">Not Found</h1>
|
|
44
|
+
<p className="text-zinc-500 mb-4">Decision not found.</p>
|
|
45
|
+
<Link to="/" viewTransition className="text-[#5a7d7f] hover:underline">← Back to Dashboard</Link>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
21
49
|
}
|
|
22
50
|
|
|
23
51
|
return (
|
|
24
|
-
<div className="
|
|
25
|
-
{/* Header */}
|
|
52
|
+
<div className="flex-1 overflow-y-auto bg-zinc-50 dark:bg-zinc-950">
|
|
26
53
|
<header className="border-b border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900">
|
|
27
54
|
<div className="max-w-4xl mx-auto px-6 py-6">
|
|
28
|
-
<Link
|
|
29
|
-
← Back to Dashboard
|
|
30
|
-
</Link>
|
|
55
|
+
<Link to="/" viewTransition className="text-[#5a7d7f] dark:text-[#a3bfc0] hover:underline text-base">← Back to Dashboard</Link>
|
|
31
56
|
</div>
|
|
32
57
|
</header>
|
|
33
58
|
|
|
34
59
|
<main className="max-w-4xl mx-auto px-4 py-6">
|
|
35
|
-
{/* Breadcrumb to parent work item */}
|
|
36
60
|
{decision.work_item_id && (
|
|
37
61
|
<div className="mb-6 text-base text-zinc-500">
|
|
38
|
-
<Link
|
|
62
|
+
<Link to={`/work/${decision.work_item_id}`} viewTransition className="hover:underline">
|
|
39
63
|
#{decision.work_item_id} {decision.work_item_title}
|
|
40
64
|
</Link>
|
|
41
65
|
<span className="mx-2">→</span>
|
|
@@ -43,9 +67,7 @@ export default async function DecisionPage({ params }: PageProps) {
|
|
|
43
67
|
</div>
|
|
44
68
|
)}
|
|
45
69
|
|
|
46
|
-
{/* Main card */}
|
|
47
70
|
<div className="bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-800 overflow-hidden">
|
|
48
|
-
{/* Header */}
|
|
49
71
|
<div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
|
|
50
72
|
<div className="flex items-start justify-between gap-6">
|
|
51
73
|
<div>
|
|
@@ -54,54 +76,37 @@ export default async function DecisionPage({ params }: PageProps) {
|
|
|
54
76
|
<span>•</span>
|
|
55
77
|
<span className="font-mono">#{decision.id}</span>
|
|
56
78
|
</div>
|
|
57
|
-
<h1 className="text-2xl font-bold text-zinc-900 dark:text-zinc-100">
|
|
58
|
-
{decision.aspect}
|
|
59
|
-
</h1>
|
|
79
|
+
<h1 className="text-2xl font-bold text-zinc-900 dark:text-zinc-100">{decision.aspect}</h1>
|
|
60
80
|
</div>
|
|
61
81
|
</div>
|
|
62
82
|
</div>
|
|
63
83
|
|
|
64
|
-
{/* Decision content */}
|
|
65
84
|
<div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
|
|
66
|
-
<h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-3">
|
|
67
|
-
|
|
68
|
-
</h2>
|
|
69
|
-
<p className="text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap text-lg">
|
|
70
|
-
{decision.decision}
|
|
71
|
-
</p>
|
|
85
|
+
<h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-3">Decision</h2>
|
|
86
|
+
<p className="text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap text-lg">{decision.decision}</p>
|
|
72
87
|
</div>
|
|
73
88
|
|
|
74
|
-
{/* Rationale */}
|
|
75
89
|
{decision.rationale && (
|
|
76
90
|
<div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
|
|
77
|
-
<h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-3">
|
|
78
|
-
|
|
79
|
-
</h2>
|
|
80
|
-
<p className="text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap">
|
|
81
|
-
{decision.rationale}
|
|
82
|
-
</p>
|
|
91
|
+
<h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-3">Rationale</h2>
|
|
92
|
+
<p className="text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap">{decision.rationale}</p>
|
|
83
93
|
</div>
|
|
84
94
|
)}
|
|
85
95
|
|
|
86
|
-
{/* Metadata */}
|
|
87
96
|
<div className="px-8 py-6">
|
|
88
|
-
<h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-4">
|
|
89
|
-
Details
|
|
90
|
-
</h2>
|
|
97
|
+
<h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-4">Details</h2>
|
|
91
98
|
<dl className="grid grid-cols-2 gap-6 text-base">
|
|
92
99
|
<div>
|
|
93
100
|
<dt className="text-zinc-500">Related Work Item</dt>
|
|
94
101
|
<dd className="text-zinc-900 dark:text-zinc-100">
|
|
95
|
-
<Link
|
|
102
|
+
<Link to={`/work/${decision.work_item_id}`} viewTransition className="hover:underline text-[#5a7d7f] dark:text-[#a3bfc0]">
|
|
96
103
|
#{decision.work_item_id} {decision.work_item_title}
|
|
97
104
|
</Link>
|
|
98
105
|
</dd>
|
|
99
106
|
</div>
|
|
100
107
|
<div>
|
|
101
108
|
<dt className="text-zinc-500">Created</dt>
|
|
102
|
-
<dd className="text-zinc-900 dark:text-zinc-100">
|
|
103
|
-
{new Date(decision.created_at).toLocaleDateString()}
|
|
104
|
-
</dd>
|
|
109
|
+
<dd className="text-zinc-900 dark:text-zinc-100">{new Date(decision.created_at).toLocaleDateString()}</dd>
|
|
105
110
|
</div>
|
|
106
111
|
</dl>
|
|
107
112
|
</div>
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import { useState, useEffect, useCallback } from 'react';
|
|
4
2
|
import { GateCard } from '@/components/GateCard';
|
|
5
3
|
import { GateChoiceCard } from '@/components/GateChoiceCard';
|
|
@@ -124,7 +122,7 @@ export default function GateDemoPage() {
|
|
|
124
122
|
const [activeTab, setActiveTab] = useState<DemoTab>('workflow');
|
|
125
123
|
|
|
126
124
|
return (
|
|
127
|
-
<div className="
|
|
125
|
+
<div className="flex-1 overflow-y-auto bg-zinc-100 dark:bg-zinc-900 p-10">
|
|
128
126
|
<div className="max-w-5xl mx-auto">
|
|
129
127
|
{/* Header */}
|
|
130
128
|
<div className="mb-8">
|
|
@@ -254,7 +252,7 @@ function WorkflowDemo() {
|
|
|
254
252
|
<button
|
|
255
253
|
onClick={play}
|
|
256
254
|
disabled={isPlaying}
|
|
257
|
-
className="px-5 py-3 bg-zinc-900 dark:bg-zinc-100 hover:bg-zinc-800 dark:hover:bg-zinc-200 disabled:opacity-50 text-white dark:text-zinc-900 text-base font-medium rounded-xl transition-
|
|
255
|
+
className="px-5 py-3 bg-zinc-900 dark:bg-zinc-100 hover:bg-zinc-800 dark:hover:bg-zinc-200 disabled:opacity-50 text-white dark:text-zinc-900 text-base font-medium rounded-xl transition-[color,background-color,opacity] duration-200"
|
|
258
256
|
style={{ boxShadow: '0 1px 2px rgba(0,0,0,0.1), 0 2px 4px rgba(0,0,0,0.06)' }}
|
|
259
257
|
>
|
|
260
258
|
{isDone ? 'Replay' : isPaused ? 'Waiting for input...' : isPlaying ? 'Playing...' : 'Play Workflow'}
|
|
@@ -296,7 +294,7 @@ function WorkflowDemo() {
|
|
|
296
294
|
</div>
|
|
297
295
|
<div className="h-1 bg-zinc-200 dark:bg-zinc-700 rounded-full overflow-hidden">
|
|
298
296
|
<div
|
|
299
|
-
className="h-full bg-zinc-900 dark:bg-zinc-100 rounded-full transition-
|
|
297
|
+
className="h-full bg-zinc-900 dark:bg-zinc-100 rounded-full transition-[width] duration-500"
|
|
300
298
|
style={{ width: `${(visibleCount / totalSteps) * 100}%` }}
|
|
301
299
|
/>
|
|
302
300
|
</div>
|
|
@@ -72,7 +72,7 @@ export default function DesignSystemPage() {
|
|
|
72
72
|
}
|
|
73
73
|
`}</style>
|
|
74
74
|
|
|
75
|
-
<div className="
|
|
75
|
+
<div className="flex-1 overflow-y-auto bg-background text-foreground">
|
|
76
76
|
{/* Header */}
|
|
77
77
|
<header className="sticky top-0 z-10 border-b border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 px-8 py-6 flex items-center justify-between">
|
|
78
78
|
<div>
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/* Font declarations */
|
|
2
|
+
@font-face {
|
|
3
|
+
font-family: 'Satoshi';
|
|
4
|
+
src: url('/fonts/Satoshi-Variable.woff2') format('woff2');
|
|
5
|
+
font-style: normal;
|
|
6
|
+
font-display: swap;
|
|
7
|
+
font-weight: 100 900;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@font-face {
|
|
11
|
+
font-family: 'Satoshi';
|
|
12
|
+
src: url('/fonts/Satoshi-VariableItalic.woff2') format('woff2');
|
|
13
|
+
font-style: italic;
|
|
14
|
+
font-display: swap;
|
|
15
|
+
font-weight: 100 900;
|
|
16
|
+
}
|
|
17
|
+
|
|
1
18
|
@import "tailwindcss";
|
|
2
19
|
@import "tw-animate-css";
|
|
3
20
|
|
|
@@ -45,6 +62,8 @@
|
|
|
45
62
|
|
|
46
63
|
:root {
|
|
47
64
|
--radius: 0.625rem;
|
|
65
|
+
--font-satoshi: 'Satoshi', system-ui, sans-serif;
|
|
66
|
+
--font-geist-mono: 'Geist Mono Variable', 'Geist Mono', monospace;
|
|
48
67
|
--background: oklch(0.98 0.002 80);
|
|
49
68
|
--foreground: oklch(0.145 0 0);
|
|
50
69
|
--card: oklch(1 0 0);
|
|
@@ -113,14 +132,67 @@
|
|
|
113
132
|
}
|
|
114
133
|
|
|
115
134
|
@layer base {
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
*,
|
|
136
|
+
::before,
|
|
137
|
+
::after {
|
|
138
|
+
border-color: var(--color-border);
|
|
118
139
|
}
|
|
119
140
|
body {
|
|
120
141
|
@apply bg-background text-foreground;
|
|
142
|
+
font-family: var(--font-satoshi);
|
|
143
|
+
-webkit-font-smoothing: antialiased;
|
|
144
|
+
-moz-osx-font-smoothing: grayscale;
|
|
121
145
|
}
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
html {
|
|
125
149
|
font-size: 16px;
|
|
126
150
|
}
|
|
151
|
+
|
|
152
|
+
/* Kanban card hover — instant shadow swap, no transition (WebKit-friendly) */
|
|
153
|
+
.kanban-card {
|
|
154
|
+
contain: content;
|
|
155
|
+
content-visibility: auto;
|
|
156
|
+
}
|
|
157
|
+
.kanban-card:hover {
|
|
158
|
+
box-shadow: var(--hover-shadow) !important;
|
|
159
|
+
transform: translateY(-2px);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Highlight pulse — CSS-only, no framer-motion runtime cost */
|
|
163
|
+
@keyframes highlight-pulse {
|
|
164
|
+
0%, 100% { outline: 3px solid rgba(129, 157, 159, 0); outline-offset: 0; }
|
|
165
|
+
50% { outline: 3px solid rgba(129, 157, 159, 0.4); outline-offset: 0; }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* View Transitions API — fast cross-fade for route changes */
|
|
169
|
+
::view-transition-old(root) {
|
|
170
|
+
animation: vt-fade-out 120ms ease-out;
|
|
171
|
+
}
|
|
172
|
+
::view-transition-new(root) {
|
|
173
|
+
animation: vt-fade-in 200ms ease-out;
|
|
174
|
+
}
|
|
175
|
+
@keyframes vt-fade-out {
|
|
176
|
+
to { opacity: 0; }
|
|
177
|
+
}
|
|
178
|
+
@keyframes vt-fade-in {
|
|
179
|
+
from { opacity: 0; }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Skeleton loading pulse */
|
|
183
|
+
@keyframes skeleton-pulse {
|
|
184
|
+
0%, 100% { opacity: 0.5; }
|
|
185
|
+
50% { opacity: 0.25; }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Respect reduced motion globally — disables all animations/transitions */
|
|
189
|
+
@media (prefers-reduced-motion: reduce) {
|
|
190
|
+
*,
|
|
191
|
+
::before,
|
|
192
|
+
::after {
|
|
193
|
+
animation-duration: 0.01ms !important;
|
|
194
|
+
animation-iteration-count: 1 !important;
|
|
195
|
+
transition-duration: 0.01ms !important;
|
|
196
|
+
scroll-behavior: auto !important;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import { useState } from 'react';
|
|
4
2
|
import { InstallClaudeScreen } from '@/components/InstallClaudeScreen';
|
|
3
|
+
import { isTauri, claudeCode } from '@/lib/tauri-bridge';
|
|
5
4
|
|
|
6
5
|
export default function InstallClaudePage() {
|
|
7
6
|
const [isInstalling, setIsInstalling] = useState(false);
|
|
@@ -12,15 +11,14 @@ export default function InstallClaudePage() {
|
|
|
12
11
|
setError(null);
|
|
13
12
|
setIsInstalling(true);
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
if (!window.electronAPI?.isElectron) {
|
|
14
|
+
if (!isTauri()) {
|
|
17
15
|
setError('Installation is only available in the desktop app.');
|
|
18
16
|
setIsInstalling(false);
|
|
19
17
|
return;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
try {
|
|
23
|
-
const result = await
|
|
21
|
+
const result = await claudeCode.install();
|
|
24
22
|
|
|
25
23
|
if (!result.success) {
|
|
26
24
|
setError(result.error || 'Installation failed');
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import { useState, useEffect, useRef } from 'react';
|
|
4
|
-
import
|
|
5
|
-
import Link from 'next/link';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
6
3
|
import { Button } from '@/components/ui/Button';
|
|
7
4
|
import { Input } from '@/components/ui/Input';
|
|
5
|
+
import { isTauri, auth } from '@/lib/tauri-bridge';
|
|
8
6
|
|
|
9
7
|
const API_BASE = 'https://jettypod-update-server.spangbaryn2.workers.dev';
|
|
10
8
|
|
|
@@ -17,18 +15,18 @@ export default function LoginPage() {
|
|
|
17
15
|
const [error, setError] = useState<string | null>(null);
|
|
18
16
|
|
|
19
17
|
// Poll for auth completion after Google sign-in.
|
|
20
|
-
// The deep link handler
|
|
21
|
-
// and navigates to the dashboard
|
|
18
|
+
// The deep link handler saves the token — this polling detects it
|
|
19
|
+
// and navigates to the dashboard.
|
|
22
20
|
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
23
21
|
|
|
24
22
|
// Redirect already-authenticated users to dashboard
|
|
25
23
|
useEffect(() => {
|
|
26
24
|
async function checkIfAlreadyAuthenticated() {
|
|
27
|
-
if (
|
|
25
|
+
if (isTauri()) {
|
|
28
26
|
try {
|
|
29
|
-
const status = await
|
|
27
|
+
const status = await auth.getStatus();
|
|
30
28
|
if (status.authenticated) {
|
|
31
|
-
const path = await
|
|
29
|
+
const path = await auth.getPostLoginPath() || '/';
|
|
32
30
|
window.location.href = path;
|
|
33
31
|
}
|
|
34
32
|
} catch {
|
|
@@ -46,16 +44,16 @@ export default function LoginPage() {
|
|
|
46
44
|
}, []);
|
|
47
45
|
|
|
48
46
|
const handleGoogleSignIn = () => {
|
|
49
|
-
if (!
|
|
50
|
-
|
|
47
|
+
if (!isTauri()) return;
|
|
48
|
+
auth.loginWithGoogle();
|
|
51
49
|
|
|
52
50
|
// Start polling for auth status (token saved by deep link handler)
|
|
53
51
|
pollRef.current = setInterval(async () => {
|
|
54
52
|
try {
|
|
55
|
-
const status = await
|
|
53
|
+
const status = await auth.getStatus();
|
|
56
54
|
if (status.authenticated) {
|
|
57
55
|
if (pollRef.current) clearInterval(pollRef.current);
|
|
58
|
-
const path = await
|
|
56
|
+
const path = await auth.getPostLoginPath() || '/';
|
|
59
57
|
window.location.href = path;
|
|
60
58
|
}
|
|
61
59
|
} catch {
|
|
@@ -123,12 +121,12 @@ export default function LoginPage() {
|
|
|
123
121
|
|
|
124
122
|
const data = await res.json() as { token: string; user: { id: string; email: string; plan: string } };
|
|
125
123
|
|
|
126
|
-
// Save auth state via
|
|
127
|
-
if (
|
|
128
|
-
await
|
|
124
|
+
// Save auth state via Tauri IPC
|
|
125
|
+
if (isTauri()) {
|
|
126
|
+
await auth.saveToken(data.token, data.user);
|
|
129
127
|
}
|
|
130
128
|
|
|
131
|
-
const path = await
|
|
129
|
+
const path = await auth.getPostLoginPath() || '/';
|
|
132
130
|
window.location.href = path;
|
|
133
131
|
} catch {
|
|
134
132
|
setError('Failed to verify code. Check your connection.');
|
|
@@ -141,12 +139,11 @@ export default function LoginPage() {
|
|
|
141
139
|
<div className="max-w-md w-full space-y-10">
|
|
142
140
|
{/* Logo */}
|
|
143
141
|
<div className="flex flex-col items-center space-y-6">
|
|
144
|
-
<
|
|
142
|
+
<img
|
|
145
143
|
src="/jettypod_wordmark.png"
|
|
146
144
|
alt="JettyPod"
|
|
147
145
|
width={160}
|
|
148
146
|
height={40}
|
|
149
|
-
priority
|
|
150
147
|
/>
|
|
151
148
|
<h1 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100 text-center">
|
|
152
149
|
Sign in to JettyPod
|
|
@@ -240,7 +237,7 @@ export default function LoginPage() {
|
|
|
240
237
|
{/* Switch to signup */}
|
|
241
238
|
<p className="text-center text-base text-zinc-500 dark:text-zinc-400">
|
|
242
239
|
Don't have an account?{' '}
|
|
243
|
-
<Link
|
|
240
|
+
<Link to="/signup" className="font-medium hover:underline" style={{ color: '#819D9F' }}>
|
|
244
241
|
Create one
|
|
245
242
|
</Link>
|
|
246
243
|
</p>
|
|
@@ -1,61 +1,114 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
|
|
3
3
|
import { RealTimeKanbanWrapper } from '@/components/RealTimeKanbanWrapper';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const dynamic = 'force-dynamic';
|
|
4
|
+
import { dataBridge, prefetch } from '@/lib/data-bridge';
|
|
5
|
+
import type { KanbanData } from '@/lib/data-bridge';
|
|
7
6
|
|
|
8
7
|
export default function Home() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const navigate = useNavigate();
|
|
9
|
+
const { pathname } = useLocation();
|
|
10
|
+
const isChildRoute = pathname !== '/';
|
|
11
|
+
const [data, setData] = useState<KanbanData | null>(null);
|
|
12
|
+
const [projectPath, setProjectPath] = useState('');
|
|
13
|
+
const [isBlank, setIsBlank] = useState(false);
|
|
14
|
+
const [error, setError] = useState<string | null>(null);
|
|
15
|
+
const [loading, setLoading] = useState(true);
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
async function loadData() {
|
|
19
|
+
try {
|
|
20
|
+
// getProjectRoot() is cached after first call — no redundant IPC
|
|
21
|
+
const root = await dataBridge.getProjectRoot();
|
|
22
|
+
if (!root) {
|
|
23
|
+
navigate('/welcome', { replace: true });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
const serializedData = {
|
|
21
|
-
inFlight: data.inFlight,
|
|
22
|
-
backlog: Array.from(data.backlog.entries()),
|
|
23
|
-
done: Array.from(data.done.entries()),
|
|
24
|
-
};
|
|
27
|
+
const kanbanData = await prefetch.backlog();
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
setData(kanbanData);
|
|
30
|
+
setProjectPath(root || '');
|
|
31
|
+
// isBlank detection: fresh project with only the seeded "Project Planning" epic
|
|
32
|
+
const onlyGroup = kanbanData.backlog.size === 1
|
|
33
|
+
? [...kanbanData.backlog.values()][0]
|
|
34
|
+
: null;
|
|
35
|
+
const hasOnlyOnboarding = kanbanData.inFlight.length === 0
|
|
36
|
+
&& kanbanData.done.size === 0
|
|
37
|
+
&& onlyGroup?.epicTitle === 'Project Planning';
|
|
38
|
+
setIsBlank(hasOnlyOnboarding);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
41
|
+
} finally {
|
|
42
|
+
setLoading(false);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
loadData();
|
|
46
|
+
}, [navigate]);
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
// Serialize Map data for RealTimeKanbanWrapper (it expects this format)
|
|
49
|
+
const serializedData = data ? {
|
|
50
|
+
inFlight: data.inFlight,
|
|
51
|
+
backlog: Array.from(data.backlog.entries()),
|
|
52
|
+
done: Array.from(data.done.entries()),
|
|
53
|
+
} : null;
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
// Kanban board content based on loading/error/data state
|
|
56
|
+
const kanbanContent = loading ? (
|
|
57
|
+
<div className="max-w-7xl w-full mx-auto px-4 py-4">
|
|
58
|
+
<div className="flex gap-4" style={{ height: 'calc(var(--main-h, 100vh) - 2rem)' }}>
|
|
59
|
+
{/* Backlog column skeleton */}
|
|
60
|
+
<div className="flex-1 max-w-[600px] flex flex-col min-h-0">
|
|
61
|
+
<div className="bg-zinc-100 dark:bg-zinc-900 rounded-xl p-4 flex-1 min-h-0">
|
|
62
|
+
<div className="flex items-center justify-between mb-4">
|
|
63
|
+
<div className="h-6 w-24 bg-zinc-200 dark:bg-zinc-800 rounded" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
64
|
+
<div className="h-6 w-10 bg-zinc-200 dark:bg-zinc-800 rounded-full" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
65
|
+
</div>
|
|
66
|
+
<div className="space-y-3">
|
|
67
|
+
{[1, 2, 3, 4, 5].map(i => (
|
|
68
|
+
<div key={i} className="h-20 bg-zinc-200 dark:bg-zinc-800 rounded-xl" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
47
71
|
</div>
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
</div>
|
|
73
|
+
{/* Done column skeleton */}
|
|
74
|
+
<div className="flex-1 max-w-[600px] flex flex-col min-h-0">
|
|
75
|
+
<div className="bg-zinc-100 dark:bg-zinc-900 rounded-xl p-4 flex-1 min-h-0">
|
|
76
|
+
<div className="flex items-center justify-between mb-4">
|
|
77
|
+
<div className="h-6 w-16 bg-zinc-200 dark:bg-zinc-800 rounded" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
78
|
+
<div className="h-6 w-10 bg-zinc-200 dark:bg-zinc-800 rounded-full" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
79
|
+
</div>
|
|
80
|
+
<div className="space-y-3">
|
|
81
|
+
{[1, 2, 3].map(i => (
|
|
82
|
+
<div key={i} className="h-16 bg-zinc-200 dark:bg-zinc-800 rounded-xl" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
50
85
|
</div>
|
|
51
|
-
{errorStack && (
|
|
52
|
-
<details className="mt-4">
|
|
53
|
-
<summary className="text-base text-gray-500 cursor-pointer">Stack trace</summary>
|
|
54
|
-
<pre className="mt-2 text-base text-gray-500 overflow-auto p-3 bg-gray-50 rounded">{errorStack}</pre>
|
|
55
|
-
</details>
|
|
56
|
-
)}
|
|
57
86
|
</div>
|
|
58
87
|
</div>
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
</div>
|
|
89
|
+
) : error ? (
|
|
90
|
+
<div className="flex-1 flex items-center justify-center p-8">
|
|
91
|
+
<div className="max-w-2xl w-full bg-red-50 border-2 border-red-200 rounded-lg p-8">
|
|
92
|
+
<h1 className="text-xl font-bold text-red-800 mb-4">Failed to load project</h1>
|
|
93
|
+
<div className="bg-white border-2 border-red-100 rounded p-6 mb-6">
|
|
94
|
+
<p className="font-mono text-base text-red-700 whitespace-pre-wrap">{error}</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
) : serializedData ? (
|
|
99
|
+
<div className="max-w-7xl w-full mx-auto px-4 py-4">
|
|
100
|
+
<RealTimeKanbanWrapper initialData={serializedData} isBlank={isBlank} projectPath={projectPath} />
|
|
101
|
+
</div>
|
|
102
|
+
) : null;
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
{/* Kanban board — always mounted, hidden when viewing a child route */}
|
|
107
|
+
<div style={isChildRoute ? { display: 'none' } : undefined}>
|
|
108
|
+
{kanbanContent}
|
|
109
|
+
</div>
|
|
110
|
+
{/* Child route content (work detail, proof dashboard, decision) */}
|
|
111
|
+
<Outlet />
|
|
112
|
+
</>
|
|
113
|
+
);
|
|
61
114
|
}
|