feat(layout): add on-overflow focused column centering

QE
3d ago
+456-42

Adds center_focused_column = "on-overflow". When focus moves to another column, Defi centers the new column only if it cannot share the screen with the column that was focused before. Otherwise it scrolls just enough to reveal it, like "never". The default stays "never", and "never"/"always" behave as before.

Closes #102

Behavior

  • Fit. The span runs from the left edge of the leftmost column to the right edge of the rightmost, including any columns between them. It fits when it is at most the usable monitor width. Column slots include their gaps, and an exact fit counts as fitting.
  • Prior column. This is the selected window that Defi last resolved the workspace's scroll target for. With no prior column (first window, prior window closed), Defi only reveals.
  • Stability. The decision is made once, when the selected window changes, and is stored with that window (Workspace.scrollFocus). Later recomputation keeps it: desktop syncs, stale completions, column moves and learned minimum widths. Focusing the selected window again changes nothing.
  • Oversized columns. A column wider than the screen is left-aligned in every mode.
  • Settings. Settings > Layout > Focused column gains "Center on overflow". The mode is documented in CONFIGURATION.md under "Focused column centering".

Verification

  • python3 script/verify.py local passed on the final head.
  • New pure tests:
    • a config decoding test;
    • a never/always/on-overflow decision table over prior and new columns, plus an oversized-column test;
    • a runtime transition sequence covering exact fit, adjacent and nonadjacent focus, refocus, a stale scroll offset and a column move;
    • daemon native-focus cases.
  • Desktop: verify.py desktop on the staged bundle. The first full run had 5 failing tests (hotkeys, Carbon reservations, pointer, keyboard activation) and the second had 1 (native stacking precondition). The sets did not overlap. Each failing test passed when rerun alone on the same build, and the session store restored each time.
  • Installed journey: verify-defi centering_check.sh (new). On this build, it passed all 6 width/mode rows driven by the CLI. It passed again driven by real hyper hotkeys at about 0.8 s spacing, with captured=12 and drift=0[]. One daemon remained and the initial workspace was restored.
  • Not run: Computer Use, because it is unavailable in this session. Multi-monitor isolation was not exercised because only one display was attached.

Manual test (about 5 minutes)

<mod> is your default_key_modifier (hyper in your config).

  1. Open Settings > Layout > Focused column and choose Center on overflow. Alternatively, add center_focused_column = "on-overflow" under [layout] in ~/.config/defi/config.toml.
  2. On an empty workspace, open 4 windows. TextEdit with Cmd-N works. Columns are 0.5 wide by default.
  3. Press <mod>-[ to focus the first column, then <mod>-Right. Expected: the second column shows at the right half and nothing scrolls, because two half-width columns fit exactly.
  4. Press <mod>-Right again. Expected: the third column is revealed at the right edge, not centered.
  5. Press <mod>-[, then <mod>-Right twice quickly. Expected: each step reveals and nothing is centered.
  6. On each column, press <mod>-= (cycle-width next) to make it 0.66 wide. Then press <mod>-[ and <mod>-Right. Expected: the second column is centered because the two columns overflow.
  7. Wait a few seconds, then click the centered window. Expected: it stays centered and the view does not move.
  8. Switch the setting back to Reveal as needed. Repeat step 6. Expected: the second column lands at the right edge instead.

[!NOTE]

Add on-overflow focused-column centering mode

  • Adds a third focused-column centering option: center a column only when the span from the previously selected column exceeds the usable viewport; otherwise reveal it as needed. The overflow decision persists per workspace until the selected window changes.
  • Plumbs the new value through config (CenterFocusedColumnConfig), runtime (CenterFocusedColumn.init(config:)), core scrolling (focusedColumnScrollTarget), a new ScrollFocus model field on Workspace, and the macOS settings UI plus settings search.
  • Adds an end-to-end verification script (centering_check.sh) that drives TextEdit across all centering modes, records traces, and restores workspace and config state on exit.
  • Behavioral Change: workspaces now persist a ScrollFocus record via synchronizeScrollOffsets in ScrollReconciliation.swift; the recorded on-overflow decision survives mode switches, manual offset changes, and width changes until the selection changes. Default remains never.

Macroscope summarized 916288c.