/navigation-scroll
Navigation and scroll orchestration — tab navigation, error highlights, search scrolling, auto-scroll coordination, and common bug patterns. Use when working on useTabNavigationController, scroll restore, or navigation requests.
How it fires
How this command gets triggered: by you, by Claude, or both.
- Fires itselfClaude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/navigation-scroll
Context preview
What this command does when you run it.
Navigation and scroll orchestration — tab navigation, error highlights, search scrolling, auto-scroll coordination, and common bug patterns. Use when working on useTabNavigationController, scroll restore, or navigation requests.
Command definition
navigation-scroll.mdname: claude-devtools:navigation-scroll
description: Navigation and scroll orchestration — tab navigation, error highlights, search scrolling, auto-scroll coordination, and common bug patterns. Use when working on useTabNavigationController, scroll restore, or navigation requests.
Navigation & Scroll Orchestration
How tab navigation (error highlights, search scrolling, auto-scroll) works end-to-end.
Architecture
Navigation Request Model (Nonce-Based)
// src/renderer/types/tabs.ts
interface TabNavigationRequest {
id: string; // crypto.randomUUID() — fresh nonce per click
kind: 'error' | 'search' | 'autoBottom';
highlight: 'red' | 'yellow' | 'none';
payload: ErrorNavigationPayload | SearchNavigationPayload | {};
source: 'notification' | 'triggerPreview' | 'commandPalette' | 'sessionOpen';
}
// Stored on Tab:
interface Tab {
pendingNavigation?: TabNavigationRequest; // Set by enqueue, cleared by consume
lastConsumedNavigationId?: string; // Tracks last processed request
}Store Actions (tabSlice.ts)
| Action | Purpose | |--------|---------| | `enqueueTabNavigation(tabId, request)` | Set `pendingNavigation` on a tab | | `consumeTabNavigation(tabId, requestId)` | Clear `pendingNavigation`, record `lastConsumedNavigationId` |
Navigation Sources
| Source | Slice | Creates | |--------|-------|---------| | Notification click / test trigger | `notificationSlice.navigateToError()` | `ErrorNavigationRequest` (red) | | CommandPalette search result | `tabSlice.navigateToSession()` | `SearchNavigationRequest` (yellow) |
Controller Hook: `useTabNavigationController`
**Location:** `src/renderer/hooks/useTabNavigationController.ts`
Phase state machine:
idle → pending → expanding → scrolling → highlighting → complete → idle
Key behaviors:
- **Active-tab-only:** Ignores `!isActiveTab` to prevent cross-tab races
- **Nonce dedup:** `activeRequestIdRef.current === pendingNavigation.id` prevents reprocessing
- **Failure debounce:** 500ms cooldown after failed navigation (`lastFailureAtRef`)
- **Abort support:** New navigation aborts in-progress one via `AbortController`
- **Highlight-first:** Highlight is set BEFORE scroll (best-effort scroll, guaranteed highlight)
Scroll Precedence (ChatHistory.tsx)
Three scroll systems compete — navigation wins:
| System | Guard | Priority | |--------|-------|----------| | Navigation scroll | Controller's `executeNavigation` | Highest | | Scroll restore (tab switch) | `!shouldDisableAutoScroll` | Medium | | Auto-scroll to bottom | `disabled: shouldDisableAutoScroll` | Lowest |
`shouldDisableAutoScroll` is `true` during ANY navigation phase or when `pendingNavigation` exists.
Key Files
| File | Role | |------|------| | `src/renderer/hooks/useTabNavigationController.ts` | Unified navigation controller | | `src/renderer/hooks/navigation/utils.ts` | Shared helpers (scroll calc, element lookup, visibility) | | `src/renderer/components/chat/ChatHistory.tsx` | Scroll restore + auto-scroll coordination | | `src/renderer/store/slices/tabSlice.ts` | `enqueueTabNavigation`, `consumeTabNavigation`, `navigateToSession` | | `src/renderer/store/slices/notificationSlice.ts` | `navigateToError` | | `src/renderer/store/slices/sessionDetailSlice.ts` | `fetchSessionDetail` (sets `conversationLoading`) | | `src/renderer/types/tabs.ts` | `TabNavigationRequest` types + factory helpers |
Common Bug Patterns
1. Scroll Restore Overrides Navigation
**Symptom:** Scrolls to target, then snaps back to top/previous position.
**Root cause:** The scroll restore effect fires after `consumeTabNavigation` clears `pendingNavigation`. If the guard only checks `!pendingNavigation`, it triggers while navigation highlight is still active.
**Fix pattern:** Guard scroll restore with `!shouldDisableAutoScroll` instead of `!pendingNavigation`. The controller's `shouldDisableAutoScroll` covers the FULL lifecycle (pending → complete), not just while `pendingNavigation` exists.
**Additional:** Save scroll position when `shouldDisableAutoScroll` transitions true→false (navigation completed) to prevent stale `savedScrollTop` from being restored later.
// ChatHistory.tsx — scroll restore effect
useEffect(() => {
const wasDisabled = prevShouldDisableRef.current;
prevShouldDisableRef.current = shouldDisableAutoScroll;
// Navigation just completed — save current position, skip restore
if (wasDisabled && !shouldDisableAutoScroll && scrollContainerRef.current) {
saveScrollPosition(scrollContainerRef.current.scrollTop);
return;
}
if (isThisTabActive && savedScrollTop !== undefined && !conversationLoading && !shouldDisableAutoScroll) {
// ... restore logic
}
}, [isThisTabActive, savedScrollTop, conversationLoading, shouldDisableAutoScroll, saveScrollPosition]);2. Redundant `fetchSessionDetail` Unmounts ChatHistory
**Symptom:** Navigation doesn't scroll at all, or session "reloads" unnecessarily.
**Root cause:** `navigateToSession` or `navigateToError` calls `fetchSessionDetail` even when the session is already loaded in an existing tab. This sets `conversationLoading: true`, causing ChatHistory to unmount (show loading spinner) and remount — losing scroll container and controller state.
**Fix pattern:** Only call `fetchSessionDetail` for NEW tabs. For existing tabs, `setActiveTab` already handles the fetch when `sessionChanged` is true.
// tabSlice.ts — navigateToSession
if (existingTab) {
state.setActiveTab(existingTab.id);
// NO fetchSessionDetail — setActiveTab handles it
} else {
state.openTab({ ... });
void state.fetchSessionDetail(projectId, sessionId); // Only for new tabs
}3. Highlight Not Showing (Strict Post-Scroll Gates)
**Symptom:** Scrolls to correct location but no red/yellow highlight ring appears.
**Root cause:** `executeErrorNavigation` / `executeSearchNavigation` returns `false` after scroll due
Read more
name: claude-devtools:navigation-scroll description: Navigation and scroll orchestration — tab navigation, error highlights, search scrolling, auto-scroll coordination, and common bug patterns. Use when working on useTabNavigationController, scroll restore, or navigation requests.
Navigation & Scroll Orchestration
How tab navigation (error highlights, search scrolling, auto-scroll) works end-to-end.
Architecture
Navigation Request Model (Nonce-Based)
// src/renderer/types/tabs.ts
interface TabNavigationRequest {
id: string; // crypto.randomUUID() — fresh nonce per click
kind: 'error' | 'search' | 'autoBottom';
highlight: 'red' | 'yellow' | 'none';
payload: ErrorNavigationPayload | SearchNavigationPayload | {};
source: 'notification' | 'triggerPreview' | 'commandPalette' | 'sessionOpen';
}
// Stored on Tab:
interface Tab {
pendingNavigation?: TabNavigationRequest; // Set by enqueue, cleared by consume
lastConsumedNavigationId?: string; // Tracks last processed request
}Store Actions (tabSlice.ts)
| Action | Purpose | |--------|---------| | `enqueueTabNavigation(tabId, request)` | Set `pendingNavigation` on a tab | | `consumeTabNavigation(tabId, requestId)` | Clear `pendingNavigation`, record `lastConsumedNavigationId` |
Navigation Sources
| Source | Slice | Creates | |--------|-------|---------| | Notification click / test trigger | `notificationSlice.navigateToError()` | `ErrorNavigationRequest` (red) | | CommandPalette search result | `tabSlice.navigateToSession()` | `SearchNavigationRequest` (yellow) |
Controller Hook: `useTabNavigationController`
**Location:** `src/renderer/hooks/useTabNavigationController.ts`
Phase state machine:
idle → pending → expanding → scrolling → highlighting → complete → idle
Key behaviors:
- **Active-tab-only:** Ignores `!isActiveTab` to prevent cross-tab races
- **Nonce dedup:** `activeRequestIdRef.current === pendingNavigation.id` prevents reprocessing
- **Failure debounce:** 500ms cooldown after failed navigation (`lastFailureAtRef`)
- **Abort support:** New navigation aborts in-progress one via `AbortController`
- **Highlight-first:** Highlight is set BEFORE scroll (best-effort scroll, guaranteed highlight)
Scroll Precedence (ChatHistory.tsx)
Three scroll systems compete — navigation wins:
| System | Guard | Priority | |--------|-------|----------| | Navigation scroll | Controller's `executeNavigation` | Highest | | Scroll restore (tab switch) | `!shouldDisableAutoScroll` | Medium | | Auto-scroll to bottom | `disabled: shouldDisableAutoScroll` | Lowest |
`shouldDisableAutoScroll` is `true` during ANY navigation phase or when `pendingNavigation` exists.
Key Files
| File | Role | |------|------| | `src/renderer/hooks/useTabNavigationController.ts` | Unified navigation controller | | `src/renderer/hooks/navigation/utils.ts` | Shared helpers (scroll calc, element lookup, visibility) | | `src/renderer/components/chat/ChatHistory.tsx` | Scroll restore + auto-scroll coordination | | `src/renderer/store/slices/tabSlice.ts` | `enqueueTabNavigation`, `consumeTabNavigation`, `navigateToSession` | | `src/renderer/store/slices/notificationSlice.ts` | `navigateToError` | | `src/renderer/store/slices/sessionDetailSlice.ts` | `fetchSessionDetail` (sets `conversationLoading`) | | `src/renderer/types/tabs.ts` | `TabNavigationRequest` types + factory helpers |
Common Bug Patterns
1. Scroll Restore Overrides Navigation
**Symptom:** Scrolls to target, then snaps back to top/previous position.
**Root cause:** The scroll restore effect fires after `consumeTabNavigation` clears `pendingNavigation`. If the guard only checks `!pendingNavigation`, it triggers while navigation highlight is still active.
**Fix pattern:** Guard scroll restore with `!shouldDisableAutoScroll` instead of `!pendingNavigation`. The controller's `shouldDisableAutoScroll` covers the FULL lifecycle (pending → complete), not just while `pendingNavigation` exists.
**Additional:** Save scroll position when `shouldDisableAutoScroll` transitions true→false (navigation completed) to prevent stale `savedScrollTop` from being restored later.
// ChatHistory.tsx — scroll restore effect
useEffect(() => {
const wasDisabled = prevShouldDisableRef.current;
prevShouldDisableRef.current = shouldDisableAutoScroll;
// Navigation just completed — save current position, skip restore
if (wasDisabled && !shouldDisableAutoScroll && scrollContainerRef.current) {
saveScrollPosition(scrollContainerRef.current.scrollTop);
return;
}
if (isThisTabActive && savedScrollTop !== undefined && !conversationLoading && !shouldDisableAutoScroll) {
// ... restore logic
}
}, [isThisTabActive, savedScrollTop, conversationLoading, shouldDisableAutoScroll, saveScrollPosition]);2. Redundant `fetchSessionDetail` Unmounts ChatHistory
**Symptom:** Navigation doesn't scroll at all, or session "reloads" unnecessarily.
**Root cause:** `navigateToSession` or `navigateToError` calls `fetchSessionDetail` even when the session is already loaded in an existing tab. This sets `conversationLoading: true`, causing ChatHistory to unmount (show loading spinner) and remount — losing scroll container and controller state.
**Fix pattern:** Only call `fetchSessionDetail` for NEW tabs. For existing tabs, `setActiveTab` already handles the fetch when `sessionChanged` is true.
// tabSlice.ts — navigateToSession
if (existingTab) {
state.setActiveTab(existingTab.id);
// NO fetchSessionDetail — setActiveTab handles it
} else {
state.openTab({ ... });
void state.fetchSessionDetail(projectId, sessionId); // Only for new tabs
}3. Highlight Not Showing (Strict Post-Scroll Gates)
**Symptom:** Scrolls to correct location but no red/yellow highlight ring appears.
**Root cause:** `executeErrorNavigation` / `executeSearchNavigation` returns `false` after scroll due
The missing DevTools for Claude Code — inspect session logs, tool calls, token usage, subagents, and context window in a visual UI. Free, open source.
Repo: matt1398/claude-devtools
Other commands on claude-devtools.
- /chatgroup-architecture
ChatGroup architecture — how conversation data flows from raw JSONL to rendered chat groups. Use when working on UserGroup, AIGroup, SystemGroup, display items, tool linking, chunks, or the rendering hierarchy.
Open command - /design-system
Design system and visual language — theming, CSS variables, Tailwind config, component styling patterns, icon usage, animations, and z-index layers. Use when creating or modifying UI components, working with the dark/light theme, or debugging visual issues.
Open command - /explain-visible-context
Explains what "Visible Context" is — the 6 trackable token categories, what falls outside tracking, how it's displayed, and why it matters. Use when someone asks about visible context, token attribution, or context window usage.
Open command - /markdown-search-logic
Markdown search logic — how in-session and cross-session search works. Use when working on SearchBar, search highlighting, searchHighlightUtils, markdownTextSearch, or SessionSearcher.
Open command

