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,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whole-app 404 (#1548) — what a URL that matches no route in the tree at
|
|
3
|
+
* all renders, e.g. a typo'd path or a link to a feature that was removed.
|
|
4
|
+
*
|
|
5
|
+
* Distinct from `Detail`'s own not-found state (a syntactically valid
|
|
6
|
+
* `/pipeline/:repo/:issue` whose issue isn't in today's pipeline — a *stale*
|
|
7
|
+
* link, not an *unknown* one): that one already exists per-item and knows
|
|
8
|
+
* the repo/issue it failed to find. This one is the catch-all `*` route
|
|
9
|
+
* inside `ShellLayout`, so it still renders inside the shell (rail, status
|
|
10
|
+
* bar) rather than a blank document — "a real not-found state, not a blank
|
|
11
|
+
* panel or a crash" per the story's acceptance criteria.
|
|
12
|
+
*/
|
|
13
|
+
import { Compass } from 'lucide-react'
|
|
14
|
+
import { Link } from 'react-router-dom'
|
|
15
|
+
import { paths } from '@/routes/paths'
|
|
16
|
+
|
|
17
|
+
export function RouteNotFound() {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 py-12 text-center">
|
|
20
|
+
<Compass className="h-7 w-7 text-faint" aria-hidden="true" />
|
|
21
|
+
<p className="text-sm font-medium text-foreground">Page not found</p>
|
|
22
|
+
<p className="max-w-xs text-xs text-muted-foreground">
|
|
23
|
+
There's nothing at this address.
|
|
24
|
+
</p>
|
|
25
|
+
<Link
|
|
26
|
+
to={paths.pipeline()}
|
|
27
|
+
className="mt-1 rounded-full border border-border px-3 py-1 text-xs text-muted-foreground hover:text-foreground"
|
|
28
|
+
>
|
|
29
|
+
Go to Pipeline
|
|
30
|
+
</Link>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShellLayout (#1547, routed by #1548) — the react-router *layout route*
|
|
3
|
+
* that composes the shell, and the single place that decides what goes in
|
|
4
|
+
* which slot.
|
|
5
|
+
*
|
|
6
|
+
* The composition is deliberately boring, because that is the acceptance
|
|
7
|
+
* criterion: there is no `isMobile ? <PhoneHome/> : <DesktopHome/>`. `list`
|
|
8
|
+
* and `detail` are computed once and handed to `AppShell` unchanged; the only
|
|
9
|
+
* thing `mode` decides is where they land and which of them is mounted.
|
|
10
|
+
*
|
|
11
|
+
* The detail slot is `<Outlet/>` — so the child route (`/pipeline` ->
|
|
12
|
+
* `EmptyDetail`, `/pipeline/:repo/:issue[/:tab]` -> `Detail`,
|
|
13
|
+
* `/sessions/:id` -> `SessionDetail`, an unbuilt panel's own path ->
|
|
14
|
+
* `ComingSoon`, anything else -> `RouteNotFound`) fills it.
|
|
15
|
+
*
|
|
16
|
+
* The rail selection is derived from the URL (`shellViewFromPath`), not from
|
|
17
|
+
* persisted state: that was the explicit deferral #1547 made ("routing and
|
|
18
|
+
* deep links are the next story"), and this *is* that story. A URL is now
|
|
19
|
+
* the single source of truth for "what view am I looking at" — reload,
|
|
20
|
+
* bookmark, paste into Slack, browser back — all of it falls out of normal
|
|
21
|
+
* router behaviour instead of a second, URL-independent state machine that
|
|
22
|
+
* could disagree with the address bar.
|
|
23
|
+
*/
|
|
24
|
+
import { Outlet, useLocation, useMatch, useNavigate } from 'react-router-dom'
|
|
25
|
+
import { useCallback, type ReactNode } from 'react'
|
|
26
|
+
import { useQuery } from '@tanstack/react-query'
|
|
27
|
+
|
|
28
|
+
import Home from '@/components/Home'
|
|
29
|
+
import SessionsList from '@/components/SessionsList'
|
|
30
|
+
import { fetchPipeline, fetchSessions } from '@/api/client'
|
|
31
|
+
import { isActive, needsMe } from '@/lib/pipeline'
|
|
32
|
+
import { RAIL_VIEW_PATH, shellViewFromPath } from '@/routes/paths'
|
|
33
|
+
import { AppShell } from './AppShell'
|
|
34
|
+
import { ActivityRail } from './ActivityRail'
|
|
35
|
+
import { ComingSoon } from './ComingSoon'
|
|
36
|
+
import { RouteNotFound } from './RouteNotFound'
|
|
37
|
+
import { StatusBar } from './StatusBar'
|
|
38
|
+
import { useShellMode } from './breakpoints'
|
|
39
|
+
import { useShellState, type ShellView } from './shellState'
|
|
40
|
+
import { useRegionFocus, type ShellRegion } from './useRegionFocus'
|
|
41
|
+
|
|
42
|
+
const ATTENTION_VIEWS: ReadonlySet<ShellView> = new Set<ShellView>(['pipeline'])
|
|
43
|
+
|
|
44
|
+
export function ShellLayout() {
|
|
45
|
+
const mode = useShellMode()
|
|
46
|
+
const shell = useShellState()
|
|
47
|
+
const navigate = useNavigate()
|
|
48
|
+
const location = useLocation()
|
|
49
|
+
|
|
50
|
+
// Which rail entry the current URL belongs to — `null` for a path with no
|
|
51
|
+
// owning view (an unmatched route; root `/` never reaches here, see
|
|
52
|
+
// `App.tsx`'s redirect).
|
|
53
|
+
const currentView = shellViewFromPath(location.pathname)
|
|
54
|
+
|
|
55
|
+
// The detail slot is "active" (narrow: shown instead of the list; medium:
|
|
56
|
+
// overlaid on it) exactly when the route names a specific item — a
|
|
57
|
+
// pipeline issue (with or without a tab segment) or a session. Matched
|
|
58
|
+
// explicitly rather than inferred from `currentView`, because "there is an
|
|
59
|
+
// Outlet route that fills the detail slot" and "which rail item is lit up"
|
|
60
|
+
// are genuinely different questions once there's more than one detail-
|
|
61
|
+
// capable list.
|
|
62
|
+
const pipelineItemMatch = useMatch('/pipeline/:repo/:issue')
|
|
63
|
+
const pipelineItemTabMatch = useMatch('/pipeline/:repo/:issue/:tab')
|
|
64
|
+
const sessionItemMatch = useMatch('/sessions/:id')
|
|
65
|
+
const detailActive = !!(pipelineItemMatch || pipelineItemTabMatch || sessionItemMatch)
|
|
66
|
+
|
|
67
|
+
// Same query keys the panels use, so this is a cache read, not a second
|
|
68
|
+
// fetch (see main.tsx: staleTime Infinity + SSE-driven invalidation).
|
|
69
|
+
const { data: pipeline } = useQuery({ queryKey: ['pipeline'], queryFn: fetchPipeline })
|
|
70
|
+
const { data: sessions } = useQuery({ queryKey: ['sessions'], queryFn: fetchSessions })
|
|
71
|
+
|
|
72
|
+
const inFlight = pipeline?.filter(isActive).length
|
|
73
|
+
const attention = pipeline?.some(needsMe) ?? false
|
|
74
|
+
// `machine` is nullable on SessionInfo (a session on a host the roster
|
|
75
|
+
// hasn't resolved yet) — a `null` in the status bar would render as an empty
|
|
76
|
+
// segment between two separators.
|
|
77
|
+
const machines = sessions
|
|
78
|
+
? Array.from(new Set(sessions.map((s) => s.machine).filter((m): m is string => !!m))).sort()
|
|
79
|
+
: []
|
|
80
|
+
|
|
81
|
+
// Kept in step with AppShell's own `showList` / `showDetail` — F6 must not
|
|
82
|
+
// offer a region that isn't mounted.
|
|
83
|
+
const listMounted = mode === 'narrow' ? !detailActive : !(mode === 'wide' && shell.listCollapsed)
|
|
84
|
+
const detailMounted = mode === 'wide' || detailActive
|
|
85
|
+
|
|
86
|
+
// F6 must skip regions that aren't on screen — cycling into an unmounted
|
|
87
|
+
// container would silently drop focus to <body>.
|
|
88
|
+
const visibleRegions: ShellRegion[] = [
|
|
89
|
+
'rail' as const,
|
|
90
|
+
...(listMounted ? (['list'] as const) : []),
|
|
91
|
+
...(detailMounted ? (['detail'] as const) : []),
|
|
92
|
+
]
|
|
93
|
+
const { focusedRegion, registerRegion } = useRegionFocus(visibleRegions)
|
|
94
|
+
|
|
95
|
+
const handleSelectView = useCallback(
|
|
96
|
+
(view: ShellView) => {
|
|
97
|
+
const path = RAIL_VIEW_PATH[view]
|
|
98
|
+
// Every 'ready' rail entry has a path; a 'soon' entry never reaches
|
|
99
|
+
// here (ActivityRail doesn't wire onClick for it). The guard is for
|
|
100
|
+
// type honesty (`RAIL_VIEW_PATH` is a `Partial`), not a real runtime case.
|
|
101
|
+
if (path) navigate(path)
|
|
102
|
+
},
|
|
103
|
+
[navigate],
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
let list: ReactNode
|
|
107
|
+
if (currentView === 'pipeline') {
|
|
108
|
+
list = <Home />
|
|
109
|
+
} else if (currentView === 'sessions') {
|
|
110
|
+
list = <SessionsList />
|
|
111
|
+
} else if (currentView === null) {
|
|
112
|
+
list = <RouteNotFound />
|
|
113
|
+
} else {
|
|
114
|
+
list = <ComingSoon view={currentView} />
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<AppShell
|
|
119
|
+
mode={mode}
|
|
120
|
+
railCollapsed={shell.railCollapsed}
|
|
121
|
+
listCollapsed={shell.listCollapsed}
|
|
122
|
+
listWidthPx={shell.listWidthPx}
|
|
123
|
+
onListWidthChange={shell.setListWidthPx}
|
|
124
|
+
detailActive={detailActive}
|
|
125
|
+
registerRegion={registerRegion}
|
|
126
|
+
focusedRegion={focusedRegion}
|
|
127
|
+
rail={
|
|
128
|
+
<ActivityRail
|
|
129
|
+
mode={mode}
|
|
130
|
+
collapsed={shell.railCollapsed}
|
|
131
|
+
view={currentView}
|
|
132
|
+
onSelect={handleSelectView}
|
|
133
|
+
onToggleCollapsed={shell.toggleRail}
|
|
134
|
+
listCollapsed={shell.listCollapsed}
|
|
135
|
+
onToggleList={shell.toggleList}
|
|
136
|
+
counts={{ pipeline: inFlight, sessions: sessions?.length }}
|
|
137
|
+
attentionViews={attention ? ATTENTION_VIEWS : undefined}
|
|
138
|
+
regionRef={registerRegion('rail')}
|
|
139
|
+
regionFocused={focusedRegion === 'rail'}
|
|
140
|
+
/>
|
|
141
|
+
}
|
|
142
|
+
list={list}
|
|
143
|
+
detail={<Outlet />}
|
|
144
|
+
status={<StatusBar inFlight={inFlight} sessions={sessions?.length} machines={machines} />}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell status bar (#1547) — the mock's `.statusbar`, promoted from inside
|
|
3
|
+
* `.main` to a full-width foot row so it is one element at every breakpoint
|
|
4
|
+
* instead of appearing and disappearing with the detail column.
|
|
5
|
+
*
|
|
6
|
+
* It is also now the app's *only* `ConnectionBadge`. It used to be rendered
|
|
7
|
+
* once per screen (Home and Detail each had their own); with wide showing both
|
|
8
|
+
* at once that would be two live-region `role="status"` nodes saying the same
|
|
9
|
+
* thing, which is noise for a screen reader and an ambiguous locator for a
|
|
10
|
+
* test.
|
|
11
|
+
*
|
|
12
|
+
* Everything else here is mono, because everything else here is a value the
|
|
13
|
+
* machine owns. Nothing is invented: counts come from the same React-Query
|
|
14
|
+
* caches the panels read, and a figure with no data source (the mock's
|
|
15
|
+
* "$14.82 today") is simply absent until Spend lands.
|
|
16
|
+
*/
|
|
17
|
+
import { ConnectionBadge } from '@/components/ConnectionBadge'
|
|
18
|
+
|
|
19
|
+
export interface StatusBarProps {
|
|
20
|
+
/** Pipeline items not yet merged. */
|
|
21
|
+
inFlight?: number
|
|
22
|
+
/** Live interactive sessions. */
|
|
23
|
+
sessions?: number
|
|
24
|
+
/** Distinct machines those sessions are on. */
|
|
25
|
+
machines?: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function StatusBar({ inFlight, sessions, machines }: StatusBarProps) {
|
|
29
|
+
return (
|
|
30
|
+
<footer
|
|
31
|
+
aria-label="Status"
|
|
32
|
+
className="flex items-center gap-4 overflow-x-auto whitespace-nowrap border-t border-border bg-surface px-3 py-1.5 font-mono text-[.69rem] text-faint md:px-5"
|
|
33
|
+
>
|
|
34
|
+
<ConnectionBadge />
|
|
35
|
+
{inFlight != null && <span>{inFlight} in flight</span>}
|
|
36
|
+
{sessions != null && (
|
|
37
|
+
<span>
|
|
38
|
+
{sessions} {sessions === 1 ? 'session' : 'sessions'}
|
|
39
|
+
</span>
|
|
40
|
+
)}
|
|
41
|
+
{machines && machines.length > 0 && (
|
|
42
|
+
<span className="hidden md:inline">{machines.join(' · ')}</span>
|
|
43
|
+
)}
|
|
44
|
+
</footer>
|
|
45
|
+
)
|
|
46
|
+
}
|