Skip to content

Search within the session log viewer - #47

Merged
jongracecox merged 1 commit into
mainfrom
worktree/20260826-refine-transcript-search
Aug 28, 2026
Merged

jongracecox merged 1 commit into
mainfrom
worktree/20260826-refine-transcript-search

Conversation

@jongracecox

Copy link
Copy Markdown
Owner

Reading a transcript back gave you no way to find anything in it, and the footer
advertised the home screen's keys regardless of the view.

What's new

/ searches the open transcript. Matches are highlighted in place, n/N
step through them with wraparound, and Ctrl+R/Ctrl+I toggle regex and case
as they do in the s transcript search. A match inside a folded tool call opens
that fold — a highlight you can't scroll to is worse than none.

Searching never rebuilds the view. _render_transcript now returns the
widgets plus a LogBody per body (widget, source text, the folds hiding it);
a query only calls Static.update() on the bodies whose spans actually changed.
Matching itself runs in a worker thread and returns plain span data — no widgets
are built off-thread — guarded by a generation token, since a scan can land
after the query has moved on.

Measured on a real 838-message log:

before after
per query ~540 ms, full remount ~1.7 ms (thread) + ~6 ms (event loop)
initial open ~190 ms ~140 ms, once

The remount was visible as the query box disappearing mid-keystroke.

The messages moved into their own scroll pane, so the header and query box
stay put while the transcript scrolls. The viewer's hint row is gone: / and
Escape are in the footer already, and a second strip of grey text under a
screenful of transcript reads as a second footer.

Backing out of a search result now lands on the result you opened rather
than the top of the list, so a set of results can be walked one at a time.

The footer only shows keys that do something here. check_action gates
them; / is two bindings on one key so it can read Filter on the home screen
and Search in the viewer, and the mode chords are no longer hidden now that
they only appear where they apply.

Two crashes fixed on the way

  • A Collapsible title built from transcript bytes was parsed as console
    markup, so a Bash command containing [ raised MarkupError and took the
    viewer down. Titles, the viewer header and the session picker rows are now
    Content. Same reasoning for the query box's select_on_focus, which was
    eating the query whenever focus returned to it.
  • A transcript the parser can't structure now shows a warning banner and the raw
    log — searchable like any other — instead of an error screen. A file that
    can't be read is still an error. iter_hits swallows the same class of
    failure per log, so one bad transcript can't end a scan over hundreds.

Instrumentation

Both swallowing paths are the ones --debug most needs to see, since neither is
visible in the UI: claude.transcript_unparsed carries the traceback and
search.scan_failed names the log it dropped. The viewer logs tui.log_view
and log_search.start/.done/.dropped, whose repainted count is what shows
the in-place update still working. Verified under --debug-redacted against a
real transcript: no username leak, query redacted.

Also

Documents that a global install needs --no-cache. uv's build cache is keyed on
the derived version, which hatch-vcs makes identical across untagged commits,
so --force --reinstall reports success while leaving the old wheel in place —
which looks exactly like a feature that doesn't work.

Testing

977 tests pass, coverage on cli.py holds at 95%, pre-commit clean.

@jongracecox
jongracecox force-pushed the worktree/20260826-refine-transcript-search branch from ef81aaa to 00c6e07 Compare August 27, 2026 22:33
Reading a transcript back gave you no way to find anything in it, and the
footer advertised the home screen's keys regardless of the view.

- `/` searches the open transcript: matches highlighted in place, `n`/`N`
  stepping through them with wraparound, `Ctrl+R`/`Ctrl+I` toggling regex
  and case as they do in the transcript search. A match inside a folded
  tool call opens that fold, since a highlight you cannot scroll to is
  worse than none.
- Matching runs in a worker thread and returns spans, not widgets; only
  the bodies whose matches changed are repainted. A full rebuild costs
  ~540ms on an 838-message log and takes the query box down with it,
  against ~1.7ms of matching plus ~6ms of Content building now.
- The messages moved into their own scroll pane, so the header, query box
  and hint stay put while the transcript scrolls.
- Backing out of a search result's actions menu now lands on the result
  you opened rather than the top of the list, so a set of results can be
  walked one at a time.
- `check_action` drops bindings that do nothing in the current view. `/`
  is two bindings on one key so it can read "Filter" on the home screen
  and "Search" in the viewer.

Two crashes fixed on the way:

- A `Collapsible` title built from transcript bytes was parsed as console
  markup, so a Bash command containing `[` raised MarkupError and took
  the viewer down. Titles, the viewer header and the session picker rows
  are now Content. Same reasoning for the query box's `select_on_focus`,
  which was eating the query whenever focus returned to it.
- A transcript the parser cannot structure now shows a warning banner and
  the raw log — searchable like any other — instead of an error screen.
  A file that cannot be read is still an error. `iter_hits` swallows the
  same class of failure per log, so one bad transcript cannot end a scan.

Both swallowing paths are instrumented, since neither is visible in the
UI: `claude.transcript_unparsed` carries the traceback, `search.scan_failed`
names the log it dropped. The viewer logs `tui.log_view` and
`log_search.start`/`.done`/`.dropped`, whose `repainted` count is what
shows the in-place update still working.

Also documents that a global install needs `--no-cache`: uv's build cache
is keyed on the hatch-vcs version, which is identical across untagged
commits, so `--force --reinstall` reports success while leaving the old
wheel in place.
@jongracecox
jongracecox force-pushed the worktree/20260826-refine-transcript-search branch from 00c6e07 to 9c5705b Compare August 28, 2026 04:53
@jongracecox
jongracecox merged commit 2f05e31 into main Aug 28, 2026
5 checks passed
@jongracecox
jongracecox deleted the worktree/20260826-refine-transcript-search branch August 28, 2026 05:06
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.

1 participant