code-coordinator 0.5.46__py3-none-any.whl

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 (295) hide show
  1. code_coordinator-0.5.46.dist-info/METADATA +625 -0
  2. code_coordinator-0.5.46.dist-info/RECORD +295 -0
  3. code_coordinator-0.5.46.dist-info/WHEEL +5 -0
  4. code_coordinator-0.5.46.dist-info/entry_points.txt +2 -0
  5. code_coordinator-0.5.46.dist-info/licenses/LICENSE +110 -0
  6. code_coordinator-0.5.46.dist-info/top_level.txt +1 -0
  7. coord/__init__.py +176 -0
  8. coord/_board_mapping.py +229 -0
  9. coord/acceptance.py +468 -0
  10. coord/acceptance_drivers.py +632 -0
  11. coord/agent.py +7517 -0
  12. coord/agent_app.py +1555 -0
  13. coord/agent_update.py +417 -0
  14. coord/agents/opencode/.gitignore +13 -0
  15. coord/agents/opencode/agents/work.md +129 -0
  16. coord/agents/opencode/routing.jsonc +49 -0
  17. coord/audit.py +301 -0
  18. coord/auto_loop.py +1440 -0
  19. coord/board_bool_guard.py +72 -0
  20. coord/board_service.py +141 -0
  21. coord/board_wire.py +309 -0
  22. coord/brain.py +581 -0
  23. coord/branch_model.py +214 -0
  24. coord/cargo_cache.py +258 -0
  25. coord/ci_github.py +386 -0
  26. coord/ci_store.py +560 -0
  27. coord/claim.py +353 -0
  28. coord/cli.py +454 -0
  29. coord/client.py +610 -0
  30. coord/commands/__init__.py +1 -0
  31. coord/commands/_common.py +329 -0
  32. coord/commands/acceptance.py +916 -0
  33. coord/commands/agent_ops.py +1339 -0
  34. coord/commands/audit.py +131 -0
  35. coord/commands/chat.py +320 -0
  36. coord/commands/dispatch.py +1780 -0
  37. coord/commands/dispatch_workers.py +4894 -0
  38. coord/commands/drive.py +616 -0
  39. coord/commands/drive_queue.py +1203 -0
  40. coord/commands/gate_a.py +217 -0
  41. coord/commands/gates.py +89 -0
  42. coord/commands/issues.py +681 -0
  43. coord/commands/lifecycle.py +513 -0
  44. coord/commands/merge.py +1900 -0
  45. coord/commands/milestone.py +2081 -0
  46. coord/commands/plan_followup.py +1243 -0
  47. coord/commands/plans.py +156 -0
  48. coord/commands/release.py +2232 -0
  49. coord/commands/report.py +341 -0
  50. coord/commands/review.py +1523 -0
  51. coord/commands/scorecard.py +252 -0
  52. coord/commands/sessions.py +1930 -0
  53. coord/commands/setup.py +576 -0
  54. coord/commands/status.py +2089 -0
  55. coord/commands/terminal.py +385 -0
  56. coord/commands/test_gate.py +775 -0
  57. coord/commands/tui.py +288 -0
  58. coord/comments.py +718 -0
  59. coord/config.py +3032 -0
  60. coord/conflict_fix.py +633 -0
  61. coord/dao.py +483 -0
  62. coord/dashboard/__init__.py +0 -0
  63. coord/dashboard/fixture.py +376 -0
  64. coord/dashboard/index.html +658 -0
  65. coord/dashboard/server.py +1894 -0
  66. coord/dashboard/terminal.py +382 -0
  67. coord/dashboard/webapp/.gitignore +9 -0
  68. coord/dashboard/webapp/components.json +17 -0
  69. coord/dashboard/webapp/dist/assets/Gallery-da3qNiIw.js +71 -0
  70. coord/dashboard/webapp/dist/assets/Terminal-9CEnUXvW.css +32 -0
  71. coord/dashboard/webapp/dist/assets/Terminal-skVFCxPU.js +63 -0
  72. coord/dashboard/webapp/dist/assets/index-DltfZR5f.js +184 -0
  73. coord/dashboard/webapp/dist/assets/index-Dq4kwTdw.css +1 -0
  74. coord/dashboard/webapp/dist/assets/workbox-window.prod.es5-BqEJf4Xk.js +2 -0
  75. coord/dashboard/webapp/dist/icons/icon-192.png +0 -0
  76. coord/dashboard/webapp/dist/icons/icon-512.png +0 -0
  77. coord/dashboard/webapp/dist/icons/icon.svg +5 -0
  78. coord/dashboard/webapp/dist/index.html +38 -0
  79. coord/dashboard/webapp/dist/manifest.webmanifest +1 -0
  80. coord/dashboard/webapp/dist/sw.js +1 -0
  81. coord/dashboard/webapp/dist/workbox-e4022e15.js +1 -0
  82. coord/dashboard/webapp/e2e/available-gates-terminal.spec.ts +75 -0
  83. coord/dashboard/webapp/e2e/deep-link.spec.ts +172 -0
  84. coord/dashboard/webapp/e2e/fixtureServer.ts +155 -0
  85. coord/dashboard/webapp/e2e/live-update-fixture.spec.ts +113 -0
  86. coord/dashboard/webapp/e2e/realtime.spec.ts +238 -0
  87. coord/dashboard/webapp/e2e/shell.spec.ts +309 -0
  88. coord/dashboard/webapp/e2e/smoke.spec.ts +191 -0
  89. coord/dashboard/webapp/e2e/terminal.spec.ts +420 -0
  90. coord/dashboard/webapp/e2e/theme.spec.ts +138 -0
  91. coord/dashboard/webapp/eslint.config.js +20 -0
  92. coord/dashboard/webapp/index.html +37 -0
  93. coord/dashboard/webapp/node_modules/flatted/python/flatted.py +144 -0
  94. coord/dashboard/webapp/package-lock.json +10584 -0
  95. coord/dashboard/webapp/package.json +63 -0
  96. coord/dashboard/webapp/playwright.acceptance.config.ts +166 -0
  97. coord/dashboard/webapp/playwright.config.ts +93 -0
  98. coord/dashboard/webapp/postcss.config.js +6 -0
  99. coord/dashboard/webapp/public/icons/icon-192.png +0 -0
  100. coord/dashboard/webapp/public/icons/icon-512.png +0 -0
  101. coord/dashboard/webapp/public/icons/icon.svg +5 -0
  102. coord/dashboard/webapp/src/App.tsx +140 -0
  103. coord/dashboard/webapp/src/api/client.ts +199 -0
  104. coord/dashboard/webapp/src/api/generated.ts +176 -0
  105. coord/dashboard/webapp/src/components/ConnectionBadge.tsx +52 -0
  106. coord/dashboard/webapp/src/components/Detail.tsx +800 -0
  107. coord/dashboard/webapp/src/components/Gallery.tsx +341 -0
  108. coord/dashboard/webapp/src/components/Home.tsx +435 -0
  109. coord/dashboard/webapp/src/components/MobileKeyBar.tsx +280 -0
  110. coord/dashboard/webapp/src/components/PanelHeader.tsx +59 -0
  111. coord/dashboard/webapp/src/components/PipelineCard.tsx +168 -0
  112. coord/dashboard/webapp/src/components/SessionCard.tsx +99 -0
  113. coord/dashboard/webapp/src/components/SessionDetail.tsx +140 -0
  114. coord/dashboard/webapp/src/components/SessionsList.tsx +81 -0
  115. coord/dashboard/webapp/src/components/Terminal.tsx +376 -0
  116. coord/dashboard/webapp/src/components/__tests__/ConnectionBadge.test.tsx +81 -0
  117. coord/dashboard/webapp/src/components/__tests__/Detail.test.tsx +680 -0
  118. coord/dashboard/webapp/src/components/__tests__/Gallery.test.tsx +83 -0
  119. coord/dashboard/webapp/src/components/__tests__/Home.test.tsx +271 -0
  120. coord/dashboard/webapp/src/components/__tests__/MobileKeyBar.test.tsx +197 -0
  121. coord/dashboard/webapp/src/components/__tests__/PipelineCard.test.tsx +143 -0
  122. coord/dashboard/webapp/src/components/__tests__/SessionCard.test.tsx +106 -0
  123. coord/dashboard/webapp/src/components/__tests__/Terminal.test.tsx +504 -0
  124. coord/dashboard/webapp/src/components/ui/badge.tsx +41 -0
  125. coord/dashboard/webapp/src/components/ui/button.tsx +54 -0
  126. coord/dashboard/webapp/src/components/ui/card.tsx +55 -0
  127. coord/dashboard/webapp/src/components/ui/dialog.tsx +99 -0
  128. coord/dashboard/webapp/src/components/ui/dropdown-menu.tsx +189 -0
  129. coord/dashboard/webapp/src/components/ui/empty-state.tsx +35 -0
  130. coord/dashboard/webapp/src/components/ui/sheet.tsx +123 -0
  131. coord/dashboard/webapp/src/components/ui/skeleton.tsx +9 -0
  132. coord/dashboard/webapp/src/components/ui/tabs.tsx +55 -0
  133. coord/dashboard/webapp/src/components/ui/theme-provider.tsx +78 -0
  134. coord/dashboard/webapp/src/components/ui/theme-toggle.tsx +20 -0
  135. coord/dashboard/webapp/src/components/ui/toast.tsx +123 -0
  136. coord/dashboard/webapp/src/components/ui/toaster.tsx +30 -0
  137. coord/dashboard/webapp/src/components/ui/tooltip.tsx +26 -0
  138. coord/dashboard/webapp/src/components/ui/use-toast.ts +134 -0
  139. coord/dashboard/webapp/src/index.css +210 -0
  140. coord/dashboard/webapp/src/lib/pipeline.ts +29 -0
  141. coord/dashboard/webapp/src/lib/utils.ts +6 -0
  142. coord/dashboard/webapp/src/main.tsx +46 -0
  143. coord/dashboard/webapp/src/realtime/RealtimeProvider.tsx +112 -0
  144. coord/dashboard/webapp/src/realtime/__tests__/RealtimeProvider.test.tsx +189 -0
  145. coord/dashboard/webapp/src/realtime/__tests__/connection.test.ts +255 -0
  146. coord/dashboard/webapp/src/realtime/connection.ts +227 -0
  147. coord/dashboard/webapp/src/realtime/events.ts +100 -0
  148. coord/dashboard/webapp/src/routes/__tests__/paths.test.ts +92 -0
  149. coord/dashboard/webapp/src/routes/paths.ts +92 -0
  150. coord/dashboard/webapp/src/shell/ActivityRail.tsx +335 -0
  151. coord/dashboard/webapp/src/shell/AppShell.tsx +276 -0
  152. coord/dashboard/webapp/src/shell/ComingSoon.tsx +33 -0
  153. coord/dashboard/webapp/src/shell/EmptyDetail.tsx +26 -0
  154. coord/dashboard/webapp/src/shell/RouteNotFound.tsx +33 -0
  155. coord/dashboard/webapp/src/shell/ShellLayout.tsx +147 -0
  156. coord/dashboard/webapp/src/shell/StatusBar.tsx +46 -0
  157. coord/dashboard/webapp/src/shell/__tests__/ShellLayout.test.tsx +520 -0
  158. coord/dashboard/webapp/src/shell/__tests__/shellState.test.ts +95 -0
  159. coord/dashboard/webapp/src/shell/__tests__/stubViewport.ts +40 -0
  160. coord/dashboard/webapp/src/shell/breakpoints.ts +87 -0
  161. coord/dashboard/webapp/src/shell/railItems.ts +105 -0
  162. coord/dashboard/webapp/src/shell/shellState.ts +174 -0
  163. coord/dashboard/webapp/src/shell/useRegionFocus.ts +95 -0
  164. coord/dashboard/webapp/src/test-setup.ts +41 -0
  165. coord/dashboard/webapp/src/vite-env.d.ts +2 -0
  166. coord/dashboard/webapp/tailwind.config.js +140 -0
  167. coord/dashboard/webapp/tsconfig.json +25 -0
  168. coord/dashboard/webapp/tsconfig.node.json +11 -0
  169. coord/dashboard/webapp/vite.config.ts +71 -0
  170. coord/db.py +1076 -0
  171. coord/dead_end.py +332 -0
  172. coord/deploy/README.md +33 -0
  173. coord/deploy/coord-agent.service +89 -0
  174. coord/deploy/coord-db-backup.service +60 -0
  175. coord/deploy/coord-db-backup.sh +74 -0
  176. coord/deploy/coord-db-backup.timer +18 -0
  177. coord/deploy/coord-drive-queue.service +117 -0
  178. coord/deploy/coord-drive-queue.timer +39 -0
  179. coord/deploy/coord-notify.service +48 -0
  180. coord/deploy/coord-notify.timer +24 -0
  181. coord/deploy/coord-release-propagate.service +83 -0
  182. coord/deploy/coord-release-propagate.timer +38 -0
  183. coord/deploy/coord-release-window.service +119 -0
  184. coord/deploy/coord-release-window.timer +36 -0
  185. coord/deploy/coord-serve.service +82 -0
  186. coord/deploy/coord-web-dist-build.service +43 -0
  187. coord/deploy/coord-web-dist-build.timer +36 -0
  188. coord/deploy/coord-web.service +125 -0
  189. coord/deploy_manifest.py +80 -0
  190. coord/deploy_units.py +384 -0
  191. coord/deps.py +115 -0
  192. coord/diagnose.py +1623 -0
  193. coord/dispatch.py +1009 -0
  194. coord/dist_name.py +123 -0
  195. coord/drive.py +3101 -0
  196. coord/drive_queue.py +2298 -0
  197. coord/drive_state.py +870 -0
  198. coord/events.py +381 -0
  199. coord/failure_class.py +914 -0
  200. coord/filelock.py +168 -0
  201. coord/fleet_config_health.py +300 -0
  202. coord/freshness.py +206 -0
  203. coord/gate_a.py +469 -0
  204. coord/gate_b.py +411 -0
  205. coord/gate_snapshot.py +385 -0
  206. coord/gates.py +582 -0
  207. coord/github_ops.py +1954 -0
  208. coord/goal.py +125 -0
  209. coord/graph_health.py +348 -0
  210. coord/health/__init__.py +69 -0
  211. coord/health/aggregate.py +129 -0
  212. coord/health/checks/__init__.py +13 -0
  213. coord/health/checks/agent_install.py +280 -0
  214. coord/health/checks/cargo_targets.py +171 -0
  215. coord/health/checks/claude_binary.py +65 -0
  216. coord/health/checks/deploy_lane_facts.py +458 -0
  217. coord/health/checks/disk.py +99 -0
  218. coord/health/checks/fleet_board.py +89 -0
  219. coord/health/checks/fleet_deploy_lanes.py +469 -0
  220. coord/health/checks/fleet_phantom.py +69 -0
  221. coord/health/checks/fleet_unit_drift.py +151 -0
  222. coord/health/checks/graph.py +192 -0
  223. coord/health/checks/plan_usage.py +88 -0
  224. coord/health/checks/repo_state.py +161 -0
  225. coord/health/checks/spawned_coord.py +465 -0
  226. coord/health/checks/timer_active.py +254 -0
  227. coord/health/checks/toolchain.py +547 -0
  228. coord/health/checks/unit_drift.py +648 -0
  229. coord/health/checks/unit_enablement.py +171 -0
  230. coord/health/checks/worktrees.py +96 -0
  231. coord/health/cli.py +121 -0
  232. coord/health/context.py +106 -0
  233. coord/health/fleet_snapshot.py +477 -0
  234. coord/health/models.py +250 -0
  235. coord/health/pypi.py +231 -0
  236. coord/health/registry.py +240 -0
  237. coord/health/render.py +82 -0
  238. coord/health/units.py +60 -0
  239. coord/hooks.py +106 -0
  240. coord/housekeeping.py +204 -0
  241. coord/interactive.py +4286 -0
  242. coord/issue_store.py +1496 -0
  243. coord/liveness_auditor.py +293 -0
  244. coord/machine_pause.py +755 -0
  245. coord/merge_queue.py +4681 -0
  246. coord/milestone_chat.py +600 -0
  247. coord/milestone_dispatch.py +943 -0
  248. coord/milestone_gate.py +709 -0
  249. coord/milestone_order.py +840 -0
  250. coord/mock_author.py +334 -0
  251. coord/models.py +891 -0
  252. coord/network.py +269 -0
  253. coord/new_issue_chat.py +229 -0
  254. coord/notify.py +3226 -0
  255. coord/openapi.py +404 -0
  256. coord/overlap_fence.py +133 -0
  257. coord/parentage.py +200 -0
  258. coord/parentage_github.py +58 -0
  259. coord/pipeline.py +481 -0
  260. coord/plan_parser.py +266 -0
  261. coord/plans.py +543 -0
  262. coord/platform_paths.py +43 -0
  263. coord/pr_body_lint.py +67 -0
  264. coord/prereqs.py +533 -0
  265. coord/progress.py +425 -0
  266. coord/providers/__init__.py +683 -0
  267. coord/providers/base.py +218 -0
  268. coord/providers/claude.py +284 -0
  269. coord/providers/claude_pty.py +610 -0
  270. coord/providers/opencode.py +896 -0
  271. coord/reconcile.py +2233 -0
  272. coord/refine_chat.py +485 -0
  273. coord/release_cordon.py +525 -0
  274. coord/release_propagate.py +1176 -0
  275. coord/release_verify.py +777 -0
  276. coord/release_window.py +322 -0
  277. coord/reports.py +1643 -0
  278. coord/revalidate.py +1101 -0
  279. coord/review.py +3317 -0
  280. coord/scorecard.py +484 -0
  281. coord/serve_app.py +7192 -0
  282. coord/skills/update-issue/SKILL.md +93 -0
  283. coord/smoke.py +1030 -0
  284. coord/split_work.py +210 -0
  285. coord/stage_projection.py +650 -0
  286. coord/state.py +5720 -0
  287. coord/test_author.py +1064 -0
  288. coord/test_chat.py +352 -0
  289. coord/test_orchestrator.py +494 -0
  290. coord/test_report.py +178 -0
  291. coord/tui_release.py +271 -0
  292. coord/usage.py +753 -0
  293. coord/usage_limits.py +358 -0
  294. coord/usage_rollup.py +709 -0
  295. coord/worker_events.py +954 -0
@@ -0,0 +1,99 @@
1
+ import * as React from 'react'
2
+ import * as DialogPrimitive from '@radix-ui/react-dialog'
3
+ import { X } from 'lucide-react'
4
+ import { cn } from '@/lib/utils'
5
+
6
+ const Dialog = DialogPrimitive.Root
7
+ const DialogTrigger = DialogPrimitive.Trigger
8
+ const DialogPortal = DialogPrimitive.Portal
9
+ const DialogClose = DialogPrimitive.Close
10
+
11
+ const DialogOverlay = React.forwardRef<
12
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
13
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
14
+ >(({ className, ...props }, ref) => (
15
+ <DialogPrimitive.Overlay
16
+ ref={ref}
17
+ className={cn(
18
+ 'fixed inset-0 z-50 bg-black/60 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ ))
24
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
25
+
26
+ const DialogContent = React.forwardRef<
27
+ React.ElementRef<typeof DialogPrimitive.Content>,
28
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
29
+ >(({ className, children, ...props }, ref) => (
30
+ <DialogPortal>
31
+ <DialogOverlay />
32
+ <DialogPrimitive.Content
33
+ ref={ref}
34
+ className={cn(
35
+ 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border border-border bg-card p-6 text-card-foreground shadow-elevation duration-200',
36
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
37
+ className,
38
+ )}
39
+ {...props}
40
+ >
41
+ {children}
42
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm text-muted-foreground opacity-70 transition-opacity hover:opacity-100 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none">
43
+ <X className="h-4 w-4" />
44
+ <span className="sr-only">Close</span>
45
+ </DialogPrimitive.Close>
46
+ </DialogPrimitive.Content>
47
+ </DialogPortal>
48
+ ))
49
+ DialogContent.displayName = DialogPrimitive.Content.displayName
50
+
51
+ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
52
+ <div className={cn('flex flex-col gap-1.5 text-left', className)} {...props} />
53
+ )
54
+ DialogHeader.displayName = 'DialogHeader'
55
+
56
+ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
57
+ <div
58
+ className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
59
+ {...props}
60
+ />
61
+ )
62
+ DialogFooter.displayName = 'DialogFooter'
63
+
64
+ const DialogTitle = React.forwardRef<
65
+ React.ElementRef<typeof DialogPrimitive.Title>,
66
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
67
+ >(({ className, ...props }, ref) => (
68
+ <DialogPrimitive.Title
69
+ ref={ref}
70
+ className={cn('text-base font-semibold leading-none tracking-tight', className)}
71
+ {...props}
72
+ />
73
+ ))
74
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
75
+
76
+ const DialogDescription = React.forwardRef<
77
+ React.ElementRef<typeof DialogPrimitive.Description>,
78
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
79
+ >(({ className, ...props }, ref) => (
80
+ <DialogPrimitive.Description
81
+ ref={ref}
82
+ className={cn('text-sm text-muted-foreground', className)}
83
+ {...props}
84
+ />
85
+ ))
86
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
87
+
88
+ export {
89
+ Dialog,
90
+ DialogPortal,
91
+ DialogOverlay,
92
+ DialogClose,
93
+ DialogTrigger,
94
+ DialogContent,
95
+ DialogHeader,
96
+ DialogFooter,
97
+ DialogTitle,
98
+ DialogDescription,
99
+ }
@@ -0,0 +1,189 @@
1
+ import * as React from 'react'
2
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
3
+ import { Check, ChevronRight, Circle } from 'lucide-react'
4
+ import { cn } from '@/lib/utils'
5
+
6
+ const DropdownMenu = DropdownMenuPrimitive.Root
7
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
8
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
9
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
10
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
11
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
12
+
13
+ const DropdownMenuSubTrigger = React.forwardRef<
14
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
15
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }
16
+ >(({ className, inset, children, ...props }, ref) => (
17
+ <DropdownMenuPrimitive.SubTrigger
18
+ ref={ref}
19
+ className={cn(
20
+ 'flex cursor-default select-none items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-muted-foreground outline-none hover:bg-secondary hover:text-foreground data-[state=open]:bg-secondary data-[state=open]:text-foreground',
21
+ inset && 'pl-8',
22
+ className,
23
+ )}
24
+ {...props}
25
+ >
26
+ {children}
27
+ <ChevronRight className="ml-auto h-4 w-4" />
28
+ </DropdownMenuPrimitive.SubTrigger>
29
+ ))
30
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
31
+
32
+ const DropdownMenuSubContent = React.forwardRef<
33
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
34
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
35
+ >(({ className, ...props }, ref) => (
36
+ <DropdownMenuPrimitive.SubContent
37
+ ref={ref}
38
+ className={cn(
39
+ 'z-50 min-w-[10rem] overflow-hidden rounded-md border border-line-strong bg-popover p-1 text-popover-foreground shadow-elevation',
40
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
41
+ className,
42
+ )}
43
+ {...props}
44
+ />
45
+ ))
46
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
47
+
48
+ // Matches docs/mocks/web/issue-detail.html's `.menu` — the ~20-action
49
+ // surface for M-W3, 246px wide, dark surface, subtle border, elevation.
50
+ const DropdownMenuContent = React.forwardRef<
51
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
52
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
53
+ >(({ className, sideOffset = 7, ...props }, ref) => (
54
+ <DropdownMenuPrimitive.Portal>
55
+ <DropdownMenuPrimitive.Content
56
+ ref={ref}
57
+ sideOffset={sideOffset}
58
+ className={cn(
59
+ 'z-50 min-w-[246px] overflow-hidden rounded-md border border-line-strong bg-popover p-[5px] text-popover-foreground shadow-elevation',
60
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ </DropdownMenuPrimitive.Portal>
66
+ ))
67
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
68
+
69
+ const DropdownMenuItem = React.forwardRef<
70
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
71
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
72
+ inset?: boolean
73
+ /** Matches `.menu button.hot` — the review-findings escalation item. */
74
+ variant?: 'default' | 'hot' | 'danger'
75
+ }
76
+ >(({ className, inset, variant = 'default', ...props }, ref) => (
77
+ <DropdownMenuPrimitive.Item
78
+ ref={ref}
79
+ className={cn(
80
+ 'relative flex cursor-default select-none items-center gap-2.5 rounded-sm px-2.5 py-1.5 text-sm text-muted-foreground outline-none transition-colors',
81
+ 'hover:bg-secondary hover:text-foreground focus:bg-secondary focus:text-foreground',
82
+ 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
83
+ '[&_svg]:h-3.5 [&_svg]:w-3.5 [&_svg]:shrink-0 [&_svg]:opacity-80',
84
+ inset && 'pl-8',
85
+ variant === 'hot' && 'text-attn hover:text-attn focus:text-attn',
86
+ variant === 'danger' && 'text-fail hover:text-fail focus:text-fail',
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ ))
92
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
93
+
94
+ const DropdownMenuCheckboxItem = React.forwardRef<
95
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
96
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
97
+ >(({ className, children, checked, ...props }, ref) => (
98
+ <DropdownMenuPrimitive.CheckboxItem
99
+ ref={ref}
100
+ className={cn(
101
+ 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2.5 text-sm text-muted-foreground outline-none transition-colors hover:bg-secondary hover:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
102
+ className,
103
+ )}
104
+ checked={checked}
105
+ {...props}
106
+ >
107
+ <span className="absolute left-2.5 flex h-3.5 w-3.5 items-center justify-center">
108
+ <DropdownMenuPrimitive.ItemIndicator>
109
+ <Check className="h-4 w-4" />
110
+ </DropdownMenuPrimitive.ItemIndicator>
111
+ </span>
112
+ {children}
113
+ </DropdownMenuPrimitive.CheckboxItem>
114
+ ))
115
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
116
+
117
+ const DropdownMenuRadioItem = React.forwardRef<
118
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
119
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
120
+ >(({ className, children, ...props }, ref) => (
121
+ <DropdownMenuPrimitive.RadioItem
122
+ ref={ref}
123
+ className={cn(
124
+ 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2.5 text-sm text-muted-foreground outline-none transition-colors hover:bg-secondary hover:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
125
+ className,
126
+ )}
127
+ {...props}
128
+ >
129
+ <span className="absolute left-2.5 flex h-3.5 w-3.5 items-center justify-center">
130
+ <DropdownMenuPrimitive.ItemIndicator>
131
+ <Circle className="h-2 w-2 fill-current" />
132
+ </DropdownMenuPrimitive.ItemIndicator>
133
+ </span>
134
+ {children}
135
+ </DropdownMenuPrimitive.RadioItem>
136
+ ))
137
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
138
+
139
+ // Matches `.menu-label` — the "Start — interactive" / "Start — automated" section headers.
140
+ const DropdownMenuLabel = React.forwardRef<
141
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
142
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }
143
+ >(({ className, inset, ...props }, ref) => (
144
+ <DropdownMenuPrimitive.Label
145
+ ref={ref}
146
+ className={cn(
147
+ 'px-2.5 pb-1 pt-2 text-[0.63rem] font-semibold uppercase tracking-wider text-faint',
148
+ inset && 'pl-8',
149
+ className,
150
+ )}
151
+ {...props}
152
+ />
153
+ ))
154
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
155
+
156
+ const DropdownMenuSeparator = React.forwardRef<
157
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
158
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
159
+ >(({ className, ...props }, ref) => (
160
+ <DropdownMenuPrimitive.Separator
161
+ ref={ref}
162
+ className={cn('-mx-[3px] my-1 h-px bg-border', className)}
163
+ {...props}
164
+ />
165
+ ))
166
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
167
+
168
+ const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
169
+ <span className={cn('ml-auto font-mono text-xs text-faint', className)} {...props} />
170
+ )
171
+ DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
172
+
173
+ export {
174
+ DropdownMenu,
175
+ DropdownMenuTrigger,
176
+ DropdownMenuContent,
177
+ DropdownMenuItem,
178
+ DropdownMenuCheckboxItem,
179
+ DropdownMenuRadioItem,
180
+ DropdownMenuLabel,
181
+ DropdownMenuSeparator,
182
+ DropdownMenuShortcut,
183
+ DropdownMenuGroup,
184
+ DropdownMenuPortal,
185
+ DropdownMenuSub,
186
+ DropdownMenuSubContent,
187
+ DropdownMenuSubTrigger,
188
+ DropdownMenuRadioGroup,
189
+ }
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from 'react'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ export interface EmptyStateProps {
5
+ icon?: ReactNode
6
+ title: string
7
+ description?: string
8
+ action?: ReactNode
9
+ className?: string
10
+ }
11
+
12
+ /**
13
+ * A blank/zero-result state — not in a mock verbatim (none of the three
14
+ * Gate-A screens hit one), but built from the same vocabulary: faint icon,
15
+ * ui-sans title, text-dim description, generous padding, no card chrome of
16
+ * its own since it usually sits inside one (e.g. an empty `.list` or
17
+ * `.card-body`).
18
+ */
19
+ export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {
20
+ return (
21
+ <div
22
+ className={cn(
23
+ 'flex flex-col items-center gap-3 px-6 py-12 text-center',
24
+ className,
25
+ )}
26
+ >
27
+ {icon && <div className="text-faint [&_svg]:h-8 [&_svg]:w-8">{icon}</div>}
28
+ <div className="flex flex-col gap-1">
29
+ <p className="text-sm font-medium text-foreground">{title}</p>
30
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
31
+ </div>
32
+ {action && <div className="mt-1">{action}</div>}
33
+ </div>
34
+ )
35
+ }
@@ -0,0 +1,123 @@
1
+ import * as React from 'react'
2
+ import * as DialogPrimitive from '@radix-ui/react-dialog'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+ import { X } from 'lucide-react'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const Sheet = DialogPrimitive.Root
8
+ const SheetTrigger = DialogPrimitive.Trigger
9
+ const SheetClose = DialogPrimitive.Close
10
+ const SheetPortal = DialogPrimitive.Portal
11
+
12
+ const SheetOverlay = React.forwardRef<
13
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
14
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
15
+ >(({ className, ...props }, ref) => (
16
+ <DialogPrimitive.Overlay
17
+ ref={ref}
18
+ className={cn(
19
+ 'fixed inset-0 z-50 bg-black/60 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
20
+ className,
21
+ )}
22
+ {...props}
23
+ />
24
+ ))
25
+ SheetOverlay.displayName = DialogPrimitive.Overlay.displayName
26
+
27
+ // "bottom" is the default: this is the mobile action sheet the mocks'
28
+ // narrow layout (docs/mocks/web/pipeline-narrow.html) needs for the
29
+ // per-issue action list on phone widths, where a bottom sheet reads as a
30
+ // native affordance and a centered dialog doesn't.
31
+ const sheetVariants = cva(
32
+ 'fixed z-50 gap-4 bg-card p-6 text-card-foreground shadow-elevation transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:duration-300',
33
+ {
34
+ variants: {
35
+ side: {
36
+ top: 'inset-x-0 top-0 border-b border-border data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
37
+ bottom:
38
+ 'inset-x-0 bottom-0 rounded-t-lg border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
39
+ left: 'inset-y-0 left-0 h-full w-3/4 border-r border-border data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
40
+ right:
41
+ 'inset-y-0 right-0 h-full w-3/4 border-l border-border data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
42
+ },
43
+ },
44
+ defaultVariants: {
45
+ side: 'bottom',
46
+ },
47
+ },
48
+ )
49
+
50
+ interface SheetContentProps
51
+ extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
52
+ VariantProps<typeof sheetVariants> {}
53
+
54
+ const SheetContent = React.forwardRef<
55
+ React.ElementRef<typeof DialogPrimitive.Content>,
56
+ SheetContentProps
57
+ >(({ side = 'bottom', className, children, ...props }, ref) => (
58
+ <SheetPortal>
59
+ <SheetOverlay />
60
+ <DialogPrimitive.Content
61
+ ref={ref}
62
+ className={cn(sheetVariants({ side }), className)}
63
+ {...props}
64
+ >
65
+ {children}
66
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm text-muted-foreground opacity-70 transition-opacity hover:opacity-100 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none">
67
+ <X className="h-4 w-4" />
68
+ <span className="sr-only">Close</span>
69
+ </DialogPrimitive.Close>
70
+ </DialogPrimitive.Content>
71
+ </SheetPortal>
72
+ ))
73
+ SheetContent.displayName = DialogPrimitive.Content.displayName
74
+
75
+ const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
76
+ <div className={cn('flex flex-col gap-1.5 text-left', className)} {...props} />
77
+ )
78
+ SheetHeader.displayName = 'SheetHeader'
79
+
80
+ const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
81
+ <div
82
+ className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
83
+ {...props}
84
+ />
85
+ )
86
+ SheetFooter.displayName = 'SheetFooter'
87
+
88
+ const SheetTitle = React.forwardRef<
89
+ React.ElementRef<typeof DialogPrimitive.Title>,
90
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
91
+ >(({ className, ...props }, ref) => (
92
+ <DialogPrimitive.Title
93
+ ref={ref}
94
+ className={cn('text-base font-semibold text-foreground', className)}
95
+ {...props}
96
+ />
97
+ ))
98
+ SheetTitle.displayName = DialogPrimitive.Title.displayName
99
+
100
+ const SheetDescription = React.forwardRef<
101
+ React.ElementRef<typeof DialogPrimitive.Description>,
102
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
103
+ >(({ className, ...props }, ref) => (
104
+ <DialogPrimitive.Description
105
+ ref={ref}
106
+ className={cn('text-sm text-muted-foreground', className)}
107
+ {...props}
108
+ />
109
+ ))
110
+ SheetDescription.displayName = DialogPrimitive.Description.displayName
111
+
112
+ export {
113
+ Sheet,
114
+ SheetPortal,
115
+ SheetOverlay,
116
+ SheetTrigger,
117
+ SheetClose,
118
+ SheetContent,
119
+ SheetHeader,
120
+ SheetFooter,
121
+ SheetTitle,
122
+ SheetDescription,
123
+ }
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes } from 'react'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ /** Loading placeholder — a pulsing block the size of the content it stands in for. */
5
+ function Skeleton({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
6
+ return <div className={cn('animate-pulse rounded-md bg-surface-2', className)} {...props} />
7
+ }
8
+
9
+ export { Skeleton }
@@ -0,0 +1,55 @@
1
+ import * as React from 'react'
2
+ import * as TabsPrimitive from '@radix-ui/react-tabs'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ const Tabs = TabsPrimitive.Root
6
+
7
+ // Underline style, matching docs/mocks/web/issue-detail.html's `.tabs`/`.tab`
8
+ // (border-bottom on the active tab, not a pill/segmented look).
9
+ const TabsList = React.forwardRef<
10
+ React.ElementRef<typeof TabsPrimitive.List>,
11
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
12
+ >(({ className, ...props }, ref) => (
13
+ <TabsPrimitive.List
14
+ ref={ref}
15
+ className={cn('flex items-center gap-2 border-b border-border', className)}
16
+ {...props}
17
+ />
18
+ ))
19
+ TabsList.displayName = TabsPrimitive.List.displayName
20
+
21
+ const TabsTrigger = React.forwardRef<
22
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
23
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
24
+ >(({ className, ...props }, ref) => (
25
+ <TabsPrimitive.Trigger
26
+ ref={ref}
27
+ className={cn(
28
+ 'flex items-center gap-1.5 whitespace-nowrap border-b-2 border-transparent px-3 py-2.5 text-sm text-muted-foreground transition-colors',
29
+ 'hover:text-foreground',
30
+ 'data-[state=active]:border-primary data-[state=active]:text-foreground',
31
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
32
+ 'disabled:pointer-events-none disabled:opacity-50',
33
+ className,
34
+ )}
35
+ {...props}
36
+ />
37
+ ))
38
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39
+
40
+ const TabsContent = React.forwardRef<
41
+ React.ElementRef<typeof TabsPrimitive.Content>,
42
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
43
+ >(({ className, ...props }, ref) => (
44
+ <TabsPrimitive.Content
45
+ ref={ref}
46
+ className={cn(
47
+ 'mt-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ ))
53
+ TabsContent.displayName = TabsPrimitive.Content.displayName
54
+
55
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1,78 @@
1
+ import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
2
+
3
+ /**
4
+ * Dark/light theme (#1546). Binary by design -- the mocks' rail has one
5
+ * "Theme" button that flips dark <-> light (docs/mocks/web/pipeline-wide.html),
6
+ * not a three-way light/dark/system picker, so that's the surface this
7
+ * exposes too. "System" still matters as the *source* of the initial value
8
+ * (see `systemTheme` below); it just isn't a mode a user can select back to
9
+ * once they've picked explicitly, matching the mock.
10
+ */
11
+ export type Theme = 'light' | 'dark'
12
+
13
+ export const THEME_STORAGE_KEY = 'coord-web-theme'
14
+
15
+ function systemTheme(): Theme {
16
+ if (typeof window === 'undefined' || !window.matchMedia) return 'dark'
17
+ // Dark is the default per the design (docs/mocks/web/README.md): only an
18
+ // explicit OS preference for light should override it.
19
+ return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
20
+ }
21
+
22
+ function readStoredTheme(): Theme | null {
23
+ if (typeof window === 'undefined') return null
24
+ try {
25
+ const stored = window.localStorage.getItem(THEME_STORAGE_KEY)
26
+ return stored === 'light' || stored === 'dark' ? stored : null
27
+ } catch {
28
+ // localStorage can throw in private-browsing / lockdown modes.
29
+ return null
30
+ }
31
+ }
32
+
33
+ /** Mirrors the blocking script in index.html so React's first render already agrees with it -- no flash. */
34
+ function initialTheme(): Theme {
35
+ return readStoredTheme() ?? systemTheme()
36
+ }
37
+
38
+ interface ThemeContextValue {
39
+ theme: Theme
40
+ setTheme: (theme: Theme) => void
41
+ toggleTheme: () => void
42
+ }
43
+
44
+ const ThemeContext = createContext<ThemeContextValue | undefined>(undefined)
45
+
46
+ export function ThemeProvider({ children }: { children: ReactNode }) {
47
+ const [theme, setThemeState] = useState<Theme>(initialTheme)
48
+
49
+ // The inline script in index.html already set data-theme before paint;
50
+ // this keeps it in sync whenever `theme` changes from here on.
51
+ useEffect(() => {
52
+ document.documentElement.setAttribute('data-theme', theme)
53
+ }, [theme])
54
+
55
+ const setTheme = (next: Theme) => {
56
+ setThemeState(next)
57
+ try {
58
+ window.localStorage.setItem(THEME_STORAGE_KEY, next)
59
+ } catch {
60
+ // Persistence is a nice-to-have; the theme still applies this session.
61
+ }
62
+ }
63
+
64
+ const toggleTheme = () => setTheme(theme === 'dark' ? 'light' : 'dark')
65
+
66
+ return (
67
+ <ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
68
+ {children}
69
+ </ThemeContext.Provider>
70
+ )
71
+ }
72
+
73
+ // eslint-disable-next-line react-refresh/only-export-components -- a hook alongside its provider component is the standard context pattern (see components/ui/badge.tsx for the same precedent).
74
+ export function useTheme(): ThemeContextValue {
75
+ const ctx = useContext(ThemeContext)
76
+ if (!ctx) throw new Error('useTheme must be used within a ThemeProvider')
77
+ return ctx
78
+ }
@@ -0,0 +1,20 @@
1
+ import { Moon, Sun } from 'lucide-react'
2
+ import { Button } from '@/components/ui/button'
3
+ import { useTheme } from '@/components/ui/theme-provider'
4
+
5
+ /** The rail's "Theme" button (docs/mocks/web/pipeline-wide.html) — flips dark/light, persists. */
6
+ export function ThemeToggle() {
7
+ const { theme, toggleTheme } = useTheme()
8
+
9
+ return (
10
+ <Button
11
+ variant="ghost"
12
+ size="icon"
13
+ onClick={toggleTheme}
14
+ aria-label={theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
15
+ title="Theme"
16
+ >
17
+ {theme === 'dark' ? <Sun /> : <Moon />}
18
+ </Button>
19
+ )
20
+ }