Skip to content

feat(web): vouch review-ui — full #194 spec (WebSocket sync, sessions, sources, bearer auth, pagination)#198

Merged
plind-junior merged 2 commits into
vouchdev:testfrom
dripsmvcp:feat/194-review-ui-full
Jun 10, 2026
Merged

feat(web): vouch review-ui — full #194 spec (WebSocket sync, sessions, sources, bearer auth, pagination)#198
plind-junior merged 2 commits into
vouchdev:testfrom
dripsmvcp:feat/194-review-ui-full

Conversation

@dripsmvcp

@dripsmvcp dripsmvcp commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Completes the vouch review-ui console so it satisfies every acceptance criterion in #194. Stacked on top of #195 (plind-junior's MVP slice): this branch contains #195's commit plus one commit that adds the deferred spec features, so it merges without conflicting and credits the MVP. It is a strict superset — nothing the MVP did is removed.

Still a pure viewport — every approve/reject/contradict routes through vouch.proposals / vouch.lifecycle, so the audit log is identical to the CLI. Zero new on-disk schema, zero new kb.* RPC methods.

What this adds on top of the MVP slice (#195)

#194 criterion MVP (#195) This PR
Queue / claim / audit views ✅ (carried over)
Two windows sync in <1s via WebSocket ⬜ deferred /ws hub, measured ~50ms, asserted <1s
500-item queue first page <200ms ✅ storage-layer pagination (parses 1 page, not 500), ~30ms
/session/<id> (proposals by agent run)
/sources/<id> (reverse index)
0.0.0.0 requires auth partial (refused outright) ✅ refused without --auth; allowed with it
JS-off plain form POST still works ✅ (kept; WS + keys are additive)
tests/test_web_e2e.py drives approve, asserts audit entry ✅ named file

Plus /contradict; keyboard shortcuts (j/k/a/r/?); reviewer identity in the audit log.

Security (hardened, then adversarially reviewed)

A multi-agent review pass over the diff (auth-bypass, correctness, async, packaging, acceptance lenses) surfaced and I fixed:

  • Constant-time token comparison (secrets.compare_digest) on HTTP and WebSocket — no timing oracle.
  • Token transport: Authorization: Bearer header or an HttpOnly, SameSite=Strict cookie. ?token= is a one-time GET bootstrap that's moved into the cookie and 303-redirected away, so the bare token never lingers in a URL or access log. JS never touches the token.
  • broadcast() can't be stalled by one slow/dead WebSocket — each send is bounded by a per-client timeout and failures are pruned.
  • Blocking store I/O offloaded via run_in_threadpool, so a write can't block the event loop / other reviewers.
  • A corrupt proposal file is skipped + logged, not 500'd — the gate stays reviewable.
  • CI now installs .[dev,web] so the web suite actually runs (it imports FastAPI); both web test modules importorskip the extra for local runs without it.

Verification

  • Full suite (pytest --ignore=tests/embeddings) → 132 passed (41 web) · mypy src clean (31 files) · ruff check src tests clean.
  • Wheel built — all 10 web assets ship (no npm, no CDN).
  • Live (real uvicorn): first page of 500 ~30ms; WS refresh ~50ms; auth bootstrap → HttpOnly SameSite=Strict cookie, ?token= stripped via 303; cookie-authed approve attributed to the token label.

Test Plan

  • tests/test_web_e2e.py, tests/test_web.py
  • pytest --ignore=tests/embeddings passes (132)
  • mypy src + ruff check src tests clean
  • Live smoke: pagination latency, WS sync, cookie-bootstrap auth, constant-time + wrong-cookie/ws rejection

Closes #194. Supersedes #195 (or merge #195 first — this rebases cleanly on top).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added browser-based review console via vouch review-ui command with pending queue, claim details, session grouping, and audit timeline pages.
    • Implemented real-time updates and keyboard navigation shortcuts (j/k for navigation, a to approve, r to reject).
    • Added optional Bearer token authentication with secure HttpOnly cookie support and one-time bootstrap token handling.
    • Exposed JSON API endpoint for pending queue with pagination support.
  • Documentation

    • Added comprehensive guide for the review UI command covering authentication, real-time features, and routes.

mvp slice of vouchdev#194: a fastapi + jinja viewport over the existing
proposals + audit surface. every approve / reject goes through
proposals.approve / proposals.reject so the audit-log entry is
identical to the cli code path. localhost-only; bearer-auth +
websocket sync land alongside the http-transport feature.

ships behind the [web] optional extra so the base install stays
lean. wheel includes the templates and static directory.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f87670c1-72ef-4d1d-9452-4584408277c1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Implements vouch review-ui, a browser-based review console that renders the pending proposal queue, claim details, session grouping, audit trails, and allows approve/reject/contradict actions via the existing proposal and lifecycle code paths. Supports optional Bearer-token authentication with HttpOnly cookies, WebSocket realtime sync, keyboard shortcuts, and progressive enhancement without JavaScript.

Changes

Browser-Based Review Console

Layer / File(s) Summary
Dependencies and documentation
pyproject.toml, .github/workflows/ci.yml, docs/review-ui.md
Adds [web] optional extra with FastAPI, Jinja2, Uvicorn, WebSockets, and httpx to [dev]. Adds mypy overrides for optional web imports. Updates CI to test with both extras. Comprehensive user documentation for setup, auth mechanics, routes, WebSocket realtime refresh, and progressive enhancement.
Web app entry point
src/vouch/web/__init__.py
Exports create_app() factory that validates [web] extra is installed, lazily imports server helpers, builds auth config from token/label, and returns configured FastAPI app. Includes installation error guidance if dependencies are missing.
FastAPI server: routes, auth, WebSocket
src/vouch/web/server.py
Core server with WebSocket hub for per-app client fan-out. Bearer-token auth with constant-time matching and HttpOnly cookie bootstrap (?token= → 303 redirect + SameSite cookie). Paginated queue (GET /), claim detail (GET /claim/<id>), session grouping (GET /session/<id>), source reverse-index (GET /sources/<id>), audit timeline (GET /audit), and JSON API (GET /api/pending). Mutation endpoints (POST /approve, /reject, /contradict) run storage writes in threadpool, emit WebSocket refresh frames, and redirect back to UI. Auth guards all routes except /healthz and /static.
HTML templates and CSS
src/vouch/web/templates/*.html, src/vouch/web/static/app.css
Jinja2 templates: base.html for layout/navigation/blocks, queue.html for paginated pending list, claim.html for proposal detail with payload preview and approve/reject forms, session.html for agent-run grouping, source.html for reverse-index of citing claims, audit.html for timeline of review events. CSS provides theme variables, typography, layout (masthead/nav/content), component styling for rows/badges/buttons/details, and colophon.
Client-side enhancement
src/vouch/web/static/app.js
Progressive enhancement: establishes WebSocket connection to /ws for live status and debounced page reload on refresh messages. Keyboard shortcuts: j/k to focus queue rows, a to approve, r to reject (requires reason input focus), ? for help. Preserves non-JS form POST behavior; avoids token handling and relies on same-origin cookies.
CLI review-ui command
src/vouch/cli.py
New vouch review-ui command with --bind (host:port), --auth (token / generate / env), --reviewer, --open-browser flags. Enforces loopback-only when no auth token is provided. Creates app via create_app(), validates uvicorn is installed, optionally opens browser to queue URL on loopback, and starts server.
Unit and integration tests
tests/test_web.py
Fixtures initialize temporary KBStore and TestClient. Tests: queue HTML rendering and JSON pending endpoint, claim detail and 404 handling, approve/reject POST flows with durable claim creation and audit logging, reason validation for reject, audit timeline page, progressive enhancement (plain POST without JS), healthz endpoint, KB not found error, and non-loopback bind refusal without auth.
Comprehensive E2E tests
tests/test_web_e2e.py
Real HTTP flows with disk-backed storage. Tests: approve/reject durable persistence and audit JSONL entries, WebSocket realtime refresh on approve, session view grouping, source reverse-index, pagination bounds (first-page clamping, single-page YAML parse for large queues), malformed proposal file robustness, Bearer-auth enforcement and audit attribution, open /healthz even with auth, WebSocket token requirements, HttpOnly cookie bootstrap (303 redirect + SameSite), steady-state cookie authentication, rejection of forged cookies, POST with query token (no redirect), static assets absence of token-handling constructs, and contradict-gate CONTESTED marking.

Sequence Diagram

sequenceDiagram
  participant CLI as vouch CLI
  participant WebApp as FastAPI App
  participant Auth as Auth Dependency
  participant Storage as KBStore
  participant WS as WebSocket Hub
  participant Browser as Browser Client

  CLI->>CLI: Parse --bind, --auth, --reviewer
  CLI->>CLI: Resolve auth token (literal/generate/env)
  CLI->>CLI: Validate loopback or require --auth
  CLI->>WebApp: create_app(kb_root, auth_token, auth_label)
  WebApp->>Storage: Load KB, read proposals
  Storage-->>WebApp: Pending proposals
  CLI->>Browser: Open 127.0.0.1:7780/?token=ABC (if auth)
  Browser->>WebApp: GET /?token=ABC
  WebApp->>Auth: Check query token
  Auth-->>WebApp: Valid → redirect
  WebApp-->>Browser: 303 to / (Set HttpOnly Cookie)
  Browser->>WebApp: GET / (with HttpOnly cookie)
  WebApp->>Auth: Check cookie
  Auth-->>WebApp: Authenticated
  WebApp->>Storage: Read paginated proposals
  Storage-->>WebApp: Page of 20 proposals
  WebApp-->>Browser: queue.html + live.js
  Browser->>WebApp: WebSocket /ws
  WebApp->>WS: Register client
  Browser->>WebApp: POST /approve/prop-123
  WebApp->>Storage: Approve proposal, write claim, log audit
  Storage-->>WebApp: Claim created, audit entry written
  WebApp->>WS: Broadcast refresh frame
  WS-->>Browser: {type: refresh, view: queue}
  Browser->>Browser: Debounced reload
  WebApp-->>Browser: 303 redirect to /
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The PR introduces a substantial new web subsystem with 1900+ lines of new code spanning FastAPI routes, auth mechanics (Bearer token, HttpOnly cookie bootstrap), WebSocket hub, Jinja2 templates, client-side JavaScript, CSS, CLI integration, and 700+ lines of E2E test coverage. The complexity is distributed across multiple files with interdependencies (server depends on auth, templates depend on server context, tests exercise full flows), dense logic around auth bootstrap redirect and mutation workflows, and several novel patterns (in-process WebSocket broadcast hub, pagination with YAML file bounds, constant-time token comparison). The homogeneity is moderate: most changes are coherent to the single feature, though routes, auth, and views each involve distinct reasoning.

Suggested reviewers

  • plind-junior

Poem

A console blooms in bytes, dear reviewer friend 🐰
Queue views and claims, where review loops blend,
WebSocket whispers keep two screens in sync—
No React, no build step, just FastAPI's link! 💙
Forms post proudly, JS enhances with grace,
Same audit log trails in this new web space ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing the complete vouch review-ui feature with all specified capabilities including WebSocket sync, sessions, sources, bearer auth, and pagination as required by issue #194.
Linked Issues check ✅ Passed The PR fully implements all acceptance criteria from #194: vouch review-ui command, FastAPI+Jinja2 frontend, all actions route through existing storage/proposals/lifecycle handlers, WebSocket sync, paginated views, progressive enhancement, 0.0.0.0 auth enforcement, and no new schema/methods.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the #194 requirements. The CI workflow update to install [dev,web] extras is a necessary supporting change to enable testing. No unrelated modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…urces, auth, pagination)

Completes the browser review console started by the MVP slice (vouchdev#195) so it
satisfies every acceptance criterion in vouchdev#194. Still a pure viewport: every
approve / reject / contradict routes through vouch.proposals / vouch.lifecycle,
so the audit log is identical to the CLI. Zero new on-disk schema.

Adds on top of the MVP slice:

- WebSocket realtime sync (/ws): a single channel per KB broadcasts a refresh
  signal after every mutation; a second reviewer's queue updates in <1s
  (measured ~50ms). Frame is a signal, not data — clients re-fetch through the
  same routes, so there's one rendering path. Each send is bounded by a
  per-client timeout so one slow/dead socket can't stall the decision handler.
- Server-side pagination at the storage layer: only the requested page of
  proposal files is parsed (proven by test: 500 pending -> 50 parsed). First
  page renders in ~30ms (was ~260ms loading the whole queue), under the budget.
  A corrupt proposal file is skipped + logged, not 500'd.
- /session/<id> (proposals grouped by agent run) and /sources/<id> (reverse
  index: which durable claims cite a source).
- /contradict gate action; keyboard shortcuts (j/k/a/r/?) and live-refresh as a
  progressive-enhancement layer — every action is still a plain form POST that
  works with JavaScript disabled.

Auth (team mode):
- A non-loopback bind requires --auth (a literal token, 'generate', or 'env');
  loopback stays tokenless. Reviewer identity comes from the token label and is
  recorded in the audit log.
- Credentials: Authorization: Bearer header (CLI/API) or an HttpOnly,
  SameSite=Strict cookie (browser). A ?token= query param is a one-time GET
  bootstrap only — moved into the cookie and 303-redirected away so the bare
  token never lingers in a URL or access log. Token comparison is constant-time
  (secrets.compare_digest), HTTP and WebSocket alike. JS never touches the token.

Tests / CI:
- tests/test_web_e2e.py (the file vouchdev#194's acceptance names): approve flow end to
  end asserting the audit.log.jsonl entry; ws sync (with <1s timing assertion),
  session/source views, deterministic pagination (parses one page of 500) +
  malformed-file resilience, served-static-asset check, and the auth-hardening
  cases (cookie bootstrap, constant-time, wrong-cookie/ws rejection).
- tests/test_web.py updated for the paginated /api/pending envelope and the
  auth-required-for-non-loopback behaviour; both web test modules importorskip
  the [web] extra so they skip cleanly without it.
- CI installs .[dev,web] so the web suite actually runs (it imports fastapi).
- pyproject: websockets added to the [web] extra (uvicorn needs it for /ws).
- docs/review-ui.md documents the surface incl. the auth model.

All web assets ship inside the wheel (verified by building it). Full suite +
mypy src + ruff clean. Hardening verified live against a real uvicorn server.

Closes vouchdev#194.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dripsmvcp dripsmvcp force-pushed the feat/194-review-ui-full branch from c991c42 to 44c30e5 Compare June 10, 2026 15:20
@dripsmvcp dripsmvcp changed the base branch from main to test June 10, 2026 15:24
@plind-junior

Copy link
Copy Markdown
Collaborator

LGTM!

@plind-junior plind-junior merged commit b81a6dd into vouchdev:test Jun 10, 2026
4 of 5 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 17, 2026
3 tasks
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.

feat: vouch review-ui — browser-based review console [VEP]

2 participants