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'));
+ });
});