Skip to content

feat: add Pane Search Phase 2 checkpoint (v1.3.2) - #45

Merged
HelloThisWorld merged 1 commit into
mainfrom
feature/v1.3.2-pane-search-phase2
Aug 12, 2026
Merged

feat: add Pane Search Phase 2 checkpoint (v1.3.2)#45
HelloThisWorld merged 1 commit into
mainfrom
feature/v1.3.2-pane-search-phase2

Conversation

@HelloThisWorld

Copy link
Copy Markdown
Owner

Summary

Phase 2 of the Pane Search roadmap toward v1.4: the Phase 1 functional
search becomes the winTerm search experience. Two deliverables: the
restyled, width-adaptive search box, and the scrollbar search overview
with markers decoupled from the ShowMarks setting. Engineering
checkpoint 1.3.2 — not an alpha, not a beta, not a downloadable
milestone. GitHub Latest and WinGet keep pointing at v1.2.0, and
v1.3.0-beta3 remains the newest published prerelease.

Do not add the build label — quick validation is the intended CI
depth for this PR.

Related issues

None — roadmap work item (Pane Search Phase 2), building on #44.

Detailed changes

Phase 1 baseline (unchanged)

Ctrl+F / Ctrl+Shift+F → active pane → pane-local SearchBoxControl
live search over the full buffer/scrollback → all matches highlighted,
Enter/Shift+Enter wrap-around navigation, Esc cleanup, split-pane and
shell-input isolation. The upstream pipeline (SearchBoxControl,
ControlCore::Search, Search/TextBuffer::SearchText, renderer
highlights) stays the engine; Phase 2 adds no second search engine, index,
or buffer copy.

SearchBox UX changes

SearchBoxControl.xaml is restyled in place — same control, same
ownership, same events, same x:Uids:

  • winTerm compact overlay language (matching the Command Timeline overlay):
    SystemControlBackgroundChromeMediumLowBrush surface, subtle border, 8px
    overlay radius, 27px chrome-density controls
    (Design/DensityTokens.h / Design/RadiusTokens.h values), and a
    leading search glyph marked AccessibilityView="Raw".
  • Layout order: icon, input, current / total status, case toggle, regex
    toggle, previous, next, close.
  • The counter is untouched logic-wise: SetStatus/_FormatStatus keep the
    localized m/n, "No results", "Searching", bounded 999+/?, and
    invalid-regex semantics, fed by ControlCore::Search results — no second
    count, no XAML-side recounting.
  • Narrow panes: TermControl pushes the pane width into the box
    (RootGrid.SizeChangedSetAvailableWidth), which maps it through a
    pure helper to three visual states — Normal, Compact (case/regex toggles
    collapse, input narrows), Minimal (status and prev/next also collapse) —
    so the input and the close button stay usable at any width. Hidden
    toggles keep their checked state, so search options stay in effect. The
    mapping is monotonic in the pane width, so states cannot oscillate.

Scrollbar overview implementation

Reuses the existing scrollbar mark pipeline end to end — the
ScrollBarCanvas WriteableBitmap, the throttled
_throttledUpdateScrollbar path, and the existing right-third search pip
stripe with same-row deduplication. The dedup loop moved into a tiny shared
helper (ForEachDistinctSearchRow in the new SearchUxHelpers.h) used by
both the drawing code and the tests. Marker positions map buffer rows over
newMaximum + newViewportSize exactly as before, i.e. across the full
searchable scrollback, not the viewport. No new canvas, renderer, timer, or
marker database; a closed search performs no marker work.

ShowMarks decoupling

The surface gate changed from _showMarksInScrollbar alone to
generic || searchOpen (ShouldRenderScrollbarMarkSurface), with each
category keeping its own rule inside the draw:

  • generic shell/prompt marks still render only when settings.ShowMarks()
    is true (default false, unchanged);
  • search pips render whenever the search box is open.

Refresh sites follow the same split: _handleSearchResults redraws while
search is open (also on plain navigation, so the current-match pip tracks),
and closing search always requests one redraw, which clears the pips and —
new — collapses the canvas when nothing remains to show (previously the
canvas could keep a stale bitmap visible after ShowMarks turned off at
runtime). A zero-dimension guard covers the deliberately hidden scrollbar
(scrollbarState: hidden): search stays fully functional, simply without
the overview, and the user's scrollbar preference is never overridden.

Current-match behavior

ControlCore::SearchCurrentMatchRow() projects the searcher's focused
index (Search::CurrentMatch() — the single source of truth) onto its
buffer row. The pip for that row draws at double width, extending into the
bitmap's empty center stripe, so it is clearly distinguishable from
ordinary matches without new colors, flashing, animation, or timers — and
therefore theme- and high-contrast-safe by construction (it uses the same
terminal foreground color as the other search pips). In-terminal
distinction was already native: the renderer paints the focused span with
dedicated highlightFocus colors (AtlasEngine _drawHighlighted), which
is retained untouched.

Accessibility

All existing x:Uids, tooltips, automation names, the accessible status
(GetAccessibleStatus + SearchBoxResultAnnouncement notifications), and
Tab/Shift+Tab/Enter/Shift+Enter/Esc handling are preserved. The new search
glyph is decorative and excluded from the accessibility tree. The current
result is never color-only: the counter states it textually, the terminal
focus highlight uses distinct colors, and the scrollbar pip differs by
width.

Validation performed

Environment: Windows 11 x64, MSVC 14.44 + Windows SDK (local toolchain).

  • pwsh scripts\winterm\verify-version.ps1passed (all 1.3.2
    literals and the extended checkpoint-tag list).
  • pwsh scripts\winterm\verify-branding.ps1passed.
  • pwsh scripts\winterm\test-release-workflow.ps1passed (checkpoint
    guard now covers v1.3.2 at all four sites).
  • pwsh scripts\winterm\test.ps1 -Suite Smoke -Configuration Release -Platform x64
    — run before push; result recorded in the PR conversation.
  • Control.Unit.Tests.dll x64 Release built locally (includes the XAML
    compile of the restyled SearchBoxControl) and run under TAEF; results
    in the PR conversation. CI's no-label quick validation intentionally does
    not compile native tests.

New/updated tests (ControlCoreTests):

  • TestSearchInvalidRegexNoStaleResults — invalid regex reports the error
    state with zero results and no stale highlight rows; fixing the
    expression recovers the matches.
  • TestSearchCurrentMatchRowTracksNavigation — the current row is -1
    without a search, focuses the first match on live typing, follows
    Enter navigation including wrap-around, and clears with ClearSearch().
  • TestSearchSameRowOccurrencesERROR foo ERROR bar ERROR yields 3
    occurrences for the counter and exactly 1 distinct row for the overview.
  • TestSearchUxHelperContracts — the §27 eligibility matrix
    (ShowMarks × search active), the width→layout-state mapping including
    exact thresholds and the unmeasured-host default, the state names the
    XAML defines, and distinct-row enumeration for empty and multi-row
    collections.
  • Phase 1 suites (all-match spans, navigation/clear, per-core isolation,
    KeyBindings defaults) re-run as regression.

Manual smoke checklist (§29) for user validation:

  1. Counter1..20 | ForEach-Object { Write-Host "line $_ ERROR test" },
    Ctrl+F, ERROR: 20 matches, n/20 counter, all highlighted,
    overview distribution on the scrollbar.
  2. Navigation — Enter cycles 1/20 → 20/20 → 1/20; Shift+Enter reverses;
    the wider current pip follows.
  3. Same-rowWrite-Host "ERROR foo ERROR bar ERROR": counter 3,
    one scrollbar row marker.
  4. Split panes — search in pane A affects only pane A; pane B keeps its
    own independent search, counter, highlights, and pips.
  5. ShowMarks=false (default) — search pips still appear; shell marks
    stay hidden. Mandatory test.
  6. ShowMarks=true — generic and search markers coexist.
  7. Close — Esc removes the box, highlights, and search pips; generic
    marks remain per ShowMarks; focus returns to the terminal.
  8. Narrow pane — the box compacts (toggles, then status/arrows); input
    and close stay usable; no pane layout damage.
  9. Themes — dark, light, high contrast: no invisible text/controls;
    match vs current match distinguishable.
  10. Hidden scrollbar — with scrollbarState: hidden, search works
    fully without an overview surface.

Version change

1.3.11.3.2 following the same engineering-checkpoint mechanism:
plain version, channel: stable, no prerelease suffix, package
1.3.2.0, v1.3.2 appended to the release workflow's checkpoint-tag
lists (four guard sites) and to the pinned copies in
test-release-workflow.ps1 and verify-version.ps1. All other version
surfaces advanced together (both version.json files,
ReleaseMetadata.h, MSIX manifest, three .rc files, PowerShell module
manifest/runtime, workspace fallbacks, script literals, both READMEs,
docs/current-progress.md, CHANGELOG.md). Nothing is published; Latest
stays v1.2.0.

Deferred Phase 3 work

Performance investigation and hardening: debouncing, large-scrollback
optimization, caching/indexing, worker threads. Also explicitly out of
scope, unchanged: clickable/hoverable markers, result previews, cross-pane
or global search, persistent search state.

Checklist

  • The change is focused and does not include unrelated formatting.
  • Tests were added or updated where appropriate.
  • All tests claimed above actually ran and passed.
  • User-facing behavior and limitations are documented in this repository.
  • This source/docs commit updates the root CHANGELOG.md.
  • The Wiki Development-Changes.md ledger has been pushed with the final source SHA, link, summary, and checkpoint/release.
  • Documentation screenshots reuse a suitable existing sanitized winterm-site asset, or no screenshot was added.
  • Version or schema changes include compatibility and migration notes.
  • Package identity, winterm.exe, and Microsoft Terminal coexistence remain isolated.
  • No command text, terminal output, clipboard content, credentials, or private paths are logged.
  • New source and script files contain the appropriate MIT license header.
  • I did not include generated build output, secrets, certificates, or local absolute paths.

Restyle the pane-local search box to the winTerm compact overlay
language: search glyph, input, current/total counter, case and regex
toggles, previous/next, and close at chrome density with theme-aware
system brushes, plus width-driven Normal/Compact/Minimal layout states
so narrow panes keep the input and close button usable. The counter
keeps the existing localized status semantics fed by the search core.

Show a scrollbar search overview while search is open, reusing the
existing mark bitmap and throttled update path: one right-aligned pip
per matching buffer row across the full scrollback with same-row
occurrences deduplicated, and the current match row drawn at double
width into the empty center stripe. Decouple the pips from ShowMarks -
generic marks keep obeying the setting while search pips render
whenever search is open - guard the hidden-scrollbar case, collapse
the canvas when nothing remains to draw, and refresh on navigation so
the emphasized pip tracks the search core. No timers or polling.

Add ControlCore::SearchCurrentMatchRow, the SearchUxHelpers header
(layout thresholds, mark-surface eligibility, distinct-row
enumeration), and four ControlCoreTests covering invalid regex,
current-row navigation, same-row deduplication, and the helper
contracts including the ShowMarks/search eligibility matrix.

Advance the engineering version to 1.3.2 following the checkpoint
convention, append v1.3.2 to the release workflow checkpoint-tag
lists, update every pinned version literal, and document the Phase 2
behavior in CHANGELOG.md, keyboard-shortcuts.md, and
current-progress.md.
@HelloThisWorld

Copy link
Copy Markdown
Owner Author

Local compiled test results (x64 Release, TAEF):

  • Control.Unit.Tests.dll (all classes) - 81/81 passed first try, including the four new Phase 2 tests: TestSearchInvalidRegexNoStaleResults, TestSearchCurrentMatchRowTracksNavigation, TestSearchSameRowOccurrences (3 occurrences -> 1 overview row), and TestSearchUxHelperContracts (the ShowMarks x search eligibility matrix, layout thresholds, state names, distinct-row enumeration). The build includes the XAML compile of the restyled SearchBoxControl.
  • SettingsModel.Unit.Tests.dll /name:*KeyBindingsTests* - 20/20 passed (Phase 1 Ctrl+F / Ctrl+Shift+F regression).

Gates: verify-version.ps1, verify-branding.ps1, test-release-workflow.ps1, and test.ps1 -Suite Smoke -Configuration Release -Platform x64 all pass locally on 740ff18.

@HelloThisWorld
HelloThisWorld merged commit 97ecb7e into main Aug 12, 2026
7 checks passed
@HelloThisWorld
HelloThisWorld deleted the feature/v1.3.2-pane-search-phase2 branch August 12, 2026 13:01
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