9 Commits

Author SHA1 Message Date
vh 0441e319f6 feat(web): auto-scale PAD gauges for R32-1B unbounded-z (v0.20.9)
Sindra "full and unbounded": relax the debug affect console's PAD display
from a hard [-1,1] clamp to auto-scaling on the session's own max |PAD|
(padScale floor 1.0 → padFillFrac faders + _padNorm orbit). An unbounded-z
PAD (Worldtree R32-1B, ~±10) now renders at full range and never pegs or
escapes the frame; today's [-1,1] values are unchanged (scale==1); the
exact value is always shown numerically (unclamped).

Purely a debug-surface change — verified (grep, whole codebase) the only
PAD clamps lived in the web display layer: the affect store is conduit-
opaque, the read route + proxy pass verbatim, and --set-persona-pad writes
unclamped. Ratatoskr is a downstream observer, so this has zero consequence
to any agent's real affect or behavior (Worldtree-computed server-side).
Playwright-verified: z=±6.2 → faders ≤ half-bar, orbit in-box, +6.20
readout, zero regression at scale 1. Proactive R32-1B prep.
2026-07-08 16:16:29 -07:00
vh e5ec63967e feat(web): memory viewer + design iteration-3 + markdown pass-2 (v0.20.7)
Web-UI iteration-3 — three queued items landed together.

(A) Design iteration-3 into static/index.html:
- sparkline grid background (<pattern id=sparkGrid> + bg rect behind each
  relation-row spark polyline);
- PAD strips → per-turn Δ bars: replace the vertical polyline strip
  (stripPoints/proj3 removed) with padDeltas→deltaStrip, a 12-cell HTML
  column of diverging bars (newest at bottom, magnitude→width, age→opacity);
- mood orbit → dimetric open box (viewBox 124×140, az35/el25,
  D-right/A-left-back/P-up): ghost A×P wall + D×A floor +
  orbitProj/orbitShadowY/orbitWallPt/orbitAxisPt, JS-driven animated replay
  (orbitFrame per rAF via a singleton startOrbitAnim reading live ORBIT_HIST;
  reduced-motion → static final-state).

(B) Memory viewer — a non-bifrost debug read on OUR own store, mirroring the
    #18-D2 affect read:
- provider: RatatoskrMemoryStore.list_chunks + count_chunks + shared
  add_memory_read_route (GET /memory/chunks?agent_id=&end_user_id=), wired
  into build_memory_provider_app + the combined :8392 provider. end_user
  strict, agent_id lenient (an {end_user}-only chunk stays visible);
  {chunks,count,total}, empty match = 200 (not 404);
- web: _memory_chunks_endpoint (GET /api/memory/chunks) supplies end_user_id
  server-side, forwards the browser agent_id, proxies to memory_read_url;
  create_app gains memory_read_url, entrypoint reads RATATOSKR_MEMORY_READ_URL;
- pane: loadMemory/renderMemory/setMemHead — a live-polling MEMORY console
  pane (content·scope·origin·revision per chunk; count/total distinguish
  empty-store from scope-mismatch), polled on open + post-turn.

(C) Markdown pass-2 in markdownSafe: GFM pipe tables (mdTable, alignment
    colons), indentation-nested lists (child list inside the open <li>),
    ordered-list start=N numbering, streaming robustness (partial fence →
    code block; header-without-delimiter → paragraph until it streams in).
    esc-first → INV-004 held.

Contract web_debug_surface.contract.md amended in-commit (create_app
memory_read_url; dimetric-orbit + Δ-bar renderConsole POST-002; memory-viewer
+ markdown-pass-2 function contracts). 631 tests green; Playwright-verified
all render paths (dark+light).
2026-07-07 02:11:29 -07:00
vh 459e7fa602 feat(web): SVG sparklines + 3D isometric mood cube (v0.20.4)
Import the updated Claude Design prototype's SVG sparkline system + 3D graph,
replacing the unicode-char sparklines:

- per-PAD-fader vertical SVG strips (stripPoints, 26x132 beside each bar — time
  down Y newest-at-bottom, value on X, gradient-faded, dot at newest); also
  answers the earlier "next to each meter" ask
- relation-row horizontal SVG sparklines (sparkPointsH, 56x13, auto-scaled,
  gradient + end dot) — fixed-width, so the old unicode overflow onto the n
  column can't recur
- mood-orbit reworked from a 2D P×A scatter into a 3D ISOMETRIC P×A×D cube
  (proj3: P right-down / A left-down / D up, 2:1 iso, scale 26, reverse-derived
  from the design's now-point + verified) with the trajectory, a pulsing
  now-marker, and a drop line + floor-shadow ellipse for depth
- gradients in one hidden <defs> svg; removed the orphaned sparkline()/_SPARK

Contract amended. Verified: pytest tests/test_web_* (84) + node Playwright
(injected 24-sample history — 3 PAD strips + 4 relation sparklines + the 3D
cube trajectory/drop/floor all render; gradients resolve).
2026-07-07 00:29:18 -07:00
vh 0b1d9e2b15 feat(web): context-injection panel — reconstruct the full hidden affect block (v0.20.2)
The affect console now reconstructs + displays the complete affect-context block
Worldtree assembles into the agent's system prompt — never on any wire, hidden from
regular consumers, surfaced here as the reference-impl's privileged dev view.

- extend build_persona_canon.py to emit mood_directive {occ_directives (15),
  pad_band_fallback, salience, pad_band_cutoff, full_only} into the browser canon
  (strings were already in the pinned d2-mood-render-canon; regen via Worldtree loader)
- canonPadFallback(pad) + canonEmotionDirective(type): byte-exact mirrors of Worldtree
  core/persona/renderer._pad_band_fallback + derive_directive
- renderDirective -> a "CONTEXT INJECTION · reconstructed · hidden from consumers" panel:
  mood descriptor [exact] + mood directive [candidate] + relationship directive [exact]
- honest-partial (affect-egress-reference sec 3): affect.emit is type-only (no
  intensity), so the salience gate can't be evaluated -> show BOTH the OCC emotion
  directive AND the PAD-band fallback with the "injected if intensity >= 0.2" caveat,
  never asserting which fires; fallback alone is exact when no dominant_emotion
- vendor + pin affect-egress-consumer-reference.md (tolerate_drift; worldtree-dev
  co-signs + pings on change). drift 6/6 green
- contract amended for the new reconstruction fns + honest-partial provenance

Verified: pytest tests/test_web_* (84) + node Playwright (sindra dominant_emotion=joy
-> joy OCC directive candidate + PAD-band fallback both render with exact/candidate tags).
2026-07-06 23:12:16 -07:00
vh 1fcb17730e feat(web): Claude Design console — 3-column wire monitor (v0.20.0)
Adapt the Claude Design "Ratatoskr Console" prototype into the web SPA:
translate out of the .dc.html dialect (x-dc / sc-if / sc-for / {{}} /
DCLogic / external _ds CSS) into single-file / no-CDN / vanilla, and wire
all real /api/* fetch + SSE into its DOM. New 3-column command-console
replaces the tabbed telemetry layout; endpoint set + SSE vocab unchanged.

- left engine-ticker rail: DEBUG + ADMIN + tool/turn-lifecycle merged into
  one timeline (tickerAdd); tools-armed chips; full-detail Bifrost rail pane
  (endpoint / connected / consumer / caps / tools)
- center conversation: per-turn INLINE chain-of-thought
- right resizable affect console: dominant / canonical-mood centerpiece;
  bipolar PAD faders EACH with a turn-to-turn delta + sparkline; P×A mood
  orbit; relations metric rows; canonical directive
- light / dark theme toggle (dark default; full token override —
  surfaces + fg + borders + accent-as-text)
- inlined data-URI favicon (downscaled 1024->64px), kills /favicon.ico 404
- ticker spine re-anchored to a content-height wrapper (was scrolling out of
  view on auto-scroll)
- honest-shape (INV-001): dominant-emotion shows a real OCC emotion (Tier-1)
  or the canonical mood word (Tier-3), never a fabricated one; affect-derived
  grid drops non-emitted metrics (intensity / decay-tau)

All server routes unchanged. web_debug_surface.contract.md amended for the
presenter renames (renderBifrostState->renderBifrost, renderAffectPane->
renderConsole, setPersonaStrip removed).

Verified: pytest tests/test_web_* (84 passed) + node Playwright end-to-end
against personal :8081 (session open, Sindra seeded greeting, live turn SSE,
affect console + relations + bifrost detail, theme toggle, PAD deltas,
no favicon 404).
2026-07-06 21:50:22 -07:00
vh 263ec2917b fix: render the seeded first-message on the web UI (v0.19.9)
The #347 auto-seed worked (the greeting was in the session ledger at seq-0),
but the web UI never showed it: there was no GET /api/sessions/{id}/messages
route and startSession() went straight from create to persona/tools/admin
hydration, so the transcript only filled from the live turn stream + user
echoes — a seeded turn-0 was invisible.

- server: new proxy route GET /api/sessions/{id}/messages -> get_session_messages
  (mirrors the tools/bifrost proxies; status-preserving envelope).
- SPA: loadTranscript(sessionId) fetches it on open and renders existing turns
  (assistant -> .response .md-body via markdownSafe escape-first; user ->
  .prompt-echo via textContent), called after the workspace opens. Best-effort.

web_debug_surface contract amended (endpoint + loadTranscript). 2 web route
tests, suite 617 green. Playwright DOM check proved the render end-to-end
(drive the real UI -> Sindra's greeting bubble appears).
2026-07-06 15:19:56 -07:00
vh a99f2473b6 feat(web): persona pane shows the CANONICAL affect->NL Worldtree injects (v0.19.5)
The pane now renders the LITERAL mood word + relationship directive Worldtree
context-injects into the agent — adopted from Worldtree's canon, not invented:

- canonMood(pad) mirrors Worldtree describe_pad (valence×arousal grid, ±0.3 bands);
  for sindra's PAD the canonical render is "neutral" — an invented octant vocab
  would have said "faintly excited" and MISLED. Adopting canonical is the point.
- canonDirective(rel) mirrors render_d2_canonical byte-exact: "...warmth is clear
  warm regard; ability trust is strong; ...; speak with direct warmth; ..." — the
  exact stance instruction the agent receives (which makes the WAD "stranger"
  relation_context read even more incoherent, as flagged to worldtree-dev).
- Both VERIFIED byte-exact against Worldtree's OWN renderer on the live snapshot.
- Canon vendored (docs/vendor/worldtree-persona-canon/) + drift-pinned in
  .corviduo-canonicals.toml (canonical_drift green); flat browser form
  (static/persona_render_canon.json) regenerated by scripts/build_persona_canon.py
  via Worldtree's authoritative loader. Reference-impl posture: adopt canonical.
- Fail-open (canon absent -> lines omit); INV-004 esc() preserved.

JS syntax clean. Refresh + drive turns to see the canonical NL under mood + each
relation.
2026-07-01 13:51:37 -07:00
vh ca46a93171 feat(web): persona pane renders the relation_edge/1 affect model + per-value trend (v0.19.4)
The persona/affect pane read snap.valence (the pre-#265 shape) while Worldtree now
emits snap.relations (relation_edge/1) — so the whole trust/warmth model rendered as
an empty "valence (0)". Now renders the real signal, self-labelled:

- MOOD (PAD, transient): pleasure/arousal/dominance with a one-word descriptor each.
- RELATION → <target> (stage: <relation_context>): trust·ability / benevolence /
  integrity + warmth, each as value + evidence_count (n=) — the durable social model.
- Per-value TREND: Δ-vs-previous (▲/▼) + a unicode sparkline auto-scaled to the value's
  own observed range (flat when sub-0.01 stable, so noise isn't amplified). History
  accumulates client-side, one sample/turn (deduped by emitted_at), capped at 24.
- Falls back to the legacy snap.valence for an older emitter; INV-001 (no fabricated
  Tier-1 fields) + INV-004 (every cell escaped) preserved. Supersedes the #18-D2
  valence assumption + retires the stale "regard dead axis" note.

Verified: render logic asserted in node against the REAL affect.db snapshot + a
perturbed 2nd sample (relations rendered, no "valence (0)", Δ ▲ shown, 2-char
sparkline builds, INV-004 holds). JS syntax clean. No server change (static served
per-request) — refresh + drive turns to watch the trends build.
2026-07-01 13:20:24 -07:00
vh 75dec016eb fix(web): heid-review findings — SSE lifecycle teardown + test-shape gaps (v0.19.3)
Cross-frontier panel (Gróa/Hulda/Regin) on the v0.19.2 web surface, triaged:

- FIX (Gróa #1, drift): the turn EventSource `onerror` (raw transport drop)
  now calls hideThinkingNote() — a drop mid-reasoning no longer leaves the
  "<Agent> is pondering…" line + its setInterval running (INV-LIFECYCLE).
- FIX (Gróa #4 + Hulda #1, convergent drift): openAdminEvents now closes the
  EventSource + clears state.adminES on `stream_error` (server signalled end)
  and on a PERMANENT onerror (readyState CLOSED) — native EventSource no longer
  auto-reconnects into a retry loop; transient CONNECTING drops still reconnect.
- TEST (Gróa #2 + Hulda #3): test_routes_registered asserts the 3 new routes;
  test_state_attached asserts app.state.admin_key (create_app POST-001/002).
- TEST (Gróa #3 + Regin #3): AdminEvents stream_error-on-connect-failure test —
  upstream non-200 -> exactly one `stream_error` frame, then ends (POST-003).
- CONTRACT (Hulda #2 + Regin #2, accepted): clarified the Tools inventory
  renders NAMES only by design (descriptions live in the BifrostState pane);
  code unchanged. Also lands the web_debug_surface contract as the trail.

Accepted-no-op: 403-bifrost / non-404-tools tests (identical code path to the
tested 404). Panel found ZERO functional server-side drift; INV-004 escaping
confirmed clean across the new panes. 60 web tests pass; JS + ruff clean.
2026-07-01 12:52:44 -07:00