-
feat(web): in-browser debug companion — issue #16 (v0.15.0)
released this
2026-05-27 19:03:50 -07:00 | 238 commits to main since this releaseBrowser-based debug companion to the Ratatoskr TUI, reusing the
existing wire-layer modules unchanged. Same five surfaces (transcript,
thinking, tools, debug, persona) over the same Worldtree Conversation
API SSE wire, viewable from any device on the operator's LAN.Per docs/contracts/issues/16.contract.md (full v2.1 module contract
with 11 FN blocks + 9 invariants + Heid panel review pass merged).Architecture:
- New module
ratatoskr.webwithserver.py(Starlette app, ~250 LOC),
entrypoint.py(lazy-import gate, ~100 LOC),static/index.html
(single-page vanilla JS UI, ~360 LOC) - Optional-deps group
[web]= starlette + uvicorn[standard]; dev
pulls these in transitively - New console script
ratatoskr-web - Streaming via browser-native
EventSourceGET; prompt-submit is a
separate POST (load-bearing Hulda finding from R13 panel — EventSource
is GET-only) - Small in-memory turn registry maps (session_id, turn_id) → upstream
request handle for cancel + browser-disconnect cleanup
Endpoint surface (9 routes):
GET /→ static index.htmlGET /static/*→ static assetsGET /version→ {"ratatoskr": ""}GET /api/agents→ upstream /agents + local Tier 3 mergePOST /api/sessions→ upstream POST /sessionsGET /api/agents/{id}/persona_state→ upstream persona-statePOST /api/turns/{sid}→ allocate turn_id, register in turn registryGET /api/turns/{sid}/stream?turn_id=N→ proxy upstream SSE to browserPOST /api/turns/{sid}/cancel?turn_id=N→ upstream cancel
Trust model: internal LAN debug surface. Binds 0.0.0.0:8765 default;
no auth, no CORS guard (operator direction). What stays disciplined
regardless of network trust:- Transcript HTML-escapes assistant content (INV-004 — model output
is untrusted text; adversarial HTML must not execute in browser) - Upstream API key never reaches browser DOM (INV-003 — proxy-only)
Lifecycle:
- Browser disconnect mid-stream → upstream cancel (INV-005;
asyncio.CancelledError caught in stream handler) - Server Ctrl-C → lifespan shutdown drains turn registry within 5s
budget (INV-006; structured-log line on timeout)
Tests (37 new, 356 total; previous 319 baseline preserved):
- tests/test_web_server.py (23 cases): endpoint contract via Starlette
TestClient + respx mocks; covers each endpoint, browser-disconnect →
upstream cancel, lifespan shutdown draining the registry - tests/test_web_presentation_contract.py (11 cases): proxy
serialization matches tests/fixtures/presentation_contract.json
for one of each Event type — drift detection between server-side
serializer and the JS presenter without forcing a shared abstraction - tests/test_web_packaging.py (4 cases): static asset packaging via
importlib.resources; AST-checked lazy-import discipline (no top-
level starlette/uvicorn import in entrypoint.py); missing-API-key
exit-11 path; missing-extras exit-12 path
Provenance:
- Scope v1 → Heid panel review (Gróa + Hulda, R13) → 8 load-bearing
corrections (POST→GET split, Starlette > FastAPI, lazy-import
discipline, browser-disconnect → upstream cancel, presentation-
contract fixture, error event contract, static-asset packaging,
escaped plain-text Markdown deferred) merged into scope v2 - Operator direction: internal-LAN debug surface; auth + CORS
deliberately omitted
Not yet (deferred to v0.16.x+):
- Cross-reload session resume via Last-Event-ID
- Tier 3 lifecycle UI (define/patch/delete in browser)
- Markdown rendering with vendored safe-subset renderer
- TLS + real auth (only if a non-LAN use case ever surfaces)
Downloads
- New module