Skip to content

fix(hotkeys): spend a held Tab over the chart instead of walking focus - #542

Closed
guyverino wants to merge 2 commits into
mainfrom
fix/held-tab-focus-storm
Closed

guyverino wants to merge 2 commits into
mainfrom
fix/held-tab-focus-storm

Conversation

@guyverino

@guyverino guyverino commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

What & why

Report from the desk: holding Tab while sweeping the pointer across order lines to cancel them makes focus "switch through every window of the terminal" and drops the chart to 5–10 fps.

Mechanism (read in the fork, then measured): the app-level keystroke interceptor cancels the hovered order on Tab and lets the key through otherwise. A held key auto-repeats, and every repeat with nothing under the pointer reached MoonUI Root's tab -> focus_next -> Window::refresh — a full window draw per repeat, walking focus through every MoonButton (all tab stops by default). Render counters under a 3 s hold on --fixture: shell_render 1/s -> 22–23/s, frame_draws the same, ~137 ms of draw per second on the light fixture layout.

Fix: the interceptor reads KeystrokeEvent::is_held (MoonUI ef48635, companion change) and stops a bare Tab or Shift+Tab repeat while a chart is hovered — there Tab is the cancel key. A fresh press with nothing under the pointer still navigates; away from a chart a held Tab keeps its ordinary auto-repeat walk. Measured after: shell_render 3/s in the first second (the one press), 1/s for the rest of the hold.

The same flag stops a second effect of the repeat: the cancelled line stays on the chart until the core echoes, so every repeat sent the same cancel again (~30/s). ChartPanel::cancel_hovered_order takes the repeat flag and does not resend for the order it already addressed during the current hover; dispatch_hotkey / dispatch_hotkey_at carry the flag so the Delete route gets it too, while modifier hotkeys and mouse action clicks pass false.

This has been the behaviour since Tab-cancel landed on 2026-07-15 (a350b065); the focus walk itself comes from MoonUI Root and predates it — not a regression of the last release.

Notable decisions

  • Scoped to a hovered chart rather than app-wide: a held Tab in Settings or Strategies keeps cycling focus like every other application. Shift+Tab is covered because it is the identical walk backwards.
  • The cancel-dedupe slot lives exactly as long as the pointer stays on the same (core, uid) — cleared when the hover changes order or leaves the slot, kept across the line/start-cross halves of one order. A workspace refusal counts as "addressed": a repeat need not ask again.
  • KeystrokeEvent.is_held is the platform's flag; Windows, macOS and Wayland report repeats, the X11 backend does not (always false) — noted on the field, out of scope here.

Known limitations

  • Not verified live on a real core: the fixture has no live orders, so the cancel path was verified by reading and by the contract test, the render path by the counters.
  • The MoonUI pin in Cargo.lock moves to ef48635 in a separate commit: build.yml refreshes MoonUI at run time anyway, but release.yml builds --locked and a local main resolves the committed pin, and both would stop compiling at the old revision. assert-only-moonui-moved.sh passes on the diff.

Issues

none of the 16 open issues is touched by this change (#493 is Ctrl+Shift+wheel, unrelated)

How to verify

cargo fmt --all -- --check
cargo build -p moon-ui-gpui --bin moonterminal --target x86_64-pc-windows-msvc --all-targets
cargo clippy -p moon-ui-gpui --bin moonterminal --target x86_64-pc-windows-msvc --all-targets -- -D warnings
cargo test -p moon-core --target x86_64-pc-windows-msvc
cargo test -p moon-ui-gpui --target x86_64-pc-windows-msvc

fmt --check exit 0 В· build all targets green В· clippy -D warnings stops on the pre-existing build.rs debt before this crate; run without -D, every finding in the touched files sits outside this diff's hunks (none in boot.rs) В· moon-core 1947 + contract targets green, moon-ui-gpui 1799 + theme_contract 331 green (incl. the new a_held_tab_is_spent_before_focus_navigation) В· FireTest not run.

Manual: MOON_RENDER_DIAG=1, --fixture, hold Tab over the chart for 3 s, read logs/render_diag.log in the fixture temp root — shell_render stays at 1–3/s (was 22–23/s).

The desk holds Tab while sweeping the pointer across the order lines it
wants cancelled. Between two lines nothing is hovered, and every auto-
repeat fell through the app-level keystroke interceptor to MoonUI Root's
`tab` -> `focus_next` -> `Window::refresh`: focus walked every control of
the terminal and the window redrew once per repeat (measured with the
render counters: shell_render 1/s -> 23/s under a 3 s hold, one full
draw per repeat — the "fps drops to 5-10" report).

The interceptor now reads `KeystrokeEvent::is_held` (MoonUI ef48635) and
stops a bare Tab or Shift+Tab REPEAT while a chart is hovered: there Tab
is the cancel key, not navigation. A fresh press with nothing under the
pointer still navigates, and away from a chart a held Tab keeps its
ordinary auto-repeat walk. Measured after: shell_render 1-3/s under the
same hold.

The same flag stops the cancel spam the repeat produced: the cancelled
line stays on the chart until the core echoes, so every repeat sent the
same cancel again. `ChartPanel::cancel_hovered_order` takes the repeat
flag and does not resend for the order it already addressed during the
current hover; `dispatch_hotkey`/`dispatch_hotkey_at` carry the flag so
the Delete route gets it too, while modifier hotkeys and mouse action
clicks pass `false`.

Contract test `a_held_tab_is_spent_before_focus_navigation` holds the
rule. Not verified live on a real core.
The Tab fix reads `KeystrokeEvent::is_held`, which MoonUI grew in
ef48635. build.yml refreshes MoonUI at run time, but release.yml builds
`--locked` on the committed pin and a local `main` resolves the lock as
committed — both would stop compiling at the old revision. Moved with
`cargo update -p moon-gpui -p moon-gpui-platform -p moon-ui`;
`assert-only-moonui-moved.sh` confirms nothing else moved.
@guyverino guyverino closed this Sep 13, 2026
@guyverino
guyverino deleted the fix/held-tab-focus-storm branch September 13, 2026 09:08
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