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.
- code_coordinator-0.5.46.dist-info/METADATA +625 -0
- code_coordinator-0.5.46.dist-info/RECORD +295 -0
- code_coordinator-0.5.46.dist-info/WHEEL +5 -0
- code_coordinator-0.5.46.dist-info/entry_points.txt +2 -0
- code_coordinator-0.5.46.dist-info/licenses/LICENSE +110 -0
- code_coordinator-0.5.46.dist-info/top_level.txt +1 -0
- coord/__init__.py +176 -0
- coord/_board_mapping.py +229 -0
- coord/acceptance.py +468 -0
- coord/acceptance_drivers.py +632 -0
- coord/agent.py +7517 -0
- coord/agent_app.py +1555 -0
- coord/agent_update.py +417 -0
- coord/agents/opencode/.gitignore +13 -0
- coord/agents/opencode/agents/work.md +129 -0
- coord/agents/opencode/routing.jsonc +49 -0
- coord/audit.py +301 -0
- coord/auto_loop.py +1440 -0
- coord/board_bool_guard.py +72 -0
- coord/board_service.py +141 -0
- coord/board_wire.py +309 -0
- coord/brain.py +581 -0
- coord/branch_model.py +214 -0
- coord/cargo_cache.py +258 -0
- coord/ci_github.py +386 -0
- coord/ci_store.py +560 -0
- coord/claim.py +353 -0
- coord/cli.py +454 -0
- coord/client.py +610 -0
- coord/commands/__init__.py +1 -0
- coord/commands/_common.py +329 -0
- coord/commands/acceptance.py +916 -0
- coord/commands/agent_ops.py +1339 -0
- coord/commands/audit.py +131 -0
- coord/commands/chat.py +320 -0
- coord/commands/dispatch.py +1780 -0
- coord/commands/dispatch_workers.py +4894 -0
- coord/commands/drive.py +616 -0
- coord/commands/drive_queue.py +1203 -0
- coord/commands/gate_a.py +217 -0
- coord/commands/gates.py +89 -0
- coord/commands/issues.py +681 -0
- coord/commands/lifecycle.py +513 -0
- coord/commands/merge.py +1900 -0
- coord/commands/milestone.py +2081 -0
- coord/commands/plan_followup.py +1243 -0
- coord/commands/plans.py +156 -0
- coord/commands/release.py +2232 -0
- coord/commands/report.py +341 -0
- coord/commands/review.py +1523 -0
- coord/commands/scorecard.py +252 -0
- coord/commands/sessions.py +1930 -0
- coord/commands/setup.py +576 -0
- coord/commands/status.py +2089 -0
- coord/commands/terminal.py +385 -0
- coord/commands/test_gate.py +775 -0
- coord/commands/tui.py +288 -0
- coord/comments.py +718 -0
- coord/config.py +3032 -0
- coord/conflict_fix.py +633 -0
- coord/dao.py +483 -0
- coord/dashboard/__init__.py +0 -0
- coord/dashboard/fixture.py +376 -0
- coord/dashboard/index.html +658 -0
- coord/dashboard/server.py +1894 -0
- coord/dashboard/terminal.py +382 -0
- coord/dashboard/webapp/.gitignore +9 -0
- coord/dashboard/webapp/components.json +17 -0
- coord/dashboard/webapp/dist/assets/Gallery-da3qNiIw.js +71 -0
- coord/dashboard/webapp/dist/assets/Terminal-9CEnUXvW.css +32 -0
- coord/dashboard/webapp/dist/assets/Terminal-skVFCxPU.js +63 -0
- coord/dashboard/webapp/dist/assets/index-DltfZR5f.js +184 -0
- coord/dashboard/webapp/dist/assets/index-Dq4kwTdw.css +1 -0
- coord/dashboard/webapp/dist/assets/workbox-window.prod.es5-BqEJf4Xk.js +2 -0
- coord/dashboard/webapp/dist/icons/icon-192.png +0 -0
- coord/dashboard/webapp/dist/icons/icon-512.png +0 -0
- coord/dashboard/webapp/dist/icons/icon.svg +5 -0
- coord/dashboard/webapp/dist/index.html +38 -0
- coord/dashboard/webapp/dist/manifest.webmanifest +1 -0
- coord/dashboard/webapp/dist/sw.js +1 -0
- coord/dashboard/webapp/dist/workbox-e4022e15.js +1 -0
- coord/dashboard/webapp/e2e/available-gates-terminal.spec.ts +75 -0
- coord/dashboard/webapp/e2e/deep-link.spec.ts +172 -0
- coord/dashboard/webapp/e2e/fixtureServer.ts +155 -0
- coord/dashboard/webapp/e2e/live-update-fixture.spec.ts +113 -0
- coord/dashboard/webapp/e2e/realtime.spec.ts +238 -0
- coord/dashboard/webapp/e2e/shell.spec.ts +309 -0
- coord/dashboard/webapp/e2e/smoke.spec.ts +191 -0
- coord/dashboard/webapp/e2e/terminal.spec.ts +420 -0
- coord/dashboard/webapp/e2e/theme.spec.ts +138 -0
- coord/dashboard/webapp/eslint.config.js +20 -0
- coord/dashboard/webapp/index.html +37 -0
- coord/dashboard/webapp/node_modules/flatted/python/flatted.py +144 -0
- coord/dashboard/webapp/package-lock.json +10584 -0
- coord/dashboard/webapp/package.json +63 -0
- coord/dashboard/webapp/playwright.acceptance.config.ts +166 -0
- coord/dashboard/webapp/playwright.config.ts +93 -0
- coord/dashboard/webapp/postcss.config.js +6 -0
- coord/dashboard/webapp/public/icons/icon-192.png +0 -0
- coord/dashboard/webapp/public/icons/icon-512.png +0 -0
- coord/dashboard/webapp/public/icons/icon.svg +5 -0
- coord/dashboard/webapp/src/App.tsx +140 -0
- coord/dashboard/webapp/src/api/client.ts +199 -0
- coord/dashboard/webapp/src/api/generated.ts +176 -0
- coord/dashboard/webapp/src/components/ConnectionBadge.tsx +52 -0
- coord/dashboard/webapp/src/components/Detail.tsx +800 -0
- coord/dashboard/webapp/src/components/Gallery.tsx +341 -0
- coord/dashboard/webapp/src/components/Home.tsx +435 -0
- coord/dashboard/webapp/src/components/MobileKeyBar.tsx +280 -0
- coord/dashboard/webapp/src/components/PanelHeader.tsx +59 -0
- coord/dashboard/webapp/src/components/PipelineCard.tsx +168 -0
- coord/dashboard/webapp/src/components/SessionCard.tsx +99 -0
- coord/dashboard/webapp/src/components/SessionDetail.tsx +140 -0
- coord/dashboard/webapp/src/components/SessionsList.tsx +81 -0
- coord/dashboard/webapp/src/components/Terminal.tsx +376 -0
- coord/dashboard/webapp/src/components/__tests__/ConnectionBadge.test.tsx +81 -0
- coord/dashboard/webapp/src/components/__tests__/Detail.test.tsx +680 -0
- coord/dashboard/webapp/src/components/__tests__/Gallery.test.tsx +83 -0
- coord/dashboard/webapp/src/components/__tests__/Home.test.tsx +271 -0
- coord/dashboard/webapp/src/components/__tests__/MobileKeyBar.test.tsx +197 -0
- coord/dashboard/webapp/src/components/__tests__/PipelineCard.test.tsx +143 -0
- coord/dashboard/webapp/src/components/__tests__/SessionCard.test.tsx +106 -0
- coord/dashboard/webapp/src/components/__tests__/Terminal.test.tsx +504 -0
- coord/dashboard/webapp/src/components/ui/badge.tsx +41 -0
- coord/dashboard/webapp/src/components/ui/button.tsx +54 -0
- coord/dashboard/webapp/src/components/ui/card.tsx +55 -0
- coord/dashboard/webapp/src/components/ui/dialog.tsx +99 -0
- coord/dashboard/webapp/src/components/ui/dropdown-menu.tsx +189 -0
- coord/dashboard/webapp/src/components/ui/empty-state.tsx +35 -0
- coord/dashboard/webapp/src/components/ui/sheet.tsx +123 -0
- coord/dashboard/webapp/src/components/ui/skeleton.tsx +9 -0
- coord/dashboard/webapp/src/components/ui/tabs.tsx +55 -0
- coord/dashboard/webapp/src/components/ui/theme-provider.tsx +78 -0
- coord/dashboard/webapp/src/components/ui/theme-toggle.tsx +20 -0
- coord/dashboard/webapp/src/components/ui/toast.tsx +123 -0
- coord/dashboard/webapp/src/components/ui/toaster.tsx +30 -0
- coord/dashboard/webapp/src/components/ui/tooltip.tsx +26 -0
- coord/dashboard/webapp/src/components/ui/use-toast.ts +134 -0
- coord/dashboard/webapp/src/index.css +210 -0
- coord/dashboard/webapp/src/lib/pipeline.ts +29 -0
- coord/dashboard/webapp/src/lib/utils.ts +6 -0
- coord/dashboard/webapp/src/main.tsx +46 -0
- coord/dashboard/webapp/src/realtime/RealtimeProvider.tsx +112 -0
- coord/dashboard/webapp/src/realtime/__tests__/RealtimeProvider.test.tsx +189 -0
- coord/dashboard/webapp/src/realtime/__tests__/connection.test.ts +255 -0
- coord/dashboard/webapp/src/realtime/connection.ts +227 -0
- coord/dashboard/webapp/src/realtime/events.ts +100 -0
- coord/dashboard/webapp/src/routes/__tests__/paths.test.ts +92 -0
- coord/dashboard/webapp/src/routes/paths.ts +92 -0
- coord/dashboard/webapp/src/shell/ActivityRail.tsx +335 -0
- coord/dashboard/webapp/src/shell/AppShell.tsx +276 -0
- coord/dashboard/webapp/src/shell/ComingSoon.tsx +33 -0
- coord/dashboard/webapp/src/shell/EmptyDetail.tsx +26 -0
- coord/dashboard/webapp/src/shell/RouteNotFound.tsx +33 -0
- coord/dashboard/webapp/src/shell/ShellLayout.tsx +147 -0
- coord/dashboard/webapp/src/shell/StatusBar.tsx +46 -0
- coord/dashboard/webapp/src/shell/__tests__/ShellLayout.test.tsx +520 -0
- coord/dashboard/webapp/src/shell/__tests__/shellState.test.ts +95 -0
- coord/dashboard/webapp/src/shell/__tests__/stubViewport.ts +40 -0
- coord/dashboard/webapp/src/shell/breakpoints.ts +87 -0
- coord/dashboard/webapp/src/shell/railItems.ts +105 -0
- coord/dashboard/webapp/src/shell/shellState.ts +174 -0
- coord/dashboard/webapp/src/shell/useRegionFocus.ts +95 -0
- coord/dashboard/webapp/src/test-setup.ts +41 -0
- coord/dashboard/webapp/src/vite-env.d.ts +2 -0
- coord/dashboard/webapp/tailwind.config.js +140 -0
- coord/dashboard/webapp/tsconfig.json +25 -0
- coord/dashboard/webapp/tsconfig.node.json +11 -0
- coord/dashboard/webapp/vite.config.ts +71 -0
- coord/db.py +1076 -0
- coord/dead_end.py +332 -0
- coord/deploy/README.md +33 -0
- coord/deploy/coord-agent.service +89 -0
- coord/deploy/coord-db-backup.service +60 -0
- coord/deploy/coord-db-backup.sh +74 -0
- coord/deploy/coord-db-backup.timer +18 -0
- coord/deploy/coord-drive-queue.service +117 -0
- coord/deploy/coord-drive-queue.timer +39 -0
- coord/deploy/coord-notify.service +48 -0
- coord/deploy/coord-notify.timer +24 -0
- coord/deploy/coord-release-propagate.service +83 -0
- coord/deploy/coord-release-propagate.timer +38 -0
- coord/deploy/coord-release-window.service +119 -0
- coord/deploy/coord-release-window.timer +36 -0
- coord/deploy/coord-serve.service +82 -0
- coord/deploy/coord-web-dist-build.service +43 -0
- coord/deploy/coord-web-dist-build.timer +36 -0
- coord/deploy/coord-web.service +125 -0
- coord/deploy_manifest.py +80 -0
- coord/deploy_units.py +384 -0
- coord/deps.py +115 -0
- coord/diagnose.py +1623 -0
- coord/dispatch.py +1009 -0
- coord/dist_name.py +123 -0
- coord/drive.py +3101 -0
- coord/drive_queue.py +2298 -0
- coord/drive_state.py +870 -0
- coord/events.py +381 -0
- coord/failure_class.py +914 -0
- coord/filelock.py +168 -0
- coord/fleet_config_health.py +300 -0
- coord/freshness.py +206 -0
- coord/gate_a.py +469 -0
- coord/gate_b.py +411 -0
- coord/gate_snapshot.py +385 -0
- coord/gates.py +582 -0
- coord/github_ops.py +1954 -0
- coord/goal.py +125 -0
- coord/graph_health.py +348 -0
- coord/health/__init__.py +69 -0
- coord/health/aggregate.py +129 -0
- coord/health/checks/__init__.py +13 -0
- coord/health/checks/agent_install.py +280 -0
- coord/health/checks/cargo_targets.py +171 -0
- coord/health/checks/claude_binary.py +65 -0
- coord/health/checks/deploy_lane_facts.py +458 -0
- coord/health/checks/disk.py +99 -0
- coord/health/checks/fleet_board.py +89 -0
- coord/health/checks/fleet_deploy_lanes.py +469 -0
- coord/health/checks/fleet_phantom.py +69 -0
- coord/health/checks/fleet_unit_drift.py +151 -0
- coord/health/checks/graph.py +192 -0
- coord/health/checks/plan_usage.py +88 -0
- coord/health/checks/repo_state.py +161 -0
- coord/health/checks/spawned_coord.py +465 -0
- coord/health/checks/timer_active.py +254 -0
- coord/health/checks/toolchain.py +547 -0
- coord/health/checks/unit_drift.py +648 -0
- coord/health/checks/unit_enablement.py +171 -0
- coord/health/checks/worktrees.py +96 -0
- coord/health/cli.py +121 -0
- coord/health/context.py +106 -0
- coord/health/fleet_snapshot.py +477 -0
- coord/health/models.py +250 -0
- coord/health/pypi.py +231 -0
- coord/health/registry.py +240 -0
- coord/health/render.py +82 -0
- coord/health/units.py +60 -0
- coord/hooks.py +106 -0
- coord/housekeeping.py +204 -0
- coord/interactive.py +4286 -0
- coord/issue_store.py +1496 -0
- coord/liveness_auditor.py +293 -0
- coord/machine_pause.py +755 -0
- coord/merge_queue.py +4681 -0
- coord/milestone_chat.py +600 -0
- coord/milestone_dispatch.py +943 -0
- coord/milestone_gate.py +709 -0
- coord/milestone_order.py +840 -0
- coord/mock_author.py +334 -0
- coord/models.py +891 -0
- coord/network.py +269 -0
- coord/new_issue_chat.py +229 -0
- coord/notify.py +3226 -0
- coord/openapi.py +404 -0
- coord/overlap_fence.py +133 -0
- coord/parentage.py +200 -0
- coord/parentage_github.py +58 -0
- coord/pipeline.py +481 -0
- coord/plan_parser.py +266 -0
- coord/plans.py +543 -0
- coord/platform_paths.py +43 -0
- coord/pr_body_lint.py +67 -0
- coord/prereqs.py +533 -0
- coord/progress.py +425 -0
- coord/providers/__init__.py +683 -0
- coord/providers/base.py +218 -0
- coord/providers/claude.py +284 -0
- coord/providers/claude_pty.py +610 -0
- coord/providers/opencode.py +896 -0
- coord/reconcile.py +2233 -0
- coord/refine_chat.py +485 -0
- coord/release_cordon.py +525 -0
- coord/release_propagate.py +1176 -0
- coord/release_verify.py +777 -0
- coord/release_window.py +322 -0
- coord/reports.py +1643 -0
- coord/revalidate.py +1101 -0
- coord/review.py +3317 -0
- coord/scorecard.py +484 -0
- coord/serve_app.py +7192 -0
- coord/skills/update-issue/SKILL.md +93 -0
- coord/smoke.py +1030 -0
- coord/split_work.py +210 -0
- coord/stage_projection.py +650 -0
- coord/state.py +5720 -0
- coord/test_author.py +1064 -0
- coord/test_chat.py +352 -0
- coord/test_orchestrator.py +494 -0
- coord/test_report.py +178 -0
- coord/tui_release.py +271 -0
- coord/usage.py +753 -0
- coord/usage_limits.py +358 -0
- coord/usage_rollup.py +709 -0
- coord/worker_events.py +954 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive breakpoints for the app shell (#1547).
|
|
3
|
+
*
|
|
4
|
+
* Three modes, not two — the story asks explicitly that mid widths "degrade
|
|
5
|
+
* sensibly rather than snapping between two extremes":
|
|
6
|
+
*
|
|
7
|
+
* narrow (< 768px) the phone app, preserved verbatim: one column,
|
|
8
|
+
* bottom nav, drill-in navigation.
|
|
9
|
+
* medium (768–1023px) rail collapses to its 60px icon strip, the list
|
|
10
|
+
* keeps the whole content column, and the detail
|
|
11
|
+
* arrives as an overlay above it (still a drill-in,
|
|
12
|
+
* just without losing the list underneath).
|
|
13
|
+
* wide (>= 1024px) the `SidebarView` twin: rail + list + detail, all
|
|
14
|
+
* three visible at once.
|
|
15
|
+
*
|
|
16
|
+
* The story's stated wide threshold is 1024px and the Gate-A mock
|
|
17
|
+
* (docs/mocks/web/pipeline-wide.html) switches to its bottom-row layout at
|
|
18
|
+
* 900px. Those aren't in conflict once there are three modes: 1024 is where
|
|
19
|
+
* the third column appears, and the phone layout is kept for genuinely
|
|
20
|
+
* phone-sized viewports (< 768) rather than for every tablet.
|
|
21
|
+
*
|
|
22
|
+
* Default when `matchMedia` is unavailable (jsdom, SSR) is `narrow` — the
|
|
23
|
+
* conservative choice, since that's the layout that is in daily use and the
|
|
24
|
+
* one every pre-existing component test was written against.
|
|
25
|
+
*/
|
|
26
|
+
import { useEffect, useState } from 'react'
|
|
27
|
+
|
|
28
|
+
export const BREAKPOINT_MEDIUM_PX = 768
|
|
29
|
+
export const BREAKPOINT_WIDE_PX = 1024
|
|
30
|
+
|
|
31
|
+
export type ShellMode = 'narrow' | 'medium' | 'wide'
|
|
32
|
+
|
|
33
|
+
const WIDE_QUERY = `(min-width: ${BREAKPOINT_WIDE_PX}px)`
|
|
34
|
+
const MEDIUM_QUERY = `(min-width: ${BREAKPOINT_MEDIUM_PX}px)`
|
|
35
|
+
|
|
36
|
+
function readMode(): ShellMode {
|
|
37
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
38
|
+
return 'narrow'
|
|
39
|
+
}
|
|
40
|
+
if (window.matchMedia(WIDE_QUERY).matches) return 'wide'
|
|
41
|
+
if (window.matchMedia(MEDIUM_QUERY).matches) return 'medium'
|
|
42
|
+
return 'narrow'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Current shell mode, recomputed whenever either breakpoint is crossed.
|
|
47
|
+
*
|
|
48
|
+
* The initial value is computed synchronously in the `useState` initialiser
|
|
49
|
+
* (not in an effect) so a wide browser's very first paint is already the wide
|
|
50
|
+
* layout — no one-frame flash of the phone app on a 32" monitor.
|
|
51
|
+
*/
|
|
52
|
+
export function useShellMode(): ShellMode {
|
|
53
|
+
const [mode, setMode] = useState<ShellMode>(readMode)
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return
|
|
57
|
+
|
|
58
|
+
const wide = window.matchMedia(WIDE_QUERY)
|
|
59
|
+
const medium = window.matchMedia(MEDIUM_QUERY)
|
|
60
|
+
const sync = () => setMode(readMode())
|
|
61
|
+
|
|
62
|
+
// Re-sync once on mount: between the render-time read above and this
|
|
63
|
+
// effect, the viewport may have changed (or matchMedia may have been
|
|
64
|
+
// stubbed in late, as the test setup does).
|
|
65
|
+
sync()
|
|
66
|
+
|
|
67
|
+
// `addListener`/`removeListener` are the deprecated MediaQueryList API;
|
|
68
|
+
// some of the older WebKit builds this PWA is installed on (iOS < 14)
|
|
69
|
+
// never got `addEventListener` on MediaQueryList, so fall back.
|
|
70
|
+
const subscribe = (mql: MediaQueryList) => {
|
|
71
|
+
if (typeof mql.addEventListener === 'function') {
|
|
72
|
+
mql.addEventListener('change', sync)
|
|
73
|
+
return () => mql.removeEventListener('change', sync)
|
|
74
|
+
}
|
|
75
|
+
if (typeof mql.addListener === 'function') {
|
|
76
|
+
mql.addListener(sync)
|
|
77
|
+
return () => mql.removeListener(sync)
|
|
78
|
+
}
|
|
79
|
+
return () => {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const unsubscribes = [subscribe(wide), subscribe(medium)]
|
|
83
|
+
return () => unsubscribes.forEach((fn) => fn())
|
|
84
|
+
}, [])
|
|
85
|
+
|
|
86
|
+
return mode
|
|
87
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The activity rail's contents (#1547).
|
|
3
|
+
*
|
|
4
|
+
* "Panels not yet built are visibly *coming*, not silently absent — the rail
|
|
5
|
+
* is the program's own progress bar." So this list is the whole planned M-W1
|
|
6
|
+
* surface, and each entry carries its own truth about whether it exists yet.
|
|
7
|
+
* A `'soon'` entry renders dimmed with a `soon` pill and is not activatable
|
|
8
|
+
* (the Gate-A mock, docs/mocks/web/pipeline-wide.html, gives them
|
|
9
|
+
* `opacity:.42` and `cursor:default`); flipping one to `'ready'` when its
|
|
10
|
+
* story lands is a one-line change here plus a case in `ShellLayout`.
|
|
11
|
+
*
|
|
12
|
+
* Grouping follows the mock exactly: an ungrouped top block, then Flow, then
|
|
13
|
+
* Insight, with Theme / Settings / Collapse pinned to the rail foot.
|
|
14
|
+
*/
|
|
15
|
+
import {
|
|
16
|
+
BarChart3,
|
|
17
|
+
GitMerge,
|
|
18
|
+
LayoutDashboard,
|
|
19
|
+
Milestone,
|
|
20
|
+
ScrollText,
|
|
21
|
+
Server,
|
|
22
|
+
SquareTerminal,
|
|
23
|
+
Terminal,
|
|
24
|
+
Workflow,
|
|
25
|
+
type LucideIcon,
|
|
26
|
+
} from 'lucide-react'
|
|
27
|
+
|
|
28
|
+
import type { ShellView } from './shellState'
|
|
29
|
+
|
|
30
|
+
export type RailItemStatus = 'ready' | 'soon'
|
|
31
|
+
|
|
32
|
+
export interface RailItem {
|
|
33
|
+
id: ShellView
|
|
34
|
+
label: string
|
|
35
|
+
icon: LucideIcon
|
|
36
|
+
status: RailItemStatus
|
|
37
|
+
/** Group heading this item sits under; `undefined` = the top block. */
|
|
38
|
+
group?: string
|
|
39
|
+
/** Shown on hover / in the `soon` pill's tooltip. */
|
|
40
|
+
hint?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const RAIL_ITEMS: readonly RailItem[] = [
|
|
44
|
+
{ id: 'pipeline', label: 'Pipeline', icon: Workflow, status: 'ready' },
|
|
45
|
+
{ id: 'board', label: 'Board', icon: LayoutDashboard, status: 'soon', hint: 'Board panel — M-W2' },
|
|
46
|
+
{ id: 'sessions', label: 'Sessions', icon: SquareTerminal, status: 'ready' },
|
|
47
|
+
{
|
|
48
|
+
id: 'terminal',
|
|
49
|
+
label: 'Terminal',
|
|
50
|
+
icon: Terminal,
|
|
51
|
+
status: 'soon',
|
|
52
|
+
hint: 'Standalone terminal panel — open one from Sessions for now',
|
|
53
|
+
},
|
|
54
|
+
{ id: 'machines', label: 'Machines', icon: Server, status: 'soon', hint: 'Machines panel — M-W2' },
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
id: 'merge-queue',
|
|
58
|
+
label: 'Merge queue',
|
|
59
|
+
icon: GitMerge,
|
|
60
|
+
status: 'soon',
|
|
61
|
+
group: 'Flow',
|
|
62
|
+
hint: 'Merge queue panel — M-W2',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'milestones',
|
|
66
|
+
label: 'Milestones',
|
|
67
|
+
icon: Milestone,
|
|
68
|
+
status: 'soon',
|
|
69
|
+
group: 'Flow',
|
|
70
|
+
hint: 'Milestone DAG — M-W3',
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
{
|
|
74
|
+
id: 'audit',
|
|
75
|
+
label: 'Audit',
|
|
76
|
+
icon: ScrollText,
|
|
77
|
+
status: 'soon',
|
|
78
|
+
group: 'Insight',
|
|
79
|
+
hint: 'Audit trail — milestone #33',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'spend',
|
|
83
|
+
label: 'Spend',
|
|
84
|
+
icon: BarChart3,
|
|
85
|
+
status: 'soon',
|
|
86
|
+
group: 'Insight',
|
|
87
|
+
hint: 'Spend & time observability — milestone #37',
|
|
88
|
+
},
|
|
89
|
+
] as const
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Rail items in render order, split into their heading groups. Computed once
|
|
93
|
+
* at module scope: `RAIL_ITEMS` is a constant, so recomputing per render would
|
|
94
|
+
* be pure waste and would break `React.memo` on any consumer.
|
|
95
|
+
*/
|
|
96
|
+
export const RAIL_GROUPS: ReadonlyArray<{ heading?: string; items: RailItem[] }> =
|
|
97
|
+
RAIL_ITEMS.reduce<Array<{ heading?: string; items: RailItem[] }>>((groups, item) => {
|
|
98
|
+
const last = groups[groups.length - 1]
|
|
99
|
+
if (last && last.heading === item.group) {
|
|
100
|
+
last.items.push(item)
|
|
101
|
+
} else {
|
|
102
|
+
groups.push({ heading: item.group, items: [item] })
|
|
103
|
+
}
|
|
104
|
+
return groups
|
|
105
|
+
}, [])
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted shell geometry (#1547, trimmed by #1548).
|
|
3
|
+
*
|
|
4
|
+
* "Panel sizes survive a reload" is an acceptance criterion, so geometry
|
|
5
|
+
* lives in one localStorage blob under a single key, written on every change
|
|
6
|
+
* and read once at mount. Reads and writes are individually guarded:
|
|
7
|
+
* `localStorage` throws outright in Safari private browsing and in lockdown
|
|
8
|
+
* modes, and a shell that refuses to render because it couldn't remember a
|
|
9
|
+
* panel width would be a worse bug than forgetting the width. Same posture
|
|
10
|
+
* (and same key prefix) as `ui/theme-provider.tsx`.
|
|
11
|
+
*
|
|
12
|
+
* The *selected view* used to live here too, decoupled from the URL on
|
|
13
|
+
* purpose because routing was explicitly the next story. #1548 is that
|
|
14
|
+
* story: the rail selection, the selected pipeline item and the selected
|
|
15
|
+
* session are now derived from the URL (see `src/routes/paths.ts`'s
|
|
16
|
+
* `shellViewFromPath`) so they are addressable, bookmarkable and survive a
|
|
17
|
+
* reload *because the URL does* — no separate persistence needed, and none
|
|
18
|
+
* left here to go stale against a bookmarked link. What's left is exactly
|
|
19
|
+
* the class of state the story says must *not* leak into the URL: panel
|
|
20
|
+
* geometry that has no meaning to anyone reading the link over your
|
|
21
|
+
* shoulder.
|
|
22
|
+
*/
|
|
23
|
+
import { useCallback, useState } from 'react'
|
|
24
|
+
|
|
25
|
+
export const SHELL_STORAGE_KEY = 'coord-web-shell'
|
|
26
|
+
|
|
27
|
+
/** Every entry the activity rail can show, built or not. */
|
|
28
|
+
export type ShellView =
|
|
29
|
+
| 'pipeline'
|
|
30
|
+
| 'board'
|
|
31
|
+
| 'sessions'
|
|
32
|
+
| 'terminal'
|
|
33
|
+
| 'machines'
|
|
34
|
+
| 'merge-queue'
|
|
35
|
+
| 'milestones'
|
|
36
|
+
| 'audit'
|
|
37
|
+
| 'spend'
|
|
38
|
+
| 'settings'
|
|
39
|
+
|
|
40
|
+
export const LIST_WIDTH_MIN_PX = 260
|
|
41
|
+
export const LIST_WIDTH_MAX_PX = 640
|
|
42
|
+
export const LIST_WIDTH_DEFAULT_PX = 360
|
|
43
|
+
|
|
44
|
+
export const RAIL_WIDTH_PX = 216
|
|
45
|
+
export const RAIL_WIDTH_COLLAPSED_PX = 60
|
|
46
|
+
|
|
47
|
+
export interface ShellState {
|
|
48
|
+
railCollapsed: boolean
|
|
49
|
+
listCollapsed: boolean
|
|
50
|
+
listWidthPx: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const DEFAULT_STATE: ShellState = {
|
|
54
|
+
railCollapsed: false,
|
|
55
|
+
listCollapsed: false,
|
|
56
|
+
listWidthPx: LIST_WIDTH_DEFAULT_PX,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function clampListWidth(px: number): number {
|
|
60
|
+
if (!Number.isFinite(px)) return LIST_WIDTH_DEFAULT_PX
|
|
61
|
+
return Math.min(LIST_WIDTH_MAX_PX, Math.max(LIST_WIDTH_MIN_PX, Math.round(px)))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse a persisted blob field-by-field rather than trusting its shape.
|
|
66
|
+
* The key is user-writable (devtools) and, more practically, will be read by
|
|
67
|
+
* future builds after this schema grows — an unknown or half-written value
|
|
68
|
+
* must degrade to the default, never to `NaN` grid columns.
|
|
69
|
+
*
|
|
70
|
+
* A blob written by a pre-#1548 build still has a `view` field in it — it's
|
|
71
|
+
* simply ignored below rather than special-cased, the same way any other
|
|
72
|
+
* stale/unknown key would be.
|
|
73
|
+
*/
|
|
74
|
+
export function parseShellState(raw: string | null): ShellState {
|
|
75
|
+
if (!raw) return DEFAULT_STATE
|
|
76
|
+
let parsed: unknown
|
|
77
|
+
try {
|
|
78
|
+
parsed = JSON.parse(raw)
|
|
79
|
+
} catch {
|
|
80
|
+
return DEFAULT_STATE
|
|
81
|
+
}
|
|
82
|
+
if (typeof parsed !== 'object' || parsed === null) return DEFAULT_STATE
|
|
83
|
+
const obj = parsed as Record<string, unknown>
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
railCollapsed:
|
|
87
|
+
typeof obj.railCollapsed === 'boolean' ? obj.railCollapsed : DEFAULT_STATE.railCollapsed,
|
|
88
|
+
listCollapsed:
|
|
89
|
+
typeof obj.listCollapsed === 'boolean' ? obj.listCollapsed : DEFAULT_STATE.listCollapsed,
|
|
90
|
+
listWidthPx:
|
|
91
|
+
typeof obj.listWidthPx === 'number'
|
|
92
|
+
? clampListWidth(obj.listWidthPx)
|
|
93
|
+
: DEFAULT_STATE.listWidthPx,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readShellState(): ShellState {
|
|
98
|
+
if (typeof window === 'undefined') return DEFAULT_STATE
|
|
99
|
+
try {
|
|
100
|
+
return parseShellState(window.localStorage.getItem(SHELL_STORAGE_KEY))
|
|
101
|
+
} catch {
|
|
102
|
+
return DEFAULT_STATE
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function writeShellState(state: ShellState): void {
|
|
107
|
+
if (typeof window === 'undefined') return
|
|
108
|
+
try {
|
|
109
|
+
window.localStorage.setItem(SHELL_STORAGE_KEY, JSON.stringify(state))
|
|
110
|
+
} catch {
|
|
111
|
+
// Persistence is a nice-to-have; the layout still applies this session.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface UseShellState extends ShellState {
|
|
116
|
+
setRailCollapsed: (collapsed: boolean) => void
|
|
117
|
+
toggleRail: () => void
|
|
118
|
+
setListCollapsed: (collapsed: boolean) => void
|
|
119
|
+
toggleList: () => void
|
|
120
|
+
setListWidthPx: (px: number) => void
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function useShellState(): UseShellState {
|
|
124
|
+
const [state, setState] = useState<ShellState>(readShellState)
|
|
125
|
+
|
|
126
|
+
const update = useCallback((patch: Partial<ShellState>) => {
|
|
127
|
+
setState((prev) => {
|
|
128
|
+
const next = { ...prev, ...patch }
|
|
129
|
+
// Cheap identity guard: a pointer-drag resize fires many times a second
|
|
130
|
+
// and would otherwise re-render (and re-serialise) on every no-op move.
|
|
131
|
+
if (
|
|
132
|
+
next.railCollapsed === prev.railCollapsed &&
|
|
133
|
+
next.listCollapsed === prev.listCollapsed &&
|
|
134
|
+
next.listWidthPx === prev.listWidthPx
|
|
135
|
+
) {
|
|
136
|
+
return prev
|
|
137
|
+
}
|
|
138
|
+
writeShellState(next)
|
|
139
|
+
return next
|
|
140
|
+
})
|
|
141
|
+
}, [])
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...state,
|
|
145
|
+
setRailCollapsed: useCallback(
|
|
146
|
+
(railCollapsed: boolean) => update({ railCollapsed }),
|
|
147
|
+
[update],
|
|
148
|
+
),
|
|
149
|
+
toggleRail: useCallback(
|
|
150
|
+
() => setState((prev) => {
|
|
151
|
+
const next = { ...prev, railCollapsed: !prev.railCollapsed }
|
|
152
|
+
writeShellState(next)
|
|
153
|
+
return next
|
|
154
|
+
}),
|
|
155
|
+
[],
|
|
156
|
+
),
|
|
157
|
+
setListCollapsed: useCallback(
|
|
158
|
+
(listCollapsed: boolean) => update({ listCollapsed }),
|
|
159
|
+
[update],
|
|
160
|
+
),
|
|
161
|
+
toggleList: useCallback(
|
|
162
|
+
() => setState((prev) => {
|
|
163
|
+
const next = { ...prev, listCollapsed: !prev.listCollapsed }
|
|
164
|
+
writeShellState(next)
|
|
165
|
+
return next
|
|
166
|
+
}),
|
|
167
|
+
[],
|
|
168
|
+
),
|
|
169
|
+
setListWidthPx: useCallback(
|
|
170
|
+
(px: number) => update({ listWidthPx: clampListWidth(px) }),
|
|
171
|
+
[update],
|
|
172
|
+
),
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard region cycling for the shell (#1547) — the web-idiomatic answer to
|
|
3
|
+
* coord-tui's `FocusedRegion` / Ctrl-W.
|
|
4
|
+
*
|
|
5
|
+
* The binding is **F6 / Shift+F6**, which is the long-standing platform
|
|
6
|
+
* convention for "move to the next pane" (Firefox and Chrome both use it to
|
|
7
|
+
* cycle browser panes, VS Code uses it for editor groups, and Windows uses it
|
|
8
|
+
* app-wide). It is also, unusually for a function key, safe: no browser
|
|
9
|
+
* assigns F6 to anything that a web page stealing it would break, and it
|
|
10
|
+
* doesn't collide with a text field the way a Ctrl-letter chord does — so the
|
|
11
|
+
* handler doesn't need to special-case typing in the list panel's filter box.
|
|
12
|
+
*
|
|
13
|
+
* Focus lands on the *region container* (each is `tabIndex={-1}` with an
|
|
14
|
+
* accessible name), not on the first control inside it. That's the standard
|
|
15
|
+
* landmark-navigation behaviour: a screen reader announces the region you
|
|
16
|
+
* arrived in, and a plain Tab from there walks into its controls.
|
|
17
|
+
*/
|
|
18
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
19
|
+
|
|
20
|
+
export type ShellRegion = 'rail' | 'list' | 'detail'
|
|
21
|
+
|
|
22
|
+
export interface UseRegionFocus {
|
|
23
|
+
/** The region focus was last moved to via F6, for a visible ring. */
|
|
24
|
+
focusedRegion: ShellRegion | null
|
|
25
|
+
/** Ref callback factory: `ref={registerRegion('list')}`. */
|
|
26
|
+
registerRegion: (region: ShellRegion) => (el: HTMLElement | null) => void
|
|
27
|
+
/** Programmatic move (used by the resize handle's Escape, and by tests). */
|
|
28
|
+
focusRegion: (region: ShellRegion) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param visibleRegions Regions currently on screen, in visual order. Regions
|
|
33
|
+
* omitted here are skipped by F6 — on narrow only one of list/detail exists
|
|
34
|
+
* at a time, and cycling into a `display:none` container would silently drop
|
|
35
|
+
* focus to `<body>`.
|
|
36
|
+
*/
|
|
37
|
+
export function useRegionFocus(visibleRegions: ShellRegion[]): UseRegionFocus {
|
|
38
|
+
const elements = useRef<Partial<Record<ShellRegion, HTMLElement | null>>>({})
|
|
39
|
+
const [focusedRegion, setFocusedRegion] = useState<ShellRegion | null>(null)
|
|
40
|
+
|
|
41
|
+
// The keydown listener is attached once; reading the order through a ref
|
|
42
|
+
// keeps it from being torn down and rebuilt on every layout change.
|
|
43
|
+
const orderRef = useRef(visibleRegions)
|
|
44
|
+
orderRef.current = visibleRegions
|
|
45
|
+
|
|
46
|
+
const registerRegion = useCallback(
|
|
47
|
+
(region: ShellRegion) => (el: HTMLElement | null) => {
|
|
48
|
+
elements.current[region] = el
|
|
49
|
+
},
|
|
50
|
+
[],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const focusRegion = useCallback((region: ShellRegion) => {
|
|
54
|
+
const el = elements.current[region]
|
|
55
|
+
if (!el) return
|
|
56
|
+
el.focus()
|
|
57
|
+
setFocusedRegion(region)
|
|
58
|
+
}, [])
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
62
|
+
if (event.key !== 'F6') return
|
|
63
|
+
const order = orderRef.current
|
|
64
|
+
if (order.length === 0) return
|
|
65
|
+
|
|
66
|
+
// Where are we now? Prefer the region that actually contains
|
|
67
|
+
// `document.activeElement` over the last F6 target, so an F6 after a
|
|
68
|
+
// mouse click continues from where the user is rather than from where
|
|
69
|
+
// the keyboard last was.
|
|
70
|
+
const active = document.activeElement as HTMLElement | null
|
|
71
|
+
let currentIndex = order.findIndex((region) => {
|
|
72
|
+
const el = elements.current[region]
|
|
73
|
+
return !!el && !!active && el.contains(active)
|
|
74
|
+
})
|
|
75
|
+
if (currentIndex === -1) currentIndex = order.indexOf(focusedRegion as ShellRegion)
|
|
76
|
+
|
|
77
|
+
const step = event.shiftKey ? -1 : 1
|
|
78
|
+
// `currentIndex === -1` (focus is outside every region) + step 1 lands
|
|
79
|
+
// on index 0, which is the intended "enter the shell at the rail".
|
|
80
|
+
const nextIndex = (currentIndex + step + order.length) % order.length
|
|
81
|
+
const next = order[nextIndex]
|
|
82
|
+
const el = elements.current[next]
|
|
83
|
+
if (!el) return
|
|
84
|
+
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
el.focus()
|
|
87
|
+
setFocusedRegion(next)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
window.addEventListener('keydown', onKeyDown)
|
|
91
|
+
return () => window.removeEventListener('keydown', onKeyDown)
|
|
92
|
+
}, [focusedRegion])
|
|
93
|
+
|
|
94
|
+
return { focusedRegion, registerRegion, focusRegion }
|
|
95
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
|
|
3
|
+
// jsdom doesn't implement matchMedia (https://github.com/jsdom/jsdom/issues/3522).
|
|
4
|
+
// xterm.js's CoreBrowserService queries it (devicePixelRatio change listener) as
|
|
5
|
+
// soon as a Terminal is opened, which crashes every Terminal-pane test (#1068)
|
|
6
|
+
// without this stub.
|
|
7
|
+
if (typeof window !== 'undefined' && !window.matchMedia) {
|
|
8
|
+
window.matchMedia = (query: string) => ({
|
|
9
|
+
matches: false,
|
|
10
|
+
media: query,
|
|
11
|
+
onchange: null,
|
|
12
|
+
addListener: () => {},
|
|
13
|
+
removeListener: () => {},
|
|
14
|
+
addEventListener: () => {},
|
|
15
|
+
removeEventListener: () => {},
|
|
16
|
+
dispatchEvent: () => false,
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// jsdom's canvas is stubbed (no `canvas` npm package installed) -- getContext('2d')
|
|
21
|
+
// logs a noisy "Not implemented" jsdom error and returns undefined either way.
|
|
22
|
+
// xterm.js already handles a missing 2D context gracefully (its color helper
|
|
23
|
+
// falls back to manual CSS-color parsing), so short-circuit straight to that
|
|
24
|
+
// path and skip the console noise.
|
|
25
|
+
if (typeof HTMLCanvasElement !== 'undefined') {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- matching the DOM lib's overload surface isn't worth it for a test-only stub
|
|
27
|
+
HTMLCanvasElement.prototype.getContext = (() => null) as any
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// jsdom doesn't implement ResizeObserver (https://github.com/jsdom/jsdom/issues/3368).
|
|
31
|
+
// Terminal.tsx uses it to watch the pane container for layout shifts that
|
|
32
|
+
// window.resize misses (e.g. MobileKeyBar height changes, safe-area adjustments).
|
|
33
|
+
// A no-op stub prevents "ResizeObserver is not defined" crashes in every
|
|
34
|
+
// Terminal test while preserving real behaviour in a real browser.
|
|
35
|
+
if (typeof globalThis.ResizeObserver === 'undefined') {
|
|
36
|
+
globalThis.ResizeObserver = class ResizeObserver {
|
|
37
|
+
observe() {}
|
|
38
|
+
unobserve() {}
|
|
39
|
+
disconnect() {}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import animate from 'tailwindcss-animate'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tailwind config for coord web's design-token layer (#1546).
|
|
5
|
+
*
|
|
6
|
+
* Colour values are plain CSS custom properties (hex / rgba), NOT the
|
|
7
|
+
* `hsl(var(--x))` triplets shadcn/ui's default template uses -- the token
|
|
8
|
+
* seed this builds on (docs/mocks/web/_tokens.css) is authored in hex, and
|
|
9
|
+
* re-deriving HSL triplets from it would be one more place for the real
|
|
10
|
+
* theme to drift from the settled mocks. `var(--x)` works as a Tailwind
|
|
11
|
+
* colour value exactly like `hsl(var(--x))` does; only the storage format
|
|
12
|
+
* differs.
|
|
13
|
+
*
|
|
14
|
+
* Two colour vocabularies coexist deliberately:
|
|
15
|
+
* - the shadcn semantic slots (background/foreground/card/primary/...)
|
|
16
|
+
* that ui/* primitives and existing components (PipelineCard,
|
|
17
|
+
* ConnectionBadge, MobileKeyBar) already consume, so migrating the token
|
|
18
|
+
* values underneath them is a one-file change;
|
|
19
|
+
* - the raw mock palette (ground/surface/line/brand/pass/attn/fail/idle/
|
|
20
|
+
* faint) exposed under its own names for new primitives and the gallery,
|
|
21
|
+
* matching docs/mocks/web/_tokens.css exactly.
|
|
22
|
+
*
|
|
23
|
+
* `accent` is kept pointed at the neutral hover/press surface (its existing
|
|
24
|
+
* meaning -- see MobileKeyBar's `active:bg-accent`) rather than repointed at
|
|
25
|
+
* the mock's cyan "work is happening" colour, which would have silently
|
|
26
|
+
* recoloured every existing pressed-state highlight. The cyan lives under
|
|
27
|
+
* `brand` instead (`--accent` the CSS custom property, to match the mock's
|
|
28
|
+
* own naming) and drives `ring`/`primary`, matching the mocks' focus and
|
|
29
|
+
* primary-button treatment.
|
|
30
|
+
*/
|
|
31
|
+
export default {
|
|
32
|
+
darkMode: ['selector', '[data-theme="dark"]'],
|
|
33
|
+
content: ['./index.html', './src/**/*.{ts,tsx,js,jsx}'],
|
|
34
|
+
theme: {
|
|
35
|
+
container: {
|
|
36
|
+
center: true,
|
|
37
|
+
padding: '2rem',
|
|
38
|
+
},
|
|
39
|
+
extend: {
|
|
40
|
+
colors: {
|
|
41
|
+
border: 'var(--border)',
|
|
42
|
+
input: 'var(--input)',
|
|
43
|
+
ring: 'var(--ring)',
|
|
44
|
+
background: 'var(--background)',
|
|
45
|
+
foreground: 'var(--foreground)',
|
|
46
|
+
primary: {
|
|
47
|
+
DEFAULT: 'var(--primary)',
|
|
48
|
+
foreground: 'var(--primary-foreground)',
|
|
49
|
+
},
|
|
50
|
+
secondary: {
|
|
51
|
+
DEFAULT: 'var(--secondary)',
|
|
52
|
+
foreground: 'var(--secondary-foreground)',
|
|
53
|
+
},
|
|
54
|
+
destructive: {
|
|
55
|
+
DEFAULT: 'var(--destructive)',
|
|
56
|
+
foreground: 'var(--destructive-foreground)',
|
|
57
|
+
},
|
|
58
|
+
muted: {
|
|
59
|
+
DEFAULT: 'var(--muted)',
|
|
60
|
+
foreground: 'var(--muted-foreground)',
|
|
61
|
+
},
|
|
62
|
+
accent: {
|
|
63
|
+
DEFAULT: 'var(--accent-surface)',
|
|
64
|
+
foreground: 'var(--accent-surface-foreground)',
|
|
65
|
+
},
|
|
66
|
+
card: {
|
|
67
|
+
DEFAULT: 'var(--card)',
|
|
68
|
+
foreground: 'var(--card-foreground)',
|
|
69
|
+
},
|
|
70
|
+
popover: {
|
|
71
|
+
DEFAULT: 'var(--popover)',
|
|
72
|
+
foreground: 'var(--popover-foreground)',
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// ── raw mock palette (docs/mocks/web/_tokens.css) ────────────────
|
|
76
|
+
ground: 'var(--ground)',
|
|
77
|
+
surface: {
|
|
78
|
+
DEFAULT: 'var(--surface)',
|
|
79
|
+
2: 'var(--surface-2)',
|
|
80
|
+
3: 'var(--surface-3)',
|
|
81
|
+
},
|
|
82
|
+
line: {
|
|
83
|
+
DEFAULT: 'var(--line)',
|
|
84
|
+
strong: 'var(--line-strong)',
|
|
85
|
+
},
|
|
86
|
+
faint: 'var(--text-faint)',
|
|
87
|
+
brand: {
|
|
88
|
+
DEFAULT: 'var(--accent)',
|
|
89
|
+
dim: 'var(--accent-dim)',
|
|
90
|
+
wash: 'var(--accent-wash)',
|
|
91
|
+
},
|
|
92
|
+
pass: {
|
|
93
|
+
DEFAULT: 'var(--pass)',
|
|
94
|
+
wash: 'var(--pass-wash)',
|
|
95
|
+
},
|
|
96
|
+
attn: {
|
|
97
|
+
DEFAULT: 'var(--attn)',
|
|
98
|
+
wash: 'var(--attn-wash)',
|
|
99
|
+
},
|
|
100
|
+
fail: {
|
|
101
|
+
DEFAULT: 'var(--fail)',
|
|
102
|
+
wash: 'var(--fail-wash)',
|
|
103
|
+
},
|
|
104
|
+
idle: {
|
|
105
|
+
DEFAULT: 'var(--idle)',
|
|
106
|
+
wash: 'var(--idle-wash)',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
fontFamily: {
|
|
110
|
+
sans: ['var(--font-ui)'],
|
|
111
|
+
mono: ['var(--font-mono)'],
|
|
112
|
+
},
|
|
113
|
+
fontSize: {
|
|
114
|
+
'step--1': 'var(--step--1)',
|
|
115
|
+
'step-0': 'var(--step-0)',
|
|
116
|
+
'step-1': 'var(--step-1)',
|
|
117
|
+
'step-2': 'var(--step-2)',
|
|
118
|
+
'step-3': 'var(--step-3)',
|
|
119
|
+
},
|
|
120
|
+
borderRadius: {
|
|
121
|
+
sm: 'var(--r-sm)',
|
|
122
|
+
md: 'var(--r-md)',
|
|
123
|
+
lg: 'var(--r-lg)',
|
|
124
|
+
},
|
|
125
|
+
boxShadow: {
|
|
126
|
+
elevation: '0 18px 44px -12px rgba(0,0,0,.66)',
|
|
127
|
+
},
|
|
128
|
+
keyframes: {
|
|
129
|
+
'pulse-ring': {
|
|
130
|
+
'0%, 100%': { opacity: 1, boxShadow: '0 0 0 0 var(--accent-wash)' },
|
|
131
|
+
'50%': { opacity: 0.55, boxShadow: '0 0 0 6px transparent' },
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
animation: {
|
|
135
|
+
'pulse-ring': 'pulse-ring 2s ease-in-out infinite',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
plugins: [animate],
|
|
140
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"baseUrl": ".",
|
|
19
|
+
"paths": {
|
|
20
|
+
"@/*": ["./src/*"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": ["src"],
|
|
24
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
25
|
+
}
|