Files
codex/codex-rs/tui2/docs/transcript_find.md
Josh McKinney 97c54634f7 feat(tui2): add inline transcript find
Add an in-viewport find experience for the TUI2 transcript (the
scrollable region above the composer), without introducing a persistent
search bar or scroll indicator.

UX:
- Ctrl-F opens a 1-row "/ " prompt above the composer and updates
  highlights live as you type
- Ctrl-G jumps to the next match without closing the prompt, and keeps
  working after the prompt closes while the query is still active
- Esc closes the prompt but keeps the active query/highlights; Esc again
  clears the search
- Enter closes the prompt and jumps to the selected match

Implementation:
- Add a dedicated transcript_find module to own query/edit state,
  smart-case matching over flattened transcript lines, stable jump
  anchoring (cell + line-in-cell), and per-line highlight rendering
- Keep app.rs integration additive via small delegation calls from the
  key handler and render loop
- Plumb find visibility to the footer so shortcuts show Ctrl-G next match
  only while the find prompt is visible

Docs/tests:
- Add tui2/docs/transcript_find.md documenting current behavior vs the
  ideal/perfect end state and explicitly calling out deferred work
- Stabilize VT100-based rendering tests by forcing color output and
  emitting crossterm fg/bg colors directly in insert-history output
2025-12-23 13:47:45 -08:00

249 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Transcript Find (Inline Viewport)
This document describes the design for “find in transcript” in the **TUI2 inline viewport** (the
main transcript region above the composer), not the full-screen transcript overlay.
The goal is to provide fast, low-friction navigation through the in-memory transcript while keeping
the UI predictable and the implementation easy to review/maintain.
---
## Goals
- **Search the inline viewport content**, derived from the same flattened transcript lines used for
scrolling/selection, so search results track what the user sees.
- **Ephemeral UI**: no always-on search bar and no scroll bar in this iteration.
- **Fast navigation**:
- highlight all matches
- jump to the next match repeatedly without reopening the prompt
- **Stable anchoring**: jumping should land on stable content anchors (cell + line), not raw screen
rows.
- **Reviewable architecture**: keep `app.rs` changes small by placing feature logic in a dedicated
module and calling it from the render loop and key handler.
---
## Current Implementation (What We Have Today)
This section documents the current state so its easy to compare against the “ideal/perfect” end
state discussed in review.
For implementation details, see the rustdoc comments and unit tests in `tui2/src/transcript_find.rs`.
### UI
- When active, a single prompt row is rendered **above the composer**:
- `"/ query current/total"`
- Matches are highlighted in the transcript:
- all matches: underlined
- current match: reversed + bold + underlined
- The prompt is **not persistent**: it only appears while editing.
### Keys
- `Ctrl-F`: open the find prompt and start editing the query.
- While editing:
- type to edit the query (highlights update as you type)
- `Backspace`: delete one character
- `Ctrl-U`: clear the query
- `Enter`: close the prompt and jump to a match (if any)
- `Esc`: close the prompt without clearing the query (highlights remain)
- `Ctrl-G`: jump to next match.
- Works while editing (prompt stays open).
- Works even after the prompt is closed, as long as the query is still active.
- `Esc` (when not editing and a query is active): clears the search/highlights.
### Footer hints
- When the find prompt is visible, the footer shows `Ctrl-G next match`:
- in the shortcut summary line
- and in the `?` shortcut overlay
### Implementation layout
- Core logic lives in `tui2/src/transcript_find.rs`:
- key handling
- match computation/caching
- jump selection
- per-line rendering helper (`render_line`) and prompt rendering helper (`render_prompt_line`)
- `tui2/src/app.rs` is kept mostly additive by delegating:
- early key handling delegation in `App::handle_key_event`
- per-frame recompute/jump hook after transcript flattening
- per-row render hook for match highlighting
- prompt + cursor positioning while editing
- Footer hint integration is wired via `set_transcript_ui_state(..., find_visible)` through:
- `tui2/src/chatwidget.rs`
- `tui2/src/bottom_pane/mod.rs`
- `tui2/src/bottom_pane/chat_composer.rs`
- `tui2/src/bottom_pane/footer.rs`
---
## UX and Keybindings
### Entering search
- `Ctrl-F` opens the find prompt on the line immediately above the composer.
- While the prompt is open, typed characters update the query and immediately update highlights.
### Navigating results
- `Ctrl-G` jumps to the next match.
- Works while the prompt is open.
- Also works after the prompt is closed as long as a non-empty query is still active (so users can
“keep stepping” through matches).
### Exiting / clearing
- `Esc` closes the prompt without clearing the active query (and therefore keeps highlights).
- `Esc` again (when not editing and a query is active) clears the search/highlights.
### Footer hints
When the find prompt is visible, we surface the relevant navigation key (`Ctrl-G`) in:
- the shortcut summary line (the default footer mode)
- the “?” shortcut overlay
This keeps the prompt itself visually minimal.
---
## Data Model: Search Over Flattened Lines
Search operates over the same representation as scrolling and selection:
1. Cells are flattened into a list of `Line<'static>` plus parallel `TranscriptLineMeta` entries
(see `tui2/src/tui/scrolling.rs` and `tui2/docs/tui_viewport_and_history.md`).
2. The find module searches **plain text** extracted from each flattened line (by concatenating its
spans contents).
3. Each match stores:
- `line_index` (index into flattened lines)
- `range` (byte range within the flattened lines plain text)
- `anchor` derived from `TranscriptLineMeta::CellLine { cell_index, line_in_cell }`
The anchor is used to update `TranscriptScroll` when jumping so the viewport lands on stable content
even if the transcript grows.
---
## Matching Semantics
### Smart-case
The search is “smart-case”:
- If the query contains any ASCII uppercase, the match is case-sensitive.
- Otherwise, both haystack and needle are matched in ASCII-lowercased form.
This avoids expensive Unicode case folding and keeps behavior predictable in terminals.
---
## Rendering
### Highlights
- All matches are highlighted (currently: underlined).
- The “current match” is emphasized more strongly (currently: reversed + bold + underlined).
Highlighting is applied at render time for each visible line by splitting spans into segments and
patching styles for the match ranges.
### Prompt line
While editing, the line directly above the composer shows:
`/ query current/total`
It is rendered inside the transcript viewport area (not as a persistent UI element), and the cursor
is moved into this line while editing.
---
## Performance / Caching
Recomputing matches happens only when needed. The search module caches based on:
- transcript width (wrapping changes can change the flattened line list)
- number of flattened lines (transcript growth)
This keeps the work proportional to actual content changes rather than every frame.
---
## Code Layout (Additive, Review-Friendly)
The implementation is structured so `app.rs` only delegates:
- `tui2/src/transcript_find.rs` owns:
- query/edit state
- match computation and caching
- key handling for find-related shortcuts
- rendering helpers for highlighted lines and the prompt line
- producing a scroll anchor when a jump is requested
`app.rs` integration points are intentionally small:
- **Key handling**: early delegation to `TranscriptFind::handle_key_event`.
- **Render**:
- call `TranscriptFind::on_render` after building flattened lines to apply pending jumps
- call `TranscriptFind::render_line` per visible row
- render `render_prompt_line` when active and set cursor with `cursor_position`
- **Footer**:
- `set_transcript_ui_state(..., find_visible)` so the footer can show find-related hints only when
the prompt is visible.
---
## Comparison to the “Ideal” End State
### Ideal UX (what “perfect” looks like)
- **Ephemeral, minimal UI**: no always-on search bar, and no scroll bar for this feature.
- **Fast entry**: `Ctrl-F` opens a single prompt row above the composer.
- **Live feedback**: highlights update as you type, and the prompt shows `current/total`.
- **Repeat navigation without closing**: `Ctrl-G` jumps to the next match while the prompt stays
open, and continues to work after the prompt closes as long as the query is active.
- **Predictable exit semantics**:
- `Enter`: accept query, close prompt, and jump (if any matches)
- `Esc`: close the prompt but keep the query/highlights
- `Esc` again (with an active query): clear the query/highlights
- **Stable jumping**: navigation targets stable transcript anchors (cell + line-in-cell), so jumping
behaves well as the transcript grows.
- **Discoverability without clutter**: when the prompt is visible, the footer/shortcuts surface the
navigation key (`Ctrl-G`) so the prompt itself stays tight.
- **Future marker integration**: if/when a scroll indicator is introduced, match markers integrate
with it (faint ticks for match lines, stronger marker for the current match).
### Already aligned with the ideal
- Ephemeral prompt (no always-on bar).
- Live highlighting while typing.
- `Ctrl-G` repeat navigation without reopening the prompt (including while editing).
- Stable jump anchoring via `(cell_index, line_in_cell)` metadata.
- Footer hints (`Ctrl-G next match`) shown only while the prompt is visible.
- Minimal, review-friendly integration points in `app.rs` via `tui2/src/transcript_find.rs`.
### Not implemented yet (intentional deferrals)
- Prev match (e.g. `Ctrl-Shift-G`).
- “Contextual landing” when jumping (e.g. padding/centering so the match isnt pinned to the top).
- Match markers integrated with a future scroll indicator.
### Known limitations / trade-offs in the current version
- Matching is ASCII smart-case (no full Unicode case folding).
- Match ranges are byte ranges in the flattened plain text. This is fine for styling spans by byte
slicing, but any future “column-precise” behaviors should be careful with multi-byte characters.
---
## Future Work (Not Implemented Here)
- **Prev match**: add `Ctrl-Shift-G` for previous match if desired.
- **Marker integration**: if/when a scroll indicator is added, include match markers derived from
match line indices (faint ticks) and a stronger marker for the current match.
- **Contextual jump placement**: center the current match (or provide padding above) rather than
placing it at the exact top row when jumping.