Files
komorebi/docs/dev/macos-handcheck.md
T
LGUG2Z 29bdd0b2c7 docs(macos): record that the Mini runs so far are compile and unit tests only
Adds a status section to the hand-check list: cargo test --workspace passed
on the Mini (daemon 366 passed, 1 ignored) and clippy is clean, but nothing
was run as a daemon, so every walk-through item stays open.

Verified: documentation only.
2026-11-05 10:30:05 -08:00

16 KiB

macOS hand-check list

Status: written by the kfm agent for the user, 2026-10-31. Nothing here has been run: macOS cannot be built, linked or tested on the Linux host, only type-checked through a fake C compiler (tools/fast-check). Every revision below is type-checks, not run. Revision ids are jj change ids (stable) with the commit id they had when this was written (commit ids change when a revision is rewritten; use jj log -r <change id>).

How to use it: build the unified (or, until they exist, the per-tree) macOS komorebi, komorebic and komorebi-bar, run cargo test --workspace on the Mac first (the 399+ unit tests have never run against these changes), then walk the sections in order. For before/after capture use komorebi-recorder against a build of the baseline wip-main ancestor ff3711dc and the tip. A section is done when the "look for" column is true.

Status of the Mini runs (2026-10-31)

The real-Mac runs so far (Mac Mini, rustc and clippy 1.93, tip a35573d8) are compile and unit tests only: cargo test --workspace in komorebi-for-mac passed (daemon 366 passed, 1 ignored; 368 total, 0 failed) and clippy -D warnings was clean apart from the two items fixed afterwards. Nothing was run as a daemon: no focus, tiling, borders, animations or bar rendering has been observed on the Mini. Every walk-through item below is still open.

A. Revisions that change what the daemon does (BEHAVIOUR)

Revision What changed Look for on a Mac
ummqkuvp 01b9cbed fix(mac): take the rule-list locks in one order... window_is_eligible (window.rs) and apply_globals (static_config.rs) now lock floating, ignore, manage, regex in that order; before they locked regex, ignore, manage and ignore, regex, manage. The order is documented at the statics in lib.rs. Same fix as the KDE ef1d3ef0. Run komorebic reload-configuration (and komorebic replace-configuration) in a loop of 50 while opening and closing windows (new Finder windows, Terminal tabs) at the same time. Before the fix this could hang the daemon (the command is never answered, new windows are not tiled). Expected: no hang, every reload answered. Also check that float rules, ignore rules and manage rules from the config and from komorebic float-rule/ignore-rule/manage-rule still apply to new windows.
nvwvptpv 866b2840 use the shared command listener The UDS command accept loop and per-client reader are the shared komorebi-transport code (NoPolicy: no read timeouts, as before); WindowManager is a CommandTarget (lock with try_lock_for). Every komorebic command is answered; a command sent while the daemon is busy gets an answer or a clean error, not a hang; kill the daemon mid-command: the client returns.
ywwwwpwm 587defe3 use the shared pause filter allowed_while_paused lists the commands allowed while paused. komorebic toggle-pause; while paused only toggle-pause, state, global-state, stop work, everything else is ignored (and the connection ends); unpause and check windows tile again.
kzynysto b85827e6 use the shared subscriber push Subscriber sockets are pushed to by the shared notify_sockets; the macOS override events are kept. komorebic subscribe-socket (and subscribe-socket-with-options): events arrive for focus, move, workspace change, monitor change, theme and config reload; a subscriber that goes away is dropped without breaking the others. The bar is a subscriber, so the bar updating is the check.
ovsplvmn 5cc38da6 use EventChannel for the window events The window-event channel (capacity 20, try_send, drop warning, ResizeStep claim flag) is komorebi_transport::EventChannel. Drag a window edge continuously (live resize) and pan with the trackpad: no stalls, no repeated drop warnings in the log, the layout follows. Move many windows at once (workspace switch with 10+ windows).
lrqkuuzq a4996ac6 use SocketMessage from komorebi-protocol and sowrttxs 5c129ab5 carry DebugWindow as i64 One SocketMessage for all platforms; the wire format must be unchanged. DebugWindow takes an i64 (a negative id parses and matches nothing). Every command of the komorebic help works against the daemon; komorebic debug-window <id> for a real id; komorebic state and global-state JSON parse.
qqwoptqt 975f007a, yptnnkxv eb903bcc (animation prefix, Bezier doc) AnimationPrefix gains Transparency (ignored by the daemon); schema.json changed the shape of per-prefix animation settings. Config with animation.style / duration per prefix (movement, workspace) still loads; animations behave as before. Run komorebic static-config-schema and diff with komorebi-for-mac/schema.json (two description-level changes and the per-prefix shape are intended).
kvtvnouo 93e4d511, stxmqnop dec91984 (StaticConfig from komorebi-config) The config data types and their defaults come from komorebi-config; komorebic static-config-schema prints the macOS view. A real komorebi.json of yours loads with no new warning and every key still takes effect (borders, padding, animations, scrolling, rules, workspaces); komorebic static-config-schema matches the checked-in schema.json up to the intended description lines.
qukywpss c46320ea, zwkwptox 1e902542, sxptsttt 0345fccb (komorebic shares clap definitions, launcher, pan) komorebic start and stop use the shared Launcher skeleton (the odd --config arguments are kept; "Waiting for komorebi to start..." is flushed); pan commands use the shared module. komorebic start --bar, start --await-configuration, stop, stop --bar; komorebic pan-* commands; komorebic --help and a few <command> --help texts (about 15 help texts changed to the Windows wording).
oywqqkuy f0b1a448 register every Window in ax_registry and forget the dead Window::new registers the window in the id registry (ax_registry); entries are removed in Window::drop of a dead window, in reap_window, and (forget_pid) when an application terminates. No AX call is added; the table holds retained AX references and observers. Open and close 50 windows (Finder, Terminal, Safari) and quit several apps, then check the daemon's memory in Activity Monitor does not climb and nothing behaves differently (focus, tiling, borders). If a quit app's windows were not forgotten the symptom is memory growth only. Run with RUST_LOG=debug and check no new errors at app quit.
oqnvyqvw e0687167 keep ax_registry in step when a native tab changes a window's id The two places that swap a window's id and element in place for native tabs also update the registry (sync). A native-tabbed app (Terminal, Finder, Safari tabs): switch tabs, close the first tab, open a new tab, focus the window with komorebic focus ..., move and resize it. Must behave as on the baseline (no window lost, no stuck border).
Bar: nottqpms 086833af, zvuyspku b8de056b, uxzpkmnk 7cd85bd4, yopuwouu 6da27dda (bar-core, platform hooks) macOS now builds sysinfo 0.39 and starship-battery 0.11 (the Windows versions); cpu, battery, memory, date, update widgets are the shared code. Bar renders; CPU %, memory, battery % and charging state show sane values; date and the calendar icon; clicking CPU, battery, network, update (see bug list D below: these click handlers are broken on macOS and stay broken).

Revisions in section B change no behaviour on purpose (rename, move, route); they still need the sections C walk-through, because a mechanical mistake would show there.

B. Mechanical macOS revisions of the kfm agent (Step 6 and 11(i))

Revision Change
vzxkxzzy 796c3832 Window::frame() (renamed window_rect below) for the 28 sites that read a Window's frame through MacosApi::window_rect(&w.element) (window.rs, window_manager.rs, workspace.rs, process_event.rs, process_command.rs, settle.rs, macos_backend.rs)
oootynxk 7c2e1bea Window::is_valid renamed Window::alive
roxrxtru 49a6dbda Window::set_position renamed Window::set_rect (10 callers)
vmnnuzzu 803a1410 Window::set_messaging_timeout (settle.rs: 3 raw AX sites)
upylxxuw ecaa3e15 Window::close (the same close-button press as AdhocWindow::close)
tsymmvll d30a3eb6 step 11(i): MacosBackend calls only Window methods (new Window::minimized); still unused by the daemon
qsrxvsmv 931b17d7 Window::frame renamed Window::window_rect (the Backend and the Windows Window use that name)
xrtqmxkp 4c9094d7 native: komorebi_backend::Registry (the rules of the id registry; 9 unit and 1 doc test run on Linux)
tmmtuosy 386c5a79 ax_registry module; MacosBackend resolves ids through it (the adapter is still unused by the daemon)
kvkxkqnx ba22e8dc ax_registry::identify: the 7 sites that turn a system-handed element into a window id (input_event_listener, process_command, process_event x5, window_manager)
kpopuvoy 01b3bd5c live-resize step (window_manager.rs) reads the dragged window's frame through Window::window_rect

Hand check: C below. If something breaks and is not the lock order, bisect among these seven first: they only rename and route, so a regression means a call was routed to the wrong receiver (type-check cannot tell a Window from another window of the same type).

C. Walk-through (the list the plan names)

Do each with the baseline build and the tip build and compare (or record with komorebi-recorder).

  1. Focus: komorebic focus left|right|up|down, cycle-focus, focus-window <n>; mouse-follows-focus on/off (the cursor centres in the window: uses the window frame query); focus a window of a tabbed app (Terminal, Finder): the focus uses element_to_focus, check the right tab.
  2. Move and resize: move, resize-axis, resize-edge and a manual drag of a window; after a drag the layout puts the window back (settle reads the frame with a timeout); live resize of one edge moves the neighbour. A window that refuses a width (a fixed size app) is read back and the neighbours adapt.
  3. Stack: stack, unstack, cycle-stack, stack-all; hidden stack members are parked off-screen (hide) and restored (restore) correctly; no window left at a hidden corner after unstack or quit.
  4. Workspaces: switch (focus-workspace), send a window (send-to-workspace), move-to-workspace, with 2 monitors if possible; windows of the workspace you left are hidden, those of the new one restored to where they were; workspace rules after a monitor change.
  5. Scrolling layout: the scrolling layout, pan commands, scroll-*; windows slide in from the edge (the arrival/slide code reads the frame).
  6. Pan (trackpad gesture) and live resize: komorebic pan-*, three-finger style gestures as you configured them.
  7. Borders: focused/unfocused/stack colours follow focus and follow animated windows; borders vanish and come back on workspace switch; no border left behind after closing a window (the window's close and borders share the observer code).
  8. Animations: movement and workspace animations on and off; the ghost images; cancel an animation by issuing a new command in the middle.
  9. Display change: plug or unplug a monitor or change the resolution: work areas update, workspaces go to the right monitors, workspace rules re-apply. (This is the thread that can deadlock, see bug list A.)
  10. Sleep and wake: close the lid or sleep, wake: windows are where they were, the daemon answers, the bar returns.
  11. Minimize, unminimize, close: komorebic minimize, close (the foreground window), un-minimize a window from the Dock; closing a window removes it from its container (the reaper uses Window::alive).
  12. Reload the config repeatedly (see section A, first row).

D. Bugs noticed, not fixed here

A. Possible deadlock, main queue against the window-manager lock (docs/dev/seam.md section 3.3, by reading only, never reproduced). A thread holding the WM lock calls a blocking main-queue dispatch (destroy_all_borders at process_event.rs lines about 128/139, process_command.rs, retile_all; hide_borders_on in window_manager.rs; activate_finder in process_event.rs) while the monitor reconciliator has dispatched a closure to main that itself takes the WM lock (monitor_reconciliator.rs about 106 and 383). Then the event thread waits for main, main waits for the lock. parking_lot's deadlock detector cannot see it (the dispatch is a semaphore). Window of exposure: a display change at the instant of one of those events. Structural fix: no closure that runs on main may take the WM lock (or dispatch to main only after the lock is dropped). How to provoke it: spam komorebic retile / workspace switches (with ghosts) while plugging or unplugging a monitor; the symptom is a daemon that answers no command (komorebic state hangs). B. cmd.exe and explorer.exe in the macOS bar (komorebi-for-mac/komorebi-bar/src/os.rs, BarPlatform::open_system_tool): the battery click spawns cmd.exe (power settings), the update click spawns explorer.exe with the release URL; neither exists on macOS. The same file's CPU (task manager) and network settings tools should be Activity Monitor and System Settings. The fix is one function in that file; it was preserved on purpose during the bar move. C. Update widget refresh thread polls the Windows repo's GitHub API on macOS too (update.rs has a second URL that does not go through the hook), and the macOS update link is .../releases/v.. built for the Windows repo. D. Other raw AX sites remain outside Window: window.rs (38 inside Window/AdhocWindow, expected), application.rs 11 (Application, not a window), process_event.rs 7 (events that hand over a bare element: window_id of an element, the tab check AccessibilityApi::window_id(&window.element.0)), animation/animator.rs 6 (lane threads hold WindowHandle clones of the element), border_manager 7 (observers on tracked elements), window_manager.rs 4 (timeouts list holds elements, window_id of an element), reaper.rs 2 (notify_if_gone takes an element so lanes can use it), input_event_listener.rs 1, process_command.rs 1. They need either an id to AX registry (step 11(ii), not started) or a WindowHandle type; they were left alone because routing them changes who owns the element. D2. Step 11(ii) status. Done: the id registry (ax_registry, over the tested komorebi_backend::Registry), populated at Window::new, kept in step for native tabs, emptied on death, reap and application quit; MacosBackend resolves ids through it. NOT done, on purpose, because each needs a decision that cannot be verified without a Mac: (a) the animator lanes (WindowHandle) keep their own snapshot of the element: a lane must keep writing to the element of the move it was given even if a tab swap replaced the registry entry, and replacing the snapshot by a lookup per write changes what a late write hits; (b) borders keep tracking_element (observer sources are added and removed on that exact element, on main); (c) reaper::notify_if_gone and the window manager's live-resize timeouts list take elements, including application elements, that are not registry entries; (d) application.rs is the application, not a window. (e) The model (Window, containers, workspaces) still holds Window values, not bare WindowIds: that is step 11(iii), after steps 8-9. A stale registry id answers Error::WindowGone (no panic); a window that was deserialised from state (default system-wide element) is never registered. E. MacosBackend (step 7c/11(i)) needs 11(ii) before it can be used: windows() answers only what was registered, hide needs the corner, raise also focuses, monitors() reads a cache nobody fills yet (seam.md section 9.2). AdhocWindow (bare foreground element) still has its own minimize, close, hide, restore, raise. F. In window.rs AdhocWindow::hide / Window::hide duplicate the restore-position bookkeeping (WINDOW_RESTORE_POSITIONS), one on a Window and one on a bare element; they must stay in step.

E. What is not checked at all

Everything above. In particular: no macOS test (399+ unit tests incl. those in window.rs for drag state) has run against any of these revisions; the unified binaries do not exist yet; no real Accessibility call was made; komorebi-recorder captures need a Mac.