Cross to the next display at the strip's end with the move and focus shortcuts#104

opened15d agobySSamanyu Das

Note

๐Ÿค– Claude Opus 5.5 responding on behalf of Sam

Draft type: feature. Source: Sam's side-by-side display check for #87 on 26 October 2026. Release scope: v1, after #103. Blocked by: #103

What to build

When the focused window is already the last column of its strip, โŒƒโŒฅโ‡งโ†’ moves it to the display on the right. When it is already the first column, โŒƒโŒฅโ‡งโ† moves it to the display on the left. Focus works the same way: at the last column, โŒƒโŒฅโ†’ moves focus to the display on the right, and at the first column, โŒƒโŒฅโ† moves it to the display on the left. This matches niri's move-column-right-or-to-monitor-right and focus-column-right-or-monitor-right.

Decision change

This replaces part of the #23 decision in decisions/keybinding reference.md and decisions/release-scope.md. Sam decided on 26 October 2026 that horizontal move and focus cross displays at the strip's end. Up and down still stay inside the current display at a column's top or bottom edge. The โŒƒโŒฅโŒ˜ W, A, S, D focus shortcuts and the โŒƒโŒฅโŒ˜โ‡ง W, A, S, D move shortcuts keep their behavior.

Acceptance criteria

  • A press at the strip's end crosses only when a display touches that side in System Settings > Displays.
  • With no display on that side, the press does what it does today.
  • A key auto-repeat never crosses, so holding the key to send a window to the end of the strip does not throw it onto the other display.
  • A moved window follows the same sizing and maximize rules as โŒƒโŒฅโŒ˜โ‡งA and โŒƒโŒฅโŒ˜โ‡งD, including #87.
  • The moved window keeps focus.
  • A press in the opposite direction brings the window back to the strip end it left.
  • Update the decision documents and the keybinding reference.

Verification

Engine tests cover both directions, the no-neighbor case, and key repeat. Sam checks both directions on hardware with the displays side by side, after #103 lands.

Note

๐Ÿค– Claude Opus 5.5 responding on behalf of Sam

Decision on 1 November 2026: not planned. Plain โŒƒโŒฅโ†/โ†’ and โŒƒโŒฅโ‡งโ†/โ†’ stop at the strip's end and stay inside the current display.

#79 has already delivered the crossing this issue asked for, on the โŒƒโŒฅโŒ˜ layer (merged 27 October, one day after this issue was filed):

  • At the first or last column, โŒƒโŒฅโŒ˜โ† or โŒƒโŒฅโŒ˜โ†’ focuses the display on that side.
  • At the first or last column, โŒƒโŒฅโŒ˜โ‡งโ† or โŒƒโŒฅโŒ˜โ‡งโ†’ moves the column to the display on that side.
  • A key auto-repeat never crosses. This was checked live on 30 October and is recorded in PROGRESS.md.

Reasons to keep the plain keys inside the display:

  • โŒƒโŒฅโ†/โ†’ is the most-pressed shortcut. When you tap it repeatedly toward the end of the strip, one extra tap would throw focus or a window onto the other display. The repeat guard does not catch separate taps.
  • From the strip's end, crossing on the โŒ˜ layer takes one press, the same as #104 would.
  • Release scope already says only the dedicated monitor commands cross displays. Closing this keeps that rule as it is, so the decision documents need no change.

#214 removes the matching "Not built yet" row from docs/limitations.md.

Note

๐Ÿค– Claude Opus 5.5 responding on behalf of Sam

Reopened on 1 November 2026. Sam rejected the not-planned decision above, and I agree with Sam's reason.

At the strip's end, plain โŒƒโŒฅโ† and โŒƒโŒฅโ‡งโ† do nothing today. A person who presses them wants to go left, and the display on the left is the only left there is. Making them hold โŒ˜ to do that fights what they are trying to do. The overshoot risk I gave is small: the key-repeat guard already stops a held key from crossing, and an extra tap that crosses only moves focus, or one window that the user then moves back.

Open point before building: the last acceptance criterion says a press in the opposite direction brings the window back to the strip end it left. That conflicts with the #79 landing rule, which puts an arriving column after the destination's active column. Sam has to pick one rule. The scope is otherwise unchanged, and the issue stays blocked by #103.

Note

๐Ÿค– GPT-6.1 Sol responding on behalf of Sam

Merged implementation verified on a fresh signed main build at c4ba432e65f443d896c26442501a75f84d558901, with bundled engine 6b5dbc5461b58db54b09f6c06f9c304e5cb16b6a. The issue stays open because its Verification section explicitly requires Sam's side-by-side hardware check, which has not been reported.

CARGO_INCREMENTAL=0 CARGO_PROFILE_DEV_DEBUG=0 make build exited 0 on main. The complete main source tree equals CI-green head ec22fe9f of #291, where all six checks passed. The same behavior source passed local make check with 203 Swift tests, 42 engine library tests, 1182 engine binary tests, native selftests, and packaging. The full gate was not repeated on the identical merged source.

Native observations used synthetic HID events on two real horizontally touching displays, not physical keyboard presses.

Acceptance areaEvidence and limit
Touching strip endsFresh plain focus crossed both directions on landed main. Engine tests cover non-touching, corner-only, vertical, and diagonal exclusions.
No neighborFresh plain focus stayed at the outer east edge on landed main. Earlier native checks covered focus and move at both outer edges on the unchanged engine.
Key repeatA source-interior sequence reached the local end without crossing on landed main. Earlier post-cross repeat sequences are separate evidence.
Sizing and maximizeLanded main scaled width 310 to 388 and back to 310, and 388 to 310 and back to 388. Maximize and stack behavior were checked in earlier native runs on the unchanged engine, not repeated in this landed run.
Focus retainedEngine and macOS focus agreed after both landed moves and returns.
Opposite press returnsBoth fresh reverse moves returned the single-window column immediately to its original strip end.
Decisions and keybinding referenceMerged in #291. This documentation criterion is checked.

Sam's later direct-focus shortcut choice is also in the landed build. One Control-Option-Command arrow press switched from an interior fixture to the expected last-selected fixture on the other display, in both directions. The version 5 configuration retains empty edge-focus bindings and the requested direct-monitor defaults. The earlier final-candidate UI run observed both edge actions still listed without shortcut chips and all six arrow/letter monitor chords.

Local evidence is in verify-miri run 20261104-232736, with the main build log and per-action state/focus captures retained locally. No media was uploaded. Cleanup restored eight recorded strip positions and the original engine and macOS focus. Four native frame rectangles differed from the initial capture, so exact frame restoration is not claimed. The fresh main build remains running, the configuration matches its backup, and the live-test lock is free.

Remaining before closure: Sam's physical side-by-side check of focus, move, repeat confinement, sizing/maximize, and immediate return in both directions. The native-behavior checkboxes stay unticked pending that required result.

Note

๐Ÿค– Claude Opus 5.5 responding on behalf of Sam

Triage on 6 November 2026: no defect, so there is no root cause to find. Every native #104 behavior still works on the current main engine, in synthetic key presses on two touching displays. The only open item is still Sam's physical side-by-side check.

Why I rechecked. Three engine PRs merged after #104 and touch how a crossing column lands: paneru#126 (pacing), paneru#130 (shared edges), and paneru#131 (columns leaving a strip). The crossing decision code did not change. Since 6b5dbc5, src/platform/input.rs is untouched, and src/commands.rs changed only the order of the maximize system.

Build. The running build at /tmp/miri123/final, engine a20f56e, which is the engine pin of main ffefedfa. Its app sources, Package.swift, and build script equal main (git diff 5b6d6fea ffefedfa is empty for them). I could not build ffefedfa itself, because the disk ran out of space. The worktree that built it no longer exists, so I cannot rule out uncommitted edits in that build.

Results. verify-miri run 20261106-213329, display 1 (2048 wide) directly left of display 3 (2560 wide), three AXFixture windows. Engine focus and macOS frontmost app agreed after every press below.

CheckObserved
Focus west at display 3 first columnFocus crossed to display 1's active window.
Focus east at display 1 last columnFocus crossed to display 3's active window.
Move west, then one press eastFixture went to display 1 last column (3 of 4), then back to display 3 column 0, focused both times.
Move east, then one press westFixture went to display 3 column 0, then back to display 1 last column, focused both times.
Width388 on display 3 became 310 on display 1 and 388 again on return, the same share of each display.
MaximizeMaximized width 2040 on display 1 became 2552 on display 3 and 2040 on return. Unmaximize restored 310.
Outer edges, no display besideFocus and move at display 3 last column and display 1 first column changed nothing.
Held focus key from display 1 first column (1 press, 8 repeats)Focus stopped at display 1's last column, with a fixture active on display 3 to catch a crossing.
Held move key from display 1 first column (1 press, 8 repeats)Fixture stopped at display 1 last column.

Two earlier repeat attempts in the same run are discarded. Someone was at the Mac and a macOS password prompt was frontmost, so their starting focus was wrong. I reran both with a 5-second idle guard.

Not rechecked: stacked columns crossing, and the Command-layer monitor shortcuts. These are synthetic HID events, not physical key presses. Cleanup restored all 10 recorded windows to their strip, display, and position, and released the live-test lock. Evidence stays local in the run folder.

Remaining before closure: Sam's physical check of both directions, as the Verification section asks.