jettypod 4.4.118 → 4.4.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/.env +4 -3
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -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 (!window.electronAPI?.isElectron) {
6
+ if (!isTauri()) {
8
7
  return { success: false, error: 'Only available in the desktop app.' };
9
8
  }
10
- return await window.electronAPI.claudeCode.login();
9
+ return await claudeCode.login();
11
10
  };
12
11
 
13
12
  const handleCheckAuth = async () => {
14
- if (!window.electronAPI?.isElectron) return false;
15
- return await window.electronAPI.claudeCode.isAuthenticated();
13
+ if (!isTauri()) return false;
14
+ return await claudeCode.isAuthenticated();
16
15
  };
17
16
 
18
17
  return (
@@ -1,41 +1,65 @@
1
- import { getDecision } from '@/lib/db';
2
- import Link from 'next/link';
3
- import { notFound } from 'next/navigation';
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
- interface PageProps {
6
- params: Promise<{ id: string }>;
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
- export default async function DecisionPage({ params }: PageProps) {
10
- const { id } = await params;
11
- const decisionId = parseInt(id, 10);
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
- if (isNaN(decisionId)) {
14
- notFound();
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
- const decision = getDecision(decisionId);
37
+ if (loading) return null;
18
38
 
19
- if (!decision) {
20
- notFound();
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="min-h-screen bg-zinc-50 dark:bg-zinc-950">
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
- <div className="max-w-4xl mx-auto px-4 py-4">
28
- <Link href="/" className="text-blue-600 dark:text-blue-400 hover:underline text-sm">
29
- ← Back to Dashboard
30
- </Link>
54
+ <div className="max-w-4xl mx-auto px-6 py-6">
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
- <div className="mb-4 text-sm text-zinc-500">
38
- <Link href={`/work/${decision.work_item_id}`} className="hover:underline">
61
+ <div className="mb-6 text-base text-zinc-500">
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,65 +67,46 @@ 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
- <div className="px-6 py-4 border-b border-zinc-200 dark:border-zinc-800">
50
- <div className="flex items-start justify-between gap-4">
71
+ <div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
72
+ <div className="flex items-start justify-between gap-6">
51
73
  <div>
52
- <div className="flex items-center gap-2 text-sm text-zinc-500 mb-1">
74
+ <div className="flex items-center gap-3 text-base text-zinc-500 mb-1.5">
53
75
  <span>📋 Decision</span>
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
- <div className="px-6 py-4 border-b border-zinc-200 dark:border-zinc-800">
66
- <h2 className="text-sm font-semibold text-zinc-500 uppercase tracking-wide mb-2">
67
- Decision
68
- </h2>
69
- <p className="text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap text-lg">
70
- {decision.decision}
71
- </p>
84
+ <div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
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
- <div className="px-6 py-4 border-b border-zinc-200 dark:border-zinc-800">
77
- <h2 className="text-sm font-semibold text-zinc-500 uppercase tracking-wide mb-2">
78
- Rationale
79
- </h2>
80
- <p className="text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap">
81
- {decision.rationale}
82
- </p>
90
+ <div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
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
- <div className="px-6 py-4">
88
- <h2 className="text-sm font-semibold text-zinc-500 uppercase tracking-wide mb-3">
89
- Details
90
- </h2>
91
- <dl className="grid grid-cols-2 gap-4 text-sm">
96
+ <div className="px-8 py-6">
97
+ <h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-4">Details</h2>
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 href={`/work/${decision.work_item_id}`} className="hover:underline text-blue-600 dark:text-blue-400">
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,18 +122,18 @@ export default function GateDemoPage() {
124
122
  const [activeTab, setActiveTab] = useState<DemoTab>('workflow');
125
123
 
126
124
  return (
127
- <div className="min-h-screen bg-zinc-100 dark:bg-zinc-900 p-8">
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
- <div className="mb-6">
131
- <h1 className="text-2xl font-bold text-zinc-900 dark:text-zinc-100 mb-1">Gate Protocol Demo</h1>
132
- <p className="text-zinc-500 dark:text-zinc-400 text-sm">
128
+ <div className="mb-8">
129
+ <h1 className="text-2xl font-bold text-zinc-900 dark:text-zinc-100 mb-1.5">Gate Protocol Demo</h1>
130
+ <p className="text-zinc-500 dark:text-zinc-400 text-base">
133
131
  Prototype narrated UI components for workflow status and user input.
134
132
  </p>
135
133
  </div>
136
134
 
137
135
  {/* Tab nav */}
138
- <div className="flex gap-1 mb-6 bg-white dark:bg-zinc-800 rounded-xl border border-zinc-200 dark:border-zinc-700 p-1 w-fit"
136
+ <div className="flex gap-1.5 mb-8 bg-white dark:bg-zinc-800 rounded-xl border border-zinc-200 dark:border-zinc-700 p-1.5 w-fit"
139
137
  style={{ boxShadow: '0 1px 2px rgba(0,0,0,0.03)' }}
140
138
  >
141
139
  {([
@@ -146,7 +144,7 @@ export default function GateDemoPage() {
146
144
  <button
147
145
  key={id}
148
146
  onClick={() => setActiveTab(id)}
149
- className={`px-4 py-1.5 text-sm font-medium rounded-lg transition-colors ${
147
+ className={`px-5 py-2 text-base font-medium rounded-lg transition-colors ${
150
148
  activeTab === id
151
149
  ? 'bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900'
152
150
  : 'text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200'
@@ -250,11 +248,11 @@ function WorkflowDemo() {
250
248
  return (
251
249
  <>
252
250
  {/* Controls */}
253
- <div className="flex items-center gap-3 mb-6">
251
+ <div className="flex items-center gap-4 mb-8">
254
252
  <button
255
253
  onClick={play}
256
254
  disabled={isPlaying}
257
- className="px-4 py-2 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-sm font-medium rounded-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0"
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'}
@@ -262,20 +260,20 @@ function WorkflowDemo() {
262
260
  {(isPlaying || isDone) && (
263
261
  <button
264
262
  onClick={reset}
265
- className="px-4 py-2 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 text-sm font-medium rounded-xl transition-colors"
263
+ className="px-5 py-3 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 text-base font-medium rounded-xl transition-colors"
266
264
  >
267
265
  Reset
268
266
  </button>
269
267
  )}
270
268
  <div className="flex-1" />
271
- <div className="flex bg-white dark:bg-zinc-800 rounded-xl border border-zinc-200 dark:border-zinc-700 p-1 overflow-hidden"
269
+ <div className="flex bg-white dark:bg-zinc-800 rounded-xl border border-zinc-200 dark:border-zinc-700 p-1.5 overflow-hidden"
272
270
  style={{ boxShadow: '0 1px 2px rgba(0,0,0,0.03)' }}
273
271
  >
274
272
  {(['narrated', 'direct', 'split'] as ViewMode[]).map(mode => (
275
273
  <button
276
274
  key={mode}
277
275
  onClick={() => setViewMode(mode)}
278
- className={`px-3 py-1.5 text-xs font-medium rounded-lg transition-colors ${
276
+ className={`px-4 py-2 text-base font-medium rounded-lg transition-colors ${
279
277
  viewMode === mode
280
278
  ? 'bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900'
281
279
  : 'text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200'
@@ -290,13 +288,13 @@ function WorkflowDemo() {
290
288
  {/* Progress */}
291
289
  {(isPlaying || isDone) && (
292
290
  <div className="mb-6">
293
- <div className="flex items-center justify-between text-xs text-zinc-500 dark:text-zinc-400 mb-1.5">
291
+ <div className="flex items-center justify-between text-base text-zinc-500 dark:text-zinc-400 mb-2">
294
292
  <span>{gateCount} of {WORKFLOW_GATES.length} gates</span>
295
293
  <span>{visibleCount} of {totalSteps} events</span>
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-all duration-500"
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>
@@ -305,7 +303,7 @@ function WorkflowDemo() {
305
303
 
306
304
  {/* Content */}
307
305
  {viewMode === 'split' ? (
308
- <div className="grid grid-cols-2 gap-4">
306
+ <div className="grid grid-cols-2 gap-6">
309
307
  <Panel title="Summary View" subtitle="What the user sees" highlight>
310
308
  <NarratedView gates={visibleGates} isPlaying={isPlaying} answeredQuestions={answeredQuestions} onAnswerQuestion={handleAnswerQuestion} />
311
309
  </Panel>
@@ -324,26 +322,26 @@ function WorkflowDemo() {
324
322
  )}
325
323
 
326
324
  {!isPlaying && !isPaused && !isDone && (
327
- <div className="mt-6 bg-white dark:bg-zinc-800 rounded-xl border border-zinc-200 dark:border-zinc-700 p-4"
325
+ <div className="mt-6 bg-white dark:bg-zinc-800 rounded-xl border border-zinc-200 dark:border-zinc-700 p-6"
328
326
  style={{ boxShadow: '0 1px 2px rgba(0,0,0,0.03)' }}
329
327
  >
330
- <h3 className="text-sm font-semibold text-zinc-700 dark:text-zinc-300 mb-3">How it works</h3>
331
- <div className="grid grid-cols-4 gap-4 text-xs text-zinc-500 dark:text-zinc-400">
328
+ <h3 className="text-base font-semibold text-zinc-700 dark:text-zinc-300 mb-4">How it works</h3>
329
+ <div className="grid grid-cols-4 gap-6 text-base text-zinc-500 dark:text-zinc-400">
332
330
  <div>
333
331
  <span className="font-medium text-zinc-700 dark:text-zinc-300">1. User sends request</span>
334
- <p className="mt-1">"Add form validation to the signup page"</p>
332
+ <p className="mt-1.5">"Add form validation to the signup page"</p>
335
333
  </div>
336
334
  <div>
337
335
  <span className="font-medium text-zinc-700 dark:text-zinc-300">2. Question gates pause</span>
338
- <p className="mt-1">When Claude needs input, an interactive choice card appears</p>
336
+ <p className="mt-1.5">When Claude needs input, an interactive choice card appears</p>
339
337
  </div>
340
338
  <div>
341
339
  <span className="font-medium text-zinc-700 dark:text-zinc-300">3. Claude works (hidden)</span>
342
- <p className="mt-1">Reads files, makes edits, runs commands — behind the scenes</p>
340
+ <p className="mt-1.5">Reads files, makes edits, runs commands — behind the scenes</p>
343
341
  </div>
344
342
  <div>
345
343
  <span className="font-medium text-zinc-700 dark:text-zinc-300">4. Gates signal progress</span>
346
- <p className="mt-1">Clean status cards appear at key workflow milestones</p>
344
+ <p className="mt-1.5">Clean status cards appear at key workflow milestones</p>
347
345
  </div>
348
346
  </div>
349
347
  </div>
@@ -376,10 +374,10 @@ function CardsShowcase() {
376
374
 
377
375
  return (
378
376
  <div>
379
- <p className="text-sm text-zinc-500 dark:text-zinc-400 mb-4">
377
+ <p className="text-base text-zinc-500 dark:text-zinc-400 mb-4">
380
378
  All gate card types shown with sample data, including the interactive question gate. Each card matches the kanban card design system.
381
379
  </p>
382
- <div className="grid grid-cols-2 gap-3">
380
+ <div className="grid grid-cols-2 gap-4">
383
381
  {allGateTypes.map((gate, i) => (
384
382
  <GateCard
385
383
  key={i}
@@ -411,8 +409,8 @@ function ChoicesShowcase() {
411
409
  <div className="space-y-8">
412
410
  {/* Approach selection */}
413
411
  <div>
414
- <h3 className="text-sm font-semibold text-zinc-700 dark:text-zinc-300 mb-1">UX Approach Selection</h3>
415
- <p className="text-xs text-zinc-500 dark:text-zinc-400 mb-3">
412
+ <h3 className="text-base font-semibold text-zinc-700 dark:text-zinc-300 mb-1">UX Approach Selection</h3>
413
+ <p className="text-base text-zinc-500 dark:text-zinc-400 mb-3">
416
414
  Shown during feature-planning when Claude proposes multiple approaches.
417
415
  </p>
418
416
  <div className="max-w-lg">
@@ -427,8 +425,8 @@ function ChoicesShowcase() {
427
425
 
428
426
  {/* Confirmation */}
429
427
  <div>
430
- <h3 className="text-sm font-semibold text-zinc-700 dark:text-zinc-300 mb-1">Plan Confirmation</h3>
431
- <p className="text-xs text-zinc-500 dark:text-zinc-400 mb-3">
428
+ <h3 className="text-base font-semibold text-zinc-700 dark:text-zinc-300 mb-1">Plan Confirmation</h3>
429
+ <p className="text-base text-zinc-500 dark:text-zinc-400 mb-3">
432
430
  Shown when Claude needs a go/no-go decision before starting implementation.
433
431
  </p>
434
432
  <div className="max-w-lg">
@@ -443,8 +441,8 @@ function ChoicesShowcase() {
443
441
 
444
442
  {/* Disabled state (after selection) */}
445
443
  <div>
446
- <h3 className="text-sm font-semibold text-zinc-700 dark:text-zinc-300 mb-1">After Selection (Locked)</h3>
447
- <p className="text-xs text-zinc-500 dark:text-zinc-400 mb-3">
444
+ <h3 className="text-base font-semibold text-zinc-700 dark:text-zinc-300 mb-1">After Selection (Locked)</h3>
445
+ <p className="text-base text-zinc-500 dark:text-zinc-400 mb-3">
448
446
  How the card looks after the user has made their choice and the workflow continues.
449
447
  </p>
450
448
  <div className="max-w-lg">
@@ -476,21 +474,21 @@ function Panel({ title, subtitle, highlight, children }: {
476
474
  <div
477
475
  className={`bg-white dark:bg-zinc-800 rounded-xl border overflow-hidden ${
478
476
  highlight
479
- ? 'border-blue-200 dark:border-blue-800'
477
+ ? 'border-[#819D9F]/30 dark:border-[#819D9F]/30'
480
478
  : 'border-zinc-200 dark:border-zinc-700'
481
479
  }`}
482
480
  style={{ boxShadow: PANEL_SHADOW }}
483
481
  >
484
482
  <div className={`px-4 py-3 border-b ${
485
483
  highlight
486
- ? 'border-blue-100 dark:border-blue-900 bg-blue-50/50 dark:bg-blue-900/10'
484
+ ? 'border-[#819D9F]/20 dark:border-[#819D9F]/20 bg-[#e8f0f0]/50 dark:bg-[#819D9F]/10'
487
485
  : 'border-zinc-100 dark:border-zinc-700'
488
486
  }`}>
489
- <h2 className={`text-sm font-semibold ${
490
- highlight ? 'text-blue-900 dark:text-blue-300' : 'text-zinc-700 dark:text-zinc-300'
487
+ <h2 className={`text-base font-semibold ${
488
+ highlight ? 'text-zinc-900 dark:text-[#a3bfc0]' : 'text-zinc-700 dark:text-zinc-300'
491
489
  }`}>{title}</h2>
492
- <p className={`text-xs ${
493
- highlight ? 'text-blue-500 dark:text-blue-400' : 'text-zinc-400 dark:text-zinc-500'
490
+ <p className={`text-base ${
491
+ highlight ? 'text-[#819D9F] dark:text-[#a3bfc0]' : 'text-zinc-400 dark:text-zinc-500'
494
492
  }`}>{subtitle}</p>
495
493
  </div>
496
494
  <div className="p-4 min-h-[400px] max-h-[600px] overflow-y-auto">
@@ -503,13 +501,13 @@ function Panel({ title, subtitle, highlight, children }: {
503
501
  function UserBubble({ text }: { text: string }) {
504
502
  return (
505
503
  <div
506
- className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-xl p-3 ml-8"
504
+ className="bg-[#e8f0f0] dark:bg-[#819D9F]/20 border border-[#819D9F]/30 dark:border-[#819D9F]/30 rounded-xl p-3 ml-8"
507
505
  style={{ boxShadow: '0 1px 2px rgba(0,0,0,0.03)' }}
508
506
  >
509
507
  <div className="flex items-center gap-2 mb-1">
510
- <span className="text-xs font-medium text-blue-600 dark:text-blue-400">You</span>
508
+ <span className="text-base font-medium text-[#5a7d7f] dark:text-[#a3bfc0]">You</span>
511
509
  </div>
512
- <p className="text-sm text-blue-900 dark:text-blue-100">{text}</p>
510
+ <p className="text-base text-zinc-900 dark:text-zinc-100">{text}</p>
513
511
  </div>
514
512
  );
515
513
  }
@@ -522,7 +520,7 @@ function NarratedView({ gates, isPlaying, answeredQuestions, onAnswerQuestion }:
522
520
  }) {
523
521
  if (gates.length === 0 && !isPlaying) {
524
522
  return (
525
- <div className="flex items-center justify-center h-full text-zinc-400 dark:text-zinc-500 text-sm">
523
+ <div className="flex items-center justify-center h-full text-zinc-400 dark:text-zinc-500 text-base">
526
524
  Press "Play Workflow" to start
527
525
  </div>
528
526
  );
@@ -558,7 +556,7 @@ function DirectView({ messages, gates, isPlaying, answeredQuestions, onAnswerQue
558
556
  }) {
559
557
  if (messages.length === 0 && !isPlaying) {
560
558
  return (
561
- <div className="flex items-center justify-center h-full text-zinc-400 dark:text-zinc-500 text-sm">
559
+ <div className="flex items-center justify-center h-full text-zinc-400 dark:text-zinc-500 text-base">
562
560
  Press "Play Workflow" to start
563
561
  </div>
564
562
  );
@@ -603,7 +601,7 @@ function DirectView({ messages, gates, isPlaying, answeredQuestions, onAnswerQue
603
601
  if (msg.type === 'assistant') {
604
602
  return (
605
603
  <div key={i} className="bg-zinc-50 dark:bg-zinc-800/50 rounded-xl p-3" style={{ boxShadow: '0 1px 2px rgba(0,0,0,0.02)' }}>
606
- <p className="text-sm text-zinc-700 dark:text-zinc-300">{msg.content}</p>
604
+ <p className="text-base text-zinc-700 dark:text-zinc-300">{msg.content}</p>
607
605
  </div>
608
606
  );
609
607
  }
@@ -645,8 +643,8 @@ function DirectView({ messages, gates, isPlaying, answeredQuestions, onAnswerQue
645
643
 
646
644
  function WorkingIndicator() {
647
645
  return (
648
- <div className="flex items-center gap-2 text-xs text-zinc-400 dark:text-zinc-500 py-2 pl-1">
649
- <span className="w-1.5 h-1.5 bg-blue-400 rounded-full animate-pulse" />
646
+ <div className="flex items-center gap-2 text-base text-zinc-400 dark:text-zinc-500 py-2 pl-1">
647
+ <span className="w-1.5 h-1.5 bg-[#819D9F] rounded-full animate-pulse" />
650
648
  Working...
651
649
  </div>
652
650
  );