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).
This commit is contained in:
@@ -41,16 +41,13 @@ upstream API key stays server-side (INV-003).
|
||||
|
||||
_As of 2026-07-07 (session cont.):_
|
||||
|
||||
**⏭ QUEUED FOR FRESH-CONTEXT IMPLEMENTATION (operator: "/snapshot and implement that and memory viewer on fresh context").** Three work items, all pre-scoped:
|
||||
**✅ SHIPPED — web-UI iteration-3, all three queued items (`v0.20.7`, patch, agent-discretion; 631 tests green; :8392 combined provider + :8765 web both restarted on the new code).**
|
||||
|
||||
**(A) Design prototype iteration-3 — re-pull + adapt** (`DesignSync get_file` projectId `bc0b65d1-a33e-422a-8bc1-3635c9112775`, file `Ratatoskr Console.dc.html`; design scopes already granted — no /design-login). RE-PULL for exact coords; three changes vs the shipped v0.20.4:
|
||||
1. **Sparkline background** — the relation-row 56×13 spark SVG gets a bg `<rect x=0.5 y=0.5 width=55 height=12 fill="url(#sparkGrid)" stroke=var(--border-subtle) stroke-width=0.5 stroke-opacity=0.6>` BEHIND the polyline. New def: `<pattern id="sparkGrid" width=10 height=6.5 patternUnits=userSpaceOnUse><path d="M 10 0 L 0 0 0 6.5" fill=none stroke=var(--border-subtle) stroke-width=0.5 stroke-opacity=0.6></pattern>` (subtle grid cell). Add pattern to the hidden `<defs>` + the rect to the metric-row spark (and consider the same bg on the PAD area).
|
||||
2. **PAD strips → per-turn Δ bars ("read as vertical shifts")** — REPLACE the current SVG polyline strip (`stripPoints`) with a column of 12 HTML rows (newest at BOTTOM), each 8px tall holding a horizontal bar offset L/R from the center line (`left:12.5px`) by that turn's Δ: `<div style="left:{l}px;width:{w}px;height:{h}px;opacity:{o};background:<axis-color>;top:50%;transform:translateY(-50%);border-radius:2px">`. Container `data-hook=strip-{p,a,d}` 26×132 `position:relative` with a center line + an inset flex column `justify-content:space-between padding:4px 0`. Eyebrow → "Δ/turn · last 12 · newest ↓". Compute `stripP/A/D` = arrays of `{l,w,h,o,t}` from per-turn deltas of AFFECT_HIST.pad[axis] (delta sign → left/right of center; magnitude → width; age → opacity fade).
|
||||
3. **Mood orbit → DIMETRIC OPEN BOX + JS-animated replay** — REPLACE the v0.20.4 isometric cube. New frame: viewBox **124×140**; "dimetric open box · **azimuth 35° elevation 25° · D→right, A→left-back, P→up**" (NEW projection — different axes from the current `proj3`). Two faces: a **ghost WALL** (A×P plane at d=−1, `fill=var(--aus-green) op=0.05`, grid lines, P-axis = the wall's rear edge, carries the **P readout**) + a **FLOOR** (D×A plane, positive halves shaded `--aus-cyan op=0.07` + `--aus-blue op=0.07`, grid lines). Axis labels colored: **+D cyan** (112,100) · **+A blue** (14,108) · **+P green** (54,10.5). The dynamic part is a template hole `{{ orbitDynamics }}` commented "**animated trajectory + shadow + P readout (JS-DRIVEN replay)**" → implement in JS: derive the dimetric projection (az35/el25, D-right/A-left-back/P-up), plot the PAD-history trail + a floor-shadow + the P readout on the wall, ANIMATED as a replay over time (no SMIL/CSS-keyframes in the prototype — it's explicitly JS-driven). RE-PULL the static frame polygon/line coords (they're in the prototype; too many to bank here). Existing `sparkFade`/`stripFade{P,A,D}` gradient defs unchanged.
|
||||
**(A) Design prototype iteration-3 adapted into `index.html`** (re-pulled `Ratatoskr Console.dc.html`, project `bc0b65d1-…`): (1) sparkline **grid bg** — `<pattern id=sparkGrid>` in the hidden defs + a bg `<rect fill=url(#sparkGrid)>` behind every relation-row spark polyline; (2) **PAD strips → per-turn Δ bars** — REMOVED the vertical polyline strip (`stripPoints`/`proj3` gone) for `padDeltas`→`deltaStrip`: a 12-cell HTML column (newest at bottom) of diverging bars offset L/R of a center line by that turn's Δ (magnitude→width, age→opacity, zero→faint center dot); head legend now "Δ/turn · last 12 · newest ↓"; (3) **mood orbit → DIMETRIC OPEN BOX** (viewBox 124×140, az35/el25, D-right/A-left-back/P-up) — ghost A×P wall (P readout) + D×A floor, `orbitProj/orbitShadowY/orbitWallPt/orbitAxisPt` projections, **JS-driven animated replay** (`orbitFrame` rebuilt per rAF by a singleton `startOrbitAnim` reading live `ORBIT_HIST`; reduced-motion → static final-state; no SMIL). Playwright-verified (dimetric frame + 17 dyn children + 21 Δ-bars + 5 grid rects; dark+light screenshots).
|
||||
|
||||
**(B) Memory viewer (step ②, operator picked the CONSOLE PANE).** New debug read route on the combined provider `GET /memory/chunks?agent_id=&end_user_id=` (reads `memory.db`; MIRRORS the #18-D2 `GET /affect/state/{agent_id}` provider-read pattern — bifrost's memory protocol has NO list-all verb, so this is explicitly OUR debug read on OUR store per [[feedback_debug_surface_uses_canonical_surface_only]]) → web proxy `GET /api/memory/chunks` (server-supplied end_user, like `/api/affect`) → a live-polling "MEMORY" pane in the right console showing content·scope·origin per chunk. **Bundle a controlled bound 6-turn session** (+ a salient fact, via `--bifrost-url http://10.100.10.50:8392`) to PROVE the round-trip + settle why memory stayed 0/0 (memory-watch timed out 13min at 0 chunks / 0 idempotency → suspect the operator's session bind wasn't memory-granted OR closed pre-promotion; the viewer makes bind+promotion state visible). Contract-worthy (amend `web_debug_surface.contract.md`, mirror the D2 provider-read + proxy + pane shape).
|
||||
**(B) Memory viewer SHIPPED + the 0/0 mystery ROOT-CAUSED.** New non-bifrost debug read `GET /memory/chunks?agent_id=&end_user_id=` on the combined `:8392` provider (`RatatoskrMemoryStore.list_chunks` + `count_chunks` + shared `add_memory_read_route`, wired into `build_memory_provider_app` + `combined.py`; **end_user STRICT, agent_id LENIENT** so `{end_user}`-only WT chunks aren't hidden; returns `{chunks,count,total}`, empty=200 not 404) → web proxy `GET /api/memory/chunks` (server-supplied end_user, new `RATATOSKR_MEMORY_READ_URL` env, default `:8391`, set to `:8392` in env.sh) → a live-polling MEMORY console pane (`loadMemory`/`renderMemory`/`setMemHead`, polled on open + post-turn). **ROOT CAUSE of the operator's 0/0** (settled via a bound 6-turn probe + op-feed): the Bifrost memory plane **binds and grants fine** (handshake `caps_requested:[affect,memory]` → `caps_granted:[memory,affect]`), but **sindra dispatches ZERO memory ops** (not even a recall search) — her reset-clean agent definition has **no `memory:{}` block**, so Worldtree never runs the memory pipeline for her. NOT a bind-grant failure, NOT promotion-timing. **PROVEN end-to-end** with a throwaway memory-enabled `ratatoskr:memprobe` (defined with `memory:{}`): 6 bound turns → 6 `memory.search` recalls + **4 `memory.upsert_many` → 4 real chunks in `memory.db`** → the pane renders all 4 (content·scope·origin·revision) live. **⚠ LEFTOVER debug state to optionally clean:** `ratatoskr:memprobe` agent on personal WT + its 4 test chunks (scope `{end_user:ratatoskr-tui}`) now sit in the live `memory.db` — harmless (they make the pane show real data) but they're test cruft; `scripts/reset-sindra-stores.sh` or a `DELETE /agents/ratatoskr:memprobe` clears them. **To fix sindra's memory for real: DELETE+redefine her WITH `memory:{}`** (memory is immutable post-define) — operator's call on her agent.
|
||||
|
||||
**(C) Markdown pass-2** — tables, nested lists, ordered-list numbering, streaming robustness (pass-1 RP speech/action coloring + paragraph reflow shipped `v0.20.6`).
|
||||
**(C) Markdown pass-2 SHIPPED** — `markdownSafe` extended: GFM pipe tables (`mdTable`, alignment colons), indentation-nested lists (stack of `<ul>`/`<ol>`, child list inside the open `<li>` = valid nested HTML), ordered-list `start=N` numbering, and streaming robustness (unterminated fence → partial code block; header-without-delimiter → paragraph until the delimiter streams in; never throws). esc-first → INV-004 held. Playwright-verified all cases.
|
||||
|
||||
**SHIPPED — web UI redesign via Claude Design (`v0.20.0`, MINOR, operator-approved).** The Claude Design prototype **`Ratatoskr Console.dc.html`** (project `bc0b65d1-a33e-422a-8bc1-3635c9112775`) was pulled via `DesignSync get_file` (design scopes already granted this session — no `/design-login` needed) and adapted into `src/ratatoskr/web/static/index.html`: translated OUT of the `.dc.html` dialect (`<x-dc>`/`<sc-if>`/`<sc-for>`/`{{}}`/`DCLogic`/external `_ds/` CSS — none runnable) into single-file/no-CDN/vanilla, with ALL real `/api/*` fetch + SSE wired into its DOM (endpoint set + SSE vocab unchanged from the prior SPA — ported verbatim, only DOM hooks re-targeted). New shape = a **3-column command-console**: left engine-ticker rail (DEBUG+ADMIN+tool/turn-lifecycle MERGED into one timeline via `tickerAdd` + a tools-armed chip list + a FULL-detail Bifrost rail pane) · center conversation (per-turn INLINE chain-of-thought, replacing the Think pane) · right RESIZABLE affect console (dominant/canonical-mood centerpiece + bipolar PAD faders EACH with a turn-to-turn Δ+sparkline + a P×A mood orbit + relations metric rows + canonical directive). ADDED (round 2, operator-requested): a **light/dark theme toggle** (dark default; FULL token override — surfaces+fg+borders+accent-as-text, since the designer's light theme only did surfaces → would've been light-on-light) + a **full-detail Bifrost pane** (endpoint/connected/consumer/caps/tools) + fixed the **engine-ticker spine** (was a container-anchored `::before` that scrolled out of view on auto-scroll → re-anchored to a content-height `.ticker-inner` wrapper) + **per-fader PAD turn-to-turn Δ+sparkline** (fills the room beside each meter, from the deduped-per-turn AFFECT_HIST) + an **INLINED data-URI favicon** (operator's `/home/lkraven/rata.png` — chibi aurora squirrel — downscaled 1024→64px via PIL, ~8.6KB base64, kills the /favicon.ico 404). ALL server routes UNCHANGED (**84 web tests green**). Verified BOTH lenses: `pytest tests/test_web_*` (84) + node Playwright drove the real UI end-to-end against personal :8081 (session open → Sindra seeded greeting → live turn SSE → affect console + relations + bifrost detail; theme toggle + PAD deltas + ticker spine + no-favicon-404 all confirmed, dark+light screenshots). `:8765` restarted on the new code. Contract `web_debug_surface.contract.md` amended in-commit (v0.20.0 presenter renames: `renderBifrostState`→`renderBifrost`, `renderAffectPane`→`renderConsole`, `setPersonaStrip` removed; INV-001/INV-004 held). **HONEST-SHAPE call (INV-001, agent-discretion within settled policy):** the dominant-emotion centerpiece shows a real OCC emotion (Tier-1) OR the CANONICAL mood word (Tier-3 e.g. Sindra→"positive and energized", dimmed) OR "—", NEVER a fabricated emotion; the affect-derived grid drops non-emitted intensity/decay-τ, shows only real/client-derived cells. **OPEN (operator's call):** the per-fader PAD Δ placement is a sensible default — operator offered to have the designer spec the exact treatment (hooks are in place to swap it). **`v0.20.1` patch (operator-reported UI):** fixed the relations sparkline overflowing onto the `n` (evidence-count) column — the sparkline grows one char/sample (HIST_CAP=24) and overflowed its fixed grid cell, covering `n`; now capped (relations last-8, faders last-7) + `overflow:hidden` clip; verified via Playwright injecting a 24-sample history (sparkline→n bounding-box overlap = 0). ADDED native `title` mouseover hints on all 3 PAD faders + every relationship metric row (meaning + range; static METRIC_HINTS, esc()'d). Added `state.lastSnap` (console can re-render without a refetch). Playwright-verified.
|
||||
|
||||
@@ -180,6 +177,7 @@ decision. Captures rationale that won't be obvious from code alone.
|
||||
|
||||
- `[2026-07-06]` **Web UI: pivot from incremental CSS polish to a designed prototype (Claude Design) that I wire into.** Operator saw an Australis polish pass ("looks fine, but we're attacking it differently") and chose the prototype route — a designer builds the visual shell, I wire real data/SSE into its DOM. Authored the full design brief `docs/design/ratatoskr-web-design-brief.md` (complete information inventory of every pane/datum/state + Australis direction + single-file/no-CDN/vanilla wire-ability constraints). **Tracking surface:** the brief file + Claude Design project `bc0b65d1-a33e-422a-8bc1-3635c9112775` (file `Ratatoskr Console.dc.html`). Import mechanism = the `DesignSync` MCP; blocked on `/design-login` (claude.ai design scopes) — see Current state for the post-auth wiring plan.
|
||||
- `[2026-07-07]` **Affect-egress reference delivered by worldtree-dev + a we-framing render DRIFT-WATCH banked.** worldtree-dev shipped `docs/affect-egress-consumer-reference.md` (`74d2408`, their origin/main) — the authoritative DELIVERED-on-wire vs HIDDEN (system-prompt-only) map for our affect surface. Confirms the v0.20.x console consumes it correctly: DELIVERED via affect.emit = pad + relations[RelationEdge] + dominant_emotion (**type-only, b23**; the #204 `affect_update` SSE is SUPPRESSED for Tier-3, so its richer `emotions_active` is Tier-1-only — we don't get it, and correctly poll our own affect store post-turn instead). HIDDEN render OUTPUTS are deterministically reconstructable from the canon; we reconstruct only the two FULLY-reconstructable (mood descriptor via `canonMood`, relationship directive via `canonDirective`) and SKIP the PARTIAL mood-directive (dominant_emotion is type-only/no-intensity → can't tell if the OCC directive fires at salience≥0.2 vs the PAD-band fallback) — honest per INV-001. Vendoring the ref doc as a `tolerate_drift` pin is SURFACED to Vuong (worldtree-dev will co-sign + honor a ping-on-change handshake, same as the d2-canon). **WE-FRAMING DRIFT-WATCH → STOOD DOWN (worldtree-dev 2026-07-07, `01KWXMQPHN…`).** The b24 3-gate we-framing conditional (drop "; avoid premature we-framing" under unsafe_capable+deep-warmth+expressive) was **REVERTED** — Vuong reframed it. So **`canonDirective` STAYS UNCONDITIONAL** (always appends the clause), which is CORRECT: it matches the currently-deployed renderer (b23) AND my pinned `affect-egress-consumer-reference.md` §2d (the doc reverted too — nothing changed on the wire or in my pin; NO re-vendor needed). The HOLD was right → ZERO rework. **NEW forward-watch (replaces this one):** the we-framing gate was a SYMPTOM — the render bakes enterprise safety-guards into the directive strings, so even `full`-tier characters get DEFANGED emotions (a hostile villain told to "keep a firm emotional boundary"). Fix = a **use-case-segregated persona render** (assistant / companion / RP-gaming), now a fresh **brokkr deep-research epic**. When it lands + is implemented, the render behavior for character/RP agents (→ our `canonDirective` + `canonEmotionDirective` reconstruction + the vendored d2 canons) will change MATERIALLY — worldtree-dev re-engages ratatoskr-dev then with the new reconstruction spec + a coordinated re-vendor. Until then: NO action, reconstruction stays as-is. [my unsafe_capable/mood_tier heuristic — character→full, agent→safe — was confirmed correct + is banked for whenever tier-gating returns]
|
||||
- `[2026-07-07]` **Web-UI iteration-3 SHIPPED (`v0.20.7`, patch) — all three queued items + the memory 0/0 root-caused.** (A) design iteration-3 (sparkline grid-bg, PAD Δ-bar strips replacing the polyline strips, dimetric-open-box mood orbit w/ JS replay) adapted into index.html; (B) memory viewer (provider `GET /memory/chunks` debug read on `:8392` → web `/api/memory/chunks` proxy → MEMORY console pane) + the mystery SETTLED: bind grants memory fine (`caps_granted:[memory,affect]`) but sindra emits ZERO memory ops → her reset-clean def lacks `memory:{}`; PROVEN via throwaway `ratatoskr:memprobe` (memory-enabled) → 4 real chunks landed + rendered in the pane; (C) markdown pass-2 (tables/nested-lists/ordered-start/streaming). 631 tests green; `:8392`+`:8765` restarted on new code. New env `RATATOSKR_MEMORY_READ_URL=:8392`. Contract `web_debug_surface.contract.md` amended in-commit. **Leftover:** memprobe agent + its 4 test chunks live in `memory.db` (optional cleanup). See Current state § ✅ SHIPPED for full detail. [supersedes the QUEUED entry below]
|
||||
- `[2026-07-07]` **Web-UI next-work QUEUED for a fresh-context session (operator-directed `/snapshot` handoff) — full specs in Current state § ⏭ QUEUED.** Three deferred-but-scoped items: **(A)** Claude Design prototype ITERATION-3 re-import (sparkline bg grid `<pattern>`, PAD strips → per-turn-Δ-bar HTML columns, mood orbit → DIMETRIC open-box az35/el25 D-right/A-left-back/P-up + JS-animated replay `orbitDynamics`); **(B)** memory viewer console pane (provider `GET /memory/chunks` read → web proxy → polling pane, mirror #18-D2; bundle a 6-turn bound round-trip proof to settle the 0/0-memory mystery); **(C)** markdown pass-2 (tables / nested lists / streaming). **Tracking surface:** the Claude Design projectId `bc0b65d1-a33e-422a-8bc1-3635c9112775` (durable — re-pull for exact coords) + this snapshot capture; operator-directed. Design scopes already granted (no `/design-login`).
|
||||
- `[2026-07-07]` **Markdown pass-1 SHIPPED (`v0.20.6`) — RP semantic coloring + paragraph reflow (operator-directed markdown rework, step ①).** The transcript renderer (`markdownSafe`/`mdInline`) now colors the two roleplay registers: `"quoted"` dialogue → SPEECH (bright `--md-speech`=fg-0), `*asterisk*` → ACTION/narration (muted-italic `--md-action`=fg-3, on `em.md-action`); both tunable via 2 CSS vars that cascade through `--fg-*` so they auto-adapt to the light theme. Plain text stays default narration. **KEY ORDERING:** speech-wrap runs BEFORE the em/link passes so a generated `class="md-action"` / `href="…"` quote can't be mis-read as dialogue (adversarially verified). Straight + smart quotes; apostrophes don't trigger; unbalanced/half-streamed quotes stay uncolored until they close. ALSO fixed the ugliest existing bug: single newlines were hard-`<br>`s → now CommonMark soft-breaks (space); a hard break needs 2+ trailing spaces or a trailing `\`. INV-004 escape-first preserved (html inside a quote escaped). Verified: 10-case Playwright unit-check of `markdownSafe` (speech/action/attr-trap/apostrophes/mixed/reflow/hard-break/escaping/unbalanced/paragraphs) all green + a visual render. Patch bump. **Markdown rework remaining: pass-2 (tables / nested lists / ordered-list numbering / streaming robustness); + the MEMORY VIEWER (step ②, console pane recommended) still queued.**
|
||||
- `[2026-07-07]` **Affect-derived tooltips (`v0.20.5`) + transparent squirrel vendored + v0.20.0–.5 PUSHED to origin (operator-authorized).** Native `title` hints on all 6 affect-derived cells (samples/updated/baseline P·A/drift Δv/volatility — each with meaning + Tier-1-vs-Tier-3 availability). Committed the transparent full-res brand mark at `docs/design/ratatoskr-mark.png` (1024², alpha; the bg-removed source the inlined favicon derives from — reproducible via the documented ImageMagick corner floodfill). The web-UI redesign arc (v0.20.0 Claude Design console → .1 sparkline-overflow+tooltips → .2 context-injection → .3 squirrel brand/favicon → .4 SVG sparklines+3D cube → .5 derived tooltips) is now all on `origin/main`.
|
||||
|
||||
Reference in New Issue
Block a user