Skip to content

feat(sessions): Batch 1 — full-prompt search, match snippets, junk fold, highlight fix#132

Merged
grimmerk merged 10 commits into
mainfrom
feat/session-search-noise
Jul 13, 2026
Merged

feat(sessions): Batch 1 — full-prompt search, match snippets, junk fold, highlight fix#132
grimmerk merged 10 commits into
mainfrom
feat/session-search-noise

Conversation

@grimmerk

@grimmerk grimmerk commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Session-finding Batch 1 — "search & noise" (plan: docs/session-finding-plan.md, included in this PR). Fixes #131.

1. Full search: ALL sessions × ALL user prompts (fixes #131)

Before: the search box only filtered the ~100 loaded sessions' visible fields (filterSessionsLocally), and the main-side searchClaudeSessions (500-pool) was dead code — ~314 of 414 sessions were unfindable.

Now, dual-path union:

  • Main-side deep search (rewritten searchClaudeSessions): scans every session's every prompt (from history.jsonl, all accounts), debounced 180ms, stale-response guarded. Prompt text stays in the main process (promptsBySession map rebuilt with the existing 5s session cache) — only small snippets cross IPC.
  • Renderer local filter (unchanged) still covers enrichment-only fields: custom title, branch, PR link, last AI reply, terminal badge.
  • Deep matches beyond the loaded list are appended with lazy enrichment (both bounded by the deep-search result cap, 100), everything re-sorts by recency.

2. Matched-prompt snippet line

When the hit is in a middle prompt (invisible in the row), the row shows ⌕ #N …±40 chars context… so you can see why it matched. Suppressed when it would duplicate the visible first/last lines (respects the first/last/both display-mode setting).

3. High-contrast search highlight

All 9 react-highlight-words sites now share one amber-background/dark-text style (SEARCH_HIGHLIGHT_STYLE). The old per-field translucent styles were near-invisible on colored text.

4. Minor-session folding

Closed, untitled, PR-less sessions with ≤2 messages collapse into an expandable ▸ N minor sessions row at the bottom (recency order preserved inside). Search always shows everything. Conservative: unknown messageCount and active sessions never fold; folding waits for the first active-session detection. Fold UX: sticky boundary header + an always-visible fold bar below the list while expanded; folding resets on each popup show; fold controls are keyboard-accessible. Manual per-session hide arrives with the pins PR (Batch 1 PR-2).

5. Added during live testing: enrichment dropout + perf fixes (pre-existing bugs, #134)

Testing surfaced that custom titles / branches vanished at random and the cold path took 8–10s. Fixed here since search quality depends on these fields:

  • Bounded scan batches (was ~400 concurrent exec greps starving the biggest transcripts past a silent timeout) and a 50-line→tail-read branch window (an active session's tail is often tool output with no gitBranch field).
  • mtime-keyed per-file cache: unchanged transcripts are never re-scanned (a ~ms stat pass replaces the 5s wall-clock TTL, whose entry-time stamp made any >5s scan stale on arrival); concurrent callers serialize onto one accumulated cache. Cold start measured 8–10s → ~4s, and it's now once per app launch.
  • Shell-free reads (execFile + in-process tail): no interpolated paths near a shell; timed-out/failed reads don't mark the transcript scanned — they retry next pass.

Implementation notes

  • New pure module src/session-search.ts (no fs/electron): matchesAllWords / extractSnippet / findPromptMatch / isMinorSession — unit-testable.
  • searchClaudeSessions return shape changed ({sessions, snippets}); it was previously uncalled, so no consumers were affected. IPC handler/preload are passthrough; electron-api.d.ts updated.
  • Keyboard navigation (↑↓/PageUp/PageDown/Enter) switched to the displayed list so indexes stay aligned with the fold.
  • Not covered here (by design): closed VS Code sessions' prompts aren't in history.jsonl — full transcript search is Batch 2 (FTS5, plan §5.2).

Known trade-off (side effect)

Custom titles/branches take slightly longer to first-paint after app launch: the enrichment cold pass is now one bounded scan per launch (~4s measured for ~100 transcripts / ~900MB corpus). The old code often looked faster because its unbounded 400-process burst raced ahead — but it randomly dropped titles/branches (silent timeouts) and, due to the TTL bug, kept rescanning forever. After the one-time scan, updates are incremental (a ~ms stat pass; only changed transcripts are re-read). Eliminating the remaining cold scan is tracked in #134 (persist the cache to disk, or let the Batch-2 FTS index subsume it).

Tests

  • 10 new unit tests for the pure search/fold helpers (AND semantics, 2-char CJK match, snippet ellipses/whitespace, prompt-index attribution, conservative fold predicate) — 45 total pass.
  • npx tsc --noEmit clean.

🤖 On behalf of @grimmerk — generated with Claude Code


Summary by cubic

Adds full‑prompt search across all sessions with clearer matches and less noise in the Sessions switcher, plus safer and faster metadata enrichment. Fixes #131 and #134.

  • New Features

    • Search scans all sessions and every user prompt in the main process, unions with the local field filter, and appends deep matches with lazy enrichment (debounced 180ms).
    • Rows show a matched‑prompt snippet (⌕ #N …context…) when the hit isn’t visible; suppressed if it duplicates the first/last lines.
    • Unified high‑contrast search highlight across all react-highlight-words sites.
    • Minor‑session folding: closed, untitled, PR‑less sessions with ≤2 messages collapse into an expandable “minor sessions” row; sticky header while scrolled, an always‑visible fold bar below the list when expanded, folds again on popup show; search always shows everything.
  • Bug Fixes

    • Prevented focus‑triggered list resets by reading the current search from a ref.
    • Fold only after the first active‑session detection so a just‑started session never folds at app start.
    • Deep‑match enrichment no longer silently caps; aligned with append path and bounded by the search result cap (100).
    • Stopped random title/branch dropouts and sped up cold starts: mtime+size‑keyed enrichment cache with serialized scans (batches of 25), shell‑free execFile reads, and an async in‑process 256KB tail for branch; explicit detached HEAD clears stale branches; failures don’t mark a file fresh and retry on the next pass.

Written for commit 0ce8447. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Search now scans every session and every previously typed user prompt (not limited to the loaded subset).
    • Search results include contextual matched-prompt snippets and improved high-contrast highlighting.
    • Added debounced “deep search” that expands results with lazy enrichment and supports minor-session folding for short one-offs.
  • Bug Fixes

    • Search matching now consistently uses full-text, AND-style word semantics across prompts.
    • Snippets correctly handle ellipses and whitespace normalization for clearer context.
  • Documentation

    • Updated the changelog and README to reflect full coverage, snippet context, and minor-session grouping.
  • Release

    • Version bumped to 1.0.83.

- Search ALL sessions x ALL user prompts main-side (fixes #131);
  matches beyond the loaded 100 append with lazy enrichment
- Matched-prompt snippet line (prompt #N + context) when the hit
  isn't visible in the row's first/last lines
- Unified high-contrast amber search highlight (9 sites)
- Fold minor sessions (<=2 msgs, untitled, no PR, closed) into an
  expandable row
- New pure module session-search.ts + 10 unit tests (45 total)
- Plan doc: docs/session-finding-plan-zh-tw.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Session search now scans all sessions and prompts in the main process, returns contextual snippets, and merges deep results into the switcher. The UI adds consistent highlighting, minor-session folding, debounced search coordination, lazy enrichment, and updated documentation and release metadata.

Changes

Session search experience

Layer / File(s) Summary
Prompt matching and folding helpers
src/session-search.ts, src/session-search.test.ts
Adds multi-word matching, contextual snippets, first-match detection, minor-session classification, and unit coverage.
Main-process prompt search
src/claude-session-utility.ts, src/electron-api.d.ts
Caches prompt text while reading history, searches all sessions and prompts, and exposes structured sessions and snippets through IPC.
Incremental enrichment scanning
src/claude-session-utility.ts
Tracks transcript freshness per file, serializes enrichment scans, bounds grep and assistant-response concurrency, and resets related caches on invalidation.
Deep search and session list rendering
src/switcher-ui.tsx
Debounces deep searches, merges and enriches matches outside the loaded list, folds minor sessions, updates navigation, and renders unified highlights and matched snippets.
Search documentation and release metadata
README.md, CHANGELOG.md, docs/session-finding-plan.md, package.json
Documents expanded search, snippets, folding, implementation constraints, planned work, and version 1.0.83.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SwitcherUI
  participant ElectronAPI
  participant searchClaudeSessions
  participant historyjsonl
  SwitcherUI->>ElectronAPI: request debounced search query
  ElectronAPI->>searchClaudeSessions: searchClaudeSessions(query)
  searchClaudeSessions->>historyjsonl: read sessions and prompt text
  historyjsonl-->>searchClaudeSessions: session and prompt data
  searchClaudeSessions-->>ElectronAPI: matched sessions and snippets
  ElectronAPI-->>SwitcherUI: deep search results
  SwitcherUI->>SwitcherUI: merge, enrich, fold, and highlight results
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #131 by searching all sessions and prompts in the main process, returning snippets, and appending lazy-enriched deep matches.
Out of Scope Changes check ✅ Passed No unrelated changes are apparent; the docs, changelog, README, and version bump all support the session-search feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main changes: full-prompt search, match snippets, folding minor sessions, and highlight fixes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/session-search-noise

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/electron-api.d.ts (1)

133-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Typed payload is correct; consider sharing the shape instead of triple-declaring it.

The snippets record shape ({ snippet, promptIndex, isLastPrompt }) is duplicated here, as SessionSearchMatch in claude-session-utility.ts, and inline in switcher-ui.tsx's searchSnippets state. A type-only import (erased at compile time, so it's safe across the main/renderer boundary) would give a single source of truth and catch drift at compile time instead of silently at runtime.

♻️ Share the type instead of duplicating it
+import type { SessionSearchMatch } from './claude-session-utility';
+
   searchClaudeSessions: (query: string) => Promise<{
     sessions: any[];
-    snippets: Record<
-      string,
-      { snippet: string; promptIndex: number; isLastPrompt: boolean }
-    >;
+    snippets: Record<string, SessionSearchMatch>;
   }>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/electron-api.d.ts` around lines 133 - 139, Reuse the existing
SessionSearchMatch type from claude-session-utility.ts for the snippets record
value in searchClaudeSessions, and update switcher-ui.tsx’s searchSnippets state
to use the same shared type. Remove the duplicated inline `{ snippet,
promptIndex, isLastPrompt }` declarations while preserving the current payload
shape.
src/claude-session-utility.ts (1)

215-239: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Full-corpus scan+lowercase runs synchronously on the main process for every search.

searchClaudeSessions rebuilds target (projectName + project + all prompts joined) and lowercases it for every session on every call — this is exactly the workload issue #131 wants to unlock (searching beyond the ~100 loaded sessions across all accounts/history). Since the function is synchronous and likely invoked via ipcMain.handle, this blocks the Electron main process for the full scan duration on each debounced keystroke. With large multi-account histories (the target use case), this could introduce noticeable UI jank/freezes app-wide (window drag, other IPC, status updates), not just in the switcher.

Two independent, low-cost mitigations:

  1. Cache pre-lowercased prompt text alongside promptsBySession (built once per cache refresh) instead of re-lowering the same megabytes of text on every keystroke-triggered search.
  2. If profiling on real large histories shows meaningful main-thread blocking, consider offloading the scan to a worker_threads worker or chunking it with setImmediate between session batches.

Please verify actual latency against realistic account sizes/history volumes before deciding whether (2) is necessary; (1) is cheap and worth doing regardless.

⚡ Minimal mitigation: cache lowercased prompts once per rebuild
-let promptsBySession: Map<string, string[]> = new Map();
+let promptsBySession: Map<string, string[]> = new Map();
+let promptsBySessionLower: Map<string, string[]> = new Map();

Populate promptsBySessionLower alongside promptsBySession in readClaudeSessions, then in searchClaudeSessions build target from the pre-lowered arrays instead of calling .toLowerCase() on the joined string each time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/claude-session-utility.ts` around lines 215 - 239, Cache lowercased
prompt text during the cache rebuild in readClaudeSessions by maintaining a
promptsBySessionLower structure alongside promptsBySession. Update
searchClaudeSessions to use the pre-lowered prompt array when constructing its
search target, avoiding repeated lowercasing of joined prompts on every query;
preserve the existing project-name/project matching and result behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/session-finding-plan-zh-tw.md`:
- Line 125: Update the D3 `/pin` row in the Batch 3 table to include a third
cell matching the `項 | 內容 | 備註` header. Preserve the existing content in the
first two cells and add the appropriate notes cell, even if it is empty.

In `@src/claude-session-utility.ts`:
- Line 215: Run Prettier on searchClaudeSessions and the template-literal
assignment around the reported lines so both follow the project’s print-width
and line-break rules. Ensure any multiline arrays or objects in the affected
code use trailing commas.

In `@src/session-search.test.ts`:
- Line 12: Format the affected code in the session-search test using the
project’s Prettier configuration, including wrapping long string literals and
multi-argument calls and adding required trailing commas. Apply the changes to
the test cases around the target declaration and the referenced sections,
without altering their behavior.

In `@src/session-search.ts`:
- Around line 26-57: Format the return object in findPromptMatch so it fits the
configured print width, wrapping the snippet property as needed, and add the
required trailing comma to the object literal. Keep the matching logic
unchanged.

In `@src/switcher-ui.tsx`:
- Around line 411-429: Cap the non-duplicate deep matches appended by
applySearchFilter to MAX_APPENDED_DEEP_MATCHES before merging and sorting,
preserving the existing base results and ordering. In scheduleDeepSearch,
replace the appended-result literal 30 with the same MAX_APPENDED_DEEP_MATCHES
constant so display and enrichment limits remain synchronized.

---

Nitpick comments:
In `@src/claude-session-utility.ts`:
- Around line 215-239: Cache lowercased prompt text during the cache rebuild in
readClaudeSessions by maintaining a promptsBySessionLower structure alongside
promptsBySession. Update searchClaudeSessions to use the pre-lowered prompt
array when constructing its search target, avoiding repeated lowercasing of
joined prompts on every query; preserve the existing project-name/project
matching and result behavior.

In `@src/electron-api.d.ts`:
- Around line 133-139: Reuse the existing SessionSearchMatch type from
claude-session-utility.ts for the snippets record value in searchClaudeSessions,
and update switcher-ui.tsx’s searchSnippets state to use the same shared type.
Remove the duplicated inline `{ snippet, promptIndex, isLastPrompt }`
declarations while preserving the current payload shape.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9209980-4fc6-4fe1-bf29-2c30c5200fe7

📥 Commits

Reviewing files that changed from the base of the PR and between 88eb84d and 1ef13ea.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • README.md
  • docs/session-finding-plan-zh-tw.md
  • package.json
  • src/claude-session-utility.ts
  • src/electron-api.d.ts
  • src/session-search.test.ts
  • src/session-search.ts
  • src/switcher-ui.tsx

Comment thread docs/session-finding-plan-zh-tw.md Outdated
Comment thread src/claude-session-utility.ts Outdated
Comment thread src/session-search.test.ts Outdated
Comment thread src/session-search.ts
Comment thread src/switcher-ui.tsx

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/switcher-ui.tsx Outdated
Comment thread docs/session-finding-plan-zh-tw.md Outdated
Comment thread src/switcher-ui.tsx
grimmerk and others added 2 commits July 12, 2026 17:34
- Read search value via ref in fetchClaudeSessions (stale closure
  could clear the filtered list on window focus) [cubic P1]
- Gate minor-session folding on first active-detection completion
  so a just-started session never folds at app start [cubic P2]
- Un-cap deep-match enrichment (was 30) to match the uncapped
  append; both now bounded by the search result cap of 100
  [CodeRabbit Major, resolved toward no-silent-caps]
- Prettier: new files wholesale; changed lines only in
  claude-session-utility.ts / switcher-ui.tsx
- Docs: plan status header reflects PR-1 in flight; D3 table row
  third cell [cubic P2 / CodeRabbit Minor]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add docs/session-finding-plan.md (full English translation,
  same status/decisions/gotchas)
- Untrack the zh-TW copy (stays as a local working copy; note
  added pointing at the English canonical version)
- CHANGELOG plan reference updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/switcher-ui.tsx (1)

793-797: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

onSessionStatusesUpdated still uses filterSessionsLocally — deep matches vanish during status updates.

This PR updated all other search-aware setSessions call sites (lines 509, 550, 592) to use applySearchFilter, but missed this one. When a session status update arrives during an active search, filterSessionsLocally filters out deep-matched sessions (matched by prompt text in the main process, not by local enrichment fields). Deep matches disappear and don't reappear until the user types again, undermining the PR's core objective.

🔧 Use `applySearchFilter` for consistency
             setSessions((prev: any[]) => {
               const updated = updateSessions(prev);
               const search = sessionSearchRef2.current;
-              return search.trim() ? filterSessionsLocally(updated, search) : updated;
+              return search.trim() ? applySearchFilter(updated, search) : updated;
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/switcher-ui.tsx` around lines 793 - 797, Update the search-aware
setSessions callback in onSessionStatusesUpdated to use applySearchFilter
instead of filterSessionsLocally, preserving the existing updateSessions flow
and current search value handling so deep-matched sessions remain visible during
status updates.
🧹 Nitpick comments (1)
src/switcher-ui.tsx (1)

1604-1611: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fold/expand rows lack keyboard interaction support.

Both the expand row (line 1604) and the collapse row (line 1371) only have onClick handlers. Keyboard users cannot toggle minor-session folding. Consider adding tabIndex={0}, role="button", and an onKeyDown handler for Enter/Space.

⌨️ Add keyboard support to the expand row
               {!isSearchingSessions && !minorsExpanded && minorSessions.length > 0 && (
                 <div
+                  tabIndex={0}
+                  role="button"
+                  aria-label={`Expand ${minorSessions.length} minor sessions`}
                   onClick={() => setMinorsExpanded(true)}
+                  onKeyDown={(e) => {
+                    if (e.key === 'Enter' || e.key === ' ') {
+                      e.preventDefault();
+                      setMinorsExpanded(true);
+                    }
+                  }}
                   style={{ padding: '6px 10px 8px 24px', color: '`#777`', fontSize: '12px', cursor: 'pointer' }}
                 >
                   ▸ {minorSessions.length} minor sessions (≤2 msgs, untitled)
                 </div>
               )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/switcher-ui.tsx` around lines 1604 - 1611, Update the minor-session
expand row near the isSearchingSessions/minorsExpanded conditional to add
keyboard accessibility with tabIndex={0}, role="button", and an onKeyDown
handler that triggers setMinorsExpanded(true) for Enter and Space. Apply the
same interaction support to the collapse row's existing toggle near the
minorsExpanded collapse control, preserving their current click behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/switcher-ui.tsx`:
- Around line 454-456: Apply Prettier formatting to the session-loading code
around the loaded and appended declarations, and the related code at the second
flagged location, so it matches the repository’s formatting rules and passes
prettier/prettier ESLint checks. Preserve the existing behavior and logic.

---

Outside diff comments:
In `@src/switcher-ui.tsx`:
- Around line 793-797: Update the search-aware setSessions callback in
onSessionStatusesUpdated to use applySearchFilter instead of
filterSessionsLocally, preserving the existing updateSessions flow and current
search value handling so deep-matched sessions remain visible during status
updates.

---

Nitpick comments:
In `@src/switcher-ui.tsx`:
- Around line 1604-1611: Update the minor-session expand row near the
isSearchingSessions/minorsExpanded conditional to add keyboard accessibility
with tabIndex={0}, role="button", and an onKeyDown handler that triggers
setMinorsExpanded(true) for Enter and Space. Apply the same interaction support
to the collapse row's existing toggle near the minorsExpanded collapse control,
preserving their current click behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a3b1b12-3367-43a8-ab3b-1b9af4599271

📥 Commits

Reviewing files that changed from the base of the PR and between 1ef13ea and 6fa6316.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • docs/session-finding-plan.md
  • src/claude-session-utility.ts
  • src/session-search.test.ts
  • src/session-search.ts
  • src/switcher-ui.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/session-search.test.ts
  • src/session-search.ts
  • src/claude-session-utility.ts

Comment thread src/switcher-ui.tsx Outdated
grimmerk and others added 2 commits July 12, 2026 23:51
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-existing bug surfaced during Batch 1 testing:

- Enrichment spawned ~400 concurrent exec greps (100 sessions x 4);
  the biggest transcripts regularly blew the silent 3s timeout
  (errors resolve to ''), so their title/branch vanished at random.
  Now batched 10 sessions at a time; timeout 3s -> 5s. Same batching
  applied to loadLastAssistantResponses (100 concurrent tails).
- Branch was read from the last 5 transcript lines; an active
  session's tail is often tool output with no gitBranch field
  (measured on a live 26MB session: tail -5 hit 0, tail -20 hit 12).
  Window widened to 50 lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/claude-session-utility.ts Outdated
Fixes the cubic finding (entry-time TTL stamp: any scan slower than
the 5s TTL wrote an already-stale cache, so every popup interaction
triggered another multi-second full rescan) and the 8-10s cold path
the user measured (issue #134):

- Per-file {mtimeMs, size} scan state: transcripts unchanged since
  their last scan are never re-grepped; a ~ms statSync pass replaces
  the wall-clock TTL entirely
- titles/branches/prLinks become persistent accumulator maps;
  concurrent callers serialize through a promise queue and hit the
  accumulated cache (correct across different session sets)
- File state recorded after a session's scan, so a failed pass
  retries; invalidateSessionCache clears the state (+ prLinks, which
  it previously missed)
- Scan batches 10 -> 25 (both enrichment and last-assistant reads)
  for a faster first cold pass

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/claude-session-utility.ts`:
- Line 1737: Format the changed enrichment block in loadSessionEnrichment,
including the referenced lines, with the project’s Prettier configuration so all
prettier/prettier lint errors are resolved. Limit formatting changes to this
block and preserve its behavior.
- Around line 1744-1749: Update execPromise to preserve command failures
distinctly from a successful empty stdout, including timeouts and spawn errors,
and propagate that failure to the enrichment scan. In the scan logic around
enrichedFileState.set, only mark the transcript fresh after a successful
command; leave failed scans eligible for retry.
- Around line 1743-1780: Replace the shell-based execPromise grep/tail pipeline
in scan with shell-free filesystem reads and in-process parsing of each
jsonlPath, preserving the latest matching title, AI-title, branch, and PR-link
results. Remove interpolated transcript paths from command execution, and ensure
read/parse failures do not update enrichedFileState as successfully scanned so
they are retried on the next scan.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 751e4d4d-27a3-4489-ab32-24ec1b495eb3

📥 Commits

Reviewing files that changed from the base of the PR and between 6fa6316 and f8cfa43.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/claude-session-utility.ts
  • src/switcher-ui.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • src/switcher-ui.tsx

Comment thread src/claude-session-utility.ts Outdated
Comment thread src/claude-session-utility.ts Outdated
Comment thread src/claude-session-utility.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/claude-session-utility.ts Outdated
Expanded minors header now sticks to the top of the scroll area so
collapsing never requires scrolling back to the boundary row; each
fresh popup show starts folded again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/switcher-ui.tsx (1)

798-802: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

onSessionStatusesUpdated still uses filterSessionsLocally — deep matches dropped during search.

All other search-mode setSessions call sites (lines 512-514, 555, 597) were updated to use applySearchFilter, but this one was missed. When a session status changes via fs.watch while the user is searching, filterSessionsLocally filters by enrichment fields only, dropping deep matches that were found by prompt text in the main process. This causes deep-matched sessions to disappear from the list mid-search.

🔧 Use `applySearchFilter` to preserve deep matches
             setSessions((prev: any[]) => {
               const updated = updateSessions(prev);
               const search = sessionSearchRef2.current;
-              return search.trim() ? filterSessionsLocally(updated, search) : updated;
+              return search.trim() ? applySearchFilter(updated, search) : updated;
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/switcher-ui.tsx` around lines 798 - 802, Update the search branch in the
onSessionStatusesUpdated setSessions callback to use applySearchFilter instead
of filterSessionsLocally, preserving deep prompt-text matches when session
statuses change during an active search; leave the non-search updated path
unchanged.
🧹 Nitpick comments (1)
src/switcher-ui.tsx (1)

1295-1302: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use allSessionsRef.current instead of allSessions for consistency.

Line 1300 passes allSessions (React state) to applySearchFilter, while the debounced callback at line 451 and the initial load at line 513 use allSessionsRef.current. If an async callback (e.g., detectActiveSessions) updates allSessionsRef.current between renders, the synchronous onChange handler would use a stale allSessions snapshot. The ref-mirroring pattern exists precisely for this scenario.

Based on learnings, allSessionsRef is intentionally kept in sync with setAllSessions to ensure async IPC callbacks read the latest state; using the ref here is the safer choice.

♻️ Use ref for consistency with other call sites
             onChange={(e) => {
               const val = e.target.value;
               setSessionSearchValue(val);
               sessionSearchRef2.current = val;
               scheduleDeepSearch(val);
-              setSessions(applySearchFilter(allSessions, val));
+              setSessions(applySearchFilter(allSessionsRef.current, val));
               setSelectedSessionIndex(0);
             }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/switcher-ui.tsx` around lines 1295 - 1302, Update the session search
onChange handler to pass allSessionsRef.current to applySearchFilter instead of
the potentially stale allSessions state value, while preserving the existing
search, scheduling, and selection-reset behavior.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/switcher-ui.tsx`:
- Around line 1381-1392: Reformat the fold-header div’s inline style object in
the relevant fold-header render block to match Prettier’s expected indentation,
including all properties from padding through backgroundColor. Do not change the
style values or behavior.

---

Outside diff comments:
In `@src/switcher-ui.tsx`:
- Around line 798-802: Update the search branch in the onSessionStatusesUpdated
setSessions callback to use applySearchFilter instead of filterSessionsLocally,
preserving deep prompt-text matches when session statuses change during an
active search; leave the non-search updated path unchanged.

---

Nitpick comments:
In `@src/switcher-ui.tsx`:
- Around line 1295-1302: Update the session search onChange handler to pass
allSessionsRef.current to applySearchFilter instead of the potentially stale
allSessions state value, while preserving the existing search, scheduling, and
selection-reset behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c8dfc9d2-51a2-4e3b-99e1-9feff9927abf

📥 Commits

Reviewing files that changed from the base of the PR and between f8cfa43 and 62f9700.

📒 Files selected for processing (1)
  • src/switcher-ui.tsx

Comment thread src/switcher-ui.tsx Outdated
Sticky header only pins while scrolled inside the minors zone; the
new bar sits outside the scroll area so collapsing is one click from
any scroll position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/switcher-ui.tsx
Review round: exec -> execFile (grep args, no shell interpolation);
branch tail via in-process 256KB read (no spawn); failures resolve
null and skip the fresh-mark so they retry next pass; fold controls
get role=button + tabIndex + Enter/Space; fold styles extracted to
consts (fixes flagged prettier indents).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/claude-session-utility.ts
Comment thread src/claude-session-utility.ts Outdated
Review round: readTailUtf8 -> fs.promises (no sync IO on the main
event loop; joins the per-file Promise.all); an explicit gitBranch
HEAD in the tail now clears the stale branch, while a tail with no
gitBranch line keeps the old value (usually transient tool output).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@grimmerk grimmerk merged commit 821ae86 into main Jul 13, 2026
3 checks passed
grimmerk added a commit that referenced this pull request Jul 13, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

search: only the ~100 loaded sessions are searchable; main-side searchClaudeSessions (500-pool) is dead code

1 participant