feat(sidebar): keep the cursor when a row disappears [6]#153
Draft
AbysmalBiscuit wants to merge 15 commits into
Draft
feat(sidebar): keep the cursor when a row disappears [6]#153AbysmalBiscuit wants to merge 15 commits into
AbysmalBiscuit wants to merge 15 commits into
Conversation
Search-mode Enter/Esc were hardcoded in panel_filter and could not be rebound, and Enter never scrolled the activated row into view. Add three search-scoped actions — SidebarSearchConfirm (Enter), SidebarSearchCancel (Esc), and SidebarSearchCancelToTerminal (Shift+Esc) — dispatched through the keyboard binding system from the focused panel's nav pass. handle_shortcuts drops search-scoped actions so the terminal's own Enter/Esc still reach the PTY. Confirm scrolls the acted-on row into view via a focus-independent pending target, resolving a now-hidden child to its owning project header. Cancel returns the cursor to the seed row and preserves the s/a toggle filters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Confirming a fuzzy search ran the row's full activation: a project header toggled its expansion, and a worktree or session switched workspace and moved focus to the terminal. Search is how you find a row, so acting on it in the same keystroke is surprising — and for a header destructive, collapsing the project you had just gone looking for. Confirm now only leaves search, puts the cursor on the highlighted row and scrolls it in, keeping focus in the sidebar. Activation stays a second, explicit Enter in browsing mode. That makes confirm the twin of cancel, so both share one landing helper per panel, and the git panel follows the same rule instead of opening a diff. Search lists matched worktrees and sessions whatever their project's expanded flag says, so a child under a collapsed project vanished the moment the query cleared. Confirm expands that project first — expand-only, and never for a header, which is what keeps selecting a project from behaving as a toggle. Dropping activation also drops the reason for the focus-independent pending scroll target, which existed only because activation focused the terminal and nulled the paint-time cursor. The ordinary cursor-move scroll suffices once its moved flag is forced rather than keyed on cursor identity: restoring the unfiltered list can move the same row far off-screen. That also fixes git cancel, which could leave a surviving row unscrolled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A failed wsl.exe round trip returned Project::placeholder, the same single pseudo-worktree a non-git folder gets, so refresh and poll_project_refreshes copied it over the real worktree list. One transient WSL outage was indistinguishable from "every worktree in this project was deleted", and the sidebar collapsed to a single row until a later refresh happened to succeed. Discovery now returns Discovered, pairing the project with whether the backend could vouch for the answer, and callers adopt through Project::apply, which drops a non-authoritative result. A distro that answers "not a repository" is still authoritative; only a round trip that never landed, or came back without even a main checkout, is not. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assembles the sidebar_focus arena from live app state: a free build_sidebar_snapshot() that lockstep-merges live (workspace, id) session pairs against the projected rows, plus AlacritreeApp helpers (session_pairs, session_inputs, sidebar_snapshot) that feed it. Model membership comes from live sessions, never from the listed-rows projection, so a workspace's last session never reads as deleted just because it fell below the sidebar's listing threshold, and a session whose project was removed stays in the model as a detached node instead of vanishing. Adds the sidebar_rows_cache field the next task wires into paint.
The helper is called only from the snapshot test module, so a non-test build sees it as dead code. Gating it #[cfg(test)] keeps it honestly test-only and clears the warning.
AbysmalBiscuit
force-pushed
the
feat/sidebar-focus-preservation
branch
from
July 25, 2026 08:32
3c6de3b to
ae881c8
Compare
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.
DO NOT MERGE
When a projects-sidebar row holding the cursor stops being rendered, the cursor no longer drops to row 0. A row hidden by a filter or a collapse climbs to its nearest visible ancestor and returns when the filter widens; a deleted row slides to a sibling, bounded by its parent.
The mechanism is an observing reconciler:
sidebar_focus.rsdiffs aTreeSnapshotof the sidebar between frames and repairs the cursor from the diff, rather than every mutation site having to remember to fix focus itself. That is whyafter_filter_changedand the inline close-navigation dispatch are gone — deletion and filtering now report what happened and let the reconciler decide.Config
"follow"additionally moves the terminal to a delete landing that has a live session.Behavior change worth a decision
preserveis the default, and there is no value that restores the old drop-to-first-row behavior. If you would rather this ship opt-in, say so and I will add a third value and flip the default — the reconciler is already gated on the config, so it is a small change.Stacked
This branch is stacked; against
masterit carries three prerequisite commits that are their own PRs:bca668e7,154e43aa— sidebar search confirm/cancel66236260— keep worktrees when unreachableOnly the twelve commits
9c6ddbda..3c6de3bcare new here. Reviewing per-commit, or after those two land, is much easier than reading the combined diff.Testing
cargo test -p alacritree: 565 passing. New coverage includes snapshot/model agreement, climb and slide behavior, anchor return, andsteady_state.rs, which gates the unchanged-frame cost so the per-frame snapshot cannot silently start allocating.Not yet done: the manual GUI passes for the deferred-navigation paths. Worth doing before this leaves draft.
Known edge
An anchor can be retired as a false positive when a structural removal changes the active session while you are mid-filter with a climbed cursor — you lose the "return to the filtered-out row" convenience for that one interaction. No cursor or terminal corruption; the visible cursor still repairs correctly.