|
| 1 | +# Transcript Find (Inline Viewport) |
| 2 | + |
| 3 | +This document describes the design for “find in transcript” in the **TUI2 inline viewport** (the |
| 4 | +main transcript region above the composer), not the full-screen transcript overlay. |
| 5 | + |
| 6 | +The goal is to provide fast, low-friction navigation through the in-memory transcript while keeping |
| 7 | +the UI predictable and the implementation easy to review/maintain. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Goals |
| 12 | + |
| 13 | +- **Search the inline viewport content**, derived from the same flattened transcript lines used for |
| 14 | + scrolling/selection, so search results track what the user sees. |
| 15 | +- **Ephemeral UI**: no always-on search bar and no scroll bar in this iteration. |
| 16 | +- **Fast navigation**: |
| 17 | + - highlight all matches |
| 18 | + - jump to the next match repeatedly without reopening the prompt |
| 19 | +- **Stable anchoring**: jumping should land on stable content anchors (cell + line), not raw screen |
| 20 | + rows. |
| 21 | +- **Reviewable architecture**: keep `app.rs` changes small by placing feature logic in a dedicated |
| 22 | + module and calling it from the render loop and key handler. |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## Current Implementation (What We Have Today) |
| 27 | + |
| 28 | +This section documents the current state so it’s easy to compare against the “ideal/perfect” end |
| 29 | +state discussed in review. |
| 30 | + |
| 31 | +### UI |
| 32 | + |
| 33 | +- When active, a single prompt row is rendered **above the composer**: |
| 34 | + - `"/ query current/total"` |
| 35 | +- Matches are highlighted in the transcript: |
| 36 | + - all matches: underlined |
| 37 | + - current match: reversed + bold + underlined |
| 38 | +- The prompt is **not persistent**: it only appears while editing. |
| 39 | + |
| 40 | +### Keys |
| 41 | + |
| 42 | +- `Ctrl-F`: open the find prompt and start editing the query. |
| 43 | +- While editing: |
| 44 | + - type to edit the query (highlights update as you type) |
| 45 | + - `Backspace`: delete one character |
| 46 | + - `Ctrl-U`: clear the query |
| 47 | + - `Enter`: close the prompt and jump to a match (if any) |
| 48 | + - `Esc`: close the prompt without clearing the query (highlights remain) |
| 49 | +- `Ctrl-G`: jump to next match. |
| 50 | + - Works while editing (prompt stays open). |
| 51 | + - Works even after the prompt is closed, as long as the query is still active. |
| 52 | +- `Esc` (when not editing and a query is active): clears the search/highlights. |
| 53 | + |
| 54 | +### Footer hints |
| 55 | + |
| 56 | +- When the find prompt is visible, the footer shows `Ctrl-G next match`: |
| 57 | + - in the shortcut summary line |
| 58 | + - and in the `?` shortcut overlay |
| 59 | + |
| 60 | +### Implementation layout |
| 61 | + |
| 62 | +- Core logic lives in `tui2/src/transcript_find.rs`: |
| 63 | + - key handling |
| 64 | + - match computation/caching |
| 65 | + - jump selection |
| 66 | + - per-line rendering helper (`render_line`) and prompt rendering helper (`render_prompt_line`) |
| 67 | +- `tui2/src/app.rs` is kept mostly additive by delegating: |
| 68 | + - early key handling delegation in `App::handle_key_event` |
| 69 | + - per-frame recompute/jump hook after transcript flattening |
| 70 | + - per-row render hook for match highlighting |
| 71 | + - prompt + cursor positioning while editing |
| 72 | +- Footer hint integration is wired via `set_transcript_ui_state(..., find_visible)` through: |
| 73 | + - `tui2/src/chatwidget.rs` |
| 74 | + - `tui2/src/bottom_pane/mod.rs` |
| 75 | + - `tui2/src/bottom_pane/chat_composer.rs` |
| 76 | + - `tui2/src/bottom_pane/footer.rs` |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +## UX and Keybindings |
| 81 | + |
| 82 | +### Entering search |
| 83 | + |
| 84 | +- `Ctrl-F` opens the find prompt on the line immediately above the composer. |
| 85 | +- While the prompt is open, typed characters update the query and immediately update highlights. |
| 86 | + |
| 87 | +### Navigating results |
| 88 | + |
| 89 | +- `Ctrl-G` jumps to the next match. |
| 90 | + - Works while the prompt is open. |
| 91 | + - Also works after the prompt is closed as long as a non-empty query is still active (so users can |
| 92 | + “keep stepping” through matches). |
| 93 | + |
| 94 | +### Exiting / clearing |
| 95 | + |
| 96 | +- `Esc` closes the prompt without clearing the active query (and therefore keeps highlights). |
| 97 | +- `Esc` again (when not editing and a query is active) clears the search/highlights. |
| 98 | + |
| 99 | +### Footer hints |
| 100 | + |
| 101 | +When the find prompt is visible, we surface the relevant navigation key (`Ctrl-G`) in: |
| 102 | + |
| 103 | +- the shortcut summary line (the default footer mode) |
| 104 | +- the “?” shortcut overlay |
| 105 | + |
| 106 | +This keeps the prompt itself visually minimal. |
| 107 | + |
| 108 | +--- |
| 109 | + |
| 110 | +## Data Model: Search Over Flattened Lines |
| 111 | + |
| 112 | +Search operates over the same representation as scrolling and selection: |
| 113 | + |
| 114 | +1. Cells are flattened into a list of `Line<'static>` plus parallel `TranscriptLineMeta` entries |
| 115 | + (see `tui2/src/tui/scrolling.rs` and `tui2/docs/tui_viewport_and_history.md`). |
| 116 | +2. The find module searches **plain text** extracted from each flattened line (by concatenating its |
| 117 | + spans’ contents). |
| 118 | +3. Each match stores: |
| 119 | + - `line_index` (index into flattened lines) |
| 120 | + - `range` (byte range within the flattened line’s plain text) |
| 121 | + - `anchor` derived from `TranscriptLineMeta::CellLine { cell_index, line_in_cell }` |
| 122 | + |
| 123 | +The anchor is used to update `TranscriptScroll` when jumping so the viewport lands on stable content |
| 124 | +even if the transcript grows. |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +## Matching Semantics |
| 129 | + |
| 130 | +### Smart-case |
| 131 | + |
| 132 | +The search is “smart-case”: |
| 133 | + |
| 134 | +- If the query contains any ASCII uppercase, the match is case-sensitive. |
| 135 | +- Otherwise, both haystack and needle are matched in ASCII-lowercased form. |
| 136 | + |
| 137 | +This avoids expensive Unicode case folding and keeps behavior predictable in terminals. |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## Rendering |
| 142 | + |
| 143 | +### Highlights |
| 144 | + |
| 145 | +- All matches are highlighted (currently: underlined). |
| 146 | +- The “current match” is emphasized more strongly (currently: reversed + bold + underlined). |
| 147 | + |
| 148 | +Highlighting is applied at render time for each visible line by splitting spans into segments and |
| 149 | +patching styles for the match ranges. |
| 150 | + |
| 151 | +### Prompt line |
| 152 | + |
| 153 | +While editing, the line directly above the composer shows: |
| 154 | + |
| 155 | +`/ query current/total` |
| 156 | + |
| 157 | +It is rendered inside the transcript viewport area (not as a persistent UI element), and the cursor |
| 158 | +is moved into this line while editing. |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## Performance / Caching |
| 163 | + |
| 164 | +Recomputing matches happens only when needed. The search module caches based on: |
| 165 | + |
| 166 | +- transcript width (wrapping changes can change the flattened line list) |
| 167 | +- number of flattened lines (transcript growth) |
| 168 | + |
| 169 | +This keeps the work proportional to actual content changes rather than every frame. |
| 170 | + |
| 171 | +--- |
| 172 | + |
| 173 | +## Code Layout (Additive, Review-Friendly) |
| 174 | + |
| 175 | +The implementation is structured so `app.rs` only delegates: |
| 176 | + |
| 177 | +- `tui2/src/transcript_find.rs` owns: |
| 178 | + - query/edit state |
| 179 | + - match computation and caching |
| 180 | + - key handling for find-related shortcuts |
| 181 | + - rendering helpers for highlighted lines and the prompt line |
| 182 | + - producing a scroll anchor when a jump is requested |
| 183 | + |
| 184 | +`app.rs` integration points are intentionally small: |
| 185 | + |
| 186 | +- **Key handling**: early delegation to `TranscriptFind::handle_key_event`. |
| 187 | +- **Render**: |
| 188 | + - call `TranscriptFind::on_render` after building flattened lines to apply pending jumps |
| 189 | + - call `TranscriptFind::render_line` per visible row |
| 190 | + - render `render_prompt_line` when active and set cursor with `cursor_position` |
| 191 | +- **Footer**: |
| 192 | + - `set_transcript_ui_state(..., find_visible)` so the footer can show find-related hints only when |
| 193 | + the prompt is visible. |
| 194 | + |
| 195 | +--- |
| 196 | + |
| 197 | +## Comparison to the “Ideal” End State |
| 198 | + |
| 199 | +### Ideal UX (what “perfect” looks like) |
| 200 | + |
| 201 | +- **Ephemeral, minimal UI**: no always-on search bar, and no scroll bar for this feature. |
| 202 | +- **Fast entry**: `Ctrl-F` opens a single prompt row above the composer. |
| 203 | +- **Live feedback**: highlights update as you type, and the prompt shows `current/total`. |
| 204 | +- **Repeat navigation without closing**: `Ctrl-G` jumps to the next match while the prompt stays |
| 205 | + open, and continues to work after the prompt closes as long as the query is active. |
| 206 | +- **Predictable exit semantics**: |
| 207 | + - `Enter`: accept query, close prompt, and jump (if any matches) |
| 208 | + - `Esc`: close the prompt but keep the query/highlights |
| 209 | + - `Esc` again (with an active query): clear the query/highlights |
| 210 | +- **Stable jumping**: navigation targets stable transcript anchors (cell + line-in-cell), so jumping |
| 211 | + behaves well as the transcript grows. |
| 212 | +- **Discoverability without clutter**: when the prompt is visible, the footer/shortcuts surface the |
| 213 | + navigation key (`Ctrl-G`) so the prompt itself stays tight. |
| 214 | +- **Future marker integration**: if/when a scroll indicator is introduced, match markers integrate |
| 215 | + with it (faint ticks for match lines, stronger marker for the current match). |
| 216 | + |
| 217 | +### Already aligned with the ideal |
| 218 | + |
| 219 | +- Ephemeral prompt (no always-on bar). |
| 220 | +- Live highlighting while typing. |
| 221 | +- `Ctrl-G` repeat navigation without reopening the prompt (including while editing). |
| 222 | +- Stable jump anchoring via `(cell_index, line_in_cell)` metadata. |
| 223 | +- Footer hints (`Ctrl-G next match`) shown only while the prompt is visible. |
| 224 | +- Minimal, review-friendly integration points in `app.rs` via `tui2/src/transcript_find.rs`. |
| 225 | + |
| 226 | +### Not implemented yet (intentional deferrals) |
| 227 | + |
| 228 | +- Prev match (e.g. `Ctrl-Shift-G`). |
| 229 | +- “Contextual landing” when jumping (e.g. padding/centering so the match isn’t pinned to the top). |
| 230 | +- Match markers integrated with a future scroll indicator. |
| 231 | + |
| 232 | +### Known limitations / trade-offs in the current version |
| 233 | + |
| 234 | +- Matching is ASCII smart-case (no full Unicode case folding). |
| 235 | +- Match ranges are byte ranges in the flattened plain text. This is fine for styling spans by byte |
| 236 | + slicing, but any future “column-precise” behaviors should be careful with multi-byte characters. |
| 237 | + |
| 238 | +--- |
| 239 | + |
| 240 | +## Future Work (Not Implemented Here) |
| 241 | + |
| 242 | +- **Prev match**: add `Ctrl-Shift-G` for previous match if desired. |
| 243 | +- **Marker integration**: if/when a scroll indicator is added, include match markers derived from |
| 244 | + match line indices (faint ticks) and a stronger marker for the current match. |
| 245 | +- **Contextual jump placement**: center the current match (or provide padding above) rather than |
| 246 | + placing it at the exact top row when jumping. |
0 commit comments