Skip to content

feat(cockpit): browser UI — split cockpit (#354) - #369

Merged
dngioidev merged 2 commits into
mainfrom
feat/354-cockpit-browser-ui
Aug 3, 2026
Merged

feat(cockpit): browser UI — split cockpit (#354)#369
dngioidev merged 2 commits into
mainfrom
feat/354-cockpit-browser-ui

Conversation

@dngioidev

Copy link
Copy Markdown
Owner

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.1 behind the #352 loopback guard, against the committed visual spec docs/design/2026-08-03-cockpit-ui.md (owner pick: Variant C, docs/design/variants-354/variant-c.html).

Closes #354

What landed

  • FastAPI static serving (server.py): GET / returns index.html with 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 /static StaticFiles mount — same-origin, same guard.
  • Variant C UI (forge_cockpit/web/): persistent fleet sidebar + main pane toggling Usage / Terminal / Logs, Terminal default.
  • Real xterm.js (5.3.0 + fit 0.8.0, MIT, vendored under web/vendor/ with NOTICE.md) over the /api/terminal websocket (token as query param), fit addon + resize messages.

Acceptance criteria

  • AC.1 — FastAPI serves the UI at /; 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.
  • AC.2 — live data from /api/fleet, /api/usage, /api/logs; mutating controls (start/stop/restart, re-provision) send X-Forge-Session; a failed/tokenless action surfaces an inline role=alert error state (never alert()).
  • AC.3 — real xterm.js over the /api/terminal websocket; typing works (xterm.js owns emulation — the Cockpit — embedded terminal (pywinpty ConPTY + WSL tab) #275 fix). xterm.js pinned + vendored MIT, recorded in web/vendor/NOTICE.md; node plugin/scripts/gates/license.mjs stays zero-exception.
  • AC.4 — responsive at 375/768/1280 (sidebar reflow per spec) + the a11y contract (visible focus, roles/labels incl. roving tablist, ≥24px targets, non-color-only status, reduced-motion). Dark-only.
  • AC.5 — pytest TestClient covers the static-serving routes + token injection + the key regions (sidebar/mode bar/terminal mount) + the ws token gate still rejecting a tokenless upgrade; vitest covers the pure frontend helpers (format/status/controls/chart geometry).

Verification (this branch)

  • pnpm verify710 passed / 59 files (incl. the new tests/cockpit/format.test.mjs, 18 cases).
  • uv run pytest -q (tools/runner-ui, Windows) — 186 passed, 1 skipped (POSIX-only pty leg). New tests/test_server_ui.py: 10 passed.
  • node plugin/scripts/gates/license.mjsclean, 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 in web/vendor/NOTICE.md.
  • Live smoke under real uvicorn: GET / serves the shell with the token injected and all key regions; /static/app.mjs served as text/javascript; a mismatched-port request is correctly 403'd by the loopback guard.
  • Design review (forge:design-reviewer against 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 (mirrors provision._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

🤖 Generated with Claude Code

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
@dngioidev
dngioidev merged commit 687d720 into main Aug 3, 2026
12 of 13 checks passed
@dngioidev
dngioidev deleted the feat/354-cockpit-browser-ui branch August 3, 2026 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant