-```
-
-### TypeScript Constants
-
-`src/renderer/constants/cssVariables.ts` centralizes CSS variable strings:
-
-```typescript
-import { COLOR_TEXT_MUTED, CARD_BG, CARD_BORDER_STYLE } from '@renderer/constants/cssVariables';
-
-
Muted text
-
Card
-```
-
-Constants cover: text colors, surfaces, borders, code blocks, diff, cards, tags, prose.
-
-## CSS Variable Reference
-
-All defined in `src/renderer/index.css` under `:root` (dark) and `:root.light`.
-
-### Surfaces
-
-| Variable | Dark | Light | Usage |
-|----------|------|-------|-------|
-| `--color-surface` | `#141416` | `#f9f9f7` | Main background |
-| `--color-surface-raised` | `#27272a` | `#f0efed` | Elevated surfaces |
-| `--color-surface-overlay` | `#27272a` | `#e8e7e4` | Overlays/modals |
-| `--color-surface-sidebar` | `#0f0f11` | `#f1f0ee` | Sidebar background |
-
-### Text
-
-| Variable | Dark | Light | Usage |
-|----------|------|-------|-------|
-| `--color-text` | `#fafafa` | `#1c1b19` | Primary text |
-| `--color-text-secondary` | `#a1a1aa` | `#4d4b46` | Secondary text |
-| `--color-text-muted` | `#71717a` | `#6d6b65` | Muted text |
-
-### Borders
-
-| Variable | Dark | Light |
-|----------|------|-------|
-| `--color-border` | `rgba(255,255,255,0.05)` | `#d5d3cf` |
-| `--color-border-subtle` | `rgba(255,255,255,0.05)` | `#e3e1dd` |
-| `--color-border-emphasis` | `rgba(255,255,255,0.1)` | `#a8a5a0` |
-
-### Chat Bubbles
-
-**User bubble** (right-aligned):
-| Variable | Dark | Light |
-|----------|------|-------|
-| `--chat-user-bg` | `#27272a` | `#eae9e6` |
-| `--chat-user-text` | `#a1a1aa` | `#5a5955` |
-| `--chat-user-border` | `rgba(255,255,255,0.08)` | `#d5d3cf` |
-| `--chat-user-shadow` | `0 1px 0 0 rgba(255,255,255,0.03)` | `0 1px 2px 0 rgba(0,0,0,0.04)` |
-| `--chat-user-tag-bg` | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.05)` |
-| `--chat-user-tag-text` | `#e4e4e7` | `#3a3935` |
-| `--chat-user-tag-border` | `rgba(255,255,255,0.12)` | `rgba(0,0,0,0.08)` |
-
-**AI message**:
-| Variable | Dark | Light |
-|----------|------|-------|
-| `--chat-ai-border` | `rgba(255,255,255,0.05)` | `#d5d3cf` |
-| `--chat-ai-icon` | `#71717a` | `#6d6b65` |
-
-**System bubble**:
-| Variable | Dark | Light |
-|----------|------|-------|
-| `--chat-system-bg` | `rgba(39,39,42,0.5)` | `#eae9e6` |
-| `--chat-system-text` | `#d4d4d8` | `#3a3935` |
-
-### Code & Syntax
-
-| Variable | Dark | Light |
-|----------|------|-------|
-| `--code-bg` | `#1c1c1e` | `#f0efed` |
-| `--code-header-bg` | `#1c1c1e` | `#eae9e6` |
-| `--code-border` | `rgba(255,255,255,0.1)` | `#d5d3cf` |
-| `--code-line-number` | `#52525b` | `#a8a5a0` |
-| `--code-filename` | `#60a5fa` | `#2563eb` |
-| `--inline-code-bg` | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.05)` |
-| `--inline-code-text` | `#e4e4e7` | `#3a3935` |
-
-Syntax highlighting: `--syntax-string`, `--syntax-comment`, `--syntax-number`, `--syntax-keyword`, `--syntax-type`, `--syntax-operator`, `--syntax-function`. Dark uses vibrant colors; light uses GitHub-inspired palette.
-
-### Semantic Blocks
-
-**Thinking**: Purple tones (`--thinking-bg`, `--thinking-border`, `--thinking-text`)
-**Tool call**: Amber tones (`--tool-call-bg`, `--tool-call-border`, `--tool-call-text`)
-**Tool result success**: Green tones (`--tool-result-success-bg/border/text`)
-**Tool result error**: Red tones (`--tool-result-error-bg/border/text`)
-**Output**: Gray tones (`--output-bg`, `--output-border`, `--output-text`)
-**Interruption**: Red (`--interruption-bg/border/text`)
-**Warning**: Amber (`--warning-bg/border/text`)
-**Plan exit**: Green (`--plan-exit-bg/header-bg/border/text`)
-
-### Diff Viewer
-
-| Variable | Dark | Light |
-|----------|------|-------|
-| `--diff-added-bg` | `rgba(34,197,94,0.15)` | `rgba(34,197,94,0.1)` |
-| `--diff-added-text` | `#4ade80` | `#166534` |
-| `--diff-removed-bg` | `rgba(239,68,68,0.15)` | `rgba(239,68,68,0.1)` |
-| `--diff-removed-text` | `#f87171` | `#991b1b` |
-
-### Cards (Subagents)
-
-| Variable | Dark | Light |
-|----------|------|-------|
-| `--card-bg` | `#121212` | `#f9f9f7` |
-| `--card-border` | `#27272a` | `#d5d3cf` |
-| `--card-header-bg` | `#18181b` | `#f0efed` |
-| `--card-header-hover` | `#1f1f23` | `#eae9e6` |
-| `--card-icon-muted` | `#52525b` | `#a8a5a0` |
-| `--card-separator` | `#3f3f46` | `#d5d3cf` |
-
-### Badges
-
-Status badges: `--badge-error-bg/text`, `--badge-warning-bg/text`, `--badge-success-bg/text`, `--badge-info-bg/text`, `--badge-neutral-bg/text`.
-Tags: `--tag-bg`, `--tag-text`, `--tag-border`.
-
-### Search Highlights
-
-| Variable | Dark | Light |
-|----------|------|-------|
-| `--highlight-bg` | `rgba(202,138,4,0.7)` | `#facc15` |
-| `--highlight-bg-inactive` | `rgba(113,63,18,0.5)` | `#fef08a` |
-| `--highlight-ring` | `#facc15` | `#ca8a04` |
-
-### Scrollbar
-
-Custom scrollbar styling via `--scrollbar-thumb`, `--scrollbar-thumb-hover`, `--scrollbar-thumb-active`.
-
-## Tailwind Config
-
-`tailwind.config.js` maps CSS variables to Tailwind classes:
-
-```javascript
-colors: {
- surface: {
- DEFAULT: 'var(--color-surface)',
- raised: 'var(--color-surface-raised)',
- overlay: 'var(--color-surface-overlay)',
- sidebar: 'var(--color-surface-sidebar)',
- code: 'var(--code-bg)',
- },
- border: {
- DEFAULT: 'var(--color-border)',
- subtle: 'var(--color-border-subtle)',
- emphasis: 'var(--color-border-emphasis)',
- },
- text: {
- DEFAULT: 'var(--color-text)',
- secondary: 'var(--color-text-secondary)',
- muted: 'var(--color-text-muted)',
- },
- semantic: {
- success: '#22c55e',
- error: '#ef4444',
- warning: '#f59e0b',
- info: '#3b82f6',
- },
- // Backward-compatible alias
- 'claude-dark': { bg: 'var(--color-surface)', surface: 'var(--color-surface-raised)', ... },
-}
-```
-
-Plugin: `@tailwindcss/typography` for markdown prose.
-
-## Icon Library
-
-`lucide-react` across 55+ components.
-
-**Sizes**: `size-3` (tiny), `size-3.5` (small), `size-4` (standard), `size-5` (medium), `size-10` (empty states)
-
-**Pattern**: `className` for size, `style` for color:
-```tsx
-
-
-```
-
-**Common icons**: `Bot` (AI), `User` (user), `ChevronRight`/`ChevronDown` (expansion), `Check`/`Copy` (clipboard), `Info` (tooltips), `Loader2` (loading), `Clock` (duration), `Terminal` (traces), `CheckCircle2` (completed).
-
-## Team Colors
-
-`src/renderer/constants/teamColors.ts` defines 8 color sets for teammate visualization:
-
-```typescript
-import { getTeamColorSet, TeamColorSet } from '@renderer/constants/teamColors';
-
-const colors = getTeamColorSet('blue');
-// colors.border: '#3b82f6' — border accent
-// colors.badge: 'rgba(59, 130, 246, 0.15)' — badge background
-// colors.text: '#60a5fa' — text color
-```
-
-Available colors: blue, green, red, yellow, purple, cyan, orange, pink.
-
-## Component Patterns
-
-### User Chat Bubble
-
-Right-aligned with rounded corners, subtle shadow, copy overlay on hover:
-
-```tsx
-
-```
-
-### AI Group Header
-
-Collapsible with model badge, summary, metrics:
-
-```tsx
-
-
- Claude
- {model.name}
- {itemsSummary}
-
-
-```
-
-### Subagent Card
-
-Linear-style card with nested expansion:
-
-```tsx
-
-
-
- {/* colored dot + badge + description + metrics */}
-
- {isExpanded &&
{/* content */}
}
-
-```
-
-### Copy Button (Overlay)
-
-Gradient-fade overlay that appears on group hover:
-
-```tsx
-
-```
-
-### Popover via Portal
-
-Token usage and context badges use portaled popovers to escape stacking context:
-
-```tsx
-{showPopover && createPortal(
-
- {/* content */}
-
,
- document.body
-)}
-```
-
-## Animations
-
-### CSS Keyframes (in `index.css`)
-
-- `shimmer` — skeleton loading shimmer effect
-- `skeleton-fade-in` — staggered fade-in for skeleton cards
-- `splash-slide` — splash screen loading bar
-
-### Tailwind Utilities
-
-| Class | Usage |
-|-------|-------|
-| `animate-spin` | Loading spinners (`Loader2`) |
-| `animate-ping` | Pulsing dots (ongoing state) |
-| `transition-transform` | Chevron rotation |
-| `transition-colors` | Hover color changes |
-| `transition-opacity` | Copy button fade-in |
-| `transition-all duration-300` | Card highlight transitions |
-| `duration-[3000ms]` | Highlight ring fade-out |
-
-## Z-Index Layers
-
-| Z-Index | Usage |
-|---------|-------|
-| `z-10` | Copy button overlays, dropdown backdrops |
-| `z-20` | Dropdown menus, search bar |
-| `z-30` | Pane split drop zones |
-| `z-40` | Pane view overlays |
-| `z-50` | Context menus, command palette, settings selects |
-| `99999` | Portaled popovers (token usage, context badge, metrics pill) |
-
-## Light Theme Notes
-
-The light theme uses **warm neutrals** (not pure white/gray):
-- Backgrounds: `#f9f9f7`, `#f0efed`, `#eae9e6` (warm off-white)
-- Borders: `#d5d3cf`, `#e3e1dd` (warm gray)
-- Text: `#1c1b19`, `#4d4b46`, `#6d6b65` (warm dark)
-- Syntax highlighting: GitHub-inspired palette
-
-Body transition: `background-color 0.2s ease, color 0.2s ease` for smooth theme switching.
diff --git a/.claude/commands/devtools/explain-visible-context.md b/.claude/commands/devtools/explain-visible-context.md
deleted file mode 100644
index c48c66a6..00000000
--- a/.claude/commands/devtools/explain-visible-context.md
+++ /dev/null
@@ -1,104 +0,0 @@
----
-name: claude-devtools:explain-visible-context
-description: 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.
----
-
-Present the following explanation directly to the user. Output the full content below as your response — do not summarize, ask follow-up questions, or treat this as background context.
-
-# Visible Context
-
-## What It Is
-
-"Visible Context" is the portion of Claude's context window that we can identify and measure. Every time Claude processes a turn, its context window fills with various pieces of information — your messages, file contents, tool outputs, thinking, and more. Visible Context tracks what we **can** attribute to a known source, so you can see where your tokens are going.
-
-## What We Track (6 Categories)
-
-### CLAUDE.md Files
-
-Memory files that Claude loads automatically at the start of every session and after each compaction. These include:
-
-- **Global** CLAUDE.md (`~/.claude/CLAUDE.md`) — your personal instructions across all projects
-- **Project** CLAUDE.md (`.claude/CLAUDE.md` or `CLAUDE.md` at project root) — project-specific instructions
-- **Directory** CLAUDE.md — instructions scoped to subdirectories (e.g., `src/renderer/CLAUDE.md`)
-
-These are injected repeatedly (once per compaction phase), so their token cost accumulates. A 500-token CLAUDE.md file injected across 3 compaction phases costs ~1,500 tokens total.
-
-### @-Mentioned Files
-
-Files you reference with `@path/to/file` in your messages. When you mention a file, Claude Code injects the full file contents into the context. Large files consume significant tokens — a 1,000-line source file could use 5,000+ tokens per mention.
-
-### Tool Outputs
-
-Results returned from tool executions: file reads (`Read`), command output (`Bash`), search results (`Grep`, `Glob`), and others. Every tool result stays in the context window until compaction. A `Bash` command that prints 500 lines of output or a `Read` of a large file both count here.
-
-### Thinking + Text Output
-
-Claude's own output that consumes context:
-
-- **Extended thinking** — Claude's internal reasoning (when thinking mode is active). This can be substantial for complex tasks.
-- **Text output** — Claude's visible responses to you. Longer explanations and code blocks use more tokens.
-
-### Task Coordination
-
-Messages and operations from Claude Code's team/orchestration features:
-
-- `SendMessage` — messages between teammates
-- `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` — task management
-- `TeamCreate`, `TeamDelete` — team lifecycle
-
-Each coordination message adds to the context window of the receiving agent.
-
-### User Messages
-
-Your actual prompt text for each turn. This includes the raw text you type, but not the system-injected metadata around it.
-
-## What We Don't Track
-
-Visible Context does **not** cover everything in Claude's context window. The following are present but not attributable by our tracking:
-
-- **Claude Code's system prompt** — the base instructions that tell Claude how to behave, use tools, format output, etc.
-- **Tool descriptions** — the schema and documentation for each built-in tool (Read, Write, Edit, Bash, Grep, Glob, etc.)
-- **MCP tool descriptions** — schemas for any MCP (Model Context Protocol) servers you have connected
-- **Custom agent definitions** — instructions from `.claude/agents/` configurations
-- **Skill descriptions** — the short descriptions of available skills that Claude sees so it knows what's available (visible via `/context` in Claude Code)
-- **Internal system reminders** — `
` injections that Claude Code adds for session state, git status, available skills, etc.
-- **Conversation structure overhead** — the message formatting, role markers, and protocol framing around each message
-
-These untracked items form a "base cost" that's always present. You can see what Claude Code injects via the `/context` command in Claude Code itself.
-
-## How It's Displayed
-
-### Per-Turn Popover (Context Badge)
-
-Each AI group in the chat shows a small badge. Hovering reveals what was injected at that specific turn — which CLAUDE.md files, which @-mentioned files, which tool outputs contributed tokens.
-
-### Token Usage Popover
-
-The token count next to each AI group has an info icon. Hovering shows the standard input/output/cache breakdown, plus an expandable "Visible Context" section showing the percentage of total tokens attributable to each tracked category.
-
-### Session Context Panel
-
-A dedicated panel (toggle via the context badge or header button) that shows the full session-wide view:
-
-- All tracked injections grouped by category
-- Token estimates per injection
-- Phase filtering (if compaction events split the session into phases)
-- Total visible context as a percentage of total session tokens
-
-## Compaction Phases
-
-When Claude's context window fills up, Claude Code compacts the conversation — summarizing older messages to free space. Each compaction creates a new "phase." Visible Context tracks injections per phase because:
-
-- CLAUDE.md files are re-injected after each compaction
-- Previous tool outputs and file contents are summarized away
-- The phase selector lets you see what's in context **right now** (current phase) vs. what was present earlier
-
-## Why Visible Context Matters
-
-Understanding where tokens go helps you:
-
-- **Spot expensive injections** — a massive CLAUDE.md file or a frequently-mentioned large file could be using 20%+ of your context
-- **Optimize CLAUDE.md** — keep memory files concise; every token is repeated across phases
-- **Be strategic with @-mentions** — mentioning a 2,000-line file costs real context space
-- **Understand compaction impact** — see how much context resets after compaction
-- **Debug unexpected behavior** — if Claude seems to "forget" something, check whether it was compacted away
diff --git a/.claude/commands/devtools/markdown-search-logic.md b/.claude/commands/devtools/markdown-search-logic.md
deleted file mode 100644
index e9279720..00000000
--- a/.claude/commands/devtools/markdown-search-logic.md
+++ /dev/null
@@ -1,179 +0,0 @@
----
-name: claude-devtools:markdown-search
-description: Markdown search logic — how in-session and cross-session search works. Use when working on SearchBar, search highlighting, searchHighlightUtils, markdownTextSearch, or SessionSearcher.
----
-
-# Markdown Search Logic
-
-How in-session and cross-session markdown search works end-to-end.
-
-## Scope
-
-Current in-session search intentionally covers:
-
-- User message markdown text
-- AI `lastOutput` text markdown
-
-Current in-session search intentionally excludes:
-
-- System items
-- Tool result text blocks
-- Thinking/subagent/internal display items
-
-Primary source files:
-
-- `src/renderer/components/search/SearchBar.tsx`
-- `src/renderer/store/slices/conversationSlice.ts`
-- `src/renderer/components/chat/ChatHistory.tsx`
-- `src/renderer/components/chat/searchHighlightUtils.ts`
-- `src/shared/utils/markdownTextSearch.ts`
-- `src/main/services/discovery/SessionSearcher.ts`
-
-## Core Data Model
-
-`SearchMatch` (renderer store) in `src/renderer/store/types.ts`:
-
-- `itemId`: chat group id (`user-*`, `ai-*`)
-- `itemType`: `user | ai`
-- `matchIndexInItem`: 0-based index inside one searchable item
-- `globalIndex`: 0-based index across all matches
-- `displayItemId`: optional (`lastOutput` for AI output)
-
-Important distinction:
-
-- `matchIndexInItem` is local to one item.
-- `currentSearchIndex` is global position in the search result list.
-
-## Pipeline Overview
-
-### 1) Query input and initial match generation
-
-`SearchBar` updates the query with tab-scoped conversation data:
-
-- `setSearchQuery(query, conversation)` in `src/renderer/components/search/SearchBar.tsx`
-
-`setSearchQuery` in `src/renderer/store/slices/conversationSlice.ts`:
-
-- Scans conversation items
-- Uses `findMarkdownSearchMatches` (shared parser logic) per searchable item
-- Builds initial `searchMatches`, `searchResultCount`, `currentSearchIndex`
-
-### 2) Rendering highlights
-
-Search highlighting is rendered in markdown component trees through:
-
-- `createSearchContext(...)` in `src/renderer/components/chat/searchHighlightUtils.ts`
-- `highlightSearchInChildren(...)` in `src/renderer/components/chat/searchHighlightUtils.ts`
-
-Each rendered highlight mark includes:
-
-- `data-search-item-id`
-- `data-search-match-index`
-- `data-search-result` (`current` or `match`)
-
-### 3) Canonicalization to rendered DOM (critical)
-
-`ChatHistory` collects rendered `` elements in DOM order and calls:
-
-- `syncSearchMatchesWithRendered(renderedMatches)` in `src/renderer/store/slices/conversationSlice.ts`
-
-Why this exists:
-
-- Real UI navigation must match visible marks exactly.
-- Parser results can temporarily differ during render timing.
-- DOM order is the final source of truth for nth navigation.
-
-Safety guard:
-
-- `ChatHistory` delays syncing when a transient empty mark snapshot appears, to avoid wiping results mid-render.
-
-### 4) Next/prev navigation and scrolling
-
-`nextSearchResult` / `previousSearchResult` in `src/renderer/store/slices/conversationSlice.ts`:
-
-- Move `currentSearchIndex` with wrap-around
-
-`ChatHistory` scroll effect:
-
-- First tries exact selector:
- - `mark[data-search-item-id="..."][data-search-match-index="..."]`
-- If missing, falls back to the global nth rendered mark (same `currentSearchIndex`)
-- Final fallback walks text nodes under `[data-search-content]` roots
-
-## Shared Markdown Search Engine
-
-`src/shared/utils/markdownTextSearch.ts` is used by both renderer and main process:
-
-- `findMarkdownSearchMatches`
-- `countMarkdownSearchMatches`
-- `extractMarkdownPlainText`
-
-Design principle:
-
-- Search parser mirrors markdown render behavior (remark + gfm + HAST traversal)
-- Matching is segment-based (no cross-node match)
-
-## Cross-Session Search (Command Palette / IPC)
-
-Main process search path:
-
-- IPC handler: `src/main/ipc/search.ts`
-- Engine: `src/main/services/discovery/SessionSearcher.ts`
-
-`SessionSearcher` also uses shared markdown search utils, and returns:
-
-- `groupId`
-- `itemType`
-- `matchIndexInItem`
-- `matchStartOffset`
-
-These are passed into tab navigation context so opening a search result can jump to the exact in-session match.
-
-## Invariants to Keep
-
-When changing markdown/search code, keep these invariants:
-
-1. Parser and renderer must agree on searchable text boundaries.
-2. `matchIndexInItem` semantics must stay stable per item.
-3. `currentSearchIndex` must represent the global nth visible match.
-4. `searchResultCount` must reflect actual rendered match count after canonicalization.
-5. Search source scope must be explicit (no accidental inclusion of hidden/internal text).
-
-## If You Add New Searchable Markdown Surfaces
-
-If you make a new markdown surface searchable:
-
-1. Ensure it uses search context + `highlightSearchInChildren`.
-2. Ensure emitted marks include `data-search-item-id` and `data-search-match-index`.
-3. Ensure the content is included in `setSearchQuery` source scanning.
-4. Ensure parser collection logic in `src/shared/utils/markdownTextSearch.ts` still mirrors render behavior.
-5. Add/adjust alignment tests.
-
-## Debug Playbook
-
-Enable debug logs:
-
-- `localStorage.setItem('search-debug', '1')`
-
-Useful logs:
-
-- `[search] query` / `[search] sample` from `setSearchQuery`
-- `[search] sync-rendered` from DOM canonicalization
-- `[search] next` / `[search] prev` navigation logs
-
-Quick checks when behavior is off:
-
-1. Compare `searchResultCount` vs number of rendered marks.
-2. Verify `currentSearchIndex` increments exactly once per click.
-3. Check whether exact mark selector exists for current match.
-4. Confirm the active tab conversation is the same one used for `setSearchQuery`.
-5. Confirm virtualization is disabled during active search.
-
-## Tests
-
-Main tests relevant to this logic:
-
-- `test/shared/utils/markdownTextSearch.test.ts`
-- `test/shared/utils/markdownSearchRendererAlignment.test.ts`
-
-The alignment test ensures parser match indexes and rendered mark indexes stay identical across representative markdown cases.
diff --git a/.claude/commands/devtools/navigation-scroll.md b/.claude/commands/devtools/navigation-scroll.md
deleted file mode 100644
index 27984649..00000000
--- a/.claude/commands/devtools/navigation-scroll.md
+++ /dev/null
@@ -1,232 +0,0 @@
----
-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)
-
-```typescript
-// 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.
-
-```typescript
-// 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.
-
-```typescript
-// 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 to strict gates:
-- `userInterrupted` — any accidental wheel/touch event during smooth scroll
-- `isElementVisibleInContainer` — element partially off-screen after centering (tall elements)
-- Element not found within 600ms timeout
-
-When `success = false`, `executeNavigation` clears all highlight state (`setHighlightedGroupId(null)`).
-
-**Fix pattern:** Set highlight BEFORE scroll attempt. Make scroll best-effort. Always return `true` once target group is found.
-
-```typescript
-// In executeErrorNavigation:
-// 1. Find target group
-// 2. Expand group
-// 3. SET HIGHLIGHT HERE (before scroll)
-setHighlightedGroupId(targetGroupId);
-setIsSearchHighlight(false);
-if (toolUseId) setCurrentToolUseId(toolUseId);
-// 4. Best-effort scroll (don't gate highlight on scroll outcome)
-// 5. Return true (highlight already visible)
-```
-
-### 4. Test Trigger Shows No Highlight
-
-**Symptom:** "Test trigger" creates an error with a timestamp that doesn't match any AI group. Navigation scrolls but nothing is highlighted.
-
-**Root cause:** Same as #3. The error timestamp doesn't match, so `findAIGroupByTimestamp` falls back to closest/last group. But post-scroll gates prevent the highlight from being applied.
-
-**Fix:** Same as #3 — highlight-first pattern.
-
-### 5. `conversationLoading` Race During Tab Switch
-
-**Symptom:** Navigation queued on tab, but when switching to that tab, loading state causes ChatHistory unmount. Navigation controller state is lost.
-
-**Root cause:** `fetchSessionDetail` immediately sets `conversationLoading: true`. ChatHistory returns ``, unmounting the controller hook.
-
-**Recovery:** The controller is designed to survive remount — when ChatHistory remounts with `pendingNavigation` still set and `conversationLoading: false`, the detection effect starts fresh navigation. BUT scroll restore can race with it (see #1).
-
-## Debugging Checklist
-
-When navigation isn't working:
-
-1. **Check `pendingNavigation` exists on the tab** — is `enqueueTabNavigation` called?
-2. **Check `isActiveTab` is true** — controller ignores inactive tabs
-3. **Check `conversationLoading`** — if true, controller waits in `pending` phase
-4. **Check `conversation` exists** — if null, controller waits
-5. **Check timestamp matching** — does `findAIGroupByTimestamp` find the right group?
-6. **Check element refs** — are `aiGroupRefs` / `chatItemRefs` populated?
-7. **Check `shouldDisableAutoScroll`** — is scroll restore racing with navigation?
-8. **Check for double `fetchSessionDetail`** — is ChatHistory unmounting unnecessarily?
-9. **Check `phase` progression** — is it stuck in `pending` or failing at `scrolling`?
-
-## Navigation Helper Functions (navigation/utils.ts)
-
-| Function | Purpose |
-|----------|---------|
-| `findAIGroupByTimestamp(items, timestamp)` | Find AI group containing/closest to timestamp |
-| `findChatItemByTimestamp(items, timestamp)` | Find any chat item by timestamp |
-| `findAIGroupBySubagentId(items, subagentId)` | Find AI group by subagent ID |
-| `calculateCenteredScrollTop(element, container, offset)` | Calculate scroll position to center element |
-| `waitForElementStability(element, timeout, stableFrames)` | Wait for element size to stop changing |
-| `waitForScrollEnd(container, timeout)` | Wait for smooth scroll to finish |
-| `isElementVisibleInContainer(element, container, offset)` | Check if element is in viewport |
-| `findCurrentSearchResultInContainer(container)` | Find `[data-search-result="current"]` element |
-
-## Factory Helpers (tabs.ts)
-
-```typescript
-createErrorNavigationRequest({ errorId, errorTimestamp, toolUseId, lineNumber })
-createSearchNavigationRequest({ query, messageTimestamp, matchedText })
-isErrorPayload(request) // type guard
-isSearchPayload(request) // type guard
-```
-
-## Tests
-
-Related test files:
-- `test/renderer/store/tabSlice.test.ts` — `enqueueTabNavigation` / `consumeTabNavigation`
-- `test/renderer/store/notificationSlice.test.ts` — `navigateToError` behavior
-- `test/renderer/hooks/navigationUtils.test.ts` — Navigation utility functions
-- `test/renderer/hooks/useSearchContextNavigation.test.ts` — Search result finding
-
-Test patterns:
-```typescript
-// Mock crypto for predictable nonces
-vi.stubGlobal('crypto', { randomUUID: () => `test-uuid-${++counter}` });
-
-// Verify navigation request shape
-expect(tab.pendingNavigation?.kind).toBe('error');
-expect(tab.pendingNavigation?.highlight).toBe('red');
-
-// Verify nonce uniqueness on repeated clicks
-store.getState().navigateToError(error);
-const firstId = store.getState().openTabs[0].pendingNavigation?.id;
-store.getState().navigateToError(error);
-const secondId = store.getState().openTabs[0].pendingNavigation?.id;
-expect(firstId).not.toBe(secondId);
-```
diff --git a/.claude/rules/react.md b/.claude/rules/react.md
deleted file mode 100644
index 7e07988e..00000000
--- a/.claude/rules/react.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-globs: ["src/renderer/**/*.tsx"]
----
-
-# React Conventions
-
-## Component Structure
-- Components in `src/renderer/components/` organized by feature
-- One component per file, PascalCase naming
-- Colocate related hooks and utilities
-
-## State Management (Zustand)
-```typescript
-// Slices pattern
-projects: Project[]
-selectedProjectId: string | null
-projectsLoading: boolean
-projectsError: string | null
-```
-
-Each domain slice includes:
-- Data array or object
-- Selected/active item ID
-- Loading state
-- Error state
-
-## Hooks
-- Custom hooks in `src/renderer/hooks/`
-- Prefix with `use`: `useAutoScrollBottom`, `useTheme`
-- Keep hooks focused and composable
-
-## Component Organization
-```
-components/
-├── chat/ # Chat display, items, viewers, SessionContextPanel
-├── common/ # Shared components (badges, token display)
-├── dashboard/ # Dashboard views
-├── layout/ # Layout components (headers, shells)
-├── notifications/ # Notification panels and badges
-├── search/ # Search UI and results
-├── settings/ # Settings pages and controls
-│ ├── components/ # Reusable setting controls
-│ ├── hooks/ # Settings-specific hooks
-│ ├── sections/ # Setting sections
-│ └── NotificationTriggerSettings/ # Trigger config UI
-└── sidebar/ # Sidebar navigation
-```
-
-## Data Access: Store over Props
-When data is available in the Zustand store, child components should read it directly via `useStore()` instead of receiving it through props. This avoids unnecessary prop drilling and keeps parent components clean.
-
-```tsx
-// Preferred — child reads from store
-const ProcessesSection = () => {
- const teamName = useStore((s) => s.selectedTeamName);
- const data = useStore((s) => s.selectedTeamData);
- // ...
-};
-
-// Avoid — parent drills store data as props
-
-```
-
-Only pass props when the data is NOT in the store (e.g. local state, computed values, callbacks).
-
-## Contexts
-- `contexts/TabUIContext.tsx` - Per-tab UI state isolation
diff --git a/.claude/rules/tailwind.md b/.claude/rules/tailwind.md
deleted file mode 100644
index 387a353c..00000000
--- a/.claude/rules/tailwind.md
+++ /dev/null
@@ -1,61 +0,0 @@
----
-globs: ["**/*.css", "src/renderer/**/*.tsx"]
----
-
-# Tailwind CSS Conventions
-
-## Theme Architecture
-Uses CSS custom properties for theme-aware colors defined in `src/renderer/index.css`.
-
-### Core Surface Colors
-```css
---color-surface: #141416 /* Main background */
---color-surface-raised: #27272a /* Elevated surfaces */
---color-surface-overlay: #27272a /* Overlays/modals */
---color-surface-sidebar: #0f0f11 /* Sidebar background */
-```
-
-### Border Colors
-```css
---color-border: rgba(255, 255, 255, 0.05)
---color-border-subtle: rgba(255, 255, 255, 0.05)
---color-border-emphasis: rgba(255, 255, 255, 0.1)
-```
-
-### Text Colors
-```css
---color-text: #fafafa /* Primary text */
---color-text-secondary: #a1a1aa /* Secondary text */
---color-text-muted: #71717a /* Muted text */
-```
-
-## Tailwind Usage
-Use theme-aware classes that reference CSS variables:
-```tsx
-// Preferred - uses CSS variables for theme support
-
-
-
-// Also available via claude-dark namespace
-
-```
-
-## Additional CSS Variable Categories
-- Chat bubbles: `--chat-user-*`, `--chat-ai-*`, `--chat-system-*`
-- Code blocks: `--code-*`, `--syntax-*`, `--inline-code-*`
-- Diff viewer: `--diff-added-*`, `--diff-removed-*`
-- Tool blocks: `--tool-call-*`, `--tool-result-*`
-- Tool items: `--tool-item-name`, `--tool-item-summary`, `--tool-item-muted`, `--tool-item-hover-bg`
-- Badges: `--badge-*`, `--tag-*`
-- Search: `--highlight-*`
-- Scrollbar: `--scrollbar-thumb`, `--scrollbar-thumb-hover`, `--scrollbar-thumb-active`
-- Prose/Markdown: `--prose-heading`, `--prose-body`, `--prose-link`, `--prose-code-*`, `--prose-pre-*`
-- Thinking blocks: `--thinking-bg`, `--thinking-border`, `--thinking-text`, `--thinking-content-*`
-- Output blocks: `--output-bg`, `--output-border`, `--output-text`, `--output-content-border`
-- Cards/Subagents: `--card-bg`, `--card-border`, `--card-header-*`, `--card-icon-muted`, `--card-separator`
-- Highlights: `--skill-highlight-*`, `--path-highlight-*`
-- UI elements: `--interruption-*`, `--warning-*`, `--plan-exit-*`, `--error-highlight-*`, `--kbd-*`, `--context-btn-*`
-
-## Dark/Light Theme
-Both themes supported via `:root` and `:root.light` in index.css.
-Toggle via `useTheme` hook which adds/removes `light` class on root.
diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md
deleted file mode 100644
index ed92f71b..00000000
--- a/.claude/rules/testing.md
+++ /dev/null
@@ -1,76 +0,0 @@
----
-globs: ["test/**/*", "**/*.test.ts", "**/*.spec.ts"]
----
-
-# Testing Conventions
-
-## Test Framework
-Uses Vitest with `happy-dom` environment. Config in `vitest.config.ts`.
-
-## Test Commands
-```bash
-pnpm test # Run all vitest tests
-pnpm test:watch # Watch mode
-pnpm test:coverage # Coverage report
-pnpm test:coverage:critical # Critical path coverage
-pnpm test:chunks # Chunk building tests
-pnpm test:semantic # Semantic step extraction
-pnpm test:noise # Noise filtering tests
-pnpm test:task-filtering # Task tool filtering
-```
-
-## Test Structure
-```
-test/
-├── main/
-│ ├── ipc/ # IPC handler tests
-│ │ ├── configValidation.test.ts
-│ │ └── guards.test.ts
-│ ├── services/ # Service tests
-│ │ ├── analysis/ (ChunkBuilder)
-│ │ ├── discovery/ (ProjectPathResolver, SessionSearcher)
-│ │ ├── infrastructure/ (FileWatcher)
-│ │ └── parsing/ (MessageClassifier, SessionParser)
-│ └── utils/ # Main process utilities
-│ ├── jsonl.test.ts
-│ ├── pathDecoder.test.ts
-│ ├── pathValidation.test.ts
-│ ├── regexValidation.test.ts
-│ └── tokenizer.test.ts
-├── renderer/
-│ ├── hooks/ # Hook tests
-│ │ ├── navigationUtils.test.ts
-│ │ ├── useAutoScrollBottom.test.ts
-│ │ ├── useSearchContextNavigation.test.ts
-│ │ └── useVisibleAIGroup.test.ts
-│ ├── store/ # Zustand store slices
-│ │ ├── notificationSlice.test.ts
-│ │ ├── paneSlice.test.ts
-│ │ ├── pathResolution.test.ts
-│ │ ├── sessionSlice.test.ts
-│ │ ├── tabSlice.test.ts
-│ │ └── tabUISlice.test.ts
-│ └── utils/ # Renderer utilities
-│ ├── claudeMdTracker.test.ts
-│ ├── dateGrouping.test.ts
-│ ├── formatters.test.ts
-│ └── pathUtils.test.ts
-├── shared/
-│ └── utils/ # Shared utilities
-│ ├── markdownSearchRendererAlignment.test.ts
-│ ├── markdownTextSearch.test.ts
-│ ├── modelParser.test.ts
-│ └── tokenFormatting.test.ts
-├── mocks/ # Test fixtures and mocks
-└── setup.ts # Test setup/config
-```
-
-## Files to Test After Changes
-- `services/analysis/ChunkBuilder.ts` - Chunk building logic
-- `services/parsing/SessionParser.ts` - JSONL parsing
-- `services/parsing/MessageClassifier.ts` - Message classification
-- Store slices in `src/renderer/store/slices/`
-- Utility functions in `*/utils/`
-
-## Test Data
-Test fixtures use real JSONL session data from `~/.claude/projects/`.
diff --git a/.claude/settings.json b/.claude/settings.json
deleted file mode 100644
index 1c1162bc..00000000
--- a/.claude/settings.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "alwaysThinkingEnabled": true,
- "permissions": {
- "allow": [
- "Edit",
- "Bash",
- "ReadFile(*)",
- "Web Search",
- "WebSearch",
- "WebFetch",
- "Fetch"
- ],
- "deny": [],
- "ask": []
- }
-}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index dbfec4cf..951ddde3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,9 +41,7 @@ yarn-error.log*
package-lock.json
notification_example/
temp/
-.claude/*.local.json
-.claude/agent-memory/*
-.claude/worktrees/
+.claude/
eslint-fix/