A keyboard slide to a Finder window can start up to half a second late#326

opened4d agobySSamanyu Das

Steps to reproduce

  1. Run Miri Developer built from main at 546cfb04.
  2. On the 27-inch display, tile T3 Code, a Finder window, Zen, and three other windows, and focus T3 Code.
  3. Press the jump-by-number shortcut for the Finder column (Control-Option-2) and record window-server bounds at about 140 samples a second.
  4. Repeat steps 2 and 3, and compare with slides to other apps.

Expected result

Every keyboard slide starts at once. The first window moves within about 100 ms of the key press, and no window stops partway through the slide.

Actual result

In 1 of 3 slides to the Finder column, the first window moved 275 ms after the key press, another moved after 490 ms, and Zen stopped for 179 ms partway through the slide. In the other 12 slides I measured, to Finder, Zen, T3 Code, Spotify, Helium and Notion, the first window moved 25 to 165 ms after the key and the longest pause was 25 to 83 ms. Measured on 6 November 2026 (verify-miri run 20261106-150855), with synthetic key presses. No window became unreachable.

Likely cause, from the engine code at 20ba298 (not yet confirmed with timing logs):

  • A focus command calls focus_with_raise (src/manager/windows.rs:1388-1397) before the first frame is laid out. It sets the front process, makes the window key, and performs AXRaise on the target window.
  • AXRaise is a synchronous Accessibility call on the engine's main loop, with a 250 ms timeout (src/manager/app.rs:35). Finder is known to answer such calls slowly, so a raise that times out delays the first frame by about 250 ms, which matches the 275 ms start.
  • The 179 ms pause in Zen alone does not fit a main-loop stall, which would freeze every moving window at once. It may come from Zen itself.

This is one concrete case under #78.

Acceptance criteria

  • Timing logs around focus_with_raise and the first frame, over 10 keyboard slides to a Finder column on the real desktop, show whether the late starts match a blocked raise. Record the result in this issue before changing behavior.
  • After the fix, in 10 keyboard slides to a Finder column, the first window moves within 100 ms of the key press in every slide, measured from window-server bounds.
  • An engine test shows that a target app whose raise blocks does not delay the first frame of the slide. Run with make engine-test.

Miri version or commit

546cfb04 (engine 6c15fd6; the code above is unchanged on 4599ae7a, engine 20ba298)

macOS version and Mac model

macOS 27.0.1 (26A434), MacBook Air M3 (Mac15,12)

Displays and arrangement

Built-in (2048 x 1332 pt) to the left of a 27-inch external (2560 x 1440 pt, 100 Hz, main display), Displays have separate Spaces on; tested on the external display

Note

🤖 Claude Opus 5.5 responding on behalf of samanyudas

The timing logs confirm the cause for the late starts: focus_with_raise blocks the engine's main loop before the slide's first frame. The raise does not time out, though. Finder answers AXRaise in 66 to 154 ms, and no frame runs until it returns. This records the first acceptance criterion. No behavior is changed yet.

What I ran

  • Build: main at ffefedfa, engine a20f56e, plus local tracing::info! timing logs (engine commit f97a13e, not pushed). The logs time each call in focus_with_raise, every engine frame that runs longer than 20 ms, each pacing hold, and each window writer call.
  • verify-miri runs 20261106-202516 (10 slides to System Settings and 10 to Zen) and 20261106-202719 (10 slides from T3 Code to Finder with Control-Option-6, and 10 back with Control-Option-3). Row on the 27-inch display: Spotify, Zen, T3 Code, Ghostty, System Settings, Finder. Synthetic key presses, window-server bounds sampled on every change.
  • Latency is measured from the engine's own key command log line. The key tool's "posted" time, which the original report used, comes 50 to 143 ms after the engine has already logged the key, so it reads too low.

Slides to Finder (run 20261106-202719)

SlideAXRaisefocus_with_raise totalFirst engine frameFirst window moves
1154 ms165 ms+169 ms192 ms
279 ms90 ms+93 ms142 ms
385 ms93 ms+94 ms150 ms
47 ms14 ms39 ms
566 ms80 ms130 ms
684 ms93 ms133 ms
769 ms82 ms118 ms
873 ms78 ms96 ms
9123 ms131 ms183 ms
1071 ms85 ms107 ms
  • 8 of 10 slides start later than 100 ms. The first window moves 18 to 57 ms after focus_with_raise returns, in every slide.
  • In the 3 slides I checked frame by frame, the first pacing frame of the slide runs within 3 ms after the raise returns. Before that, neither a window nor a photo moves.
  • For Finder, SetFrontProcess takes 4 to 12 ms and make_key_window 1 to 5 ms. The raise is the cost.

Why it blocks

focus_entity (src/ecs.rs:1084) triggers FocusWindow, and focus_window_trigger (src/ecs/focus.rs:757) calls focus_with_raise (src/manager/windows.rs:1388) while the command system's commands apply. That is before the animation and frame-write systems of the same frame. AXRaise is served on the target app's main thread, so the engine waits for that app.

Other apps are slow too

  • System Settings: in 9 of 10 slides AXRaise took 109 to 206 ms and returned kAXErrorAttributeUnsupported (-25205). Those slides started 142 to 250 ms after the key.
  • Zen: AXRaise took 0.5 to 170 ms. Slides to it started 83 to 255 ms after the key.
  • T3 Code: AXRaise took 0.3 to 34 ms.

So the late start is not specific to Finder. Every app whose main thread is busy delays the slide by its raise time.

Not explained, and not reproduced

  • One slide to System Settings had a 282 ms busy frame with a 12 ms raise. Something else blocked that frame, and these logs do not show what.
  • The second frame of most slides runs about 25 to 95 ms, even when the raise is short. I guess it is the photo capture for the windows that enter, but I have not checked.
  • The 275 ms and 490 ms starts and the 179 ms pause in Zen did not come back. The slowest Finder start was 192 ms.
  • Pauses in app-window bounds are a weak signal here. An entering window slides as an engine photo and its real window jumps at the end, and the sampler did not record photo panels.

Fix options

  1. Raise off the main loop (recommended). Keep SetFrontProcess and make_key_window inline, and send the AXRaise to the target app's window writer thread, which already serializes AX calls per app. The first frame then runs at once. raise_without_focus in raise_window_trigger raises every visible strip window on the main loop too, so it should move the same way. The engine test for criterion 3 can use a mock window whose raise blocks, and assert that the first frame write happens without waiting for it.
  2. Run the first frame before the focus. Defer FocusWindow by one frame. This is smaller, but the raise then stalls the second frame for the same 66 to 206 ms, so the slide stops right after it starts.
  3. Drop AXRaise when SetFrontProcess with the window ID already brings the window forward. This needs an experiment per app first, because upstream Paneru raises for multi-window apps.

Criterion 2 also needs a decision. Measured from the key tool's posted time, it passes more easily than a user feels it. I suggest measuring from the engine's key log line and counting engine photo panels as motion.

Artifacts on the test Mac: /tmp/m326/ (instrumentation diff, drive and analysis scripts, per-run samples and engine logs).