feat(cockpit): browser UI — split cockpit (#354) - #369
Merged
Conversation
Build the Cockpit v2 browser UI (Variant C, ADR-0008) served by the #351–#353 FastAPI backend on 127.0.0.1, against the committed visual spec (docs/design/2026-08-03-cockpit-ui.md). - FastAPI serves the UI: `GET /` returns index.html with the per-session capability token injected (no token-leaking GET endpoint); CSS/JS/vendored assets from a `/static` StaticFiles mount — same-origin, behind the #352 loopback guard. - Variant C: a persistent fleet sidebar (per-repo runner health with non-color-only status glyph+label, inline start/stop/restart + a lock-glyph re-provision for orphans) beside a main pane toggling Usage / Terminal / Logs, Terminal default. mis-target is data-driven (mirrors provision._is_mistargeted: running + known repo + 0 online). - Live data: /api/fleet, /api/usage (token chart + by-model breakdown), /api/logs; mutations carry X-Forge-Session; failures surface an inline error state. The 5s fleet poll is signature-guarded so it never rebuilds the DOM (or steals focus) when nothing changed. - Real xterm.js terminal over the /api/terminal websocket (token as query param), fit addon + resize messages. xterm.js 5.3.0 + fit 0.8.0 vendored (MIT) with a NOTICE; the license gate stays zero-exception. - Responsive at 375/768/1280 + the a11y contract (roles/labels, roving tablist, >=24px targets, non-color-only status, reduced-motion), dark-only, smithy tokens only. - Tests: pytest TestClient covers static serving + token injection + the key regions + the ws token gate; vitest covers the pure format/status/controls helpers. Design-reviewer: pass (all critical/major findings resolved; #368 filed for the two pre-existing raw-hex nits carried from the mockup). Closes #354 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SATRHKa6mDHDuirhP6QuwL
The minified MIT xterm.js UMD bundle vendored for the cockpit UI trips the generic-api-key rule on ordinary upstream identifiers (`t.FourKeyMap=t.TwoKeyMap` — the "Key" keyword beside a token-shaped run), a pure false positive in third-party library code. Add a narrowly-scoped, anchored path allowlist for the `forge_cockpit/web/vendor/` dir only, so first-party cockpit source stays fully scanned. Full-history `gitleaks detect --log-opts=--all` is clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SATRHKa6mDHDuirhP6QuwL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cockpit v2: browser UI — split cockpit (Variant C)
Builds the browser front-end (ADR-0008, epic #350) on the #351–#353 FastAPI cores, served on
127.0.0.1behind the #352 loopback guard, against the committed visual specdocs/design/2026-08-03-cockpit-ui.md(owner pick: Variant C,docs/design/variants-354/variant-c.html).Closes #354What landed
server.py):GET /returnsindex.htmlwith the per-session capability token injected in place of__FORGE_SESSION_TOKEN__(no token-leaking GET endpoint);app.css/app.mjs/format.mjs/ vendored xterm.js served from a/staticStaticFiles mount — same-origin, same guard.forge_cockpit/web/): persistent fleet sidebar + main pane toggling Usage / Terminal / Logs, Terminal default.web/vendor/withNOTICE.md) over the/api/terminalwebsocket (token as query param), fit addon + resize messages.Acceptance criteria
/; Variant C: persistent fleet sidebar (per-repo runner/container health, non-color-only status glyph+label, inline start/stop/restart controls) + main pane toggling Usage (cost/token chart) / Terminal / Logs, Terminal default./api/fleet,/api/usage,/api/logs; mutating controls (start/stop/restart, re-provision) sendX-Forge-Session; a failed/tokenless action surfaces an inlinerole=alerterror state (neveralert())./api/terminalwebsocket; typing works (xterm.js owns emulation — the Cockpit — embedded terminal (pywinpty ConPTY + WSL tab) #275 fix). xterm.js pinned + vendored MIT, recorded inweb/vendor/NOTICE.md;node plugin/scripts/gates/license.mjsstays zero-exception.Verification (this branch)
pnpm verify— 710 passed / 59 files (incl. the newtests/cockpit/format.test.mjs, 18 cases).uv run pytest -q(tools/runner-ui, Windows) — 186 passed, 1 skipped (POSIX-only pty leg). Newtests/test_server_ui.py: 10 passed.node plugin/scripts/gates/license.mjs— clean, zero exceptions ("plugin declares MIT; all npm dependency licenses within the allowlist (9 ids); 7 Python dep(s), 0 documented exception(s)"). xterm.js is a vendored web asset (neither an npm nor a Python dep the gate scans) — recorded inweb/vendor/NOTICE.md.GET /serves the shell with the token injected and all key regions;/static/app.mjsserved astext/javascript; a mismatched-port request is correctly 403'd by the loopback guard.forge:design-revieweragainst the spec): pass. First pass flagged a critical (mis-target classification) + majors (missing re-provision control, hardcoded error text, focus-stealing poll) — all fixed and re-verified: mis-target is now data-driven (mirrorsprovision._is_mistargeted: running + known repo + 0 online), the lock-glyph re-provision control routes to/api/provision, the error line is dynamic, and the fleet poll is signature-guarded so it never rebuilds the DOM / steals focus when nothing changed.Follow-up
#0d0b09terminal ground,#f0813fchart gradient stop) to smithy tokens; both are verbatim from the approved mockup and non-blocking (design-reviewer's one remaining nit).🤖 Generated with Claude Code