Skip to content

feat(sidebar): optional line counts, scrolling tags, popovers that close on outside clicks, and diff cursor fixes - #87

Merged
noahbclarkson merged 5 commits into
mainfrom
fix/sidebar-popovers-line-counts
Oct 6, 2026
Merged

noahbclarkson merged 5 commits into
mainfrom
fix/sidebar-popovers-line-counts

Conversation

@noahbclarkson

@noahbclarkson noahbclarkson commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Summary

A batch of sidebar and diff-viewer polish:

Reported Fix
Two-letter co-author initials look squashed Initials scale with the avatar: 9.6px in the 16px co-author avatar (was 12px); larger avatars unchanged
Badge corners too rounded Badge radius 10/9 → 6/5; the sidebar's count chips 8 → 5
Arrow keys in the diff move the cursor somewhere unexpected Stepping past an edge scrolls one row; the first press starts on screen; clicking a line sets the cursor
Tags section grows without limit Capped at 8 rows with a scrollbar, like Branches
Want the +N −N counts in Staged/Unstaged Toggle in a new ⋯ popover on both headers, saved, off by default, and not computed while off
Branch-filter popover and stash menu stay open when clicking the graph Closed by any press outside them

Changes

Diff cursor (0d0797b)

  • Stepping past the top or bottom edge scrolls one row at a time (ScrollStrategy::Nearest) instead of jumping a page. Hunk, first/last and search jumps still scroll the target to the top.
  • With no cursor yet, the first arrow press starts from the first visible row rather than row 0.
  • Clicking a line moves the keyboard cursor there.
  • Toggling unified/split with wrap off kept jumping to the top: UniformListScrollHandle::top_item() is always 0, so the first visible row is now derived from the scroll offset and content height (uniform_first_visible_row, unit tested).

Avatar initials (c2c7b55)

  • avatar_initials_size = 60% of the circle, capped at the previous 12px.

Sidebar (31e3d1a)

  • Line counts: ⋯ on the Staged and Unstaged headers opens a popover with Show line counts. It's saved as show_change_line_stats (default off) and applies to both lists. Counts are right-aligned so they line up across rows.
    • rgitui_git used to run two batch_diff_stats passes (including untracked file contents) on every uncached status refresh and never displayed them. They now run only while the setting is on. The setting is published through an AtomicBool in rgitui_settings, following current_git_auth_runtime, and is folded into the status fingerprint so toggling it invalidates the cache. Turning it on refreshes open repos so counts appear straight away.
  • Popovers: the branch filters and the new options popover share render_popover_trigger/popover_frame. They're deferred + anchored under the button that opened them, which also fixes the remote-branch filter opening at the top of the sidebar.
  • Outside-click dismissal: the old handler was on_mouse_down on the sidebar panel, which never sees presses in other panels. Popovers and the stash context menu now use on_mouse_down_out. A press on the open popover's own ⋯ is left to that button (tracked via on_hover) so it closes instead of closing-then-reopening.
  • Tags: capped at 8 rows (section_row_cap) with a vertical scrollbar.
  • Corners: Badge radius 10/9 → 6/5 (covers Committed, HEAD, branch names, ahead/behind, Unmerged, Stale, …); four sidebar count chips 8 → 5.

Changelog (9c7f170): [Unreleased] entries under Added, Changed and Fixed.

Testing

  • Tested on Windows
  • Tested on macOS
  • Tested on Linux
  • All existing tests pass (cargo test --workspace: 1773 passed, 0 failed)
  • Code passes clippy (cargo clippy --workspace --all-targets -- -D warnings)
  • Code is formatted (cargo fmt --all -- --check)

New tests, each confirmed to fail with its fix reverted:

  • Diff view: stepping past the bottom scrolls one row; the first arrow after scrolling starts on screen; a click moves the cursor; uniform_first_visible_row offset/clamping.
  • Sidebar view: a click in another panel closes the popover; a click elsewhere in the sidebar closes it; a click in another panel closes the stash menu; the popover's own button isn't treated as outside; tags stop at 8 rows.
  • rgitui_git: line stats are skipped when off, computed when on, and the fingerprint changes with the setting.
  • Avatar initials sizing.

In the real Windows app, driven by the perf-harness replay on a throwaway repo with isolated settings:

  • both popovers open under their buttons;
  • a click on the graph closes them, and clicking the same ⋯ closes without reopening;
  • unticking the checkbox hides the counts and persists the setting.

The stash menu fix is covered by the view test only.

Not addressed: the originally reported "up arrow doesn't bring the diff viewport along" didn't reproduce, either headless (both render paths, wrap on/off) or in the app. The cursor fixes here are the adjacent defects found while investigating it.

Review fixes

Round 1: Codex auto-review of 31e3d1a, fixed in e013428

One P2 finding, valid: with line counts on, editing an untracked file left its cached count stale. The status cache's fingerprint only sampled the contents of WT_MODIFIED/WT_RENAMED/WT_TYPECHANGE files, and an untracked file stays WT_NEW however it is edited.

  • Untracked files are now sampled into the fingerprint while line counts are on. With them off nothing extra is read.
  • Sweeping for the same class found two more ways a count could outlive a change, both fixed: a soft reset moving HEAD under a staged file (the HEAD-side blob wasn't hashed), and a mixed reset moving the index under an unstaged edit (the index-side blob was only hashed for files that also had a staged change).
  • Four new tests in rgitui_git: one per case, each confirmed to fail without its fix, plus one pinning that an untouched tree keeps its cache entry. cargo test --workspace: 1777 passed, 0 failed.

🤖 Generated with Claude Code

noahbclarkson and others added 4 commits September 24, 2026 18:44
- Stepping past the bottom edge scrolls one row at a time instead of
  jumping a full page, so the new row stays next to the old one.
- With no cursor yet, the first arrow press starts from the top row on
  screen rather than the top of the file.
- Clicking a line moves the keyboard cursor there.
- Switching between unified and split keeps the scroll position when
  wrapping is off; the uniform list's top_item() is always 0, so the
  first visible row is now derived from the scroll offset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fallback initials scale with the avatar circle (60% of its size, capped at
the previous 12px), so two capitals no longer look squashed in the 16px
co-author avatars. Larger avatars are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t close on outside clicks

- Staged and Unstaged headers get an options button whose popover toggles
  per-file line counts (+N -N with the diff bar). The choice is saved as
  `show_change_line_stats`, off by default. Working-tree line stats are
  only computed while it is on; before, two diff passes ran on every
  uncached status refresh and the result was never shown.
- Header popovers and the stash context menu close on a press anywhere
  outside them, including in other panels, via on_mouse_down_out. A press
  on the open popover's own button is left to the button so it closes
  rather than reopening. Popovers are anchored under the button that
  opened them, which also fixes the remote branch filter opening at the
  top of the sidebar.
- The Tags section stops growing at 8 rows and scrolls with a scrollbar.
- Badge and sidebar count chip corners are less rounded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T06:48:47.023652Z 31e3d1a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31e3d1a65b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +405 to +409
fn status_fingerprint(repo_path: &Path, statuses: &git2::Statuses<'_>, line_stats: bool) -> u64 {
let mut hasher = DefaultHasher::new();
// A status cached without line counts must not be served once they are
// wanted, and the reverse.
line_stats.hash(&mut hasher);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include untracked contents in the status fingerprint

When line counts are enabled, editing an already-untracked file leaves its cached count stale: status_fingerprint incorporates the new line_stats mode but only hashes contents for WT_MODIFIED, WT_RENAMED, and WT_TYPECHANGE, while an untracked file remains WT_NEW as its contents change. The watcher and explicit GitProject::refresh() both use this cache, so subsequent refreshes reuse the original batch_diff_stats result until some unrelated fingerprint input changes. Include WT_NEW file metadata/content when line stats are requested so the displayed additions track edits.

Useful? React with 👍 / 👎.

…sets

The working-tree status cache is keyed on a fingerprint of
`repo.statuses()`. With line counts on, the cached status carries each
file's +N -N, but the fingerprint did not cover everything those counts
are computed from, so a refresh could hit the cache and keep showing the
counts from before the change.

- An untracked file stays `WT_NEW` however it is edited, and only
  `WT_MODIFIED`, `WT_RENAMED` and `WT_TYPECHANGE` files had their size,
  mtime and sampled contents hashed. `batch_diff_stats` diffs untracked
  contents, so editing one left its additions stale. Untracked files are
  now sampled the same way, but only while line counts are on: without
  them nothing reported for an untracked file depends on its contents,
  so the default path reads no more than it did. (Codex P2 on #87.)
- A staged count is a diff from HEAD to the index, and only the index
  blob was hashed. A soft reset moves HEAD under a staged file without
  touching its index entry or its flags. The HEAD-side blob is hashed
  too.
- An unstaged count is a diff from the index to the working tree, and
  the index blob was hashed only for files that also had a staged
  change. A mixed reset rewrites the index entry beneath an unstaged
  edit while the file on disk, and with it the flags, size and mtime,
  stays as it was. The index-side blob of the unstaged delta is hashed.

The last two came from sweeping for the same class as the first. Each of
the three has a test that fails without its line. A fourth pins that an
untouched tree keeps its cache entry, since an unstable input would
turn every refresh into a miss without anything visibly breaking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@noahbclarkson
noahbclarkson merged commit 12ab4b8 into main Oct 6, 2026
4 checks passed
noahbclarkson added a commit that referenced this pull request Oct 7, 2026
Changelog entry for everything since 0.4.1: #78, #79, #80, #82, #83, #84,
#86, #87 and the model-picker follow-up pushed to main. The version is bumped
across the workspace, and the AppStream metainfo lists 0.5.0, which the Linux
metadata test requires before the crate version can move.

The AI work (#79, #80, #82) had no changelog entries; it now has them, with
each fix checked against the 0.4.1 code so that only bugs that shipped are
listed as fixed. The model-picker fixes in #82 are folded into the feature
for that reason: the picker did not exist in 0.4.1.

The drafts already under [Unreleased] are checked against the code and
corrected where they were off: binary, symlink, submodule and modify/delete
conflicts are the whole-file cases (add/add is resolved per region), Stage
All skips conflicted files rather than refusing, double-clicking the commit
the current branch is on shows a toast, and the diff viewer grows until the
graph is down to 120px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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