Skip to content

Prompt UI: horizontal input scrolling, overflow scrollbar on suggestion dropdowns - #2954

Merged
sinelaw merged 4 commits into
masterfrom
claude/fix-promptui-dropdowns
Aug 10, 2026
Merged

Prompt UI: horizontal input scrolling, overflow scrollbar on suggestion dropdowns#2954
sinelaw merged 4 commits into
masterfrom
claude/fix-promptui-dropdowns

Conversation

@sinelaw

@sinelaw sinelaw commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Fixes #2876
Fixes #623
Fixes #1593

Motivation

Three related affordance gaps in the prompt/dropdown UI:

The fix

Commit 1 (#2876), view/ui/status_bar.rs: the prompt label now stays anchored at the left edge while the input renders in the remaining columns with a horizontal scroll (Paragraph::scroll) computed by a new input_hscroll(cursor_cells, width) helper — zero while the cursor fits, otherwise just enough that the cursor rides the last column — and the terminal cursor is always placed. Both prompt renderers (generic and file-open) share the new tail; this also replaces the file-open renderer's approximate cursor-column arithmetic with the actual display width of the label spans. Applies to all bottom prompt-line inputs (search, palette, open file / switch project / save as, plugin prompts).

Commit 2 (#623 + #1593), app/render.rs + view/ui/suggestions.rs:

  • When the list overflows the viewport, SuggestionsRenderer draws the shared scrollbar widget (view/ui/scrollbar.rs) over the popup's right border — same thumb geometry and theme colors as every other scrollbar. That is the missing indicator both issues asked for.
  • The scrollbar rect is registered in the chrome layout (suggestions_scrollbar_rect), so the existing prompt-scrollbar mouse handlers (click-to-jump, thumb drag) work for the bottom popup exactly like in the Live Grep overlay. Scrollbar interaction latches the prompt's manual_scroll flag so the renderer's keep-selection-visible pass doesn't immediately undo the user's scroll; the latch is released when the selection moves.
  • Two small cleanups the change surfaced: suggestions_scrollbar_rect now resets every frame (previously it could go stale after the overlay prompt closed), and the now-unused Prompt::ensure_selected_visible wrapper is removed — every caller passes the actual rendered height to ensure_selected_visible_within.

The dropdown keeps its 10-row height. An earlier revision of this PR also grew the dropdown to fill the rows above the prompt (cap 20). That was dropped — see the "approach changed" comment below. Neither issue asks for more rows, and the taller popup made the palette measurably worse to use: on an 80x24 terminal it covers all but one line of the buffer, and in a split layout it hides the other pane and the split separator entirely. MAX_VISIBLE_SUGGESTIONS now documents that reasoning.

Follow-up: two bugs found in maintainer testing (commits 3 and 4)

Commit 3 — fix: land the dropdown scrollbar thumb on the row that was clicked. Clicking the palette's scrollbar put the thumb one row above the clicked row, and the bottom of the track could never reach the end of the list. The registered rect and the drawn track line up exactly; the bug was the mapping. Both prompt-scrollbar handlers used ScrollbarState::click_to_offset, which divides the click row by the whole track height, whereas the thumb's top only travels track_height - thumb_size rows — so the result drifts up by thumb_size / track_height (one row for a palette-sized dropdown) and never quite reaches max_scroll. Press and drag now share one helper built on offset_for_thumb_top, the real inverse of the geometry the renderer draws. offset_for_thumb_top was itself only accurate to within a row (hence its <= 1 round-trip tolerance): thumb_geometry floors offset → thumb row, so a rounding inverse still landed high. It now takes the smallest offset that reaches the requested row and falls back to the closest-rendering neighbour when a row is unreachable (a list barely longer than its viewport has fewer scroll positions than track rows). Because this is a shared primitive, press-to-position also gets more precise for the keybinding editor and the floating widget panels.

Commit 4 — fix: make the mouse wheel scroll prompt dropdowns without moving the selection. Editor-wide rule (and what VS Code does): the wheel scrolls the view, never the selection. handle_prompt_scroll walked selected_suggestion on each tick and rewrote the prompt input to match, so the wheel silently retargeted what Enter would commit — and cleared the manual_scroll latch on the grounds that it had "moved the selection", which made the renderer yank the offset back and produced the reported jump after a scrollbar click. It now defers to Prompt::scroll_results, the view-only scroll the Live Grep overlay has used since #2119, sized by the row count the renderer reported instead of a hard-coded 10. The selection stays put and may scroll out of sight; keyboard navigation still clears the latch and brings it back, so the latch now means one thing only — "the viewport is user-positioned, stop following the selection" — rather than papering over the coupling. Popup::scroll_by had the same coupling for view/popup.rs List popups (dragging the selection to keep it inside the viewport on every wheel tick and scrollbar click) and is view-only now too; the keyboard paths (select_next, page_down, select_last, …) are unchanged.

Audited and already compliant, so untouched: the file browser (handle_file_open_scroll), the Live Grep overlay results and preview, the widget-panel List/Tree/Text wheels (Git Log, diagnostics, package manager, orchestrator dock), and the keybinding editor. Settings is a separate change.

Tests

New tests (each verified to fail without the corresponding fix and pass with it):

  • status_bar.rs unit tests: input_hscroll cursor-visibility invariant sweep (scroll <= cursor, cursor - scroll < width, no scroll while it fits) and a TestBackend render test asserting the scrolled tail, the anchored label, and the cursor pinned to the last column.
  • suggestions.rs unit tests: scrollbar cells replace the right-border glyphs when the list overflows; the plain border is kept when it fits.
  • scrollbar.rs unit tests: test_offset_for_thumb_top_round_trip now asserts exact equality for every reachable thumb row (including the two prompt-dropdown shapes, 30-in-10 and 14-in-10) and fails on the pre-fix rounding inverse; test_offset_for_thumb_top_picks_nearest_when_rows_unreachable guards the 11-in-10 case where exactness is impossible.
  • E2E issue_2876_prompt_input_hscroll.rs: 80-col search prompt, 100 typed chars — tail visible, hardware cursor at the right edge, and still visible after 15 Left presses while the window shifts.
  • E2E issue_623_prompt_dropdown_scrollbar.rs (11 tests, driving real mouse/keyboard events and asserting only on rendered cells): the overflowing command palette carries a scrollbar cell on every suggestion row; the Select Locale dropdown (14 locales, 10 rows) does too — issue can the select locale scroll, or add a progress bar at the right. #623 verbatim; a filtered short list keeps its plain border (negative control); clicking low on the track scrolls the list and keeps it scrolled; clicking every reachable track row lands the rendered thumb top on exactly that row, including both extremes; a continuous press-and-drag sweep down and back up keeps the thumb on the cursor row; and four wheel tests — over the palette, over Select Locale, over Set Language — assert the rendered rows change while the highlighted entry stays the same, that a selection scrolled out of view comes back unchanged, and that an arrow key releases the latch and brings it into view.

Test runs observed on the current tree: the 11 issue_623_prompt_dropdown_scrollbar e2e tests (the 7 new ones all fail on the unpatched tree, from a clean rebuild, while the 4 pre-existing ones pass both ways) and the scrollbar.rs offset_for_thumb_top unit tests; cargo fmt; cargo clippy -p fresh-editor with no findings in the touched files. No visual snapshot is changed by commits 3–4 — both are interaction-only and touch no rendering path. The broader suite runs quoted for commits 1–2 (full fresh-editor lib unit suite, e2e command_palette / visual_regression / prompt / prompt_editing / locale / unicode_prompt_bugs / quick_open) were observed on the tree as of commit 2; CI covers the breadth for the current head.

Updated: the Comprehensive UI B visual snapshot (commit 2). The only cell that changed is the palette's right border column, which now carries scrollbar cells instead of the plain frame glyph — the layout, the split, and the suggestion text are byte-identical.

Notes for reviewers:

  • The Live Grep overlay's input row (app/render.rs) still truncates long input at the right edge — it has its own layout (status/count cluster on the same row) and wasn't reported in input box can't use left / right when too long #2876; it would need the same input_hscroll treatment as a follow-up.
  • Two pre-existing issues found while fixing the scrollbar, both left alone as out of scope: handle_click_popup_scrollbar (generic view/popup.rs scrollbars) uses a third, differently-imprecise mapping; and a double-click on any scrollbar goes to handle_mouse_double_click, which never reaches the scrollbar handlers, so the second press of a fast double gesture is dropped.

🤖 Generated with Claude Code

https://claude.ai/code/session_01D6SkGXXTcJsytTjF1TBf8Y

@sinelaw
sinelaw force-pushed the claude/fix-promptui-dropdowns branch from de681db to 95022c2 Compare August 10, 2026 18:11
@sinelaw sinelaw changed the title Prompt UI: horizontal input scrolling, height-aware suggestion dropdowns, overflow scrollbar Prompt UI: horizontal input scrolling, overflow scrollbar on suggestion dropdowns Aug 10, 2026

sinelaw commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

CI regression triage — approach changed: the dropdown height growth is dropped

Rebased onto current master (4978b11, conflict-free) and reproduced all three CI failures locally. Verdict per failure below. Short version: all three traced back to one unnecessary change — growing the dropdown from 10 rows to "fill the rows above the prompt, cap 20". That was my addition, not something either issue asked for, and it makes the palette worse. It's gone. The scrollbar, which is what #623 and #1593 actually asked for, stays.

Why the height change was wrong to begin with

  • can the select locale scroll, or add a progress bar at the right. #623 says, in the maintainer's own reproduction: "the list does scroll (zh-CN is reachable), but there is no scrollbar, position indicator, or count." The ask is an indicator, not more rows.
  • Right-side Scroll Bar issues #1593 says: "Command Palette has no scrollbar." Again, a scrollbar.
  • Neither asks for a taller popup. Raising the cap was scope creep that changed the geometry of every bottom-anchored prompt dropdown (palette, quick open, locale select, plugin prompts).

And it isn't harmless. Reproducing test_command_palette_scroll_beyond_visible at 100x24 with the taller dropdown shows the palette occupying 20 of 24 rows — exactly one line of the document is left visible. The visual_comprehensive_b diff is even more direct: the taller popup swallowed the split separator and the entire second pane, i.e. it destroyed the very thing that snapshot exists to capture:

   10       │-~
   ...
   16       │-────────────────────────────────────────────────────────────  <- split separator, gone
   17    10 │ ┌────────────────────────────────────────────────────────────┐

Keeping the 10-row cap and adding only the scrollbar satisfies both issues (14 locales > 10 rows and ~200 commands > 10 rows both overflow, so the indicator shows in exactly the reported scenarios) and is the strictly smaller change. Dropped it.

Per-failure determination

1. e2e::command_palette::test_command_palette_scroll_beyond_visible — (b) genuine regression in the PR. Source fixed, test untouched.

The test opens the palette on a 100x24 terminal, presses Down 15 times and asserts the first command scrolled out of view. With the 10-row cap that holds. With the height-sized dropdown the viewport became 17 rows, so selection 15 was still on screen and nothing scrolled — Add Cursor Above remained visible. The test is not encoding an arbitrary old constant; it is asserting that a palette which shows N rows scrolls when you walk past row N, and the reason it broke is that the PR made N large enough to swallow the screen. Reverting the geometry fixes it with no test edit.

2. e2e::command_palette::test_command_palette_keyboard_nav_inside_viewport_does_not_scroll — (b) genuine regression in the PR. Source fixed, test untouched.

This is the #1660 regression guard: 9 Downs must not move the list (selection stays inside the viewport), the 10th must. Under the taller dropdown the first half still passed and the second half failed, because the selection no longer left the viewport at row 10. I could have "updated it to the new geometry" by changing 9/10 to 17/18, but that would have been rewriting a regression guard to accommodate a change nobody asked for — and it would have baked a terminal-height-dependent number into a test. Reverting the geometry fixes it with no test edit.

3. e2e::visual_regression::visual_comprehensive_b — (a) legitimately captures old rendering. Snapshot regenerated.

After the geometry revert, the remaining diff is exactly one thing, and it is the intended #1593 fix: on the 10 suggestion rows the right-border column changes from (U+2502) to a scrollbar cell. The scrollbar is drawn with background colors, so in the text snapshot it reads as a blank glyph:

-│  Plugin Demo: Open Help   ...   welcome│
+│  Plugin Demo: Open Help   ...   welcome

Everything else — the split, the separator, the horizontal-scrolled long line, the row count, the suggestion text — is byte-identical. Regenerated with INSTA_UPDATE=always (snapshots are insta, stored under crates/fresh-editor/tests/common/snapshots/). I deliberately did not commit the regenerated docs/visual-regression/screenshots/*.svg: those are rewritten unconditionally on every test run (should_update_image always returns true) and the committed ones already carry unrelated drift from master, so including them would have added ~1000 lines of generated churn to this PR.

Also done

  • Rewrote issue_623_prompt_dropdown_scrollbar.rs so it tests what the issues asked for rather than the height: palette-overflow scrollbar, Select Locale with its 14 locales (issue can the select locale scroll, or add a progress bar at the right. #623 verbatim), a short-list negative control, and a new mouse test that clicks low on the palette scrollbar track and asserts the list stays scrolled. Verified against origin/master's renderer: 3 of the 4 fail without the fix (0 scrollbar cells vs 10 expected), the negative control passes both ways. The click test is what pins the manual_scroll latch — it fails on master because without the latch the keep-selection-visible pass yanks the offset straight back.
  • Commit 2 was amended rather than followed by a revert commit, so the history presents the approach that's actually being proposed.

Verification on the rebased tree

  • fresh-editor lib unit suite: 3252 passed / 0 failed
  • e2e command_palette, visual_regression, issue_623_prompt_dropdown_scrollbar, issue_2876_prompt_input_hscroll, prompt, prompt_editing, locale, unicode_prompt_bugs, quick_open: 122 passed / 0 failed
  • cargo check -p fresh-editor --all-targets, cargo clippy -p fresh-editor --all-targets (no findings in touched files), cargo fmt --check clean

app/render.rs now differs from master only by the scrollbar wiring — the min(10) line is back, expressed as MAX_VISIBLE_SUGGESTIONS with a doc comment recording why the popup stays short.


Generated by Claude Code


Generated by Claude Code

claude added 4 commits August 10, 2026 18:17
With input longer than the prompt line (search prompt, open-file prompt,
palette input), the renderer clipped the paragraph at the right edge and
skipped set_cursor_position entirely once the cursor's logical column
passed the width: the cursor vanished, the tail of the input was
unviewable, and Left presses gave no visual feedback even though the
internal cursor moved (issue #2876).

The label now stays anchored at the left edge while the input renders in
the remaining columns with a horizontal scroll that keeps the cursor
inside the viewport (pinned to the last column while past the right
edge), and the terminal cursor is always placed. Both prompt renderers
(generic and file-open) share the new tail, which also replaces the
file-open renderer's approximate cursor-column arithmetic with the
actual display width of the label spans.

Fixes #2876

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6SkGXXTcJsytTjF1TBf8Y
Prompt suggestion dropdowns (Select Locale, command palette, and every
other bottom-anchored suggestions popup) do scroll, but nothing said so:
the popup's right border stayed a plain frame line, so a 14-locale list
looked like a 10-locale list — issue #623, "at first glance, I thought
only these". The command palette showed no scrollbar at all, the
remaining item of issue #1593. Both lists render through the same
SuggestionsRenderer, so one fix covers both.

SuggestionsRenderer now draws the shared scrollbar widget over the
popup's right border whenever the list overflows, and the scrollbar rect
is registered in the chrome layout so the existing prompt-scrollbar
mouse handlers (click-to-jump, thumb drag) work here exactly like in the
Live Grep overlay. Scrollbar interaction latches the prompt's
manual-scroll flag so the renderer's keep-selection-visible pass doesn't
immediately undo it (mirroring the wheel-scroll behaviour of issue
#2119), and moving the selection clears the latch again. The stale-rect
case is also closed: the chrome's suggestions_scrollbar_rect now resets
every frame instead of surviving after the overlay prompt closes.

The dropdown deliberately keeps its 10-row height. Sizing it to the
terminal instead was tried and dropped: neither issue asks for more
rows, and a height-sized palette is worse to use — on an 80x24 terminal
it covers all but one line of the buffer, and in a split layout it hides
the other pane and the split separator entirely. MAX_VISIBLE_SUGGESTIONS
now documents that reasoning; the unused Prompt::ensure_selected_visible
wrapper is gone, since every caller passes the height it actually
rendered.

The Comprehensive UI B visual snapshot is regenerated for the one cell
that changed: the palette's right border column now carries scrollbar
cells instead of the plain frame glyph.

Fixes #623
Fixes #1593

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6SkGXXTcJsytTjF1TBf8Y
Clicking the command palette's new scrollbar put the thumb one row above
the row under the cursor, and the bottom row of the track could never
reach the end of the list — reported by the maintainer while testing
#2954.

The prompt scrollbar handlers mapped a click with
`ScrollbarState::click_to_offset`, which divides the click row by the
whole track height. The thumb's top can only travel `track_height -
thumb_size` rows, so dividing by the track over-estimates the
denominator and the result drifts up by a factor of
`thumb_size / track_height` — one row for a palette-sized dropdown, and
never quite `max_scroll` at the bottom. `offset_for_thumb_top` is the
function that inverts the geometry the renderer actually draws, so both
the press and the drag follow-up use it now, through one shared helper
so the two can't drift apart again.

`offset_for_thumb_top` itself was only accurate to within a row:
`thumb_geometry` floors offset -> thumb row, so inverting it with a
*rounding* division still landed a row high whenever the quotient wasn't
integral. It now takes the smallest offset that reaches the requested
row, and — because a list barely longer than its viewport has fewer
scroll positions than track rows, so some rows are unreachable — falls
back to whichever neighbouring offset renders closest. Its round-trip
test asserts equality instead of a one-row tolerance, and fails on the
old implementation.

Refs #623
Refs #1593

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6SkGXXTcJsytTjF1TBf8Y
…selection

Editor-wide rule (and what VS Code does): the wheel scrolls the view, it
never moves the selection. Prompt suggestion dropdowns broke it — the
command palette, Select Locale, Set Language, quick open and every other
bottom-anchored list walked `selected_suggestion` on each wheel tick and
rewrote the prompt input to match, so the wheel silently retargeted what
Enter would commit.

Once #2954 let the scrollbar pin the viewport, that also produced the
jump the maintainer reported: scroll the list with the scrollbar, then
wheel, and the list leapt somewhere else. The wheel cleared the
manual-scroll latch (on the grounds that it had "moved the selection"),
so the renderer's keep-selection-visible pass yanked the offset back to a
selection that had not visibly moved.

`handle_prompt_scroll` now defers to `Prompt::scroll_results`, the same
view-only scroll the Live Grep overlay has used since #2119, sized by the
row count the renderer reported rather than a hard-coded 10. The
selection stays where it is and may scroll out of sight; keyboard
navigation still clears the latch and brings it back into view, so the
latch now means one thing only — "the viewport is user-positioned, stop
following the selection" — rather than papering over the coupling.

`Popup::scroll_by` had the same coupling for `List` popups, dragging the
selection along to keep it inside the viewport on every wheel tick and
scrollbar click; it scrolls the view only now too. The keyboard paths
(`select_next`, `page_down`, `select_last`, …) are unchanged: they move
the selection and scroll the view to follow it.

Refs #623
Refs #1593

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6SkGXXTcJsytTjF1TBf8Y
@sinelaw
sinelaw force-pushed the claude/fix-promptui-dropdowns branch from 95022c2 to 7081481 Compare August 10, 2026 19:39

sinelaw commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Thanks for testing this — both bugs you found are fixed, in two commits on top of the branch (force-pushed, rebased on current master).

Bug 1 — the scrollbar thumb landed one row above the click

fix: land the dropdown scrollbar thumb on the row that was clicked

Not a border/geometry offset: the registered rect and the drawn track line up exactly (both are inner_area, i.e. the popup's inner rows on the right border column). The bug is the mapping.

Both prompt-scrollbar handlers converted a click with ScrollbarState::click_to_offset, which divides the click row by the whole track height. But the thumb's top can only travel track_height - thumb_size rows, so the denominator is too big and the result drifts up by a factor of thumb_size / track_height — exactly one row on a palette-sized dropdown. The same error means the bottom row of the track can never reach max_scroll: clicking the last row of a 10-row track on a 150-command palette left the list four items short of the end.

offset_for_thumb_top is the function that inverts the geometry the renderer actually draws, so the press and the drag follow-up both go through it now, via one shared helper (prompt_scrollbar_offset_for_row) so the two can't drift apart again.

offset_for_thumb_top itself was only accurate to within a row, which is why its round-trip test had a <= 1 tolerance: thumb_geometry floors offset → thumb row, so inverting it with a rounding division still landed a row high whenever the quotient wasn't integral. It now takes the smallest offset that reaches the requested row (ceiling division), and falls back to whichever neighbouring offset renders closest — necessary because a list barely longer than its viewport (e.g. 11 items in 10 rows) has fewer scroll positions than track rows, so some rows are genuinely unreachable. The round-trip test now asserts equality; it fails on the old implementation with "thumb landed at 0, expected 1".

This is a shared primitive, so the fix also tightens press-to-position for the keybinding editor and the floating widget panels, which use it through ScrollbarMouse::press.

Bug 2 — the wheel moved the selection

fix: make the mouse wheel scroll prompt dropdowns without moving the selection

You're right that this is what produced the jump. handle_prompt_scroll walked selected_suggestion on every wheel tick and rewrote the prompt input to match — so the wheel silently retargeted what Enter would commit. It then cleared the manual_scroll latch on the grounds that it had "moved the selection", and the renderer's keep-selection-visible pass yanked the offset back to a selection that had never visibly moved. Hence: scroll with the scrollbar, wheel once, list leaps.

It now defers to Prompt::scroll_results — the same view-only scroll the Live Grep overlay has used since #2119 — sized by the row count the renderer reported rather than a hard-coded 10. The selection stays put and may scroll out of sight, which matches VS Code. Keyboard navigation still clears the latch and brings the selection back into view, so the latch now means exactly one thing ("the viewport is user-positioned, stop following the selection") instead of papering over the coupling. No new state, and nothing was added to keep it honest.

I applied the rule to every list surface in this code path, not just the palette:

  • command palette / quick open / Select Locale / Set Language and every other prompt dropdown — one fix in handle_prompt_scroll, since they all share SuggestionsRenderer.
  • view/popup.rs List popups (LSP-servers menu, plugin action menus, completion lists) — Popup::scroll_by had the same coupling, dragging the selection to keep it inside the viewport on every wheel tick and scrollbar click. It scrolls the view only now too. The keyboard paths (select_next, page_down, select_last, …) are untouched: they move the selection and scroll the view to follow it.

Audit of the other wheel handlers — all already comply, no changes needed: the file browser (handle_file_open_scroll), the Live Grep overlay results and preview, the widget-panel List/Tree/Text wheels (Git Log, diagnostics, package manager, orchestrator dock — they set user_scrolled and keep selected_index), and the keybinding editor. Settings is a sibling change, not touched here.

Tests

Unit, on the arithmetic itself (view/ui/scrollbar.rs):

  • test_offset_for_thumb_top_round_trip — every reachable thumb row round-trips exactly, now including the two prompt-dropdown shapes (30-in-10 and 14-in-10). Fails on the old code.
  • test_offset_for_thumb_top_picks_nearest_when_rows_unreachable — guard for the 11-in-10 case where exactness is impossible.

E2E, driving real mouse/keyboard events and asserting only on rendered cells (tests/e2e/issue_623_prompt_dropdown_scrollbar.rs, 7 new tests):

  • …lands_thumb_on_the_clicked_row — clicks every reachable track row via SGR and asserts the rendered thumb top equals the clicked row, plus both extremes (last row reaches the end of the list; first row returns to the top).
  • …dragging_palette_scrollbar_tracks_the_cursor_row — one continuous press → drag-sweep down and back up, thumb checked at every row.
  • …wheel_over_palette_scrolls_view_without_moving_selection, and the same for Select Locale and Set Language — the rendered rows change while the highlighted entry stays the same one.
  • …wheel_may_scroll_selection_out_of_view_without_losing_it — the selection may leave the viewport, and scrolling back reveals the same entry still highlighted.
  • …arrow_key_after_wheel_brings_the_selection_back_into_view — the latch releases on keyboard navigation.

All 7 fail on this branch without the two fixes (verified from a clean build of the unpatched tree; the 4 pre-existing tests in the file pass both ways), and all 11 pass with them. cargo fmt clean, cargo clippy -p fresh-editor reports nothing in the touched files. No visual snapshot changed — both fixes are interaction-only, no rendering path was touched.

Follow-up worth filing separately, both pre-existing and outside this PR: (a) handle_click_popup_scrollbar (generic view/popup.rs scrollbars, e.g. hover/LSP popups) uses yet a third mapping — linear in scroll offset with a track_height - 1 denominator — which has the same class of imprecision as the one fixed here; (b) a double-click on any scrollbar is routed to handle_mouse_double_click, which never reaches the scrollbar handlers, so the second press of a fast double gesture is dropped.


Generated by Claude Code

@sinelaw
sinelaw merged commit 6a9596f into master Aug 10, 2026
10 checks passed
sinelaw pushed a commit that referenced this pull request Aug 10, 2026
Clicking the command palette's new scrollbar put the thumb one row above
the row under the cursor, and the bottom row of the track could never
reach the end of the list — reported by the maintainer while testing
#2954.

The prompt scrollbar handlers mapped a click with
`ScrollbarState::click_to_offset`, which divides the click row by the
whole track height. The thumb's top can only travel `track_height -
thumb_size` rows, so dividing by the track over-estimates the
denominator and the result drifts up by a factor of
`thumb_size / track_height` — one row for a palette-sized dropdown, and
never quite `max_scroll` at the bottom. `offset_for_thumb_top` is the
function that inverts the geometry the renderer actually draws, so both
the press and the drag follow-up use it now, through one shared helper
so the two can't drift apart again.

`offset_for_thumb_top` itself was only accurate to within a row:
`thumb_geometry` floors offset -> thumb row, so inverting it with a
*rounding* division still landed a row high whenever the quotient wasn't
integral. It now takes the smallest offset that reaches the requested
row, and — because a list barely longer than its viewport has fewer
scroll positions than track rows, so some rows are unreachable — falls
back to whichever neighbouring offset renders closest. Its round-trip
test asserts equality instead of a one-row tolerance, and fails on the
old implementation.

Refs #623
Refs #1593

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6SkGXXTcJsytTjF1TBf8Y
@sinelaw
sinelaw deleted the claude/fix-promptui-dropdowns branch August 10, 2026 20:17
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.

input box can't use left / right when too long Right-side Scroll Bar issues can the select locale scroll, or add a progress bar at the right.

2 participants