Repository navigation
Mount the Brain chat-wide and absorb the inline brain strip #59
Description
Important
Decision surface: the mount seam (one full-bleed Brain per Chat window, keyed on the active session, behind the timeline + landing draft + composer), the engine-API retarget (reuse the old-branch full-bleed wrapper but point it at the engine already on the branch), and the live-feed source (the current inline strip's event-derivation, extracted and made session-scoped).
Problem
The Brain renders today as one collapsible strip inside the message timeline — a row among many. This slice promotes that single render loop from a timeline row to the Chat's full-bleed background so it frames the conversation instead of scrolling within it. See #56 for the full rationale.
Approach
Bring the old-branch full-bleed BrainAtmosphere wrapper onto the branch and retarget its engine calls to the native engine already shipped on local/amicode; mount it once per Chat window as a background layer behind the conversation timeline, the new-session/landing draft, and the composer; drive it from the session sync store using the inline strip's existing event-derivation (extracted to a pure, session-scoped function); then delete the inline brain-strip timeline row while keeping the timeline's working-indicator shimmer. Reason: the wrapper and engine are already de-risked — the only real work is the seam, the API retarget, and the strip removal.
Scope
In: a single full-bleed Brain background per Chat window behind timeline + landing draft + composer, keyed on the active session (titlebar tab swaps it); the live event feed wired from the session sync store so file/tool/vault touches light nodes; deletion of the inline strip row.
Out: two-tint AA Glass / any legibility work — messages read directly over the raw Brain here, acceptable for this slice's demo (slice 3). Motion/heartbeat/at-rest/sparse-seed/reduced-motion tempo changes — the engine's current motion is reused verbatim (slice 4). The perf gate + motion-ease fallback (slice 5). Rail, titlebar, Panels — nav chrome stays solid, untouched. Cross-session persistence — a fresh session's Brain is only what its own events produce (empty on the landing).
Acceptance Criteria
- In-session and on the landing/new-session draft, exactly one full-bleed Brain background layer (
[data-component="brain-atmosphere"]) exists per Chat window, painted behind the conversation timeline, the composer, and the draft; the Rail, titlebar, and Panels contain no Brain layer. - No inline brain strip renders anywhere in the timeline: for any session, no
[data-component="amico-brain-strip"]and no[data-timeline-row="brain"]element exists, and the timeline row model produces no "brain"-keyed row. - While a turn is working, the timeline still renders its working-indicator shimmer (
[data-slot="session-turn-thinking"]/ the cycling-gerund + live-elapsed line), unchanged. - Deriving Brain events from the session sync store for a given session's messages + parts yields: touch events in message order (mapped via the tool→brain-ref lookup), a chart marker after each completed assistant message that committed ≥2 non-consider touches,
replay=truefor completed turns andfalsefor the in-flight busy turn, and no duplicate ids. A fresh session with no assistant activity yields an empty stream. - Feeding that derived stream through the branch Brain engine grows its atlas / claimed-node count (data-true: touches light nodes); a replay-only first flush restores prior turns quietly (no live pulse travel).
- Switching the active session swaps the background to that session's derived stream via a keyed remount; only one engine / render loop is alive at a time (the prior engine is destroyed, no leaked animation frames).
- A chat-theme flip (
data-color-scheme) repaints the Brain via a lossless theme swap without tearing down the atlas; both dark and light render.
Testing Decisions
- CREATE a headless unit test for the extracted event-derivation function (the inline strip's
eventslogic, lifted to a pure function): feed sync-store-shaped fixtures (messages + parts) and assert message-order touches, consider-vs-commit counting, chart-after-≥2-commits-on-completed, thereplayflags, id de-duplication, and empty-on-fresh-session. This is the "live event feed" contract (AC 4) and the reused strip logic. - EXTEND the existing headless Brain-engine unit suite (the recording-2d-context engine test — no DOM required) with a background-mount driving case: replay the derived stream through the engine and assert
stats()shows atlas/claimed growth, that a replay-first-flush restores quietly (claimed lands withstats().queued === 0— no traveling pulse), and that a theme swap preserves the atlas (AC 5, AC 7). Flag: extends the brain-engine test surface. - CREATE a headless timeline row-model test (no
message-timeline*.test.*exists today): against the pureconstructMessageRows, a working turn (isActive,busy, no error) emits aThinkingrow — a fail-first guard that breaks if the strip-removal refactor regresses the shimmer (AC 3). The strip's removal (AC 2) is enforced by dropping theBrainmember from theTimelineRowunion, which makes the append/render-case/keep-mounted/key-branch/framed-exclusion deletions typecheck-required (the app typechecks before the PR); the runtime absence of[data-component="amico-brain-strip"]and[data-timeline-row="brain"]rides browser-first and the optional DOM assertion below. - Only if the app harness already supports component/DOM mounting, add render assertions for the AC 1/2/3/6 selectors (
brain-atmospherepresent behind the pane; noamico-brain-strip/brainrow;session-turn-thinkingpresent while working; background re-keys per session). Do not stand up a DOM harness that does not exist — the derivation + row-model + engine unit tests above carry the slice.
Key Decisions
- Mount seam: one full-bleed Brain per Chat window, absolutely positioned inside the shared Chat pane that hosts the conversation-timeline/landing switch and the composer dock (and the standalone new-session draft surface), painted above the pane's own opaque surface fill and below all Chat content,
pointer-events: none, keyed on the active session id — empty on the landing. - Engine retarget: reuse the old-branch full-bleed wrapper's shape and environment plumbing but point every engine call at the engine already on the branch. Method map: the old
suspend()→ the branchpause(); the oldrefreshTokens()→ the branchsetTheme(scheme)withschemeread fromdata-color-scheme. The old-engine-onlysetActive/setReducedMotionare dropped here (active-tempo + reduced-motion tempo are slice 4); the oldrequestRenderfont-ready hook degrades to aresize()/no-op repaint (non-load-bearing this slice). - Live feed: reuse the inline strip's exact derivation — touches in message order via the tool→brain-ref map, a chart after each completed assistant message with ≥2 commits,
replay=truefor completed turns, live for the busy turn — sourced from the session sync store and scoped to the active session. Extract it as a pure function so it is unit-testable and shared by the mount. - Data contract: derived event is
{ kind:"touch", id, label, type?, consider?, replay } | { kind:"chart", id, title }(the strip's existing shape), diffed/de-duplicated byid, first flush restoring charts silently. - Strip removal: delete the Brain timeline row — its tagged-class, its append into the row model, its render case, its keep-mounted special-case, its row-key branch, and its exclusion from the framed-row type. Keep the Thinking row / working-indicator shimmer untouched. The strip's click-to-expand/collapse is obviated (the engine has no node hit-testing, so nothing interactive is lost).
- Tab swap: a keyed remount on the active session id restores that session's atlas (completed turns replay silently on first flush), guaranteeing exactly one live engine/render loop.
Constraints & Invariants
- The full-bleed layer must be
pointer-events: noneand must never intercept scroll, wheel, touch, or click events bound for the timeline or composer — the timeline's scroll, bottom-lock, and history-load machinery must behave exactly as before. - Exactly one Brain engine / render loop per Chat window at any instant; a session switch destroys the prior engine with no leaked
requestAnimationFramechain and no parallel loops. Carry over the wrapper's visibility + intersection hard-suspend verbatim (hidden window or off-screen pane must stop frames) — it is reused behavior, not new. - Do not change the engine's motion, tempo, at-rest, or palette behavior and do not add any glass tint in this slice.
- Rail, titlebar, Panels, and the timeline's shimmer line are untouched.
- The Brain graph palette is exempt from the single-yellow chrome law (
#fff676stays reserved for live thought) — do not restyle the graph. data-color-schemedrives the engine scheme (dark default); a flip is a lossless repaint; both themes are first-class from day one.- Browser-first iteration (vite dev on the app package), not VSIX rebuilds.
npm run lintclean and the app typechecks before the PR.
Prior Art / Patterns
- Reuse (retarget) the full-bleed wrapper:
packages/ui/src/amicode/brain/brain-atmosphere.tsxon theopencode-brain-atmosphere-wtbranch — itsResizeObserver→resize,data-color-schemeMutationObserver→theme, document-visibility +IntersectionObserverhard-suspend,amicode:brain-hoverglance handler, cumulative-events-diffed-by-id, and silent-first-flush (initialFlush) plumbing are the shape to bring over. Point its engine calls at the engine already onlocal/amicode:packages/ui/src/amicode/brain-engine.ts(APItouch/chart/highlight/setTheme/resize/pause/resume/destroy/stats), re-exported viapackages/ui/src/components/brain-engine.tsx. - Reuse the event-derivation verbatim: the
eventsmemo +sent-set dedupe +initialFlushsemantics inpackages/app/src/pages/session/brain-strip.tsx, with the tool→node mapping inpackages/ui/src/amicode/brain-ref.ts. - Strip removal targets:
TimelineRow.Brain(tagged-class +keybranch) inpackages/app/src/pages/session/message-timeline.data.ts, and its render case +keepMountedbrain-index special-case + row-model append + framed-row exclusion inpackages/app/src/pages/session/message-timeline.tsx. KeepTimelineThinkingRow/ theThinkingLineimport there. - Mount points: the shared Chat pane in
packages/app/src/pages/session.tsx(the card wrapping the timeline/landing<Switch>andcomposerRegion("dock")), and the standalone draft pagepackages/app/src/pages/new-session.tsx(NewSessionDesignView). - Tests to extend / fixtures: the headless engine suite
packages/ui/src/amicode/brain-engine.test.ts; fixtures underpackages/ui/src/amicode/fixtures. The app has a headlessbun run test:unitsuite (.test.ts, no DOM) for the CREATE derivation + row-model tests. - Do NOT pull this slice: the glass tiers (
packages/ui/src/amicode/glass-tokens.ts,packages/ui/src/amicode/glass.css) are slice-3 material.
Source
- Part of Make the Brain the Chat's living background; float components on legibility-floored Glass #56
- Durable record: ADR
packages/app/docs/adr/0002-brain-as-chat-background.md
Activity
feat(amicode): route the session-busy signal into the Brain heartbeat (…