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,63 @@
1
+ {
2
+ "name": "coord-webapp",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview",
10
+ "typecheck": "tsc --noEmit",
11
+ "codegen": "cd ../../.. && .venv/bin/python scripts/codegen.py",
12
+ "codegen:check": "cd ../../.. && .venv/bin/python scripts/codegen.py --check",
13
+ "lint": "eslint src --report-unused-disable-directives --max-warnings 0",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "test:e2e": "playwright test",
17
+ "test:e2e:ui": "playwright test --ui",
18
+ "test:acceptance": "playwright test --config=playwright.acceptance.config.ts"
19
+ },
20
+ "dependencies": {
21
+ "@radix-ui/react-dialog": "^1.1.23",
22
+ "@radix-ui/react-dropdown-menu": "^2.1.24",
23
+ "@radix-ui/react-slot": "^1.3.3",
24
+ "@radix-ui/react-tabs": "^1.1.21",
25
+ "@radix-ui/react-toast": "^1.2.23",
26
+ "@radix-ui/react-tooltip": "^1.2.16",
27
+ "@tanstack/react-query": "^5.62.2",
28
+ "@xterm/addon-fit": "^0.11.0",
29
+ "@xterm/xterm": "^6.0.0",
30
+ "class-variance-authority": "^0.7.0",
31
+ "clsx": "^2.1.1",
32
+ "lucide-react": "^0.462.0",
33
+ "react": "^18.3.1",
34
+ "react-dom": "^18.3.1",
35
+ "react-router-dom": "^6.28.0",
36
+ "tailwind-merge": "^2.5.5",
37
+ "tailwindcss-animate": "^1.0.7"
38
+ },
39
+ "devDependencies": {
40
+ "@playwright/test": "^1.61.1",
41
+ "@testing-library/jest-dom": "^6.6.3",
42
+ "@testing-library/react": "^16.1.0",
43
+ "@testing-library/user-event": "^14.5.2",
44
+ "@types/node": "^26.1.2",
45
+ "@types/react": "^18.3.12",
46
+ "@types/react-dom": "^18.3.1",
47
+ "@typescript-eslint/eslint-plugin": "^8.14.0",
48
+ "@typescript-eslint/parser": "^8.14.0",
49
+ "@vitejs/plugin-react": "^4.3.3",
50
+ "autoprefixer": "^10.4.20",
51
+ "eslint": "^9.14.0",
52
+ "eslint-plugin-react-hooks": "^5.0.0",
53
+ "eslint-plugin-react-refresh": "^0.4.14",
54
+ "jsdom": "^25.0.1",
55
+ "postcss": "^8.4.47",
56
+ "tailwindcss": "^3.4.14",
57
+ "typescript": "^5.6.3",
58
+ "vite": "^5.4.10",
59
+ "vite-plugin-pwa": "^0.21.1",
60
+ "vitest": "^2.1.8",
61
+ "workbox-build": "^7.3.0"
62
+ }
63
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Playwright config for the SEALED oracle-loop acceptance suite (#1540),
3
+ * as opposed to `playwright.config.ts` (the `e2e/` smoke net — untouched,
4
+ * keeps running via `npm run test:e2e`).
5
+ *
6
+ * Why a sibling config rather than editing `playwright.config.ts` in place
7
+ * (docs/ORACLE_LOOP.md, #1552's "wiring failure, not a test failure" bug,
8
+ * arriving here in Playwright-shaped form):
9
+ *
10
+ * 1. `playwright.config.ts`'s `testDir: './e2e'` is invisible to anything
11
+ * under repo-root `tests/acceptance/ms-NN/` — a path filter pointing
12
+ * outside `testDir` matches zero files and Playwright exits 0 with 0
13
+ * tests. That is a silent, confidently-wrong green, not a red. This
14
+ * config's `testDir` points at the sealed tree instead so a slice
15
+ * actually gets discovered.
16
+ * 2. The acceptance run must select a machine-readable reporter, not
17
+ * inherit `e2e`'s CI-conditional `'github'`. In practice the
18
+ * `web-playwright` driver (#1539, coord/acceptance_drivers.py
19
+ * `_run_web_playwright`) always appends `--reporter=json` on the CLI,
20
+ * which overrides whatever `reporter:` this file declares — but this
21
+ * file still picks an explicit, non-CI-conditional default (`'list'`)
22
+ * so a human running it directly (outside the driver) gets readable
23
+ * output instead of silently inheriting a `CI` env var's default.
24
+ *
25
+ * `run:` (acceptance.drivers.claude-coordinator.routes, coordinator.yml):
26
+ * cd coord/dashboard/webapp && npm run test:acceptance -- {ms}
27
+ * `{ms}` (e.g. "ms-40") is substituted by `render_run_command` and passed as
28
+ * Playwright's positional test-file filter — it matches by substring against
29
+ * each spec file's resolved path, so `tests/acceptance/ms-40/foo.spec.ts`
30
+ * matches the filter `ms-40` without needing an exact relative path. This
31
+ * config also reads that same `ms-NN` token back out of `process.argv` (see
32
+ * `resolveFixturePath` below) to decide what to seed the webServer with.
33
+ *
34
+ * `entrypoint:` — deliberately NOT set on this route (#1552). Playwright
35
+ * discovers specs by walking `testDir`, exactly like `cli-pytest`'s
36
+ * `pytest tests/acceptance/{ms}` — there is no crate-root-style file that
37
+ * must `include!`/import each slice before it becomes reachable, so there is
38
+ * nothing to fold into `AcceptanceConfig.sealed_paths()`.
39
+ *
40
+ * `webServer` (#1818): boots a REAL `coord web --fixture <file> --dist dist`
41
+ * process (`coord/dashboard/fixture.py`, #1538) instead of the Vite dev
42
+ * server — deterministic reads with no daemon, no live fleet, no network,
43
+ * mirroring what `e2e/fixtureServer.ts` already does for
44
+ * `live-update-fixture.spec.ts`. `npm run build` runs first because
45
+ * `--dist` needs a production bundle on disk; both steps are chained into
46
+ * one `command` string so nothing outside this file has to remember to
47
+ * build before testing. `coord` must be resolvable on `$PATH` — true by
48
+ * construction here: this suite is only ever invoked by the `web-playwright`
49
+ * acceptance driver, which is itself `coord acceptance run`/`record`, so the
50
+ * `coord` CLI is always already installed on whatever machine reaches this
51
+ * config.
52
+ *
53
+ * Seeding convention for a slice (#1818): a milestone directory may declare
54
+ * `tests/acceptance/ms-NN/fixtures/<name>.json` — a `coord/dashboard/
55
+ * fixture.py`-shaped fixture, sealed alongside that milestone's
56
+ * `mocks/*.html`, so a slice's seed is part of the tree the test-author
57
+ * owns (see `coord.test_author.TEST_AUTHOR_SYSTEM_PROMPT`, which tells the
58
+ * next web test-author to write one of these instead of inventing an inline
59
+ * `page.route()` payload). Exactly one `*.json` file is expected per `ms-NN`
60
+ * directory; `resolveFixturePath` raises at config-load time if it finds
61
+ * more than one, rather than silently picking one and hiding the ambiguity.
62
+ * A milestone with no `fixtures/` directory falls back to the repo-wide
63
+ * `tests/fixtures/board-pipeline-basic.json` default (the same fixture
64
+ * `e2e/fixtureServer.ts` uses) — harmless for a slice that seeds itself
65
+ * another way.
66
+ *
67
+ * ms-51 (#1544's proving rig) is deliberately left on `page.route()` — see
68
+ * that slice's own header comment and #1818's "recommend leaving ms-51
69
+ * alone" — so it falls back to the default fixture above and ignores
70
+ * whatever it serves; ms-52 onward is where a slice is expected to declare
71
+ * its own `fixtures/*.json` and drop `page.route()` entirely. Known hazard
72
+ * ms-51 no longer inherits from a dev server (worth stating precisely,
73
+ * since a stale hazard note is worse than none): React 18 StrictMode's
74
+ * double-invoked effects were a *Vite dev mode* quirk, not a production
75
+ * bundle one, so a fixture-backed slice authored from here on doesn't need
76
+ * to write around it — but assertions should still prefer eventual state
77
+ * (`expect(...).toHaveText(...)`, `waitFor`) over exact effect/call counts,
78
+ * which is good practice regardless of backend.
79
+ */
80
+
81
+ import { defineConfig, devices } from '@playwright/test'
82
+ import fs from 'node:fs'
83
+ import path from 'node:path'
84
+ import { fileURLToPath } from 'node:url'
85
+
86
+ const here = path.dirname(fileURLToPath(import.meta.url))
87
+ const REPO_ROOT = path.resolve(here, '../../..')
88
+ const ACCEPTANCE_DIR = path.resolve(here, '../../../tests/acceptance')
89
+ const DIST_DIR = path.join(here, 'dist')
90
+ const DEFAULT_FIXTURE = path.join(REPO_ROOT, 'tests/fixtures/board-pipeline-basic.json')
91
+
92
+ /**
93
+ * Resolve which fixture JSON seeds `coord web --fixture` for this run.
94
+ *
95
+ * The driver's `run:` command passes the target milestone as a positional
96
+ * filter (`npm run test:acceptance -- ms-51`), and Playwright forwards CLI
97
+ * args through verbatim, so `process.argv` already carries the `ms-NN`
98
+ * token this config needs — no second side-channel required. Falls back to
99
+ * `DEFAULT_FIXTURE` when no `ms-NN` arg is present (a bare `npm run
100
+ * test:acceptance` running the whole tree) or the matched milestone has no
101
+ * `fixtures/` directory of its own.
102
+ */
103
+ function resolveFixturePath(): string {
104
+ const msArg = process.argv.find((a) => /^ms-\d+$/.test(a))
105
+ if (msArg) {
106
+ const fixturesDir = path.join(ACCEPTANCE_DIR, msArg, 'fixtures')
107
+ if (fs.existsSync(fixturesDir)) {
108
+ const files = fs
109
+ .readdirSync(fixturesDir)
110
+ .filter((f) => f.endsWith('.json'))
111
+ .sort()
112
+ if (files.length > 1) {
113
+ throw new Error(
114
+ `${fixturesDir} declares ${files.length} fixture files ` +
115
+ `(${files.join(', ')}) — the acceptance webServer expects at ` +
116
+ 'most one per ms-NN directory. Split into separate ms-NN ' +
117
+ 'milestones instead of disambiguating fixtures within one.',
118
+ )
119
+ }
120
+ if (files.length === 1) return path.join(fixturesDir, files[0])
121
+ }
122
+ }
123
+ return DEFAULT_FIXTURE
124
+ }
125
+
126
+ const FIXTURE_PATH = resolveFixturePath()
127
+
128
+ export default defineConfig({
129
+ testDir: ACCEPTANCE_DIR,
130
+ testMatch: '**/*.spec.ts',
131
+
132
+ fullyParallel: false,
133
+ workers: 1,
134
+
135
+ forbidOnly: !!process.env.CI,
136
+ retries: process.env.CI ? 1 : 0,
137
+
138
+ // Explicit, non-CI-conditional default — see module docstring point 2.
139
+ // The web-playwright driver overrides this at invocation with its own
140
+ // `--reporter=json` CLI flag regardless.
141
+ reporter: 'list',
142
+
143
+ use: {
144
+ baseURL: 'http://localhost:5173',
145
+ screenshot: 'only-on-failure',
146
+ actionTimeout: 5_000,
147
+ },
148
+
149
+ projects: [
150
+ {
151
+ name: 'chromium',
152
+ use: { ...devices['Desktop Chrome'] },
153
+ },
154
+ ],
155
+
156
+ webServer: {
157
+ command:
158
+ `npm run build && coord web --fixture "${FIXTURE_PATH}" ` +
159
+ `--dist "${DIST_DIR}" --host 127.0.0.1 --port 5173`,
160
+ url: 'http://localhost:5173',
161
+ reuseExistingServer: !process.env.CI,
162
+ // `npm run build` (tsc && vite build) plus process boot, comfortably
163
+ // over the old Vite-dev-only 30s budget.
164
+ timeout: 90_000,
165
+ },
166
+ })
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Playwright E2E configuration for the coord webapp.
3
+ *
4
+ * Run: npm run test:e2e
5
+ *
6
+ * Prerequisites (one-time per machine):
7
+ * npx playwright install chromium
8
+ *
9
+ * Design:
10
+ * - The Vite dev server is started automatically by Playwright's
11
+ * `webServer` option, so no separate `npm run dev` is needed.
12
+ * - API calls (`/api/pipeline`, `/api/board`, …) are intercepted via
13
+ * `page.route()` inside each test — no live Python daemon required.
14
+ * This keeps the suite deterministic and fast. The one exception is
15
+ * `live-update-fixture.spec.ts` (#1551), which boots a real
16
+ * `coord web --fixture` process instead — see that file's header.
17
+ * - Only Chromium is targeted. Safari/Firefox variants belong in the
18
+ * coord/dashboard/webapp smoke_tests capability group; gate them on a
19
+ * machine with those browsers installed via coordinator.yml.
20
+ *
21
+ * Routing to capable hardware:
22
+ * coordinator.yml smoke_tests.capability_rules:
23
+ * - capability: browser
24
+ * paths: [coord/dashboard/webapp/**]
25
+ *
26
+ * `wide` / `narrow` projects (#1551, M-W1's exit gate): distinct Playwright
27
+ * *projects*, not `test.use({ viewport })` overrides inside `chromium` (which
28
+ * is how `shell.spec.ts` and `smoke.spec.ts` already cover breakpoints) —
29
+ * the story calls for both breakpoints as their own reportable line items.
30
+ * Scoped via `testMatch` to only the two files authored for it
31
+ * (`deep-link.spec.ts`, `theme.spec.ts`) and excluded from `chromium` via
32
+ * `testIgnore` so they don't triple-run; every other spec's breakpoint
33
+ * coverage (if any) stays exactly where it already was, under `chromium`.
34
+ */
35
+
36
+ import { defineConfig, devices } from '@playwright/test'
37
+
38
+ const WIDE_VIEWPORT = { width: 1440, height: 900 }
39
+ const NARROW_VIEWPORT = { width: 390, height: 844 }
40
+ const BREAKPOINT_PROJECT_FILES = ['deep-link.spec.ts', 'theme.spec.ts']
41
+
42
+ export default defineConfig({
43
+ testDir: './e2e',
44
+ testMatch: '**/*.spec.ts',
45
+
46
+ // Run tests sequentially (single file, small suite for now).
47
+ fullyParallel: false,
48
+ workers: 1,
49
+
50
+ // Fail the build on CI if a test is `.only`-locked.
51
+ forbidOnly: !!process.env.CI,
52
+
53
+ // One retry on CI to guard against flaky timing.
54
+ retries: process.env.CI ? 1 : 0,
55
+
56
+ reporter: process.env.CI ? 'github' : 'list',
57
+
58
+ use: {
59
+ // Base URL is the Vite dev server started by webServer below.
60
+ baseURL: 'http://localhost:5173',
61
+ // Capture a screenshot on failure.
62
+ screenshot: 'only-on-failure',
63
+ // Short action timeout — these are simple DOM assertions.
64
+ actionTimeout: 5_000,
65
+ },
66
+
67
+ projects: [
68
+ {
69
+ name: 'chromium',
70
+ use: { ...devices['Desktop Chrome'] },
71
+ testIgnore: BREAKPOINT_PROJECT_FILES.map((f) => `**/${f}`),
72
+ },
73
+ {
74
+ name: 'wide',
75
+ use: { ...devices['Desktop Chrome'], viewport: WIDE_VIEWPORT },
76
+ testMatch: BREAKPOINT_PROJECT_FILES,
77
+ },
78
+ {
79
+ name: 'narrow',
80
+ use: { ...devices['Desktop Chrome'], viewport: NARROW_VIEWPORT },
81
+ testMatch: BREAKPOINT_PROJECT_FILES,
82
+ },
83
+ ],
84
+
85
+ // Start the Vite dev server before running tests.
86
+ // Playwright waits for the URL to respond before dispatching tests.
87
+ webServer: {
88
+ command: 'npm run dev',
89
+ url: 'http://localhost:5173',
90
+ reuseExistingServer: !process.env.CI,
91
+ timeout: 30_000,
92
+ },
93
+ })
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
2
+ <rect width="512" height="512" rx="80" fill="#1f6feb"/>
3
+ <text x="256" y="358" font-family="monospace,ui-monospace" font-size="320" font-weight="bold"
4
+ fill="white" text-anchor="middle">C</text>
5
+ </svg>
@@ -0,0 +1,140 @@
1
+ import { lazy, Suspense } from 'react'
2
+ import { BrowserRouter, Navigate, Routes, Route } from 'react-router-dom'
3
+ import Detail from '@/components/Detail'
4
+ import SessionDetail from '@/components/SessionDetail'
5
+ import { ShellLayout } from '@/shell/ShellLayout'
6
+ import { EmptyDetail } from '@/shell/EmptyDetail'
7
+ import { paths } from '@/routes/paths'
8
+ import { ThemeProvider } from '@/components/ui/theme-provider'
9
+ import { Toaster } from '@/components/ui/toaster'
10
+
11
+ // Lazy: xterm.js (#1068) is a sizable dependency that's only needed once a
12
+ // user actually taps into a live session's take-over view -- code-splitting
13
+ // it out of the main bundle keeps the Home/Detail initial load lean instead
14
+ // of shipping the terminal renderer to everyone up front.
15
+ const Terminal = lazy(() => import('@/components/Terminal'))
16
+
17
+ // Dev-only component gallery (#1546) — renders every ui/* primitive in both
18
+ // themes, so a human (or a Playwright acceptance slice) can see the whole
19
+ // system at once without hunting through the app for each one. Route-guarded
20
+ // by `import.meta.env.DEV` below so production can never navigate to it; it
21
+ // still ends up as its own lazy chunk in `dist/` (Rollup splits on the
22
+ // `import()` call site regardless of the guard), so vite.config.ts also
23
+ // excludes it from the PWA precache list -- see the comment there.
24
+ const Gallery = lazy(() => import('@/components/Gallery'))
25
+
26
+ /**
27
+ * App root. BrowserRouter is used here; the dashboard server serves index.html
28
+ * as a SPA fallback for all non-API paths so deep links work on hard reload.
29
+ *
30
+ * Route tree (#1548) — every screen the app can show gets one address:
31
+ *
32
+ * / -> redirect to /pipeline (replace, so
33
+ * it doesn't leave a dead entry back
34
+ * button lands on)
35
+ * /pipeline -> EmptyDetail in the detail slot
36
+ * /pipeline/:repo/:issue -> Detail (keyed on repo+issue, not
37
+ * assignment_id -- see Detail.tsx)
38
+ * /pipeline/:repo/:issue/:tab -> Detail, tab param threaded through
39
+ * for M-W2 to consume; round-trips
40
+ * today without changing what renders
41
+ * /sessions -> EmptyDetail
42
+ * /sessions/:id -> SessionDetail
43
+ * /board /machines /merge-queue
44
+ * /milestones /audit /spend
45
+ * /settings -> ComingSoon(view) -- placeholders for
46
+ * the M-W2+ panels, addressable today
47
+ * so a link to one isn't a 404 while
48
+ * it's being built
49
+ * * (inside the shell) -> RouteNotFound -- a real not-found
50
+ * state for a genuinely unknown path,
51
+ * rendered inside the shell (rail,
52
+ * status bar) rather than a blank page
53
+ *
54
+ * Every route below `/pipeline` is a child of `ShellLayout`, the react-router
55
+ * *layout route*: the child fills the detail slot (rail + list + detail on
56
+ * wide, the phone app on narrow) and `ShellLayout` derives the rail selection
57
+ * and the list panel's content from the URL itself (`shellViewFromPath`)
58
+ * rather than from separately persisted state -- see `shellState.ts`'s doc
59
+ * comment.
60
+ *
61
+ * The `/` redirect is deliberately declared *outside* `ShellLayout`, as a
62
+ * sibling of it rather than one of its children. `AppShell` only mounts the
63
+ * detail slot -- the thing that would render a child route's `element` --
64
+ * when `showDetail` is true, and on narrow that's `false` until an item is
65
+ * selected (`detailActive`). A redirect placed under `ShellLayout` therefore
66
+ * silently never fires on a phone-sized cold load at `/`: `Outlet` is never
67
+ * mounted, so `<Navigate>`'s effect never runs, and the shell renders with no
68
+ * matched child at all -- exactly the "genuinely unknown route" case, so it
69
+ * falls through to the `*` not-found state instead of redirecting. Matching
70
+ * `/` *before* composing the shell sidesteps the whole class of bug: nothing
71
+ * about "should this redirect fire" may depend on shell layout state.
72
+ *
73
+ * `/terminal/:sessionId` stays deliberately *outside* the shell for a
74
+ * different reason: the PTY pane wants the whole viewport and brings its own
75
+ * key bar, and framing a terminal in a rail plus a status bar would cost it
76
+ * rows on exactly the device (a phone) where rows are scarcest.
77
+ */
78
+ export default function App() {
79
+ return (
80
+ <ThemeProvider>
81
+ <BrowserRouter>
82
+ <Routes>
83
+ <Route path="/" element={<Navigate to={paths.pipeline()} replace />} />
84
+
85
+ <Route element={<ShellLayout />}>
86
+ <Route path="/pipeline" element={<EmptyDetail />} />
87
+ <Route path="/pipeline/:repo/:issue" element={<Detail />} />
88
+ <Route path="/pipeline/:repo/:issue/:tab" element={<Detail />} />
89
+
90
+ <Route path="/sessions" element={<EmptyDetail />} />
91
+ <Route path="/sessions/:id" element={<SessionDetail />} />
92
+
93
+ {/* Placeholders for the M-W2+ panels (#1548) -- addressable now,
94
+ not a 404, so a bookmark or a pasted link survives the panel
95
+ shipping later. The rail marks each of these 'soon' and won't
96
+ navigate here on a click (railItems.ts); this is what a typed
97
+ or pasted link to one lands on in the meantime. */}
98
+ <Route path="/board" element={null} />
99
+ <Route path="/machines" element={null} />
100
+ <Route path="/merge-queue" element={null} />
101
+ <Route path="/milestones" element={null} />
102
+ <Route path="/audit" element={null} />
103
+ <Route path="/spend" element={null} />
104
+ <Route path="/settings" element={null} />
105
+
106
+ {/* Genuinely unknown path under the shell -- a typo'd URL, or a
107
+ link to a feature that no longer exists. `ShellLayout` puts
108
+ `RouteNotFound` in the *list* slot for this case (there is no
109
+ "current view" to hand a list to); this route's own element
110
+ stays empty so wide doesn't show the same message twice. */}
111
+ <Route path="*" element={null} />
112
+ </Route>
113
+ <Route
114
+ path="/terminal/:sessionId"
115
+ element={
116
+ <div className="min-h-screen bg-background text-foreground">
117
+ <Suspense fallback={null}>
118
+ <Terminal />
119
+ </Suspense>
120
+ </div>
121
+ }
122
+ />
123
+ {import.meta.env.DEV && (
124
+ <Route
125
+ path="/gallery"
126
+ element={
127
+ <div className="min-h-screen bg-background text-foreground">
128
+ <Suspense fallback={null}>
129
+ <Gallery />
130
+ </Suspense>
131
+ </div>
132
+ }
133
+ />
134
+ )}
135
+ </Routes>
136
+ </BrowserRouter>
137
+ <Toaster />
138
+ </ThemeProvider>
139
+ )
140
+ }