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,121 +1,138 @@
1
- import { getWorkItem, getChildWorkItems, getDecisionsForWorkItem } from '@/lib/db';
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { useParams, Link, useNavigate } from 'react-router-dom';
2
3
  import { WorkItemTree } from '@/components/WorkItemTree';
3
4
  import { WorkItemHeader } from '@/components/WorkItemHeader';
4
5
  import { EditableDetailTitle } from '@/components/EditableDetailTitle';
5
6
  import { EditableDetailDescription } from '@/components/EditableDetailDescription';
6
- import Link from 'next/link';
7
- import { notFound } from 'next/navigation';
8
-
9
- const typeLabels: Record<string, { icon: string; label: string }> = {
10
- epic: { icon: '🎯', label: 'Epic' },
11
- feature: { icon: '✨', label: 'Feature' },
12
- chore: { icon: '🔧', label: 'Chore' },
13
- bug: { icon: '🐛', label: 'Bug' },
14
- };
15
-
16
- const statusLabels: Record<string, { label: string; color: string }> = {
17
- backlog: { label: 'Backlog', color: 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300' },
18
- todo: { label: 'Todo', color: 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300' },
19
- in_progress: { label: 'In Progress', color: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300' },
20
- done: { label: 'Done', color: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300' },
21
- cancelled: { label: 'Cancelled', color: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300' },
22
- };
23
-
24
- const modeLabels: Record<string, { label: string; color: string }> = {
25
- speed: { label: 'Speed Mode', color: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200' },
26
- stable: { label: 'Stable Mode', color: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' },
27
- production: { label: 'Production Mode', color: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200' },
28
- };
29
-
30
- interface PageProps {
31
- params: Promise<{ id: string }>;
32
- }
33
-
34
- export default async function WorkItemPage({ params }: PageProps) {
35
- const { id } = await params;
36
- const workItemId = parseInt(id, 10);
37
-
38
- if (isNaN(workItemId)) {
39
- notFound();
40
- }
41
-
42
- const item = getWorkItem(workItemId);
43
-
44
- if (!item) {
45
- notFound();
7
+ import { TYPE_LABELS, STATUS_LABELS, MODE_LABELS_FULL } from '@/lib/constants';
8
+ import { TypeIcon } from '@/components/TypeIcon';
9
+ import { DetailReviewActions } from '@/components/DetailReviewActions';
10
+ import { dataBridge, prefetch } from '@/lib/data-bridge';
11
+ import type { WorkItemData, DecisionData } from '@/lib/data-bridge';
12
+
13
+ export default function WorkItemPage() {
14
+ const { id } = useParams<{ id: string }>();
15
+ const navigate = useNavigate();
16
+ const [item, setItem] = useState<WorkItemData | null>(null);
17
+ const [children, setChildren] = useState<WorkItemData[]>([]);
18
+ const [decisions, setDecisions] = useState<DecisionData[]>([]);
19
+ const [parentItem, setParentItem] = useState<WorkItemData | null>(null);
20
+ const [loading, setLoading] = useState(true);
21
+ const [notFound, setNotFound] = useState(false);
22
+
23
+ useEffect(() => {
24
+ async function loadData() {
25
+ const workItemId = parseInt(id || '', 10);
26
+ if (isNaN(workItemId)) {
27
+ setNotFound(true);
28
+ setLoading(false);
29
+ return;
30
+ }
31
+
32
+ try {
33
+ // Uses prefetch cache — if the user hovered the kanban card, data is
34
+ // already loaded. Otherwise fetches fresh (same parallel IPC calls).
35
+ const { item: workItem, children: childItems, decisions: decisionItems } =
36
+ await prefetch.workItem(workItemId);
37
+
38
+ if (!workItem) {
39
+ setNotFound(true);
40
+ setLoading(false);
41
+ return;
42
+ }
43
+
44
+ // Parent fetch depends on the work item's parent_id, but we can
45
+ // overlap it with setState calls since React batches them.
46
+ const parent = workItem.parent_id
47
+ ? await dataBridge.getWorkItem(workItem.parent_id)
48
+ : null;
49
+
50
+ setItem(workItem);
51
+ setChildren(childItems);
52
+ setDecisions(decisionItems);
53
+ setParentItem(parent);
54
+ } catch (err) {
55
+ console.error('Failed to load work item:', err);
56
+ setNotFound(true);
57
+ } finally {
58
+ setLoading(false);
59
+ }
60
+ }
61
+ loadData();
62
+ }, [id]);
63
+
64
+ const handleTitleChange = useCallback((newTitle: string) => {
65
+ setItem(prev => prev ? { ...prev, title: newTitle } : prev);
66
+ }, []);
67
+
68
+ const handleDescriptionChange = useCallback((newDescription: string) => {
69
+ setItem(prev => prev ? { ...prev, description: newDescription } : prev);
70
+ }, []);
71
+
72
+ if (loading) return null;
73
+
74
+ if (notFound) {
75
+ return (
76
+ <div className="flex-1 flex items-center justify-center">
77
+ <div className="text-center">
78
+ <h1 className="text-2xl font-bold text-zinc-900 dark:text-zinc-100 mb-2">Not Found</h1>
79
+ <p className="text-zinc-500 mb-4">Work item not found.</p>
80
+ <Link to="/" viewTransition className="text-[#5a7d7f] hover:underline">← Back to Dashboard</Link>
81
+ </div>
82
+ </div>
83
+ );
46
84
  }
47
85
 
48
- const children = getChildWorkItems(workItemId);
49
- const decisions = getDecisionsForWorkItem(workItemId);
50
- const parentItem = item.parent_id ? getWorkItem(item.parent_id) : null;
86
+ if (!item) return null;
51
87
 
52
- const typeInfo = typeLabels[item.type] || { icon: '📄', label: 'Item' };
53
- const statusInfo = statusLabels[item.status] || statusLabels.backlog;
88
+ const typeInfo = TYPE_LABELS[item.type] || { icon: '📄', label: 'Item' };
89
+ const statusInfo = STATUS_LABELS[item.status] || STATUS_LABELS.backlog;
54
90
 
55
91
  return (
56
- <div className="min-h-screen bg-zinc-50 dark:bg-zinc-950">
57
- {/* Header */}
92
+ <div className="flex-1 overflow-y-auto bg-zinc-50 dark:bg-zinc-950">
58
93
  <header className="border-b border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900">
59
- <div className="max-w-4xl mx-auto px-4 py-4">
60
- <Link href="/" className="text-blue-600 dark:text-blue-400 hover:underline text-sm">
61
- ← Back to Dashboard
62
- </Link>
94
+ <div className="max-w-4xl mx-auto px-6 py-6">
95
+ <Link to="/" viewTransition className="text-[#5a7d7f] dark:text-[#a3bfc0] hover:underline text-base">← Back to Dashboard</Link>
63
96
  </div>
64
97
  </header>
65
98
 
66
99
  <main className="max-w-4xl mx-auto px-4 py-6">
67
- {/* Breadcrumb */}
68
100
  {parentItem && (
69
- <div className="mb-4 text-sm text-zinc-500">
70
- <Link href={`/work/${parentItem.id}`} className="hover:underline">
71
- {typeLabels[parentItem.type]?.icon} #{parentItem.id} {parentItem.title}
101
+ <div className="mb-6 text-base text-zinc-500">
102
+ <Link to={`/work/${parentItem.id}`} viewTransition className="inline-flex items-center gap-1.5 hover:underline">
103
+ <TypeIcon type={parentItem.type} className="w-6 h-6" /> #{parentItem.id} {parentItem.title}
72
104
  </Link>
73
105
  <span className="mx-2">→</span>
74
106
  </div>
75
107
  )}
76
108
 
77
- {/* Main card */}
78
109
  <div className="bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-800 overflow-hidden">
79
- {/* Header */}
80
- <div className="px-6 py-4 border-b border-zinc-200 dark:border-zinc-800">
81
- <div className="flex items-start justify-between gap-4">
110
+ <div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
111
+ <div className="flex items-start justify-between gap-6">
82
112
  <div className="flex-1 min-w-0">
83
- <WorkItemHeader
84
- id={item.id}
85
- title={item.title}
86
- type={item.type}
87
- typeIcon={typeInfo.icon}
88
- typeLabel={typeInfo.label}
89
- />
90
- <EditableDetailTitle title={item.title} itemId={item.id} />
113
+ <WorkItemHeader id={item.id} title={item.title} type={item.type} typeLabel={typeInfo.label} />
114
+ <EditableDetailTitle title={item.title} itemId={item.id} onTitleChange={handleTitleChange} />
91
115
  </div>
92
116
  <div className="flex items-center gap-2">
93
- {item.mode && modeLabels[item.mode] && (
94
- <span className={`text-sm px-2 py-1 rounded ${modeLabels[item.mode].color}`}>
95
- {modeLabels[item.mode].label}
117
+ {item.mode && MODE_LABELS_FULL[item.mode] && (
118
+ <span className={`text-base px-3 py-1.5 rounded ${MODE_LABELS_FULL[item.mode].color}`}>
119
+ {MODE_LABELS_FULL[item.mode].label}
96
120
  </span>
97
121
  )}
98
- <span className={`text-sm px-2 py-1 rounded ${statusInfo.color}`}>
99
- {statusInfo.label}
100
- </span>
122
+ <span className={`text-base px-3 py-1.5 rounded ${statusInfo.color}`}>{statusInfo.label}</span>
123
+ {!!item.ready_for_review && <DetailReviewActions workItemId={item.id} />}
101
124
  </div>
102
125
  </div>
103
126
  </div>
104
127
 
105
- {/* Description */}
106
- <div className="px-6 py-4 border-b border-zinc-200 dark:border-zinc-800">
107
- <h2 className="text-sm font-semibold text-zinc-500 uppercase tracking-wide mb-2">
108
- Description
109
- </h2>
110
- <EditableDetailDescription description={item.description} itemId={item.id} />
128
+ <div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
129
+ <h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-3">Description</h2>
130
+ <EditableDetailDescription description={item.description} itemId={item.id} onDescriptionChange={handleDescriptionChange} />
111
131
  </div>
112
132
 
113
- {/* Metadata */}
114
- <div className="px-6 py-4 border-b border-zinc-200 dark:border-zinc-800">
115
- <h2 className="text-sm font-semibold text-zinc-500 uppercase tracking-wide mb-3">
116
- Details
117
- </h2>
118
- <dl className="grid grid-cols-2 gap-4 text-sm">
133
+ <div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
134
+ <h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-4">Details</h2>
135
+ <dl className="grid grid-cols-2 gap-6 text-base">
119
136
  {item.branch_name && (
120
137
  <div>
121
138
  <dt className="text-zinc-500">Branch</dt>
@@ -130,59 +147,41 @@ export default async function WorkItemPage({ params }: PageProps) {
130
147
  )}
131
148
  <div>
132
149
  <dt className="text-zinc-500">Created</dt>
133
- <dd className="text-zinc-900 dark:text-zinc-100">
134
- {new Date(item.created_at).toLocaleDateString()}
135
- </dd>
150
+ <dd className="text-zinc-900 dark:text-zinc-100">{new Date(item.created_at).toLocaleDateString()}</dd>
136
151
  </div>
137
152
  {item.completed_at && (
138
153
  <div>
139
154
  <dt className="text-zinc-500">Completed</dt>
140
- <dd className="text-zinc-900 dark:text-zinc-100">
141
- {new Date(item.completed_at).toLocaleDateString()}
142
- </dd>
155
+ <dd className="text-zinc-900 dark:text-zinc-100">{new Date(item.completed_at).toLocaleDateString()}</dd>
143
156
  </div>
144
157
  )}
145
158
  </dl>
146
159
  </div>
147
160
 
148
- {/* Decisions */}
149
161
  {decisions.length > 0 && (
150
- <div className="px-6 py-4 border-b border-zinc-200 dark:border-zinc-800">
151
- <h2 className="text-sm font-semibold text-zinc-500 uppercase tracking-wide mb-3">
152
- Decisions
153
- </h2>
154
- <div className="space-y-4">
162
+ <div className="px-8 py-6 border-b border-zinc-200 dark:border-zinc-800">
163
+ <h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-3">Decisions</h2>
164
+ <div className="space-y-6">
155
165
  {decisions.map((decision) => (
156
- <div key={decision.id} className="bg-zinc-50 dark:bg-zinc-800 rounded-lg p-4">
157
- <div className="flex items-center gap-2 mb-2">
158
- <span className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
159
- {decision.aspect}
160
- </span>
161
- <span className="text-xs text-zinc-500">
162
- {new Date(decision.created_at).toLocaleDateString()}
163
- </span>
166
+ <div key={decision.id} className="bg-zinc-50 dark:bg-zinc-800 rounded-lg p-6">
167
+ <div className="flex items-center gap-3 mb-3">
168
+ <span className="text-base font-medium text-zinc-900 dark:text-zinc-100">{decision.aspect}</span>
169
+ <span className="text-base text-zinc-500">{new Date(decision.created_at).toLocaleDateString()}</span>
164
170
  </div>
165
- <p className="text-zinc-700 dark:text-zinc-300 font-medium">
166
- {decision.decision}
167
- </p>
168
- {decision.rationale && (
169
- <p className="text-sm text-zinc-500 mt-1">
170
- {decision.rationale}
171
- </p>
172
- )}
171
+ <p className="text-zinc-700 dark:text-zinc-300 font-medium">{decision.decision}</p>
172
+ {decision.rationale && <p className="text-base text-zinc-500 mt-1.5">{decision.rationale}</p>}
173
173
  </div>
174
174
  ))}
175
175
  </div>
176
176
  </div>
177
177
  )}
178
178
 
179
- {/* Children */}
180
179
  {children.length > 0 && (
181
- <div className="px-6 py-4">
182
- <h2 className="text-sm font-semibold text-zinc-500 uppercase tracking-wide mb-3">
180
+ <div className="px-8 py-6">
181
+ <h2 className="text-base font-semibold text-zinc-500 uppercase tracking-wide mb-4">
183
182
  {item.type === 'epic' ? 'Features & Chores' : 'Child Items'} ({children.length})
184
183
  </h2>
185
- <WorkItemTree items={children.map(c => ({ ...c, children: [] }))} />
184
+ <WorkItemTree items={children.map(c => ({ ...c, type: c.type, children: [], epic_id: c.parent_id, rejection_round: c.rejection_round ?? null, rejection_history: c.rejection_history ?? null }))} />
186
185
  </div>
187
186
  )}
188
187
  </div>