Conversation
…x scroll bugs - New FilterTarget type (path | content | repo), cycled with 't' - Regex mode toggled with Tab in filter bar (invalid patterns → no-match) - src/render/filter-match.ts: makeExtractMatcher, makeRepoMatcher helpers - Filter bar text cursor: ←/→, ⌥←/⌥→ (word), Backspace, ⌥⌫/Ctrl+W - Fix isCursorVisible: check usedLines + h <= viewportHeight before returning true - Fix viewportHeight off-by-one: termHeight - 6 (HEADER_LINES 4 + indicator 2) - Expand applySelectAll / applySelectNone to respect new filter targets - Update docs: keyboard-shortcuts, filtering, interactive-mode - Bump version to 1.5.0 Closes #64
|
Coverage after merging feat/advanced-filter-targets-and-scroll-fixes into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Adds an advanced, multi-target filter system to the interactive TUI (path/content/repo) with optional regex matching, improves filter-bar text-editing behavior, and fixes scroll/viewport edge cases so the last repo/extracts remain reachable.
Changes:
- Introduce
FilterTarget(path | content | repo) plus regex matching, shared matcher utilities, and updated row building/selection behavior to respect the active target. - Implement a real filter input cursor model (left/right, word jumps, delete word, paste) and render updated filter bar UI (badges + live stats).
- Fix scroll visibility logic for multi-line rows and correct viewport height computation; update tests/docs/demo accordingly; bump version to
1.5.0.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types.ts | Adds FilterTarget type used across TUI/filter logic. |
| src/tui.ts | Implements target cycling, regex toggle, filter cursor editing, debounced live stats, and viewport height fix. |
| src/render/filter-match.ts | New pure matcher helpers for substring/regex + target-specific matching. |
| src/render/filter-match.test.ts | New unit tests for matcher behavior (substring/regex/invalid regex). |
| src/render/rows.ts | Extends buildRows to support target+regex; fixes isCursorVisible for multi-line row height. |
| src/render/filter.ts | Extends buildFilterStats for target+regex; adds visibleFiles for live stats. |
| src/render/selection.ts | Updates select-all/none to respect filter target + regex. |
| src/render.ts | Updates filter bar rendering (badges, live stats, cursor rendering) and in-list highlighting. |
| src/render.test.ts | Adds/updates tests for new filtering modes, badges, cursor rendering, stats, and scroll regression. |
| package.json | Version bump to 1.5.0. |
| docs/usage/interactive-mode.md | Documents new filter bar behavior, targets, regex mode, and selection semantics under filtering. |
| docs/usage/filtering.md | Updates filtering docs to cover targets + regex mode. |
| docs/reference/keyboard-shortcuts.md | Adds new keybindings for targets, cursor navigation, and regex toggle. |
| docs/architecture/components.md | Updates render-layer diagram/docs to include new matcher component. |
| demo/demo.tape | Updates demo script to exercise new filter behaviors (path + repo filtering). |
| CONTRIBUTING.md | Updates repo structure documentation to include new render filter-matcher files. |
- Fix: import TextMatchSegment in render.ts (was causing a TS error) - Perf: replace applyTextHighlight with makeTextHighlighter factory compiled once per renderGroups call, avoiding regex recompilation on every row - Perf: buildFilterStats now collects visibleFiles in the same pass as visibleRepos/visibleMatches, eliminating a second flatMap over all groups with freshly-allocated matchers - Docs: fix regex tip in keyboard-shortcuts.md — invalid patterns match nothing (zero results), not silently ignored - Docs: remove [path] badge from filter bar example in interactive-mode.md (renderer omits badge for default path target) - Docs: fix regex tip in interactive-mode.md — matches nothing on bad pattern - Docs: fix regex tip in filtering.md — matches nothing on bad pattern - Docs: reword architecture/components.md section 3b — not all render-layer modules are pure render functions living in src/render/ See #65
|
Coverage after merging feat/advanced-filter-targets-and-scroll-fixes into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
…th] badge - tui.ts: replace 't' handler in filter mode with KEY_SHIFT_TAB (\x1b[Z) so the letter t can be freely typed in the filter input; normal mode 't' is preserved for convenience outside the filter bar - render.ts: targetBadge is now always rendered ([path], [content], [repo], or their ·regex variants) — the active target is always explicit - render.ts: update filter-mode hint line to reflect Shift+Tab for target - render.ts: update help overlay filter-mode section likewise - render.test.ts: flip the 'does not show [path] badge' test to assert that [path] IS now always shown - docs(keyboard-shortcuts): 't' scoped to outside-filter-mode only; Shift+Tab added to filter-mode bindings table; badge description updated - docs(interactive-mode): example shows [path] badge; table replaces _(none)_ with [path]; cycling-key description updated - docs(filtering): cycling-key description updated See #65
|
Coverage after merging feat/advanced-filter-targets-and-scroll-fixes into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- Add Alt+Arrow (\x1b[1;3D/C) key sequences as additional word-jump bindings in filter mode, covering terminals that send these for Option+←/→ instead of Alt+b/f (e.g. iTerm2/xterm with 'Use Option as Meta key') - Update help overlay and keyboard-shortcuts.md to document all three word-jump variants (⌥←→, Ctrl+←→, Alt+b/Alt+f) - Make viewportHeight dynamic: getViewportHeight() accounts for filterBarLines (0/1/2) so isCursorVisible uses the same viewport as renderGroups, fixing scroll stopping early when filter bar or filter badge is visible - Fix extract cursor row: apply highlightText on path even when isCursor, consistent with the repo cursor row (search-match highlighting on magenta bg) - Remove redundant UpdateElementStyle(filterMatch) from Mermaid diagram - Bump package.json version to 1.5.0 with blog post and CHANGELOG entries See PR #65
|
Coverage after merging feat/advanced-filter-targets-and-scroll-fixes into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- Add Alt+Arrow (\x1b[1;3D/C) key sequences as additional word-jump bindings in filter mode, covering terminals that send these for Option+←/→ instead of Alt+b/f (e.g. iTerm2/xterm with 'Use Option as Meta key') - Update help overlay and keyboard-shortcuts.md to document all three word-jump variants (⌥←→, Ctrl+←→, Alt+b/Alt+f) - Make viewportHeight dynamic: getViewportHeight() accounts for filterBarLines (0/1/2) so isCursorVisible uses the same viewport as renderGroups, fixing scroll stopping early when filter bar or filter badge is visible - Fix extract cursor row: apply highlightText on path even when isCursor, consistent with the repo cursor row (search-match highlighting on magenta bg) - Remove redundant UpdateElementStyle(filterMatch) from Mermaid diagram - Bump package.json version to 1.5.0 with blog post and CHANGELOG entries See PR #65
9a97c5b to
3e09d80
Compare
|
Coverage after merging feat/advanced-filter-targets-and-scroll-fixes into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
- getViewportHeight: subtract stickyHeaderLines (scrollOffset > 0) to match the actual viewport renderGroups uses when the sticky repo header is shown - buildFilterStats: key visibleFileSet by repoFullName+path to avoid collisions across repos that share the same relative path (e.g. src/index.ts) - buildRows: propagate pending section label so remaining repos in a section still get their header even when the first repo of that section is filtered out — applies to both repo-target and path/content-target branches - mergeSegments: new helper in render.ts that sorts and coalesces overlapping TextMatchSegments before passing to highlightFragment, preventing garbled output when GitHub query matches and content-filter matches overlap - docs/blog/release-v1-5-0.md: fix filter-targets table (path is default, not content) and document t key (outside) vs Shift+Tab (inside filter bar) - Update PR #65 description to reflect the actual t / Shift+Tab bindings See PR #65
|
Coverage after merging feat/advanced-filter-targets-and-scroll-fixes into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Tests added first (src/render.test.ts) as regression guards for the three tui.ts-level state bugs — they exercise the pure functions involved and document the correct behavior that the tui.ts fixes must preserve: - isCursorVisible: stale scrollOffset > cursor (result of filter shrinking rows without clamping scrollOffset) always returns false, confirming the cursor is above the viewport and scrollOffset must be clamped - buildRows + isCursorVisible: invalid regex after toggle produces 0 rows; cursor at former position is out-of-range and must be re-clamped to 0 (Math.min(cursor, Math.max(0, rows.length - 1))) - renderGroups: sticky repo header (\u25b2) is NOT shown when cursor is on a repo row even when scrollOffset > 0 (only shown on extract rows whose repo row has scrolled above the viewport) Fixes (src/tui.ts): 1. stickyHeaderLines condition in getViewportHeight(): change \`scrollOffset > 0 ? 1 : 0\` to \`scrollOffset > 0 && cursor >= scrollOffset ? 1 : 0\` so the viewport is only shrunk by one when a sticky header will actually be rendered by renderGroups, preventing premature scroll-stop on repo rows. 2. scrollOffset not clamped after filter reduces rows: add \`scrollOffset = Math.min(scrollOffset, cursor)\` after cursor clamping in Ctrl+W/Alt+Backspace, Backspace, Del, and printable-char (paste) handlers. Without this, a previously-scrolled viewport could remain beyond the end of the shrunken row list, producing an empty groups area with no recovery. 3. Tab toggle (filterRegex) did not rebuild rows or clamp cursor/scrollOffset: after \`filterRegex = !filterRegex\`, now calls buildRows and applies the same clamping as all other filter-state-changing handlers. See PR #65
|
Coverage after merging feat/advanced-filter-targets-and-scroll-fixes into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Closes #64
Motivation
The filter bar only matched file paths as plain substrings. Three high-priority roadmap items (#63) called for regex support, fragment-text filtering, and repo-name filtering. Two scroll bugs also blocked navigation to the last repo / its extracts after unfolding.
What changed
🔍 Advanced filter targets
A new
FilterTargettype ("path" | "content" | "repo") controls what the filter bar matches against. Presstoutside the filter bar — or Shift+Tab inside the filter bar — to cycle through targets; the active target is shown as a badge in the bar.path(default)contentrepoorg/repo)🔎 Regex mode
Press
Tabinside the filter bar to toggle regex mode. A[regex]badge appears. Invalid patterns are handled gracefully — no crash, zero results shown.⌨️ Filter bar text cursor
The input now behaves like a real text field:
←/→— move cursor⌥←/⌥→(macOS) ·Ctrl+←/Ctrl+→·Alt+b/Alt+f— jump wordBackspace— delete char before cursor⌥⌫/Ctrl+W— delete word before cursor🐛 Scroll bug fixes
isCursorVisiblereturnedtruetoo early — it found the cursor row but didn't check whether its height (h) fit in remaining viewport space. Multi-line extract rows (h=2) were declared visible even whenrenderGroupswould have broken before rendering them. Fix: apply the same condition asrenderGroups:usedLines === 0 || usedLines + h <= viewportHeight.viewportHeightwas static — now computed dynamically viagetViewportHeight()to account for filter bar height (0–2 extra lines) and the sticky repo header (+1 line when scrolled past the top), soisCursorVisiblealways uses the same window asrenderGroups.How to test manually
f, type a path substring → only matching extracts visible.tuntil[content], pressf, type a code keyword → only extracts containing the keyword.tuntil[repo], pressf, type a repo name → only matching repos visible.Tab→[regex]badge; type.*service.*→ regex applied.Validation