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-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.

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

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      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-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.

      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

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          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-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.

          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

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              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-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.

              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

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  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-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.

                  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

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      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-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.

                      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

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          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-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.

                          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

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              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-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.

                              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