feat(search): ⌃F searches the output across the panes - #29
Merged
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nuKnWthnELNkrd86q5KWN
Plain text in, matching lines out: nothing here knows about panes, markup or windows, which is what keeps it in Core and what makes a match mean what it looks like. A colour tag mid-word must not split a match, and nobody should be able to search for #ff0000 and find every red line. Five decisions, each pinned: case ignored in both modes (HistorySearch already is, and (?-i) is the way back, which is why there is no third toggle); one match per line, the first, with offsets so a row can show why it is listed; an empty query matches nothing, unlike history, because a pane buffer is thousands of lines and "everything, oldest first" is the pane you are already looking at; an invalid pattern is a state, because a regex is unparseable most of the time it is being typed; and a match timeout, because this runs on the UI thread on every keystroke over every line of every window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nuKnWthnELNkrd86q5KWN
MarkupText.Plain is what a markup line actually puts on the screen, and PaneLine holds it beside the markup, computed once at append. On demand would restrip every line of every window on every keystroke of a query, and an incremental surface is the whole point. Plain rather than markup because a match has to mean what it looks like: a world may change colour mid-word, and matching the markup would find neither half — the same defect a URL split by a colour change has, one layer down — while a query for #ff0000 must not find every red line. Chrome rows carry no plain text, so a search cannot find its own boundary bars. Plain and VisibleLength are pinned against each other over a table of inputs: they share a protect-then-strip shape, and a divergence would put a match's offsets in a different coordinate system from the width renderers measure with. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nuKnWthnELNkrd86q5KWN
…t says The HistorySearchPrompt/HistorySurface split verbatim: the rules and the wording are exactly the part a headless test can pin. ⌥E and ⌥A both re-search from the top, because each changes what the list is and a pointer kept at row 12 of a different result set points at nothing the reader chose. The header states the bound it searched — '12 found · 4,812 lines held' — because ⌃F sees the pane buffer and not a session's whole history, and a reader who cannot find an old line should be able to see why rather than concluding the search is broken. The window column is drawn only when every window is searched; with one window it would be the same word on every row. The footer names only keys that work, pinned by a test that presses every one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nuKnWthnELNkrd86q5KWN
The chord, the scope and the landing. ⏎ activates the window the line is really in — through Activate, the one activation path, so the pane, the tab and the session move together rather than a pane being scrolled behind the reader's back — and marks the line with a bar, fourth of the boundary bars, which points rather than repaints: the line is worth having because it is the game's own text in the game's own colours. Two things measured rather than chosen. ⌥⇧G is *not* bound: kitty writes it as CSI 103;4u, a kitty-keyboard-protocol sequence AnsiInputParser drops, so it could never arrive — ⌥⇧1's story one letter over. And Escape does not clear the bar: a claimed Escape does not set _escapeAt, and TryAltEnter pairs an unclaimed one with a following Enter to make Alt+⏎, so binding it would break the newline chord for as long as a bar was on screen. ⌥G removes the bar *before* re-running the search. The bar is itself a row, so a search run around it returns indices in a buffer about to lose one and every hit below it lands a row early — which is what the test caught. Two kinds of inserted chrome now, so the index bookkeeping every buffer mark depends on is one pair of methods (InsertChromeRow/RemoveChromeRow) rather than written out at each site: the freeze point, the pending boundary, the activity bar and the search bar all move together or none of them do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nuKnWthnELNkrd86q5KWN
Four views, because four things about ⌃F are only visible in a frame: a plain query with its hits marked, the same query read as a pattern (the header is the only place either state is said — compose/compose-literal's reasoning), the widened scope where the window column appears, and what ⏎ leaves behind. The last is over a split, so the pane is narrower than the terminal: that is the geometry that catches a landing scrolled to the wrong row. The demo scene now loads with _watching off. It pours a spawn window's whole history in before the first frame, so every line counted as missed and any frame that later made such a window visible carried an activity bar reporting the client's own setup as news — which is exactly what the first cut of the landed frame showed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015nuKnWthnELNkrd86q5KWN
HarryCordewener
force-pushed
the
feat/window-activity-boundary
branch
from
August 11, 2026 21:00
037ae39 to
7659a87
Compare
HarryCordewener
force-pushed
the
feat/pane-search
branch
from
August 11, 2026 21:00
31ffd6f to
bee7031
Compare
HarryCordewener
merged commit Aug 11, 2026
62142b1
into
feat/window-activity-boundary
3 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third of a three-PR stack — based on
feat/window-activity-boundary(#27), which is based onfeat/find-chords(#26). Review those first.Design:
docs/superpowers/specs/2026-08-11-pane-search-and-activity-design.md, part 2.⌃Fopens a modal results surface over the panes. Typing filters,⌥Ereads the query as a pattern,⌥Awidens from the focused window to every window,⏎goes to a hit and marks it with a bar, and⌥Gwalks to the next one.What is searched, and what is said about it
The pane buffer — not
WorldSession.Scrollback, not the file-backed spill.RestoreLog'sreasoning one layer over: a spawn window's lines reach neither, so a session-keyed search would find
nothing in exactly the windows people search hardest. The bound is stated rather than implied
(
12 found · 4,812 lines held), so a reader who cannot find an old line can see why.Matching runs over the visible text, held per line and computed once at append. Over the markup, a
world changing colour mid-word would split a match — the same defect a wrapped URL has, one layer down
— and
#ff0000would find every red line. Chrome rows carry no plain text, so a search cannot find itsown bars.
Case is ignored in both modes (
HistorySearchalready is;(?-i)is the way back, which is why thereis no third toggle). An invalid pattern is a state, because a regex is unparseable most of the time
it is being typed, and there is a match timeout, because this runs on the UI thread per keystroke.
Landing
⏎goes throughActivate— the one activation path — so a hit in a background pane brings the pane,the tab and the session forward together rather than scrolling something nobody is looking at. The bar
is the fourth of the boundary bars and points rather than repaints: the line is worth having because it
is the game's own text in the game's own colours.
Three things a reviewer might otherwise ask about
⌥⇧Gdecodes fine, but kitty writes it asCSI 103;4u— a kitty-keyboard-protocol sequenceAnsiInputParser.DispatchCsidrops.⌥⇧1's storyone letter over. A decode test is not an arrival test.
_escapeAt, andTryAltEnterpairsan unclaimed one with a following Enter to make
⌥⏎. Binding it here would break the newline chordfor as long as a bar was on screen.
⌥Gremoves the bar before re-running the search. The bar is itself a row, so a search run aroundit returns indices in a buffer about to lose one, and every hit below it lands a row early — which is
what the test caught.
Two kinds of client chrome now live in the line buffers, so
InsertChromeRow/RemoveChromeRoware theone place that fixes up everything indexing into one. The demo scene also loads with
_watchingoff:it pours a spawn window's whole history in before the first frame, and every line otherwise counted as
missed.
Verification
dotnet build -c Release SharpMUTerm.slnxclean and warning-free; five suites green (2,722 tests).Four new snapshot views rendered and looked at:
search,search-regex,search-all,search-landed(over a split, so the pane is narrower than the terminal — the geometry that catches alanding scrolled to the wrong row).
🤖 Generated with Claude Code
https://claude.ai/code/session_015nuKnWthnELNkrd86q5KWN