Unified tree filter with lazy branch loading, search, and cwd prioritization#251
Conversation
…odals Replace the two separate filter modals (repo filter 'f' key, branch filter 'b' key) with a single tree-style filter where repos are expandable nodes with branches as children. Users can select a repo to filter by repo only, or expand a repo and select a branch to filter by both repo and branch. - Right arrow expands a collapsed repo (lazy-loads branches from the API) - Left arrow collapses an expanded repo (or collapses parent from a branch) - Enter on "All" clears all filters, on repo sets repo filter, on branch sets both repo and branch filter - Search filters both repo names and branch names, auto-expanding repos with matching branches - Remove 'b' key binding for separate branch filter modal
Detect the cwd repo root and branch at TUI launch and use them to sort the filter tree so the current repo appears first and, when expanded, the current branch appears first. This makes the most common filtering action a single keypress away. The detection is independent of the autoFilterRepo config option -- autoFilterRepo controls whether the filter is applied on launch, while this controls sort order in the filter modal.
Add rootPaths to tuiRepoBranchesMsg and verify repo identity in the handler to prevent stale in-flight responses from updating the wrong tree node after a filter tree rebuild. Remove unused totalCount variable in rebuildFilterFlatList (dead code from earlier approach).
Press 'b' from the queue view to open the unified filter tree with the target repo automatically expanded to its branches. Target repo is chosen by priority: single active repo filter > cwd repo > first repo. The cursor lands on the first branch entry once branches load.
…talCount) Strip fetchBranches down to backfill-only (renamed backfillBranches), extract generic moveToFront helper replacing duplicate reorder blocks and goto labels, and remove unused struct fields (flatFilterEntry.depth, tuiReposMsg.totalCount, tuiBranchesMsg branch fields).
Guard 'b' key handler to reject presses outside queue view before setting filterBranchMode, and clear the flag when exiting filter via Enter (matching the existing Esc behavior). Add regression tests for both scenarios.
roborev: Combined ReviewVerdict: 1 Medium issue found (no Critical/High findings; security reviews reported no vulnerabilities). Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
…ading stuck Three fixes from review #5317: 1. Remove h/l as tree expand/collapse keys in filter mode — they prevented typing repo/branch names with those letters. Arrow keys are now the only way to expand/collapse nodes. 2. Fix b-key auto-expansion to match multi-path active repo filters using full rootPaths equality instead of only matching single-path filters. 3. Return fetch errors via tuiRepoBranchesMsg instead of tuiErrMsg so the loading state is cleared on failure, preventing permanently stuck "..." indicators. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move error handling for tuiRepoBranchesMsg ahead of the view/staleness gate so errors are always surfaced — even after the user leaves filter view or the tree is rebuilt. Call handleConnectionError on the error path so consecutiveErrors is tracked and reconnect logic fires. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Four changes to speed up the default test suite: 1. Add //go:build integration to stream_integration_test.go (was named as integration but missing the tag). 2. Move TestDaemonRunStartsAndShutdownsCleanly to a new daemon_integration_test.go behind the integration tag — it spawns a real daemon with a 10s shutdown timeout. 3. Share a single 2048-bit RSA key across githubapp tests via sync.Once instead of generating one per test (~0.5-1s each, 7 calls). Only TestParsePrivateKey_PKCS8 still generates its own key since it needs PKCS8 encoding specifically. 4. Fix TestAnalyzeBranchFlagValidation to call ParseFlags + ValidateArgs directly instead of cmd.Execute(), which was wasting ~3s on a daemon connection timeout. Results: cmd/roborev 43s -> 14s, internal/daemon 20s -> 16s. All tests still run in CI which uses -tags integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ic fix - Add test for branch fetch connection errors triggering reconnect after 3 consecutive failures (review #5320 finding 1) - Add filterBranchMode=false assertion to out-of-view failure test (review #5320 finding 2) - Replace panic in testKey sync.Once with stored error checked via t.Fatalf on each call (review #5324 finding 1) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Branch search only matched repos that had already been expanded, since branches are lazy-loaded. When the user types search text, trigger fetchBranchesForRepo for any repos with unloaded branches (children == nil). As branches arrive, rebuildFilterFlatList re-evaluates the search and shows matching results. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: Mostly clean change set, with one Medium UI behavior regression to address before merge. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
…uring search Search-triggered lazy branch loads (fetchUnloadedBranches) no longer set expanded=true on the tree node. This prevents repos from staying expanded after the search is cleared. Added expandOnLoad flag to tuiRepoBranchesMsg so only user-initiated expansion (right-arrow, b-key) sets expanded=true. Left-arrow collapse now works during active search by tracking a userCollapsed flag on tree nodes. When set, search auto-expansion is suppressed for that repo. The flag resets when the search is cleared. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a search-triggered branch fetch is in-flight (loading=true) and the user presses right-arrow, set expanded=true immediately. The arriving response (expandOnLoad=false) won't clear it, so children display as the user intended. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: 2 Medium-severity issues should be addressed before merge. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
Avoid false-positive findings for injection/sanitization of data that originates from the user's own filesystem, and for thundering-herd concerns on bounded localhost HTTP requests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: Looks good overall, with one Medium regression risk to address before merge. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
When a search-triggered load fails after the user pressed right-arrow (setting expanded=true), the error handler clears loading but leaves expanded=true with children=nil. The right-arrow guard previously blocked retry in this state. Now right-arrow enters the fetch path whenever children is nil and not loading, regardless of expanded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: One Medium correctness issue was found; no High/Critical or security issues were identified. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
rootPathsMatch compared paths by index, so equivalent path sets in different orders were treated as different repos. This could cause stale-message misclassification when the tree is rebuilt with a different path ordering while a branch fetch is in-flight. Now sorts copies before comparing. Single-path fast path avoids allocation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Sanitize repo/branch names with sanitizeForDisplay before rendering in the filter view, consistent with how commit messages are handled - Cap fetchUnloadedBranches to 5 concurrent fetches per keystroke to bound resource usage when many repos are tracked; subsequent keystrokes pick up remaining repos - Add end-to-end test for branch response with reordered root paths (accepted) and mismatched paths (rejected as stale) - Add test verifying fetch cap at maxSearchBranchFetches Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: One Medium-severity issue should be addressed before merge. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: default, security) |
fetchUnloadedBranches now counts already in-flight requests toward the maxSearchBranchFetches cap, so rapid typing cannot exceed 5 concurrent fetches. On each tuiRepoBranchesMsg (success or error), a top-up fetch is triggered for remaining unloaded repos while search is active, so all repos eventually load regardless of keystroke count. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: Mostly clean change set, but there is 1 Medium regression risk that should be addressed before merge. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
- Use rootPathsMatch for active repo preselection instead of inline positional comparison, so multi-path repos with different ordering are correctly preselected - Add fetchFailed flag to treeFilterNode to prevent infinite retry loops when search-triggered branch fetches fail with persistent errors; failed repos are skipped by fetchUnloadedBranches but can be retried manually via right-arrow - Tighten progressive loading test to only complete in-flight repos and verify specific top-up targets - Add tests for multi-path preselection and error-path retry behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: Mostly solid refactor, but there is 1 Medium-severity correctness issue that should be fixed before merge. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
- Propagate expandOnLoad to error messages so fetchFailed is only set for search-triggered failures (!msg.expandOnLoad). Manual expand failures no longer block later search auto-loading. - Clear fetchFailed alongside userCollapsed when search is empty so each search session starts with a clean slate. - Reset consecutiveErrors on successful branch fetch, consistent with all other success handlers. - Add tests for manual-failure-then-search and fetchFailed session reset scenarios. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A search-triggered fetch failing after the user cleared search would set fetchFailed, blocking the repo in the next search session. Now fetchFailed is only set when filterSearch is non-empty. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: Mostly clean, with one Medium functional regression to address before merge. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
If the user typed search text before repos finished loading, the tuiReposMsg handler now calls fetchUnloadedBranches so branch matches appear immediately without requiring another keystroke. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: Mostly clean, with one Medium regression risk to address before merge. CriticalNone. HighNone. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
After reconnecting to a new daemon address, clear fetchFailed on all tree nodes and trigger a top-up branch fetch if search is active. Previously, repos that failed during the old connection would stay blocked until the user manually cleared and re-entered search. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Thread filterSearchSeq through fetch requests so error responses from a previous search session don't mark repos as fetchFailed in the current session. Also adds test coverage for the search-typed-before- repos-load path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined ReviewVerdict: One medium-severity regression risk identified; no critical/high issues and no new security vulnerabilities reported. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
fetchFailed persisted across non-empty search edits (e.g. "a" → "ab"), blocking auto-fetch for repos that failed in the previous search state. Now clearFetchFailed() runs on every search keystroke so changed search text retries previously failed repos. Also adds reconnect test coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Gosh, so many darn edge cases! I'm going to merge this once the build is green |
roborev: Combined ReviewVerdict: One Medium finding remains; security reviews reported no vulnerabilities. Medium
Synthesized from 4 reviews (agents: codex, gemini | types: security, default) |
…ization (roborev-dev#251) This PR replaces the separate repo/branch filter modals with a unified tree-style filter view. Repos are listed as expandable nodes with branches lazy-loaded on demand. The current working directory repo and branch are prioritized in sort order. <img width="862" height="472" alt="image" src="https://github.com/user-attachments/assets/e67b5a77-0d4f-4edb-aaab-15bba90972d8" /> ## Summary - Unified tree filter replacing separate repo and branch modals - Lazy branch loading: branches fetch per-repo on expand (right-arrow) or search - Search matches unloaded branches by triggering lazy fetches in the background - Cwd repo sorts to top of filter tree; cwd branch sorts to top within its repo - `b` key shortcut opens filter with branches auto-expanded for the most relevant repo - Left-arrow collapse works during active search (overrides search auto-expansion) - Search-triggered loads do not permanently expand repos; only user-initiated expansion persists - Stale in-flight branch responses detected via `rootPaths` identity check - Branch fetch errors surface through connection tracking and reconnect logic - Slow tests moved behind `//go:build integration` tag; shared RSA key generation via `sync.Once` - Dead code removed: old `fetchBranches`, `flatFilterEntry.depth`, unused message fields ## Details **Unified tree filter** (`5bb3631`): Replaces the two-modal flow (repo list → branch list) with a single tree view. Each repo is a collapsible node; right-arrow expands to show branches, left-arrow collapses. Enter selects a repo or branch filter. **Cwd prioritization** (`e997569`): At TUI launch, the model detects `cwdRepoRoot` and `cwdBranch`. The cwd repo sorts to index 0 in the filter tree, and the cwd branch sorts to first position when branches load. Independent of `autoFilterRepo` config. **Stale message guards** (`e06e319`): `tuiRepoBranchesMsg` carries `rootPaths` so the handler can verify the tree node at `repoIdx` still represents the same repo, preventing races when the filter is closed and reopened during in-flight fetches. **'b' key shortcut** (`ec18ede`, `db62b33`): Opens the unified filter with branches auto-expanded for the target repo (active filter > cwd > first). Cursor lands on the first branch. `filterBranchMode` clears on escape, enter, and after branches load. **Filter regressions fixed** (`49e73f8`): `h`/`l` keys no longer captured by expand/collapse (arrow-keys only), multi-root-path repos matched correctly via `rootPathsMatch()`, branch fetch failures clear loading state. **Error handling fixes** (`5e4d7f1`): Branch fetch errors bypass the view/staleness gate so connection tracking and reconnect logic always fire. Errors are no longer silently dropped when the filter view is not active. **Lazy search loading** (`527f35b`): Typing in the filter search triggers `fetchUnloadedBranches()` for repos that haven't loaded branches yet, so search can match branch names across all repos. **Search expansion fixes** (`d034bbf`, `104a9c9`): Search-triggered loads use `expandOnLoad: false` so repos don't permanently expand. Left-arrow sets `userCollapsed` to override search auto-expansion. Right-arrow during an in-flight search load preserves user expand intent. **Test speedups** (`57ace30`): `TestDaemonRunStartsAndShutdownsCleanly` and stream tests moved behind `//go:build integration`. RSA key generation shared via `sync.Once`. Analyze test uses direct arg validation instead of `cmd.Execute()`. ## Test plan - [x] Unified tree filter shows repos with expand/collapse via arrow keys - [x] Lazy branch loading on right-arrow and search triggers - [x] Search matches branches across all repos (including previously unloaded) - [x] Search-triggered loads do not permanently expand repos - [x] Left-arrow collapses repos during active search - [x] Right-arrow during in-flight search load preserves user intent - [x] `userCollapsed` resets when search is cleared - [x] Cwd repo/branch sort to first position - [x] Stale branch messages with mismatched rootPaths are dropped - [x] `b` key auto-expands target repo and positions cursor on first branch - [x] Branch fetch errors clear loading state and trigger reconnect - [x] `h`/`l` keys pass through to search; only arrow keys expand/collapse - [x] Multi-root-path repos matched correctly in b-mode expansion - [x] `go test ./...` passes; `go vet ./...` clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This PR replaces the separate repo/branch filter modals with a unified tree-style filter view. Repos are listed as expandable nodes with branches lazy-loaded on demand. The current working directory repo and branch are prioritized in sort order.
Summary
bkey shortcut opens filter with branches auto-expanded for the most relevant reporootPathsidentity check//go:build integrationtag; shared RSA key generation viasync.OncefetchBranches,flatFilterEntry.depth, unused message fieldsDetails
Unified tree filter (
5bb3631): Replaces the two-modal flow (repo list → branch list) with a single tree view. Each repo is a collapsible node; right-arrow expands to show branches, left-arrow collapses. Enter selects a repo or branch filter.Cwd prioritization (
e997569): At TUI launch, the model detectscwdRepoRootandcwdBranch. The cwd repo sorts to index 0 in the filter tree, and the cwd branch sorts to first position when branches load. Independent ofautoFilterRepoconfig.Stale message guards (
e06e319):tuiRepoBranchesMsgcarriesrootPathsso the handler can verify the tree node atrepoIdxstill represents the same repo, preventing races when the filter is closed and reopened during in-flight fetches.'b' key shortcut (
ec18ede,db62b33): Opens the unified filter with branches auto-expanded for the target repo (active filter > cwd > first). Cursor lands on the first branch.filterBranchModeclears on escape, enter, and after branches load.Filter regressions fixed (
49e73f8):h/lkeys no longer captured by expand/collapse (arrow-keys only), multi-root-path repos matched correctly viarootPathsMatch(), branch fetch failures clear loading state.Error handling fixes (
5e4d7f1): Branch fetch errors bypass the view/staleness gate so connection tracking and reconnect logic always fire. Errors are no longer silently dropped when the filter view is not active.Lazy search loading (
527f35b): Typing in the filter search triggersfetchUnloadedBranches()for repos that haven't loaded branches yet, so search can match branch names across all repos.Search expansion fixes (
d034bbf,104a9c9): Search-triggered loads useexpandOnLoad: falseso repos don't permanently expand. Left-arrow setsuserCollapsedto override search auto-expansion. Right-arrow during an in-flight search load preserves user expand intent.Test speedups (
57ace30):TestDaemonRunStartsAndShutdownsCleanlyand stream tests moved behind//go:build integration. RSA key generation shared viasync.Once. Analyze test uses direct arg validation instead ofcmd.Execute().Test plan
userCollapsedresets when search is clearedbkey auto-expands target repo and positions cursor on first branchh/lkeys pass through to search; only arrow keys expand/collapsego test ./...passes;go vet ./...clean🤖 Generated with Claude Code