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,123 @@
1
+ import * as React from 'react'
2
+ import * as ToastPrimitives from '@radix-ui/react-toast'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+ import { X } from 'lucide-react'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const ToastProvider = ToastPrimitives.Provider
8
+
9
+ const ToastViewport = React.forwardRef<
10
+ React.ElementRef<typeof ToastPrimitives.Viewport>,
11
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
12
+ >(({ className, ...props }, ref) => (
13
+ <ToastPrimitives.Viewport
14
+ ref={ref}
15
+ className={cn(
16
+ 'fixed bottom-0 right-0 z-[100] flex max-h-screen w-full flex-col-reverse gap-2 p-4 sm:bottom-4 sm:right-4 sm:top-auto sm:max-w-[380px]',
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ ))
22
+ ToastViewport.displayName = ToastPrimitives.Viewport.displayName
23
+
24
+ const toastVariants = cva(
25
+ 'group pointer-events-auto relative flex w-full items-start justify-between gap-3 overflow-hidden rounded-md border p-4 shadow-elevation transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
26
+ {
27
+ variants: {
28
+ variant: {
29
+ default: 'border-line-strong bg-popover text-popover-foreground',
30
+ success: 'border-pass bg-pass-wash text-pass',
31
+ warning: 'border-attn bg-attn-wash text-attn',
32
+ destructive: 'border-fail bg-fail-wash text-fail',
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: 'default',
37
+ },
38
+ },
39
+ )
40
+
41
+ const Toast = React.forwardRef<
42
+ React.ElementRef<typeof ToastPrimitives.Root>,
43
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
44
+ >(({ className, variant, ...props }, ref) => (
45
+ <ToastPrimitives.Root
46
+ ref={ref}
47
+ className={cn(toastVariants({ variant }), className)}
48
+ {...props}
49
+ />
50
+ ))
51
+ Toast.displayName = ToastPrimitives.Root.displayName
52
+
53
+ const ToastAction = React.forwardRef<
54
+ React.ElementRef<typeof ToastPrimitives.Action>,
55
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
56
+ >(({ className, ...props }, ref) => (
57
+ <ToastPrimitives.Action
58
+ ref={ref}
59
+ className={cn(
60
+ 'inline-flex h-8 shrink-0 items-center justify-center rounded-sm border border-line-strong bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ ))
66
+ ToastAction.displayName = ToastPrimitives.Action.displayName
67
+
68
+ const ToastClose = React.forwardRef<
69
+ React.ElementRef<typeof ToastPrimitives.Close>,
70
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
71
+ >(({ className, ...props }, ref) => (
72
+ <ToastPrimitives.Close
73
+ ref={ref}
74
+ className={cn(
75
+ 'absolute right-2 top-2 rounded-sm p-1 text-current opacity-0 transition-opacity hover:opacity-100 focus:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-70',
76
+ className,
77
+ )}
78
+ toast-close=""
79
+ {...props}
80
+ >
81
+ <X className="h-4 w-4" />
82
+ </ToastPrimitives.Close>
83
+ ))
84
+ ToastClose.displayName = ToastPrimitives.Close.displayName
85
+
86
+ const ToastTitle = React.forwardRef<
87
+ React.ElementRef<typeof ToastPrimitives.Title>,
88
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
89
+ >(({ className, ...props }, ref) => (
90
+ <ToastPrimitives.Title
91
+ ref={ref}
92
+ className={cn('text-sm font-semibold', className)}
93
+ {...props}
94
+ />
95
+ ))
96
+ ToastTitle.displayName = ToastPrimitives.Title.displayName
97
+
98
+ const ToastDescription = React.forwardRef<
99
+ React.ElementRef<typeof ToastPrimitives.Description>,
100
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
101
+ >(({ className, ...props }, ref) => (
102
+ <ToastPrimitives.Description
103
+ ref={ref}
104
+ className={cn('text-sm opacity-90', className)}
105
+ {...props}
106
+ />
107
+ ))
108
+ ToastDescription.displayName = ToastPrimitives.Description.displayName
109
+
110
+ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
111
+ type ToastActionElement = React.ReactElement<typeof ToastAction>
112
+
113
+ export {
114
+ type ToastProps,
115
+ type ToastActionElement,
116
+ ToastProvider,
117
+ ToastViewport,
118
+ Toast,
119
+ ToastTitle,
120
+ ToastDescription,
121
+ ToastClose,
122
+ ToastAction,
123
+ }
@@ -0,0 +1,30 @@
1
+ import { useToast } from '@/components/ui/use-toast'
2
+ import {
3
+ Toast,
4
+ ToastClose,
5
+ ToastDescription,
6
+ ToastProvider,
7
+ ToastTitle,
8
+ ToastViewport,
9
+ } from '@/components/ui/toast'
10
+
11
+ /** Mounted once at the app root (see App.tsx); renders whatever `toast()` (use-toast.ts) queues. */
12
+ export function Toaster() {
13
+ const { toasts } = useToast()
14
+
15
+ return (
16
+ <ToastProvider>
17
+ {toasts.map(({ id, title, description, action, ...props }) => (
18
+ <Toast key={id} {...props}>
19
+ <div className="grid gap-1">
20
+ {title && <ToastTitle>{title}</ToastTitle>}
21
+ {description && <ToastDescription>{description}</ToastDescription>}
22
+ </div>
23
+ {action}
24
+ <ToastClose />
25
+ </Toast>
26
+ ))}
27
+ <ToastViewport />
28
+ </ToastProvider>
29
+ )
30
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from 'react'
2
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ const TooltipProvider = TooltipPrimitive.Provider
6
+ const Tooltip = TooltipPrimitive.Root
7
+ const TooltipTrigger = TooltipPrimitive.Trigger
8
+
9
+ const TooltipContent = React.forwardRef<
10
+ React.ElementRef<typeof TooltipPrimitive.Content>,
11
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
12
+ >(({ className, sideOffset = 6, ...props }, ref) => (
13
+ <TooltipPrimitive.Content
14
+ ref={ref}
15
+ sideOffset={sideOffset}
16
+ className={cn(
17
+ 'z-50 overflow-hidden rounded-sm border border-line-strong bg-popover px-2.5 py-1.5 text-xs text-popover-foreground shadow-elevation',
18
+ 'data-[state=delayed-open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=delayed-open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=delayed-open]:zoom-in-95',
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ ))
24
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName
25
+
26
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -0,0 +1,134 @@
1
+ // Adapted from shadcn/ui's toast hook (itself adapted from react-hot-toast) —
2
+ // a module-level store outside React so any component can call `toast(...)`
3
+ // without threading a context down, while `useToast()` still re-renders
4
+ // subscribers (namely <Toaster/>) on change.
5
+ import * as React from 'react'
6
+ import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
7
+
8
+ const TOAST_LIMIT = 3
9
+ const TOAST_REMOVE_DELAY = 4000
10
+
11
+ type ToasterToast = ToastProps & {
12
+ id: string
13
+ title?: React.ReactNode
14
+ description?: React.ReactNode
15
+ action?: ToastActionElement
16
+ }
17
+
18
+ let count = 0
19
+
20
+ function genId() {
21
+ count = (count + 1) % Number.MAX_SAFE_INTEGER
22
+ return count.toString()
23
+ }
24
+
25
+ type Action =
26
+ | { type: 'ADD_TOAST'; toast: ToasterToast }
27
+ | { type: 'UPDATE_TOAST'; toast: Partial<ToasterToast> }
28
+ | { type: 'DISMISS_TOAST'; toastId?: ToasterToast['id'] }
29
+ | { type: 'REMOVE_TOAST'; toastId?: ToasterToast['id'] }
30
+
31
+ interface State {
32
+ toasts: ToasterToast[]
33
+ }
34
+
35
+ const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
36
+
37
+ function addToRemoveQueue(toastId: string) {
38
+ if (toastTimeouts.has(toastId)) return
39
+
40
+ const timeout = setTimeout(() => {
41
+ toastTimeouts.delete(toastId)
42
+ dispatch({ type: 'REMOVE_TOAST', toastId })
43
+ }, TOAST_REMOVE_DELAY)
44
+
45
+ toastTimeouts.set(toastId, timeout)
46
+ }
47
+
48
+ export function reducer(state: State, action: Action): State {
49
+ switch (action.type) {
50
+ case 'ADD_TOAST':
51
+ return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT) }
52
+
53
+ case 'UPDATE_TOAST':
54
+ return {
55
+ ...state,
56
+ toasts: state.toasts.map(t => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
57
+ }
58
+
59
+ case 'DISMISS_TOAST': {
60
+ const { toastId } = action
61
+ if (toastId) {
62
+ addToRemoveQueue(toastId)
63
+ } else {
64
+ state.toasts.forEach(t => addToRemoveQueue(t.id))
65
+ }
66
+ return {
67
+ ...state,
68
+ toasts: state.toasts.map(t =>
69
+ t.id === toastId || toastId === undefined ? { ...t, open: false } : t,
70
+ ),
71
+ }
72
+ }
73
+
74
+ case 'REMOVE_TOAST':
75
+ if (action.toastId === undefined) {
76
+ return { ...state, toasts: [] }
77
+ }
78
+ return { ...state, toasts: state.toasts.filter(t => t.id !== action.toastId) }
79
+
80
+ default:
81
+ return state
82
+ }
83
+ }
84
+
85
+ const listeners: Array<(state: State) => void> = []
86
+ let memoryState: State = { toasts: [] }
87
+
88
+ function dispatch(action: Action) {
89
+ memoryState = reducer(memoryState, action)
90
+ listeners.forEach(listener => listener(memoryState))
91
+ }
92
+
93
+ type Toast = Omit<ToasterToast, 'id'>
94
+
95
+ function toast({ ...props }: Toast) {
96
+ const id = genId()
97
+
98
+ const update = (props: ToasterToast) => dispatch({ type: 'UPDATE_TOAST', toast: { ...props, id } })
99
+ const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
100
+
101
+ dispatch({
102
+ type: 'ADD_TOAST',
103
+ toast: {
104
+ ...props,
105
+ id,
106
+ open: true,
107
+ onOpenChange: (open: boolean) => {
108
+ if (!open) dismiss()
109
+ },
110
+ },
111
+ })
112
+
113
+ return { id, dismiss, update }
114
+ }
115
+
116
+ function useToast() {
117
+ const [state, setState] = React.useState<State>(memoryState)
118
+
119
+ React.useEffect(() => {
120
+ listeners.push(setState)
121
+ return () => {
122
+ const index = listeners.indexOf(setState)
123
+ if (index > -1) listeners.splice(index, 1)
124
+ }
125
+ }, [])
126
+
127
+ return {
128
+ ...state,
129
+ toast,
130
+ dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
131
+ }
132
+ }
133
+
134
+ export { useToast, toast }
@@ -0,0 +1,210 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /*
6
+ * coord web — design tokens (#1546)
7
+ *
8
+ * The raw palette below is copied verbatim from the Gate-A design mocks'
9
+ * shared reference, docs/mocks/web/_tokens.css — that file says explicitly
10
+ * it is "the seed for W1-1's real token layer." Keep the two in sync; if a
11
+ * value changes here, change it there too (or vice versa).
12
+ *
13
+ * Dark is the default and the preferred reading mode; light is a real,
14
+ * designed second theme, not a mechanical inversion. Both are switched by
15
+ * `data-theme` on <html> (see src/components/theme-provider.tsx) rather
16
+ * than Tailwind's `dark:` class variant, because that's what the mocks
17
+ * already do and what avoids a flash of the wrong theme (the attribute is
18
+ * set by a blocking inline script in index.html before paint).
19
+ *
20
+ * A second layer of semantic aliases (--background, --foreground, --card,
21
+ * --primary, ...) maps the raw palette onto the shadcn/ui vocabulary that
22
+ * ui/* primitives and pre-existing components (PipelineCard,
23
+ * ConnectionBadge, MobileKeyBar) consume — see tailwind.config.js for the
24
+ * full rationale, including why shadcn's `accent` slot is kept pointed at
25
+ * a neutral hover surface instead of the mock's cyan "brand" accent.
26
+ */
27
+
28
+ @layer base {
29
+ :root {
30
+ /* ── raw palette — type ──────────────────────────────────────────── */
31
+ --font-ui: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto,
32
+ 'Helvetica Neue', Arial, sans-serif;
33
+ /* mono is reserved for values the machine owns: issue numbers,
34
+ branches, SHAs, machine names, durations, costs. Never for chrome. */
35
+ --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
36
+ 'Liberation Mono', monospace;
37
+
38
+ --step--1: 0.75rem;
39
+ --step-0: 0.875rem;
40
+ --step-1: 1rem;
41
+ --step-2: 1.25rem;
42
+ --step-3: 1.5rem;
43
+
44
+ /* ── raw palette — geometry ──────────────────────────────────────── */
45
+ --r-sm: 5px;
46
+ --r-md: 8px;
47
+ --r-lg: 12px;
48
+
49
+ --rail-w: 216px;
50
+ --rail-w-collapsed: 60px;
51
+ --panel-w: 360px;
52
+ }
53
+
54
+ /* `:root` carries the dark values too (dark is the default, and it's the
55
+ fallback if `data-theme` is ever missing) -- but the SAME selector list
56
+ also includes the bare `[data-theme='dark']` attribute, not just
57
+ `:root[data-theme='dark']`. That's deliberate: it's what lets a nested
58
+ subtree (e.g. the gallery's side-by-side theme preview,
59
+ src/components/Gallery.tsx) force dark back on, even inside a page
60
+ whose <html> is already light -- a plain `:root` rule only ever matches
61
+ the root element, so without this a nested `data-theme="dark"` div
62
+ would have no rule to reset it and would just inherit light. */
63
+ :root,
64
+ [data-theme='dark'] {
65
+ /* ── raw palette — ground & surfaces ──────────────────────────── */
66
+ --ground: #101418;
67
+ --surface: #171c22;
68
+ --surface-2: #1e242b;
69
+ --surface-3: #252d36;
70
+ --line: #2a323b;
71
+ --line-strong: #3a444f;
72
+
73
+ /* ── raw palette — text ──────────────────────────────────────────── */
74
+ --text: #dfe6ee;
75
+ --text-dim: #8b97a5;
76
+ --text-faint: #5f6b78;
77
+
78
+ /* ── raw palette — accent: "work is happening here" ────────────────
79
+ Below AA (~3.4:1) against --ground; see the README note under
80
+ `.text-faint` usage in the gallery for why that's an intentional,
81
+ carried-over Gate-A choice rather than an oversight. */
82
+ --accent: #4fb8cf;
83
+ --accent-dim: #2d7688;
84
+ --accent-wash: rgba(79, 184, 207, 0.12);
85
+
86
+ /* ── raw palette — semantic state (deliberately desaturated) ──────── */
87
+ --pass: #4ea87a;
88
+ --pass-wash: rgba(78, 168, 122, 0.14);
89
+ --attn: #d9a54c;
90
+ --attn-wash: rgba(217, 165, 76, 0.14);
91
+ --fail: #d9605e;
92
+ --fail-wash: rgba(217, 96, 94, 0.14);
93
+ --idle: #55606d;
94
+ --idle-wash: rgba(85, 96, 109, 0.14);
95
+
96
+ /* ── semantic aliases (shadcn/ui vocabulary) ────────────────────── */
97
+ --background: var(--ground);
98
+ --foreground: var(--text);
99
+
100
+ --card: var(--surface);
101
+ --card-foreground: var(--text);
102
+ --popover: var(--surface-2);
103
+ --popover-foreground: var(--text);
104
+
105
+ --primary: var(--accent);
106
+ --primary-foreground: #08161a;
107
+ --secondary: var(--surface-2);
108
+ --secondary-foreground: var(--text);
109
+ --muted: var(--surface-2);
110
+ --muted-foreground: var(--text-dim);
111
+
112
+ /* Neutral hover/press highlight — NOT the brand cyan. Existing
113
+ components (e.g. MobileKeyBar's `active:bg-accent`) already depend
114
+ on this meaning; see tailwind.config.js. */
115
+ --accent-surface: var(--surface-2);
116
+ --accent-surface-foreground: var(--text);
117
+
118
+ --destructive: var(--fail);
119
+ --destructive-foreground: #ffffff;
120
+
121
+ --border: var(--line);
122
+ --input: var(--line);
123
+ --ring: var(--accent);
124
+ --radius: var(--r-md);
125
+ }
126
+
127
+ /* Not scoped to :root: same reasoning as `[data-theme='dark']` above. */
128
+ [data-theme='light'] {
129
+ --ground: #f5f7f9;
130
+ --surface: #ffffff;
131
+ --surface-2: #eef1f5;
132
+ --surface-3: #e3e8ee;
133
+ --line: #dde3ea;
134
+ --line-strong: #c3ccd6;
135
+
136
+ --text: #1a212a;
137
+ --text-dim: #5b6672;
138
+ --text-faint: #8a95a1;
139
+
140
+ --accent: #1d7f96;
141
+ --accent-dim: #8fc9d6;
142
+ --accent-wash: rgba(29, 127, 150, 0.1);
143
+
144
+ --pass: #2f7d55;
145
+ --pass-wash: rgba(47, 125, 85, 0.11);
146
+ --attn: #a4741d;
147
+ --attn-wash: rgba(164, 116, 29, 0.12);
148
+ --fail: #b8403e;
149
+ --fail-wash: rgba(184, 64, 62, 0.11);
150
+ --idle: #97a1ac;
151
+ --idle-wash: rgba(151, 161, 172, 0.14);
152
+
153
+ /* ── semantic aliases — redeclared, not inherited ─────────────────
154
+ Custom properties resolve `var(--x)` against the *declaring*
155
+ element, then inherit the already-resolved value -- a descendant
156
+ that overrides --ground doesn't retroactively change what an
157
+ ancestor's `--background: var(--ground)` computed to. So every
158
+ alias needs restating here against the raw tokens just set above,
159
+ or `bg-background`/`text-foreground`/etc. would silently keep
160
+ whatever the dark block resolved them to. (Learned by screenshotting
161
+ the gallery's side-by-side preview mid-build: without this, the
162
+ "light" pane rendered with dark colours.) */
163
+ --background: var(--ground);
164
+ --foreground: var(--text);
165
+
166
+ --card: var(--surface);
167
+ --card-foreground: var(--text);
168
+ --popover: var(--surface-2);
169
+ --popover-foreground: var(--text);
170
+
171
+ --primary: var(--accent);
172
+ /* Light theme's --accent is a darker teal than dark theme's bright
173
+ cyan, so the button text that sits on top of it needs to flip from
174
+ dark ink to white to keep 4.5:1 contrast (measured ~4.6:1 white vs
175
+ ~4.0:1 black). */
176
+ --primary-foreground: #ffffff;
177
+ --secondary: var(--surface-2);
178
+ --secondary-foreground: var(--text);
179
+ --muted: var(--surface-2);
180
+ --muted-foreground: var(--text-dim);
181
+
182
+ --accent-surface: var(--surface-2);
183
+ --accent-surface-foreground: var(--text);
184
+
185
+ --destructive: var(--fail);
186
+ --destructive-foreground: #ffffff;
187
+
188
+ --border: var(--line);
189
+ --input: var(--line);
190
+ --ring: var(--accent);
191
+ }
192
+ }
193
+
194
+ @layer base {
195
+ * {
196
+ @apply border-border;
197
+ }
198
+ body {
199
+ @apply bg-background text-foreground;
200
+ font-family: var(--font-ui);
201
+ -webkit-font-smoothing: antialiased;
202
+ -moz-osx-font-smoothing: grayscale;
203
+ }
204
+ /* Matches the mocks' `.mono` convenience class: tabular figures so
205
+ durations/costs/ids don't jitter as digits change. */
206
+ .mono {
207
+ font-family: var(--font-mono);
208
+ font-variant-numeric: tabular-nums;
209
+ }
210
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Pipeline predicates shared between the Pipeline panel and the shell.
3
+ *
4
+ * Lifted out of `Home.tsx` (#1547) because the activity rail needs the same
5
+ * two answers Home's filter tabs need — how many items are in flight, and how
6
+ * many are waiting on a human — and a second, independently-drifting copy of
7
+ * "what counts as active" in the rail would be a lie the moment either
8
+ * definition changed.
9
+ */
10
+ import type { PipelineView } from '@/api/client'
11
+
12
+ /**
13
+ * "Active": items that haven't finished (current_stage !== "merged").
14
+ * Keeps the list (and the rail count) focused on in-flight work without
15
+ * cluttering with history.
16
+ */
17
+ export function isActive(view: PipelineView): boolean {
18
+ return view.current_stage !== 'merged'
19
+ }
20
+
21
+ /**
22
+ * "Needs me": items where at least one human gate action is available.
23
+ * E.g.: work done (needs test dispatch), review approved (needs merge queue),
24
+ * smoke passed (needs merge queue), merge ready (needs merge), failures (need
25
+ * retry/fix).
26
+ */
27
+ export function needsMe(view: PipelineView): boolean {
28
+ return view.available_gates.length > 0
29
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,46 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
4
+ import App from './App'
5
+ import { RealtimeProvider } from './realtime/RealtimeProvider'
6
+ import './index.css'
7
+
8
+ const queryClient = new QueryClient({
9
+ defaultOptions: {
10
+ queries: {
11
+ // #1549: no timer-based polling. The SSE `/events` stream
12
+ // (RealtimeProvider) invalidates exactly the query keys each server
13
+ // event affects, and a stream recovery (reconnecting -> live) triggers
14
+ // an explicit resync -- see src/realtime/events.ts. Data is therefore
15
+ // fresh until something says otherwise, which `refetchOnWindowFocus`'s
16
+ // default (true) still backstops for a tab that was backgrounded long
17
+ // enough to miss its own reconnect.
18
+ //
19
+ // Footgun for the next query added to this app: `staleTime: Infinity`
20
+ // is a *global* default. A `useQuery` whose key isn't wired into
21
+ // `EVENT_QUERY_KEYS` (src/realtime/events.ts) or manually invalidated
22
+ // elsewhere will never refetch on its own -- only `refetchOnWindowFocus`
23
+ // (or an explicit `invalidateQueries` call, e.g. Detail.tsx's actions)
24
+ // will ever refresh it. If you add a query, add its invalidation path
25
+ // in the same PR.
26
+ staleTime: Infinity,
27
+ // Retry once on error before surfacing the failure state
28
+ retry: 1,
29
+ },
30
+ },
31
+ })
32
+
33
+ // Register service worker (vite-plugin-pwa injects this at build time)
34
+ // In dev mode the import is a no-op stub from the virtual module.
35
+ import { registerSW } from 'virtual:pwa-register'
36
+ registerSW({ immediate: true })
37
+
38
+ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
39
+ <React.StrictMode>
40
+ <QueryClientProvider client={queryClient}>
41
+ <RealtimeProvider>
42
+ <App />
43
+ </RealtimeProvider>
44
+ </QueryClientProvider>
45
+ </React.StrictMode>,
46
+ )