You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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-schemeMutationObserver→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.
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
BrainAtmospherewrapper onto the branch and retarget its engine calls to the native engine already shipped onlocal/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
[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.[data-component="amico-brain-strip"]and no[data-timeline-row="brain"]element exists, and the timeline row model produces no "brain"-keyed row.[data-slot="session-turn-thinking"]/ the cycling-gerund + live-elapsed line), unchanged.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.data-color-scheme) repaints the Brain via a lossless theme swap without tearing down the atlas; both dark and light render.Testing Decisions
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.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.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.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
pointer-events: none, keyed on the active session id — empty on the landing.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).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.{ 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.Constraints & Invariants
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.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.#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.npm run lintclean and the app typechecks before the PR.Prior Art / Patterns
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.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.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.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).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.packages/ui/src/amicode/glass-tokens.ts,packages/ui/src/amicode/glass.css) are slice-3 material.Source
packages/app/docs/adr/0002-brain-as-chat-background.md