Skip to content

Mount the Brain chat-wide and absorb the inline brain strip #59

Description

@kateebonner

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=true for completed turns and false for 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 events logic, 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, the replay flags, 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 with stats().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 pure constructMessageRows, a working turn (isActive, busy, no error) emits a Thinking row — 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 the Brain member from the TimelineRow union, 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-atmosphere present behind the pane; no amico-brain-strip / brain row; session-turn-thinking present 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 branch pause(); the old refreshTokens() → the branch setTheme(scheme) with scheme read from data-color-scheme. The old-engine-only setActive / setReducedMotion are dropped here (active-tempo + reduced-motion tempo are slice 4); the old requestRender font-ready hook degrades to a resize()/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=true for 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 by id, 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: none and 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 requestAnimationFrame chain 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 (#fff676 stays reserved for live thought) — do not restyle the graph.
  • data-color-scheme drives 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 lint clean and the app typechecks before the PR.
Prior Art / Patterns
  • Reuse (retarget) the full-bleed wrapper: packages/ui/src/amicode/brain/brain-atmosphere.tsx on the opencode-brain-atmosphere-wt branch — its ResizeObserver→resize, data-color-scheme MutationObserver→theme, document-visibility + IntersectionObserver hard-suspend, amicode:brain-hover glance 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 on local/amicode: packages/ui/src/amicode/brain-engine.ts (API touch/chart/highlight/setTheme/resize/pause/resume/destroy/stats), re-exported via packages/ui/src/components/brain-engine.tsx.
  • Reuse the event-derivation verbatim: the events memo + sent-set dedupe + initialFlush semantics in packages/app/src/pages/session/brain-strip.tsx, with the tool→node mapping in packages/ui/src/amicode/brain-ref.ts.
  • Strip removal targets: TimelineRow.Brain (tagged-class + key branch) in packages/app/src/pages/session/message-timeline.data.ts, and its render case + keepMounted brain-index special-case + row-model append + framed-row exclusion in packages/app/src/pages/session/message-timeline.tsx. Keep TimelineThinkingRow / the ThinkingLine import there.
  • Mount points: the shared Chat pane in packages/app/src/pages/session.tsx (the card wrapping the timeline/landing <Switch> and composerRegion("dock")), and the standalone draft page packages/app/src/pages/new-session.tsx (NewSessionDesignView).
  • Tests to extend / fixtures: the headless engine suite packages/ui/src/amicode/brain-engine.test.ts; fixtures under packages/ui/src/amicode/fixtures. The app has a headless bun run test:unit suite (.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

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkImplement + merge unattended

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions