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,138 @@
1
+ /**
2
+ * Theme coverage for the acceptance slice (#1551, M-W1's exit gate).
3
+ *
4
+ * Nothing in the repo exercised dark/light before this file: `#1546` shipped
5
+ * the tokens, the provider and the toggle, but no E2E spec ever clicked it.
6
+ * "Both themes, including the toggle and its persistence" is one of #1551's
7
+ * named acceptance criteria precisely because a chassis milestone can ship
8
+ * looking done while a whole theme (or a whole breakpoint's access to the
9
+ * toggle) is quietly unreachable -- which is exactly what this file found:
10
+ * `ActivityRail`'s "Theme" button is `{!narrow && ...}` (wide/medium only,
11
+ * `ActivityRail.tsx`), and nothing stood in for it at narrow width. Fixed
12
+ * alongside this spec in `PanelHeader.tsx` -- a `md:hidden` `<ThemeToggle/>`
13
+ * next to the panel title, matching `docs/mocks/web/pipeline-narrow.html`'s
14
+ * per-view topbar (`id="theme"`), which already puts it there for exactly
15
+ * this reason (the bottom nav has no room for it -- see that file's own
16
+ * comment). Both controls share the same accessible name
17
+ * (`Switch to dark/light theme`), so `themeToggle()` below finds whichever
18
+ * one is actually in the accessibility tree at the current viewport --
19
+ * `md:hidden` is a real `display:none`, which a11y-tree role queries already
20
+ * exclude, so there is never a strict-mode ambiguity between the two.
21
+ *
22
+ * Runs at both breakpoints as distinct Playwright projects ('wide' /
23
+ * 'narrow', see playwright.config.ts) rather than a single project's
24
+ * `test.use({ viewport })` override, so a theme regression pinned to one
25
+ * breakpoint shows up as its own failing project/line rather than being
26
+ * folded into a single generic run.
27
+ *
28
+ * Run: npm run test:e2e
29
+ */
30
+ import { test, expect, type Page } from '@playwright/test'
31
+
32
+ const SEEDED_PIPELINE = [
33
+ {
34
+ assignment_id: 'work-1',
35
+ issue_number: 42,
36
+ issue_title: 'Fix the dashboard rendering',
37
+ repo_name: 'api',
38
+ machine_name: 'laptop',
39
+ current_stage: 'coding',
40
+ stages: [
41
+ { name: 'coding', status: 'active', is_current: true },
42
+ { name: 'review', status: 'waiting', is_current: false },
43
+ { name: 'merge', status: 'waiting', is_current: false },
44
+ ],
45
+ available_gates: [],
46
+ progress_pct: 20,
47
+ review_findings_pending: false,
48
+ review_verdict: null,
49
+ review_findings_body: null,
50
+ test_verdict: null,
51
+ },
52
+ ]
53
+
54
+ async function mockApi(page: Page): Promise<void> {
55
+ await page.route('**/api/pipeline', (route) =>
56
+ route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(SEEDED_PIPELINE) }),
57
+ )
58
+ await page.route('**/api/sessions', (route) =>
59
+ route.fulfill({ status: 200, contentType: 'application/json', body: '[]' }),
60
+ )
61
+ await page.route('**/api/board', (route) =>
62
+ route.fulfill({
63
+ status: 200,
64
+ contentType: 'application/json',
65
+ body: JSON.stringify({ round_number: 1, active: [], completed: [] }),
66
+ }),
67
+ )
68
+ }
69
+
70
+ /** Whichever theme toggle is actually in the accessibility tree at this
71
+ * viewport -- the rail's (wide/medium) or PanelHeader's (narrow). Exactly
72
+ * one exists at a time; see this file's header comment. */
73
+ function themeToggle(page: Page) {
74
+ return page.getByRole('button', { name: /Switch to (dark|light) theme/ })
75
+ }
76
+
77
+ function htmlTheme(page: Page): Promise<string | null> {
78
+ return page.evaluate(() => document.documentElement.getAttribute('data-theme'))
79
+ }
80
+
81
+ function storedTheme(page: Page): Promise<string | null> {
82
+ return page.evaluate(() => window.localStorage.getItem('coord-web-theme'))
83
+ }
84
+
85
+ // Pin the emulated OS preference so "no stored theme yet" resolves
86
+ // deterministically to dark (the documented default, docs/mocks/web/README.md)
87
+ // rather than depending on Playwright's own default colour scheme.
88
+ test.use({ colorScheme: 'dark' })
89
+
90
+ test.describe('theme (#1551)', () => {
91
+ test('defaults to dark, and the toggle flips data-theme on <html> to light and back', async ({ page }) => {
92
+ await mockApi(page)
93
+ await page.goto('/')
94
+
95
+ await expect.poll(() => htmlTheme(page)).toBe('dark')
96
+ await expect(themeToggle(page)).toHaveAccessibleName('Switch to light theme')
97
+
98
+ await themeToggle(page).click()
99
+ await expect.poll(() => htmlTheme(page)).toBe('light')
100
+ await expect(themeToggle(page)).toHaveAccessibleName('Switch to dark theme')
101
+
102
+ await themeToggle(page).click()
103
+ await expect.poll(() => htmlTheme(page)).toBe('dark')
104
+ })
105
+
106
+ test('switching theme visibly changes the rendered surface, not just the attribute', async ({ page }) => {
107
+ await mockApi(page)
108
+ await page.goto('/')
109
+
110
+ const surface = page.locator('body')
111
+ const darkBg = await surface.evaluate((el) => getComputedStyle(el).backgroundColor)
112
+
113
+ await themeToggle(page).click()
114
+ await expect.poll(() => htmlTheme(page)).toBe('light')
115
+ const lightBg = await surface.evaluate((el) => getComputedStyle(el).backgroundColor)
116
+
117
+ expect(lightBg).not.toEqual(darkBg)
118
+ })
119
+
120
+ test('the choice persists to localStorage and survives a reload with no flash back to dark', async ({
121
+ page,
122
+ }) => {
123
+ await mockApi(page)
124
+ await page.goto('/')
125
+
126
+ await themeToggle(page).click()
127
+ await expect.poll(() => htmlTheme(page)).toBe('light')
128
+ await expect.poll(() => storedTheme(page)).toBe('light')
129
+
130
+ await page.reload()
131
+
132
+ // Asserted immediately after load, not just eventually -- index.html's
133
+ // blocking inline script is what's supposed to prevent a dark flash
134
+ // before React hydrates; a poll alone wouldn't catch a one-frame flash.
135
+ await expect.poll(() => htmlTheme(page), { timeout: 2_000 }).toBe('light')
136
+ await expect(themeToggle(page)).toHaveAccessibleName('Switch to dark theme')
137
+ })
138
+ })
@@ -0,0 +1,20 @@
1
+ import tsPlugin from '@typescript-eslint/eslint-plugin'
2
+ import reactHooks from 'eslint-plugin-react-hooks'
3
+ import reactRefresh from 'eslint-plugin-react-refresh'
4
+
5
+ export default [
6
+ { ignores: ['dist'] },
7
+ ...tsPlugin.configs['flat/recommended'],
8
+ {
9
+ files: ['**/*.{ts,tsx}'],
10
+ plugins: {
11
+ 'react-hooks': reactHooks,
12
+ 'react-refresh': reactRefresh,
13
+ },
14
+ rules: {
15
+ 'react-hooks/rules-of-hooks': 'error',
16
+ 'react-hooks/exhaustive-deps': 'warn',
17
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
18
+ },
19
+ },
20
+ ]
@@ -0,0 +1,37 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/icons/icon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
7
+ <meta name="apple-mobile-web-app-capable" content="yes" />
8
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
9
+ <meta name="mobile-web-app-capable" content="yes" />
10
+ <meta name="theme-color" content="#101418" />
11
+ <title>coord dashboard</title>
12
+ <script>
13
+ // Blocking, pre-paint theme resolution (#1546) — must mirror
14
+ // src/components/ui/theme-provider.tsx's `initialTheme()` exactly, or
15
+ // the first React render disagrees with what's already on screen and
16
+ // flashes. Inlined (not a src="") so nothing external delays it.
17
+ (function () {
18
+ try {
19
+ var stored = localStorage.getItem('coord-web-theme');
20
+ var theme =
21
+ stored === 'light' || stored === 'dark'
22
+ ? stored
23
+ : window.matchMedia('(prefers-color-scheme: light)').matches
24
+ ? 'light'
25
+ : 'dark';
26
+ document.documentElement.setAttribute('data-theme', theme);
27
+ } catch (e) {
28
+ document.documentElement.setAttribute('data-theme', 'dark');
29
+ }
30
+ })();
31
+ </script>
32
+ </head>
33
+ <body>
34
+ <div id="root"></div>
35
+ <script type="module" src="/src/main.tsx"></script>
36
+ </body>
37
+ </html>
@@ -0,0 +1,144 @@
1
+ # ISC License
2
+ #
3
+ # Copyright (c) 2018-2025, Andrea Giammarchi, @WebReflection
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any
6
+ # purpose with or without fee is hereby granted, provided that the above
7
+ # copyright notice and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14
+ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ # PERFORMANCE OF THIS SOFTWARE.
16
+
17
+ import json as _json
18
+
19
+ class _Known:
20
+ def __init__(self):
21
+ self.key = []
22
+ self.value = []
23
+
24
+ class _String:
25
+ def __init__(self, value):
26
+ self.value = value
27
+
28
+ def _array_keys(value):
29
+ for i in range(len(value)):
30
+ yield i
31
+
32
+ def _object_keys(value):
33
+ for key in value:
34
+ yield key
35
+
36
+ def _is_array(value):
37
+ return isinstance(value, (list, tuple))
38
+
39
+ def _is_object(value):
40
+ return isinstance(value, dict)
41
+
42
+ def _is_string(value):
43
+ return isinstance(value, str)
44
+
45
+ def _index(known, input, value):
46
+ input.append(value)
47
+ index = str(len(input) - 1)
48
+ known.key.append(value)
49
+ known.value.append(index)
50
+ return index
51
+
52
+ def _relate(known, input, value):
53
+ if _is_string(value) or _is_array(value) or _is_object(value):
54
+ try:
55
+ return known.value[known.key.index(value)]
56
+ except:
57
+ return _index(known, input, value)
58
+
59
+ return value
60
+
61
+ def _resolver(input, lazy, parsed):
62
+ def resolver(output):
63
+ keys = _array_keys(output) if _is_array(output) else _object_keys(output) if _is_object(output) else []
64
+ for key in keys:
65
+ value = output[key]
66
+ if isinstance(value, _String):
67
+ tmp = input[int(value.value)]
68
+ output[key] = tmp
69
+ if (_is_array(tmp) or _is_object(tmp)) and tmp not in parsed:
70
+ parsed.append(tmp)
71
+ lazy.append([output, key])
72
+
73
+ return output
74
+
75
+ return resolver
76
+
77
+ def _transform(known, input, value):
78
+ if _is_array(value):
79
+ output = []
80
+ for val in value:
81
+ output.append(_relate(known, input, val))
82
+ return output
83
+
84
+ if _is_object(value):
85
+ obj = {}
86
+ for key in value:
87
+ obj[key] = _relate(known, input, value[key])
88
+ return obj
89
+
90
+ return value
91
+
92
+ def _wrap(value):
93
+ if _is_string(value):
94
+ return _String(value)
95
+
96
+ if _is_array(value):
97
+ i = 0
98
+ for val in value:
99
+ value[i] = _wrap(val)
100
+ i += 1
101
+
102
+ elif _is_object(value):
103
+ for key in value:
104
+ value[key] = _wrap(value[key])
105
+
106
+ return value
107
+
108
+ def parse(value, *args, **kwargs):
109
+ json = _json.loads(value, *args, **kwargs)
110
+ wrapped = []
111
+ for value in json:
112
+ wrapped.append(_wrap(value))
113
+
114
+ input = []
115
+ for value in wrapped:
116
+ if isinstance(value, _String):
117
+ input.append(value.value)
118
+ else:
119
+ input.append(value)
120
+
121
+ value = input[0]
122
+ lazy = []
123
+ revive = _resolver(input, lazy, [value])
124
+
125
+ value = revive(value)
126
+
127
+ i = 0
128
+ while i < len(lazy):
129
+ o, k = lazy[i]
130
+ i += 1
131
+ o[k] = revive(o[k])
132
+
133
+ return value
134
+
135
+
136
+ def stringify(value, *args, **kwargs):
137
+ known = _Known()
138
+ input = []
139
+ output = []
140
+ i = int(_index(known, input, value))
141
+ while i < len(input):
142
+ output.append(_transform(known, input, input[i]))
143
+ i += 1
144
+ return _json.dumps(output, *args, **kwargs)