From ca2c93e2700a4b871c994452c29db508777dc8e9 Mon Sep 17 00:00:00 2001 From: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:00:41 +0530 Subject: [PATCH] fix(intelligence): make page content scrollable inside Brain card (#4332) --- .claude/memory.md | 6 +++++ app/src/pages/Intelligence.tsx | 25 +++++++++++++------ app/src/pages/__tests__/Intelligence.test.tsx | 10 ++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.claude/memory.md b/.claude/memory.md index 19605869a..65c6a5ee2 100644 --- a/.claude/memory.md +++ b/.claude/memory.md @@ -328,3 +328,9 @@ Quick reference for anyone starting with Claude on this project. Updated by the - **Queue self-heal retries transient failures only** — `memory_queue/scheduler.rs` calls `store::requeue_transient_failed` every 3h (excludes `failure_class = 'unrecoverable'` so bad keys/budget don't retry-loop). Full reset stays manual via `memory_tree_retry_failed` RPC. - **`openhuman.memory_sources_reconcile` RPC** — Reports per-scope raw coverage `{total_raw_files, covered, pending}`; `execute: true` spawns background incremental rebuild. Same reconcile auto-runs after every sync via `check_and_rebuild_tree`. - **L0 seal gate is token-only by design** — `should_seal` has NO sibling-count fallback at L0 (docs used to lie about this); small buffers seal via the 3-hourly `flush_stale` path. L≥1 gates on `SUMMARY_FANOUT` (10) — an L1 buffer holding <10 summaries is healthy, not stuck. + +## Intelligence page scroll ownership (#4267) + +- **`app/src/pages/Intelligence.tsx` is rendered ONLY by `Brain.tsx`** (`/settings/intelligence` redirects to `/brain?tab=intelligence`), inside a card with `h-full overflow-hidden` (`Brain.tsx` ~line 255). A code comment there asserts the embedded panels "own their own scroll directly" — true for the sibling `PanelPage`/`PanelScaffold` panels (MemoryDataPanel etc.), but Intelligence previously returned a bare `
` with NO height/scroll, so its content overflowed the card and was clipped with no scrollbar. +- **Fix pattern (matches `PanelPage`)**: root `flex h-full min-h-0 flex-col` → pinned `SettingsHeader` wrapper (`flex-shrink-0`) → body `min-h-0 flex-1 overflow-y-auto` wrapping ChipTabs + the content card. `ToastContainer`/`ConfirmationModal` stay as siblings OUTSIDE the scroll region. The scroll body carries `data-testid="intelligence-scroll"` for the regression test. +- **Gotcha for any panel embedded in Brain's Knowledge/Memory card**: it must own its own vertical scroll (the card is `overflow-hidden`). Don't rely on a parent scroll — there isn't one. diff --git a/app/src/pages/Intelligence.tsx b/app/src/pages/Intelligence.tsx index b73afc52b..99bb841f5 100644 --- a/app/src/pages/Intelligence.tsx +++ b/app/src/pages/Intelligence.tsx @@ -201,15 +201,24 @@ export default function Intelligence({ tabParamKey = 'tab' }: IntelligenceProps const activeTabDef = tabs.find(tab => tab.id === activeTab); return ( -
- + // Rendered inside Brain's `h-full overflow-hidden` card, so this panel must + // own its own scroll (like the sibling PanelPage panels) — otherwise its + // content overflows the card and is clipped with no scrollbar (#4267). A + // flex column pins the header (`flex-shrink-0`) and gives the body the one + // vertical scroll (`min-h-0 flex-1 overflow-y-auto`). +
+
+ +
-
+
items={tabs.map(tab => ({ id: tab.id, diff --git a/app/src/pages/__tests__/Intelligence.test.tsx b/app/src/pages/__tests__/Intelligence.test.tsx index ef2e70ce5..4b7a7201c 100644 --- a/app/src/pages/__tests__/Intelligence.test.tsx +++ b/app/src/pages/__tests__/Intelligence.test.tsx @@ -97,4 +97,14 @@ describe('Intelligence URL-backed tab', () => { expect(screen.getByTestId('tab-council')).toBeInTheDocument(); expect(screen.getByText('selected:council')).toBeInTheDocument(); }); + + // Regression for #4267: rendered inside Brain's `overflow-hidden` card, the + // panel must own a vertical scroll region that wraps the tab content, or the + // content is clipped with no scrollbar. + it('wraps the tab content in an overflow-y-auto scroll container', () => { + renderAt('/intelligence'); + const scroll = screen.getByTestId('intelligence-scroll'); + expect(scroll).toHaveClass('overflow-y-auto'); + expect(scroll).toContainElement(screen.getByTestId('tab-tasks')); + }); });