Files
Gitea-Tools/docs/webui-local-dev.md
T
sysadminandClaude Opus 4.8 ecda200180 feat(webui): system-health dashboard (Closes #639)
Phase 1 child of the Web Console epic #631. Adds the operator-facing
system-health dashboard on top of the read-only system-health API landed
by #634, so runtime problems are visible on a surface instead of being
discovered late through failed LLM sessions.

- webui/system_health_views.py (new): renders the SystemHealthSnapshot as
  readiness, stale-runtime parity, version/uptime, dependency, MCP
  namespace, probe-error, and recovery cards.
- webui/app.py: GET /system-health, sharing load_system_health() with the
  JSON API so page and API cannot disagree. ?deep=1 behaves as on the API.
- webui/layout.py: nav entry and health card/badge styles.
- tests/test_webui_system_health_dashboard.py (new, 26 cases).
- docs/webui-local-dev.md: route, field authority, and redaction split.

Readiness honesty is preserved from the API: ready and readiness_complete
render separately, a probe that did not run is listed under "Not probed"
rather than counted healthy, and mutation safety is never claimed when the
runtime is stale or parity is indeterminate.

Redaction is split by field kind. Free text (probe details, reasons, probe
errors) passes through system_health.redact. Structured fields (commit
SHAs, probe names, statuses, timestamps) are HTML-escaped only: redact's
opaque-token rule matches any run of 32 or more characters, so routing a
40-character git SHA through it rendered "[redacted]" and blanked the
parity evidence the page exists to show.

Non-goals honored: no restart or reload controls (Phase 2, #642), no
manual process-kill guidance (#630). Read-only throughout.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-23 16:08:36 -04:00

15 KiB

Internal web UI — local development (#426)

Read-only MVP skeleton for the MCP Control Plane operator console. Gitea, MCP capability gates, and skills/llm-project-workflow/ remain the source of truth; this UI only provides route stubs and layout.

Prerequisites

  • Python 3.11+ with project dependencies installed (pip install -r requirements.txt)
  • No secrets in repo, config, or client bundle

Start the server

From the repository root (or an issue worktree):

./scripts/run-webui

Or directly:

python3 -m webui

Optional environment variables:

Variable Default Purpose
WEBUI_HOST 127.0.0.1 Bind address (keep local for MVP)
WEBUI_PORT 8765 Listen port
WEBUI_REPO_ROOT repository root Prompt library workflow hash root
WEBUI_PROJECT_REGISTRY packaged JSON Project registry path
GITEA_MCP_CONFIG unset Server-side MCP profile config (never sent to browser)
GITEA_MCP_PROFILE unset Active MCP profile name (server-side only)

See webui-deployment.md for internal-only serving, Cloudflare Access/WARP/VPN guidance, and unsafe bind overrides (#435).

See architecture/webui-control-plane-console-architecture-adr.md for the console architecture: layer and authority boundaries, the redaction boundary, /api/v1/... versioning, the target page map, and the phase gates that govern when a write path may open (#632, epic #631).

See webui-project-registry-api.md for the versioned project registry contract: registry schema versions 1 and 2, project status, onboarding checklist state, and the fail-closed error payloads (#635).

Routes (MVP)

Path Description
/ Home / operator overview
/health JSON liveness (status, service, mode, timestamp, uptime_seconds)
/api/v1/system/health Structured read-only system health (#634)
/system-health System-health dashboard — readiness, version/uptime, dependencies, MCP namespaces, stale-runtime parity (#639)
/queue Live PR and issue queue dashboard (#429)
/api/queue JSON queue export with pagination metadata
/projects Project registry list with status and onboarding progress (#427, #635)
/projects/{id} Project detail + onboarding checklist
/api/v1/projects Versioned JSON registry export (#635)
/api/v1/projects/{id} Versioned JSON project detail (#635)
/api/projects JSON registry export — unversioned Phase 1 alias of /api/v1/projects
/prompts Prompt library with per-prompt copy buttons (#428)
/api/prompts JSON prompt export with workflow hashes
/runtime MCP runtime health and stale detection (#430)
/api/runtime JSON runtime health export
/audit Report audit paste + validator preview (#431)
/api/audit JSON validator preview (POST report_text, optional task_kind)
/worktrees Worktree hygiene dashboard (#432)
/api/worktrees JSON worktree scan with classifications and anomalies
/actions Gated write-action registry — all disabled in MVP (#434)
/api/actions JSON action registry with capability metadata
/api/actions/{id}/preview Mutation ledger preview (GET, read-only)
/leases Lease and collision visibility (#433)
/api/leases JSON lease/collision export

Most routes are GET-only. POST/PUT/PATCH/DELETE return 405 with read-only-mvp, except /audit and /api/audit which accept POST for local validator preview only (no Gitea mutations, no server-side storage).

System health API (#634)

GET /api/v1/system/health is the structured, read-only health surface for automated readiness checks. It is the first console API under the /api/v1 prefix; the unversioned MVP exports remain as compatibility aliases.

/health is unchanged for existing consumers — every MVP key is still present — and now also carries started_at, uptime_seconds, and a system_health_api pointer. It stays deliberately cheap and runs no dependency probe, because answering readiness costs real work.

Status codes. 200 when ready, 503 when a required dependency failed or was never probed. Automation can branch on the code without parsing the body.

Query flags. The Gitea check is a network call, so it is opt-in: GET /api/v1/system/health?deep=1 runs it and caches the result for WEBUI_HEALTH_PROBE_TTL_SECONDS (default 15s) so dashboard polling does not amplify into remote load. Without the flag that probe reports skipped.

Dependencies. control_plane_db and repository are required and drive readiness. gitea is optional: when it fails the overall status degrades but readiness.ready stays true, because local inventory is still serveable. Each entry carries status, detail, required, and latency_ms.

Two honesty rules are worth knowing before reading the payload:

  • stale_runtime.mutation_safe is true only when the runtime, checkout, and remote-tracking commits are all known and equal. An unfetched remote is reported as indeterminate, never as safe.
  • mcp_namespaces entries are always unproven. A web process runs outside the IDE-managed MCP client and cannot invoke a namespace tool, so per #543 only a client_namespace probe can prove that path.

Sample response (abridged, healthy):

{
  "status": "ok",
  "service": "mcp-control-plane-webui",
  "mode": "read-only",
  "api": "/api/v1/system/health",
  "timestamp": "2026-07-22T11:04:18.512034+00:00",
  "readiness": { "ready": true, "complete": true, "reasons": [] },
  "version": {
    "git_sha": "620ed6e9a9550b8da2ceb82d9ab8744e8920490f",
    "git_describe": "v1.1.0-898-g620ed6e",
    "control_plane_schema_version": 4,
    "python_version": "3.14.5",
    "known": true
  },
  "process": { "started_at": "2026-07-22T10:58:02.114+00:00", "uptime_seconds": 376.4 },
  "deep_probes_requested": false,
  "dependencies": [
    {
      "name": "control_plane_db",
      "kind": "sqlite",
      "status": "ok",
      "detail": "schema v4 readable",
      "required": true,
      "healthy": true,
      "latency_ms": 1.482,
      "metadata": { "schema_version": 4, "active_leases": 3 }
    },
    { "name": "repository", "kind": "git", "status": "ok", "required": true, "healthy": true },
    { "name": "gitea", "kind": "http", "status": "skipped", "required": false, "healthy": false }
  ],
  "mcp_namespaces": [
    { "namespace": "gitea-author", "required_tool": "gitea_whoami", "status": "unproven" }
  ],
  "stale_runtime": { "stale": false, "determinable": true, "mutation_safe": true, "reasons": [] },
  "probe_errors": []
}

No restart, reload, or process-kill control is exposed here: those are Phase 2 at the earliest, and #630 forbids process-kill recovery outright. Every probe opens its subject read-only — the control-plane database is opened through a mode=ro URI so a health check can never create or migrate a schema.

Report audit (#431)

Paste an LLM final report at /audit or POST JSON to /api/audit. The UI reuses final_report_validator and review schema checks to surface missing proof fields, wrong validation vocabulary, mutation contradictions, and a suggested next prompt or issue-comment draft. Task kind can be auto-detected or selected explicitly.

Project registry (#427)

Versioned registry file: webui/data/projects.registry.json (schema version 1).

Override path with WEBUI_PROJECT_REGISTRY when operators keep a machine-local copy outside git. The registry stores repo identity, remotes, profile names, workflow/schema path references, and onboarding checklist steps — never tokens or credentials.

Seed entry: Gitea-Tools on https://gitea.prgs.cc with prgs-author, prgs-reviewer, and prgs-reconciler profiles.

Prompt library (#428)

Prompts are generated at load time from canonical workflow files under skills/llm-project-workflow/workflows/. SHA-256 hashes are computed from WEBUI_REPO_ROOT (defaults to the repository root). Prompt bodies are short copy/paste starters; canonical workflow files remain the only full policy source.

Live queue dashboard (#429)

/queue loads open PRs and issues for the default registry project (seed: Gitea-Tools on https://gitea.prgs.cc) using existing gitea_auth read credentials. The UI surfaces pagination proof (returned count, pages fetched, has_more, inventory_complete) and classification badges (claimed, blocked, in-review, duplicate) when evidence exists.

If credentials are missing or the fetch fails, the page shows an explicit error instead of an empty queue (fail closed).

Gated actions (#434)

/actions registers future write actions (claim, comment, review, merge, delete branch, create PR/issue). Each entry declares the MCP tool, required permission, and profile role from task_capability_map.py — aligned with gitea_resolve_task_capability. Buttons are disabled; previews always render a mutation ledger. Direct attempt_action calls fail closed without invoking MCP tools.

Worktree hygiene (#432)

/worktrees scans local branches/ directories and registered git worktrees. Each entry is classified (active-pr, active-issue, dirty, stale-clean, detached-review, unsafe-unknown, orphan). Missing preserved worktrees referenced by the issue lock file are flagged as anomalies (#404). The page includes a copy/paste canonical cleanup prompt only — no deletion actions.

Override scan root with WEBUI_REPO_ROOT (defaults to repository root).

Lease visibility (#433)

/leases surfaces read-only lease and collision state: local issue lock file, in-progress claim inventory (#268), reviewer PR lease comments when present (<!-- mcp-review-lease:v1 -->, #407), duplicate open PRs per issue (#400), and duplicate local branches per issue. Links to collision-history backend issues (#267, #268, #400, #407) are included. No lease acquire/release from UI.

Runtime health (#430)

/runtime surfaces read-only MCP/runtime diagnostics for the default registry project: active profile and role kind, authenticated identity (when credentials are available), config model/mode, local vs remote master SHA sync, shell health, workflow/schema SHA-256 hashes, and stale-runtime warnings when the checkout is behind merged safety-gate changes. Restart guidance links to #420; no tokens or MCP restart actions are exposed.

System-health dashboard (#639)

/system-health renders the same snapshot the /api/v1/system/health API returns, so the page and the API can never disagree. Cards: overall readiness, stale-runtime parity, version and uptime, dependency probes, MCP namespaces, probe errors (only when present), and recovery pointers. ?deep=1 opts into the network probe exactly as the API does; the plain page load stays cheap.

Field authority and honesty rules:

  • ready and readiness_complete are shown separately. A snapshot whose required probes never ran is not the same as one that ran them and passed, and the page never collapses the two into an unproven green.
  • A probe that did not run appears under Not probed, never as healthy.
  • stale_runtime.mutation_safe is displayed verbatim from the API. When the runtime is stale, or when parity is indeterminate, the page warns and does not claim mutation safety.
  • MCP namespaces are reported unproven: the web process runs outside the IDE-managed MCP client and cannot prove that path (#543).

Redaction is split by field kind. Free text — probe details, readiness and parity reasons, probe errors — passes through system_health.redact. Structured fields — commit SHAs, probe names, statuses, timestamps — are HTML-escaped only, because redact's opaque-token rule matches any run of 32 or more characters and would otherwise blank every 40-character git SHA, which is precisely the evidence the parity view exists to show.

The dashboard is read-only: no restart, reload, or process-kill control. Those arrive in Phase 2 (#642). Recovery guidance points at the sanctioned client reconnect / operator restart path — never a manual daemon kill (#630).

Deployment boundary (#435)

MVP serves on loopback by default. Binding 0.0.0.0 or :: is refused unless WEBUI_ALLOW_PUBLIC_BIND=1. Non-loopback hosts log a warning unless WEBUI_ALLOW_REMOTE_BIND=1. GET /health exposes deployment metadata.

Tests

pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_gated_actions.py -q
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_audit.py tests/test_webui_worktree_hygiene.py -q
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_lease_visibility.py tests/test_webui_runtime_health.py -q

pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_deployment_boundary.py -q

## Tests (#436)

Run the full hermetic web UI suite (all `test_webui_*.py` modules):

```bash
./scripts/test-webui

CI / Jenkins multibranch can call the path-filtered gate (runs only when the diff touches webui/, tests/test_webui_*, or web UI docs/scripts):

./scripts/ci-webui-check
WEBUI_CI_FORCE=1 ./scripts/ci-webui-check   # always run

scripts/test-webui sets WEBUI_TEST_OFFLINE=1 by default. In that mode the queue, lease, and runtime routes use empty offline snapshots instead of Gitea credentials, so CI can run without MCP daemon credential access. Set WEBUI_TEST_OFFLINE=0 only when deliberately validating live fetch behavior.

Or invoke unittest directly:

python3 -m unittest discover -s tests -p 'test_webui_*.py' -q

Lease visibility (#433)

/leases surfaces read-only lease and collision state: local issue lock file, in-progress claim inventory (#268), reviewer PR lease comments when present (<!-- mcp-review-lease:v1 -->, #407), duplicate open PRs per issue (#400), and duplicate local branches per issue. Links to collision-history backend issues (#267, #268, #400, #407) are included. No lease acquire/release from UI.

Runtime health (#430)

/runtime surfaces read-only MCP/runtime diagnostics for the default registry project: active profile and role kind, authenticated identity (when credentials are available), config model/mode, local vs remote master SHA sync, shell health, workflow/schema SHA-256 hashes, and stale-runtime warnings when the checkout is behind merged safety-gate changes. Restart guidance links to #420; no tokens or MCP restart actions are exposed.

Tests

pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_audit.py tests/test_webui_worktree_hygiene.py -q
pytest tests/test_webui_skeleton.py tests/test_webui_project_registry.py tests/test_webui_prompt_library.py tests/test_webui_queue_dashboard.py tests/test_webui_lease_visibility.py tests/test_webui_runtime_health.py -q