Adds crates/komorebi-core/tests/common (StubWindow, StubPlatform): a
Clone-but-not-Copy window and a platform whose window operations and every
WorkspacePlatform and MonitorPlatform hook append a line to an ordered log,
with failure injection per operation (fail("hide 3"), fail("place_container")),
scripted end_update passes and a per-workspace hide spot kept in the platform
state the way macOS keeps its corner. The log and settings are thread-local
and every test starts with fresh().
tests/container_behaviour.rs pins Container<W> (24 tests): add/remove/focus
ordering, the focus index after removal at each position (it is idx-1 whatever
was focused, and dangles after an out-of-range removal), empty-container cases,
the order of hide_window calls (from the back, a failing window is skipped),
load_focused_window stopping at the first failure, the omitted window, restore,
locking and the JSON shape.
Verified: cargo test -p komorebi-core --test container_behaviour, 24 passed
(nix shell gcc, own target dir). No non-test source changed.
Candidate 4: wip-cand3c (core line with macOS on komorebi-core, HideSpot, settings S1-S3, bar e2e scenario, handover scenario fix), wip-u-core3 (shared Workspace update_shared and the Windows converge of window_manager/process_command), wip-u-stallfix (lock-holder instrumentation, sysinfo scan before the lock, bar restart and hwnd file off the lock, subscriber write timeout) and wip-u-pkg2 (one komorebi package and binary, daemons as libraries). Conflicts, if any, are resolved here.
Gate candidate 3c: wip-cand3b (cand3 + the bar e2e scenario) plus wip-u-kdefix (handover precondition in both copies of the shared e2e lib, KDE vm-commands bring_back step). The shared e2e/lib handover scenario is also what the Windows runner runs, so the Windows gate must include it.
notify_subscribers connects to every subscriber socket and write_all()s the
notification with the window manager locked (every command, the reaper and
the event thread), with no timeout: a subscriber that stops reading stalls the
lock holder, and with it every thread.
komorebi_transport::notify_sockets skips a subscriber whose write fails with
TimedOut or WouldBlock (warns, keeps it subscribed, goes on to the next);
every other write error still aborts as before. The timeout itself is the
caller's: the connect closure passed by the Windows daemon sets a 1 s write
timeout on the stream. KDE and macOS pass connect closures without a timeout,
so nothing changes for them. A new unit test covers the skip. The connect
itself cannot be bounded (uds_windows has no connect timeout), and the
listener's lock wait is untouched.
BEHAVIOUR: a Windows subscriber that does not read for a second misses that
notification instead of blocking the daemon. Gate needed: Windows e2e commands
and config_reload, the lock stress, subscriber scenarios if any. Verified:
native cargo test -p komorebi-transport 14 passed (incl. the new test);
tools/fast-check native, Windows clippy -D warnings and KDE check + lib tests
PASS; macOS type-check run by hand (fast-check's macOS step fails to select
a package on this merged tree) PASS. e2e not run.
Scenario precondition fix for the vm-commands failure "[shared] win3 is inside the work area and
not on a tile of the layout" (see the previous revision for the log evidence).
The shared sweep sends windows to other workspaces (send-to-last-workspace and the like), so
by the time handover ran, win3 was alone on workspace 0 and win1/win2/win5 were parked on
workspace 3. New KDE step bring_back, between replace_configuration and handover: while another
workspace of the first monitor has windows, focus it and send-to-workspace 0, then focus
workspace 0 and check that it has two containers or more and is the focused one. The handover
check is untouched.
Native pytest of the VM lib: 320 passed; ruff check and format clean. The step itself only runs
in the VM.
VM verification: not yet (vm-commands on baseline-with-fix and candidate, ~20 min each, after the
KDE VM run).
update_known_hwnds (at the end of every command) and the reaper opened,
truncated and wrote komorebi.hwnd.json with the window manager locked. They
now hand the list to reaper::save_known_hwnds, which keeps only the latest
pending list and writes it from a thread of its own; errors are logged there
as before (the reaper no longer returns the write error).
BEHAVIOUR: the file is written a moment after the lock is released, not before
the command returns, and when several updates arrive together only the last
list is written. Nothing in the daemon reads the file. Gate needed: Windows e2e
commands, lifecycle, and the lock stress. Verified: Windows clippy -D warnings
via tools/fast-check PASS; e2e not run.
replace-configuration ran the PowerShell that stops and starts komorebi-bar
(one to three powershell_script::run calls, each seconds) with the window
manager locked. The block is now fn restart_bars and runs on a thread of its
own, started after the new window manager is in place (the lock is released
when the command returns); its errors are printed as before, but no longer
returned to the client.
BEHAVIOUR: the command replies before the bars are restarted (a client that
waited for the bar to exist after replace-configuration must now wait for it),
and an error reading the config for the bar section is printed instead of
returned. Only affects a daemon that has a bar running. Gate needed: Windows
e2e commands and config_reload (no bar in the e2e), a manual check with a bar.
Verified: Windows clippy -D warnings via tools/fast-check PASS; e2e not run.
replace-configuration called sysinfo::System::new_all() and refresh_processes
under the window manager lock, only to know whether komorebi-bar.exe runs.
new_all reads memory and CPU (it opens a performance-counter query, slow the
first time), and for every process the command line, environment, owner and
disk usage. The stall analysis measured the first replace-configuration of
each e2e run holding the lock for about 3 s (17 of 17 runs).
Now: komorebi_bar_is_running() reads only the process names
(System::new + refresh_processes_specifics(All, true, nothing())), and the
connection policy runs it in on_message, i.e. before the window manager is
locked for a ReplaceConfiguration, and leaves the answer in a static for the
handler to take (the handler scans itself, names only, if the answer is
missing, e.g. the message came over TCP). The result is the same: whether a
komorebi-bar.exe process exists, now decided when the message is read rather
than after the new window manager is built.
BEHAVIOUR: the lock hold of replace-configuration shrinks (expected from about
3 s to the preload/init/reload cost); a bar started or stopped between the
read of the message and the end of the reload is not noticed. Gate needed:
Windows e2e commands and config_reload, the lock stress, and the sweep timing
(first replace-configuration, see the new per-step info lines). Verified:
Windows clippy -D warnings via tools/fast-check PASS; e2e not run.
Instrumentation for the stalls of the stall analysis; no behaviour change.
lock_holder.rs: a place that has just locked the window manager calls
lock_holder::held("site") and keeps the returned guard next to the lock
guard; the holder (what, thread, since) is recorded in a slot of its own
(a mutex held only for a copy; the window manager lock itself is unchanged),
a hold of 200 ms or more is logged at warn when it ends ("window manager
lock held by X (thread T) for N ms"), and lock_holder::describe() names the
holder for the waiters: the border manager's "stuck for N ms" warning and
the listener's "could not acquire window manager lock" warning now end with
the holder. Sites: every command (held as "command <message>"), process_event,
reaper, monitor reconciliator, border manager, transparency and stackbar
managers. komorebi-transport: ConnectionPolicy::lock_holder() (default None),
used in that warning; the Windows policy implements it, KDE and macOS keep the
text they had.
replace-configuration logs info lines "replace-configuration: <step> took N
ms" for restore, preload, init, restore of the new wm, reload, swap, the
process scan for komorebi-bar and the bar restart; every command logs at
debug how long the command, update_known_hwnds and notify_subscribers took.
Verified: tools/fast-check: native, Windows clippy -D warnings, KDE check +
lib tests PASS; the macOS step of fast-check fails to select a package on this
merged tree (tool issue: -p komorebi), the macOS check run by hand with -p
komorebi-macos -p komorebic-macos -p komorebi-client-macos -p
komorebi-bar-macos passes. Not run: e2e.
Scenario precondition fix; the check "the unmanaged window is inside the work area and not on a
tile of the layout" itself stays as strict as it was.
The KDE VM suite vm-commands failed it on a calm host, on the original komorebi-for-kde tip
(7d19fd40) and on the refactored stage alike, with the same numbers each time: work area
(0,0,1920,1080), layout a single tile (10,10,1900,1060), win3 at (10,10,1896,1050) (within the
24 px tolerance of that tile), win1/win2/win5 parked on workspace 3. After the sweep win3 was the
only window on its workspace. Unmanaging a lone window leaves it on the tile it was put on, and
an emptied workspace has nothing to calculate, so it keeps its last layout; "not on a tile" could
never hold. The scenario now checks first that the focused workspace has at least two tiled
containers and, if not, fails with a message that says so (and the describe() dump) instead of
the misleading unmanage failure. Windows runs handover as its own suite with its three tiled
clients on workspace 0, so the precondition holds there.
The same file lives in e2e/lib and in komorebi-for-kde/nix/tests/vm/lib; both are edited alike. A
fake-platform test covers the precondition; the existing handover cases still fail where they
should.
Native pytest of the VM lib: 320 passed; ruff check and format clean.
VM verification: not yet (vm-commands on baseline-with-fix and candidate, ~20 min each, after the
KDE VM run).
docs/dev/core-model.md section 18: the shared share of window_manager.rs
after the converge (26% identical with Windows, 37% within four lines,
63% differing or one-sided, so not above the 60% bar), a table of the
near-identical functions and why each is cosmetic or real (the Windows
handle_unmanaged_window_behaviour calls in 18 functions against the router
call on KDE and macOS, borders, floating windows in monocle, workspace
names, custom layouts, id types), the 25 differing router arms (two
converged, the rest real; FocusWorkspaceNumber(s) and FocusLastWorkspace
probably an omission on KDE), and the seven blockers.
Verified: not applicable (documentation); numbers from the function
comparison at this tip.
Gate candidate 3b: wip-u-cand3 (macOS on komorebi-core, HideSpot signature change, settings S1-S3) plus the Windows e2e scenario for the bar (wip-u-bar2: e2e/ files and the nix e2e app only). Conflicts, if any, are resolved here.
Merge of kfm's macOS core migration (wip-u-kfm2 9cb4f2fa) and the settings
stack through S3 (wip-u-settings2 696d2929), both on wip-u-integ2. Two files
conflicted, both macOS; every hunk is resolved the same way: take kfm's
version of the file (its new shapes: komorebi_core Monitor/Workspace,
workspace_config and monitor_config as free functions) and re-apply the S3
change to it, because the settings side only touched lines kfm moved.
- komorebi-for-mac/komorebi/src/monitor.rs: conflict 1 (imports): kfm's
header doc kept, the DEFAULT_CONTAINER_PADDING / DEFAULT_WORKSPACE_PADDING
imports dropped (S3). Conflict 2 (std imports): kfm's side (no VecDeque).
The four padding reads (the two default fallbacks in the monitor's
workspace globals) call crate::settings::container_padding() /
workspace_padding(); the now unused Ordering import is removed.
- komorebi-for-mac/komorebi/src/static_config.rs: both conflicts are the
padding defaults in kfm's free functions (workspace_config, monitor_config):
kfm's text kept, the two defaults read from settings. Re-applied S3 in this
file: apply_globals applies paddings, aspect ratio, layout_defaults and
display_index_preferences as one Handle::update of a SharedDelta; the
readers (layout_defaults, aspect ratio, display preferences, paddings) use
the accessors; the statics' imports and the now unused AspectRatio imports
are removed.
komorebi-core and the shared crates read none of the removed statics
(grep: only the unrelated DEFAULT_*_PADDING consts in komorebi-config), so no
hook is needed. S3 semantics are unchanged from wip-u-settings2 (statics
removed, one update per load, reload merges, a few keys reset); the pins
are the settings crate's reload.rs and shared tests plus the KDE
tests/config.rs.
Verified: see the run in the progress log (macOS fake-CC clippy -D warnings
-p komorebi-macos --all-targets clean at this revision).
BEHAVIOUR (log text only, nothing else expected): the Windows copy of
these functions is changed to the text KDE and macOS have where the two
do the same:
- eyre::bail! is written in full (the bail import goes), as in KDE;
- toggle_lock flips the flag through Lockable (set_locked/locked), as
in KDE, instead of assigning the field;
- the log line of maximize_window says 'maximizing window' (it said
'maximizing windowj') and the one of clear_workspace_layout_rules says
'clearing workspace layout rules' (it said 'setting workspace layout');
- the SessionFloatRules arm uses unwrap_or_else and the State arm names
State, which is the text of the KDE arms.
Nothing else of the 61 functions that differ by at most four lines is
the same behaviour: the rest are real differences (core-model.md section
18).
Windows e2e scenarios needed: toggle-lock, maximize, clear layout rules,
session-float-rules, state (all cheap).
Verified: Windows clippy --locked --all-targets -D warnings: PASS (after
touching the sources: the shared target dir had stale metadata). Not
run: Windows tests, e2e.
Step 8 (Option A of docs/dev/unified-binaries.md). The main.rs of komorebi-windows, komorebi-linux and
komorebi-macos becomes daemon.rs of the library (pub fn run; komorebi:: paths are crate:: paths now, the
inner attributes and the unit tests move with it), and their [[bin]] is gone, so each is a library only.
The new package komorebi (komorebi-daemon/, version 0.1.42) is the one binary: its main calls
komorebi_windows::daemon::run, komorebi_linux::daemon::run or komorebi_macos::daemon::run by cfg_select! on
the target OS, through target-gated dependencies (one key per library, cargo allows one path per key),
and forwards the features deadlock_detection and schemars. The binary name komorebi now comes from exactly
one package. The four KDE integration tests that run the binary (no_kwin, socket, hot_reload, version)
move to komorebi-daemon/tests/linux_*.rs; the version test compares with the KDE version crate, since
the package version is now shared. Old names: komorebi-windows, komorebi-linux and komorebi-macos still
build, as libraries (no binary any more: use -p komorebi). The flakes build -p komorebi with the KDE and
macOS packages, the source filters take komorebi-daemon (manifest, main.rs, linux tests), the Windows
filter leaves its linux tests out, deny.toml clarify entries gain komorebi, the release workflow's cargo wix
and the justfile version lookup name komorebi, tools/fast-check checks -p komorebi on KDE and macOS.
Behaviour: the same code runs, the same binary name; the Windows executable is rebuilt from the library
(BEHAVIOUR for the e2e gate: the Windows e2e must run).
Gates run: Windows clippy --all-targets -D warnings PASS; KDE check; macOS fake-CC check of -p komorebi
-p komorebi-macos PASS (type check only); KDE cargo test -p komorebi -p komorebi-linux -p komorebic -p
komorebi-client -p komorebi-hotkeys -p komorebi-core: 903 passed, 0 failed (the four moved tests and the
unit test of daemon.rs included); CLI docs oracle identical x3; bar schema harness identical both bars;
kde-closure-check ok; nix evaluation of the root and both nested flakes PASS; by evaluation an edit of
komorebi/src/daemon.rs changes the Windows derivations only, of the KDE daemon.rs kfde only, of the macOS
one neither (the shared komorebi-daemon/src/main.rs changes all, as expected). Not run: nix builds, VM,
e2e, any Mac run.
New extras scenario e2e/platforms/qemu_windows/extras/bar.py, registered in
run.py as `bar` (runs after the tiling windows, like the other Windows
extras). It starts komorebi-bar in the guest with a configuration that has
storage, time, applications, komorebi workspaces and layout widgets, the
Windows-only Keyboard and Systray widgets and a `mouse` block (a message and
a command), and checks: `komorebi-bar --schema` equals the checked-in
schema.bar.json byte for byte; the process starts and its window titled
komorebi-bar appears, visible and sized; it runs 15 s (scaled); `komorebic
state` answers and komorebi does not tile the bar; commands that notify the
bar work; the bar survives `komorebic stop` and the daemon starting again as
the same process (it reconnects: it does NOT exit when the daemon stops, so
the task text "exits cleanly when komorebi stops" is checked as
`komorebic stop --bar` ending daemon and bar); screenshots are kept as
artefacts, no pixel assertions. No known failures.
Plumbing: run.py uploads komorebi-bar.exe when the binaries directory has it
(other scenarios do not need it, so older build-ref directories still work),
the nix app copies it and sets KOMOREBI_E2E_BAR_SCHEMA to the checked-in
schema (the e2e directory alone does not contain it), build-ref.sh builds it.
README and docs/dev/bar-e2e.md document it.
Verified: py_compile and ruff clean; the runner and library unit tests: 157
passed; flake.nix parses. NOT run: the scenario itself (the Windows guest is
reserved for gates), so every check is untested against a real bar and may
need adjusting (window title, timings, the Systray widget inside the VM).
Needs gate: win-e2e `bar`.
Records the HideSpot change and the three macOS layers (container, workspace,
monitor) as done, lists the functions that stay in the macOS extension traits,
and notes that ContainerExt is the old container code kept for the daemon's own
call sites.
Verified: documentation only.
Section 9 of docs/dev/settings.md: what S3 moved, how apply_globals and the
command write it, how readers get it, and that the only BEHAVIOUR is the
single publish.
Verified: not applicable (documentation).
docs/dev/core-model.md section 16: update_shared and where it departs from
section 12, and what macOS's update keeps (SkyLight batch, no maximized
branch, a separate scrolling path, an unconditional clamp, no stackbar) as
three decisions for its owner. Section 17: process_command arm by arm (110
of 155 arms in all three daemons identical, 45 not, with the clusters of
the differing ones), process_event (only three of the events exist on all
three, so it is the thin-events step), and window_manager function by
function (39 identical in all three, 48 differing, the one-sided lists,
the 20 functions that are identical in KDE and macOS and old only in
Windows), with the order and the hooks of the remaining layers of step 9.
Verified: not applicable (documentation); the counts come from the arm and
function comparison of the three trees at this tip.
Step 11(iii), the Monitor layer. BEHAVIOUR (type-checked and clippy-clean with the
fake CC; the macOS tests are the oracle and have not run).
pub type Monitor = komorebi_core::Monitor<MacosPlatform>. MacosPlatform
implements MonitorPlatform: the id is the u32 display id written as a number, the
serial number a String, and the extra fields MacosMonitorExtra {
window_hiding_position}, flattened into the monitor's JSON (the key was already
the last of the macOS monitor, so the text is the same; a test pins the keys and
their order). The shared monitor gives focus_workspace, add_container,
remove_workspace_by_idx, remove_workspaces, ensure_workspace_count,
new_workspace_idx and the two name/layout lookups (nine functions that were the
same text or differed only in their types). The rest stays macOS's as
MonitorExt: new, placeholder, update_focused_workspace, update_workspace_globals,
update_workspaces_globals, update_all_workspace_globals, load_focused_workspace,
hide_unfocused_workspaces, restore_focused_workspace, move_container_to_workspace
(and its '1' variant) and add_container_with_direction, because they differ from
the KDE ones in what they do (SkyLight batches, the corner, the animator). The
monitor writes its corner into each workspace's state when it updates the
globals. From<&Monitor> for MonitorConfig, an orphan impl now, is the function
monitor_config. Monitor::new and Monitor::placeholder are called through the
MonitorExt trait.
No silent bind: no MonitorExt method shares a name with a method of the shared
monitor (checked: empty intersection); the shared and the old text differ only
in types, and a call of an old signature does not compile.
Verified: fake-CC cargo clippy --locked --target aarch64-apple-darwin -p
komorebi-macos --all-targets -D warnings clean; tools/fast-check macOS PASS.
Not verified: any macOS test or run. Verified on the Mini: not yet. Gate: the
Mini's cargo test, then the hand check row in docs/dev/macos-handcheck.md.
Plan step 10, S3. The two default paddings, the aspect ratio of a window
toggled to floating (centre with resize), layout_defaults and
display_index_preferences are no longer statics: their writers
(apply_globals through SharedDelta::apply, the DisplayIndexPreference
command) and all readers use settings() together. The semantics are the
statics': a missing padding or display_index_preferences key keeps the
value, a missing layout_defaults clears it, a missing aspect ratio is
Standard (not the initial Widescreen), and the command inserts one entry.
Readers get an owned copy from one snapshot (accessors in settings.rs).
BEHAVIOUR only in this: apply_globals used to store these keys one at a
time and now publishes the group in one update, so a thread can no longer see
a half-applied reload of them. Value semantics are pinned by
crates/komorebi-settings tests/reload.rs and shared.rs (reload merges, a
few keys reset, a load never touches groups it does not name).
Windows: window.rs, windows_api.rs, workspace.rs, monitor.rs,
monitor_reconciliator, stackbar_manager (the gap reads the container padding
from settings), state.rs, static_config.rs, process_command.rs, lib.rs.
MONITOR_INDEX_PREFERENCES stays a static (Windows only).
Verified: Windows clippy -D warnings --locked --all-targets on
komorebi-windows (cross): clean. e2e not run: config_reload, layouts,
workspaces, stackbars (container padding gap) are needed.
BEHAVIOUR (Windows runtime): the whole layout pass of the daemon now runs
through the shared skeleton. By construction the calls are the same, in
the same order; marked because it was only type-checked.
WorkspaceExt::update is now `self.update_shared()`, and WindowsPlatform
implements the hooks with the bodies the old update had inline:
begin_update skips until INITIAL_CONFIGURATION_LOADED and otherwise holds
the AnimationBatch (boxed, dropped when the update ends);
containers_pruned is the default (Windows does not move the focus there);
place_monocle is set_position with the top flag; maximize_in_layout is the
window's maximize; adopt_widths_before_layout is animations on and no live
resize step, adopt_actual_widths the scrolling glue's;
scrolling_layouts rests the strip against the monitor rectangles of
WindowsApi; adjust_layout adds the border padding then the stackbar height
for a container with more than one window; place_container is the loop over
the windows of the container (title-bar rules for the front window,
unminimize after toggle-maximize, hide when the container rests past an
edge, else restore when should_restore_when_placed, then set_rect);
end_update asks for one more pass when animations are off, the workspace
scrolls and a window refused its width, as the pass loop did. One
difference of shape, not of effect: the title-bar rule tables
(REMOVE_TITLEBARS, NO_TITLEBAR, REGEX_IDENTIFIERS) are read in
place_container, once per container, and no longer once per pass.
Windows e2e scenarios needed: layouts (every default layout, rules and
flips), stackbars (stack heights, title bars), scrolling (animations on and
off, the second pass, live resize), resize, borders, animation, workspaces
(show/hide on switch), commands, monocle and maximize, and the komorebic
state comparison to the kfw fixtures.
Verified: Windows clippy --locked --all-targets -D warnings: PASS. Not
run: Windows tests, e2e.
Step 11(iii), the Workspace layer. BEHAVIOUR (type-checked and clippy-clean with
the fake CC; the macOS tests are the oracle and have not run).
pub type Workspace = komorebi_core::Workspace<MacosPlatform>. About 53 of the
65 functions of the macOS workspace.rs are the shared ones now (identical, or
differing only in the id type, the Container type or a hook; section 14 of
core-model.md lists them). MacosPlatform implements WorkspacePlatform:
foreground_window_id and cursor_position (the same MacosApi calls),
should_act_window(_individual) (the macOS should_act with the class as a
one-element slice), the three scrolling hooks, after_floating_removed
(focus_previous_floating_window, which is shared), and the two hide spots:
hide_spot is the monitor's corner with no display (what every Window::hide
call had), hide_spot_on_display adds the display bounds from the work area
(what Workspace::hide gave the containers). The corner is MacosWorkspaceState,
which the monitor sets next to the globals (it was a field of the macOS
WorkspaceGlobals, which is now the shared type; both are serde-skipped, so
the JSON is the same). The eight functions that took the hiding position
(hide, move_window_to_container, new_floating_window,
new_container_for_focused_window, new_container_for_window,
reintegrate_monocle_container, new_monocle_container, focus_container_by_window)
are the shared ones with the spot argument; the ones that stay macOS's are
WorkspaceExt (update, apply_wallpaper, restore, remove_window,
load_static_config, prune_duplicate_windows, visible_window_details,
reap_invalid_windows_for_application, restore_windows, calculate_layouts,
set_scrolling_columns) and, in scrolling.rs, the former inherent methods now
the trait WorkspaceScrolling (an inherent impl on a type of another crate is not
allowed). The Window implements WorkspaceWindow (class is its role, path a
String). From<&Workspace> for WorkspaceConfig, an orphan impl now, is the
function workspace_config. The shared Workspace has one field macOS lacked
(initial_window_placement_rules, None and skipped when None), so the JSON
has the same keys: new test state_pin pins them.
Behaviour that is deliberately kept: floating windows are hidden at the corner
with no display (hide_spot), containers with the display; ContainerWindow's
hide and restore skip their work under cfg!(test), as the old container code did.
No silent bind: the names of the methods of WorkspaceExt, WorkspaceScrolling
and ContainerExt share no name with a method of the shared Workspace
(checked mechanically: the intersection with the core inherent names is only
Container::restore, a different type); and each of the shared functions that
used to take the corner has a different signature from the macOS one it
replaced, so a stale call is a compile error, which was the order in which the
errors showed up.
Verified: fake-CC cargo clippy --locked --target aarch64-apple-darwin -p
komorebi-macos --all-targets -D warnings clean; tools/fast-check macOS PASS.
Not verified: any macOS test or run. Verified on the Mini: not yet. Gate: the
Mini's cargo test, then the hand check added to docs/dev/macos-handcheck.md.
Plan step 10, S3. The two default paddings, the aspect ratio of a window
toggled to floating (centre with resize), layout_defaults and
display_index_preferences are no longer statics: their writers
(apply_globals through SharedDelta::apply, the DisplayIndexPreference
command) and all readers use settings() together. The semantics are the
statics': a missing padding or display_index_preferences key keeps the
value, a missing layout_defaults clears it, a missing aspect ratio is
Standard (not the initial Widescreen), and the command inserts one entry.
Readers get an owned copy from one snapshot (accessors in settings.rs).
BEHAVIOUR only in this: apply_globals used to store these keys one at a
time and now publishes the group in one update, so a thread can no longer see
a half-applied reload of them. Value semantics are pinned by
crates/komorebi-settings tests/reload.rs and shared.rs (reload merges, a
few keys reset, a load never touches groups it does not name).
macOS: window.rs, monitor.rs, monitor_reconciliator.rs, macos_api.rs,
state.rs, static_config.rs, process_command.rs, lib.rs. rustfmt also
reflowed a few unrelated lines in window.rs and process_command.rs.
Verified: macOS fake-CC cargo check --locked --target aarch64-apple-darwin
-p komorebi-macos --all-targets: 0 errors, 0 warnings from these files.
Nothing run on a Mac.
WorkspaceExt::update is now `self.update_shared()`, and KdePlatform
implements the hooks of the update skeleton with the bodies the old update
had inline, in the same order: begin_update prunes duplicate windows;
containers_pruned keeps the focus on a container that exists;
place_monocle and maximize_in_layout are set_position and the compositor's
maximize; adopt_widths_before_layout is has_settled and no live resize;
scrolling_layouts asks the backend for the monitors and rests the strip;
adjust_layout clamps a non-scrolling tile to the work area then adds the
border padding; place_container hides a resting container, restores the
front when it was hidden, and per window centres (the percentage override)
or sets the position, logging a failed set_position; end_update records
scrolling_placed. The closure that wrapped the placement had nothing between
it and its ?, so it goes. No behaviour change.
Verified: KDE cargo test -p komorebi-linux --no-fail-fast (native, lite
env, its layout, scrolling and workspace tests included): 672 passed, 0
failed. Not run: VM suites.
The macOS corner is a property of the monitor that the workspace is on, which
the monitor gives the workspace when it updates its globals; macOS has
no field for it in the shared WorkspaceGlobals and keeps it in its
WorkspacePlatform::State, so the hooks need the workspace. hide_spot(&Workspace<P>)
and hide_spot_on_display(&Workspace<P>), called with self where they were called with
&self.globals; KDE and Windows do not override them (they return (), the
default), so nothing changes there; the test stub's two overrides follow.
Verified: native cargo test -p komorebi-core 26 passed; fast-check Windows
clippy -D warnings, KDE check + lib tests and macOS fake-CC type-check PASS. Not
verified: any run. Gates: none beyond the previous revision's (this changes no
call a daemon makes).
Plan step 10, S3. The two default paddings, the aspect ratio of a window
toggled to floating (centre with resize), layout_defaults and
display_index_preferences are no longer statics: their writers
(apply_globals through SharedDelta::apply, the DisplayIndexPreference
command) and all readers use settings() together. The semantics are the
statics': a missing padding or display_index_preferences key keeps the
value, a missing layout_defaults clears it, a missing aspect ratio is
Standard (not the initial Widescreen), and the command inserts one entry.
Readers get an owned copy from one snapshot (accessors in settings.rs).
BEHAVIOUR only in this: apply_globals used to store these keys one at a
time and now publishes the group in one update, so a thread can no longer see
a half-applied reload of them. Value semantics are pinned by
crates/komorebi-settings tests/reload.rs and shared.rs (reload merges, a
few keys reset, a load never touches groups it does not name).
KDE: window.rs, desktop.rs, monitor.rs, monitor_reconciliator.rs, state.rs,
static_config.rs, process_command.rs, lib.rs and tests/config.rs (the test
harness resets the paddings through settings()).
Verified: KDE cargo test --locked -p komorebi-linux --lib --tests (lite env,
own target dir): 63 test binaries, 631 passed, 0 failed, including
tests/config.rs that loads configurations over a fake desktop. KDE VM suites
not run (gate: config reload and workspace padding suites).
The shared half of update (core-model.md section 12) as
Workspace::update_shared: begin_update (Flow::Skip, or a guard held until
the end), the empty containers dropped and containers_pruned, the work
area, offsets and paddings, the layout and behaviour rules, then the tile
branch: place_monocle, maximize_in_layout, adopt_widths_before_layout and
adopt_actual_widths for a scrolling workspace, the layout (calculate, or
scrolling_layouts with the containers that rest hidden), adjust_layout and
place_container per container, latest_layout, and end_update, which can ask
for one more pass (Pass::Again), and the resize_dimensions resize. The
text is KDE's with the Windows pass loop in it; the hooks are the parts the
two daemons do differently. It is not called update so that a daemon keeps
its own update (macOS's, which has another shape, stays as it is); every
hook has a default so that MacosPlatform compiles unchanged, and the
defaults of the ones a daemon must provide fail. Placement carries what the
hooks of a pass need (the work areas, the paddings, the border, whether the
workspace scrolls, whether it has resize adjustments, whether a window is
maximized). Flow and Pass are the answers of begin_update and end_update.
A test with a stub platform pins the call order and the second pass.
Verified: cargo test -p komorebi-core --features schemars (native): 29
passed, 0 failed; Windows clippy --locked --all-targets -D warnings: PASS
(compiles the crate). Not run: the daemons (nothing calls update_shared
yet).
BEHAVIOUR for the shared signature; none intended on KDE or Windows. Needs
the Windows and KDE gates below.
ContainerWindow gets 'type HideSpot: Copy + Default' (() on KDE and Windows;
on macOS the monitor's corner and, when known, the display bounds) and its
four operations return eyre::Result: hide_window(spot), restore_window(),
hide_stacked(spot), restore_stacked(). Container::hide(omit, spot),
restore(), load_focused_window(spot) and add_window(window, spot) return
Result (hide logs a window that cannot be hidden and goes on, as macOS does;
the others propagate). WorkspacePlatform gets two hooks with a default of
Default::default(): hide_spot(&globals) (the spot of a window) and
hide_spot_on_display(&globals) (the containers of a workspace that is being
hidden; macOS passes the display there and not for its floating windows).
The eight shared workspace functions that hide (focus_container_by_window,
new_container_for_window, move_window_to_container,
new_container_for_focused_window, new_monocle_container,
reintegrate_monocle_container, new_container_for_floating_window,
new_floating_window) and hide take the spot from the hook; new_container_for_window
and hide now return Result.
KDE and Windows: their Window impls return Ok(()) and take (); about 90 call
sites pass () and drop the Ok (let _ = ...) or use ?; nothing is hidden or
restored with a different argument than before: the same backend().hide /
restore, hide_with_border(false) and restore_with_border(false) calls run in
the same order, the Result is always Ok. macOS: the Window impl hides with
hide_on(spot.position, spot.screen) and restores, skipping both under
cfg!(test) as the old container code did; its ContainerExt is unchanged until
the Workspace layer.
No silent bind: every shared function whose behaviour depends on the spot
changed its signature (an added argument, or a Result where there was
none), so any caller written for the old form is a compile error in all
three trees (the six unused-Result warnings it produced on each of KDE and
Windows were each fixed by hand-checked 'let _ ='). The four macOS-only
extension methods (add_window_at, load_focused_window_at, hide_on,
restore_focused_window) share no name with a core inherent method (checked: the
intersection of the two name lists is empty), so no inherent method can shadow one.
Core tests: a stub window records the spot it is given; new tests pin that
a floating window is hidden at hide_spot, the containers of a hidden workspace
at hide_spot_on_display, and that stacking passes hide_spot through
add_window and load_focused_window; the container tests assert the spot.
Verified: native cargo test -p komorebi-core 26 passed; fast-check: Windows
clippy --all-targets -D warnings PASS, KDE check + lib tests PASS, macOS fake-CC
type-check PASS; KDE cargo test -p komorebi-linux --no-fail-fast 672 passed, 0
failed. Not verified: Windows and macOS tests, any run. Verified on the Mini:
not yet.
Gates for the coordinator: Windows e2e: workspaces, stacking, monocle (the
commands scenario), floating (toggle-float), lifecycle, resize, scrolling,
borders; KDE VM fast tier plus the stack, workspace, monocle and float
scenarios. These exercise exactly the hide/restore/stack paths touched.
Adds crates/komorebi-settings/tests/reload.rs: a load applied through a
Handle merges what the file gives, resets layout_defaults and the aspect
ratio when absent (the first load without the key turns the initial
Widescreen into Standard, as the statics do), keeps paddings and display
index preferences, never touches groups it does not name, and a reader that
holds the earlier snapshot still sees the earlier load whole. Test only.
Verified: native cargo test -p komorebi-settings: 18 unit + 3 integration
passed.
Records the result of the Container move and why Workspace<MacosPlatform> is
not started: eight shared workspace functions call the no-argument infallible
hide forms while macOS passes the monitor's corner and display and
propagates the error; four of them have the same signature as macOS's own, so
the switch would bind the shared body silently (a behaviour change that the
type-check cannot see). Proposes the core change (an associated HideSpot type
on ContainerWindow, () on KDE and Windows, taken from the workspace
globals, and a fallible result) and lists the small mismatches that also
come first (path as String, class, should_act's class slice, the scrolling
hooks).
Verified: documentation only.
New module settings.rs: WindowsSettings (empty platform part) and
settings(), a LazyLock<Handle<WindowsSettings>> starting from
Shared::defaults_for(PlatformDefaults::WINDOWS). Nothing reads it yet, so
nothing changes at runtime. A test pins that the starting settings equal the
starting values of the statics (paddings, resize delta, mouse follows focus,
border enabled/width/offset, six colours; Windows has no border radius
static). Plan step 10, S2. Pure refactor.
Verified: Windows clippy -D warnings --locked --all-targets on
komorebi-windows (cross, compiles the pin test; it cannot run on this host).
The pin test was not run. e2e not run.
Step 11(iii), the Container layer. BEHAVIOUR (type-checked and unit-test
oracle only; macOS cannot run here).
pub type Container = komorebi_core::Container<Window>, with ContainerWindow
implemented for the macOS Window (id is the CGWindowID, exe_name its exe;
hide_window and restore_window use the default corner and log a failure,
because the daemon does not hide through them). The container's own struct,
accessors, preselect, idx_from_exe, contains_window, idx_for_window,
remove_window_by_idx, remove_focused_window and focus_window are now the
shared ones. The four functions that carry the monitor's WindowHidingPosition
(and for hide the display bounds), return a Result and skip the work under
cfg!(test) stay macOS's own, as ContainerExt, under names the shared type
does not use: add_window_at, load_focused_window_at, hide_on and
restore_focused_window (an inherent method of the shared type would shadow an
extension method of the same name); about 35 call sites follow. The shared
Container reads a missing 'locked' as false (macOS used to require it).
komorebi-core is a dependency of the macOS daemon (and its schemars feature).
State JSON: two new tests pin the shape ({id, locked, windows: {elements,
focused}}) and the missing-locked read; the existing round-trip test is kept.
They can only run on a Mac.
Verified: fake-CC type-check (tools/fast-check) PASS on all targets; Windows
clippy, KDE check and tests unaffected PASS. Not verified: any run. Verified
on the Mini: not yet. Gate: the Mini's cargo test; then the hand check added to
docs/dev/macos-handcheck.md (stacks, workspaces, state JSON, per-monitor
hiding corner).
DEFAULT_RESIZE_DELTA, DEFAULT_MOUSE_FOLLOWS_FOCUS and the initial values of
DEFAULT_WORKSPACE_PADDING and DEFAULT_CONTAINER_PADDING now come from
komorebi_settings::shared (PlatformDefaults::WINDOWS: 10, 10; 50; true); the
statics keep their names and types, so no reader changes. Adds the
komorebi-settings dependency edge to komorebi-windows (Cargo.lock edge only;
the crate is already a workspace member). Plan step 10, S1. Pure refactor.
Verified: Windows clippy -D warnings --all-targets on komorebi-windows
(dev shell, cross, target-fast-win): clean. e2e not run; tools/e2e-needed
will report the crate as an input of komorebi.exe, behaviour is unchanged.
None of them mutated the window; they read the element and write through
Accessibility, so &mut self only forced callers to hold a mutable binding.
The shared ContainerWindow operations take &self (a stack member is hidden
through a shared reference), so the macOS impl needs these as &self. Six
callers lose a now-unneeded mut. No behaviour change.
Verified: macOS fake-CC type-check (tools/fast-check) PASS with no new
warning. Verified on the Mini: not yet.
Merge of wip-u-core (kfde: komorebi-core with Container, Workspace<P>, Monitor<P>) and wip-u-pkg-mig3.
Conflicts and how they were resolved:
- komorebi-for-kde/Cargo.toml, komorebi-for-kde/Cargo.lock: deleted, as in the migration (one root
workspace); kfde's only edit there, the komorebi-core workspace dependency, is applied as a path
dependency in the crate manifest.
- komorebi-for-kde/komorebi/Cargo.toml: the migration's manifest (inlined dependencies, package name
komorebi-linux, [lib]/[[bin]]) plus kfde's komorebi-core dependency and the komorebi-core/schemars
feature.
- Cargo.lock: the migration's lock re-resolved with cargo metadata --offline: the only change is the
new komorebi-core 0.1.42 entry; no version of any other package moved (compared by name/version with
both parents; kfde's Windows lock differs only by the komorebi -> komorebi-windows rename).
- komorebi-bar/src/widgets/komorebi.rs: deleted (the Windows bar moved to komorebi-bar/src/windows/);
kfde's one-line edit (title: value.title() instead of value.title().ok()) applied to
komorebi-bar/src/windows/widgets/komorebi.rs.
- komorebi-for-kde/nix/root-crates.json: crates/komorebi-core added (tools/kde-closure-check now ok with
11 root crates).
Gates run: tools/fast-check --since wip-u-pkg-mig3: native tests, Windows clippy -D warnings, KDE check+lib+schemas, macOS fake-CC check, nix evaluation (root and both nested flakes) PASS; KDE cargo test -p komorebi-linux -p komorebic -p komorebi-client -p komorebi-hotkeys -p komorebi-core: 902 passed, 0 failed; CLI docs oracle identical x3; bar schema harness byte-identical for both bars (its Window::title stub now returns Option); bar-core tests 3+6; kde-closure-check ok; by evaluation an edit of the Windows bar or daemon leaves the kfde derivations unchanged, a KDE edit leaves Windows unchanged, and an edit of crates/komorebi-core changes Windows and kfde but not macOS. Not run: nix builds, VM, e2e (the Windows daemon changed: kfde's e2e gate applies), any Mac run.
Classifies every function of komorebi-for-mac/komorebi/src/{container,
workspace,monitor}.rs against komorebi-core (or the KDE extension traits):
container 5 identical, 6 text-only, 4 real (the hiding functions); workspace
11 identical, 26 text-only, 8 differing only through hooks that exist, 8
differing by the hiding position, 4 real, 3 text in the extension trait, 5
macOS-only; monitor 5 identical, 4 text-only, 8 in the extension trait, 4
macOS-only. States the one difference that decides the order: the hiding
position and the fallible hide.
Verified: counts from a function-by-function diff run on this host (stripped
text, namesake against namesake); documentation only. Not verified: that the
text-only functions behave identically on a Mac, which the macOS tests will
show once they run on the shared code.
On the Mac Mini native clippy -D warnings failed in the macOS bar module with three findings that the fake-CC
check only printed as warnings: the re-exports of ICONS_CACHE and ImageIconId in macos/widgets/mod.rs are
unused (they were pub items of the crate root before the module move, which the lint does not flag),
and Stroke::new(1.0, ...) in macos/widgets/komorebi.rs:393 falls back to f32. The two re-exports are removed
(grep: nothing in the macOS module uses them; the Windows module does and keeps them), the literal is
1.0_f32 (the type it already had). The same sweep removed the #[allow(unused_imports)] re-export of
rgba_to_color_image, also unused on macOS. Remaining pub use lines (render, selected_frame, ui, statics,
Alignment) are used. Behaviour identical.
Gates run: macOS fake-CC check of komorebi-bar and komorebi-bar-macos --all-targets: no warning left in the
bar; Windows clippy -D warnings PASS; S/bar2 schemas byte-identical. Not run: native macOS clippy (the Mini).
Step 11(iii) prerequisite. The macOS Window holds retained Accessibility
references and cannot be Copy, so the bound of ContainerWindow is Clone.
KDE and Windows windows are still Copy; they satisfy it unchanged. The seven
places in komorebi-core/src/workspace.rs that copied a window out of a
borrowed Option (if let Some(window) = self.maximized_window) borrow it
instead (&self.maximized_window), and two places clone where the old text
used the value twice (new_maximized_window, new_maximized_window's
unmaximize hook). No behaviour change on KDE or Windows: a clone of a Copy
type is the copy.
Verified: native cargo test -p komorebi-core, fast-check: Windows clippy
--all-targets -D warnings PASS, KDE check + lib tests PASS (291 passed), macOS
fake-CC type-check PASS. Not run: Windows e2e (needed only if you want a
second look: no Windows window type changed).
wip-u-kfm carries the macOS clippy fixes (&Path, drain, filter, slice fill, sort_by_key, collapsible_match allows, capture_spike, bar stroke literals). The bar stroke literal revision touches komorebi-for-mac/komorebi-bar, which the migration moved to komorebi-bar/src/macos; conflicts are resolved here by applying the same edit at the new paths.
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.
Six literals in the macOS bar (applications.rs, komorebi.rs x3, komorebi_layout.rs,
time.rs) passed 1.0 to egui's Stroke::new, which rustc 1.99 reports as an f32
fallback (a future hard error). The Windows bar and komorebi-bar-core
already say 1.0_f32 for the same calls, so this makes the macOS bar text
match them; the value is the same f32. No behaviour change.
Verified: fake-CC cargo clippy --locked --target aarch64-apple-darwin
--workspace --all-targets -D warnings: clean (whole workspace). The Windows
bar and komorebi-bar-core are untouched. Verified on the Mini: not yet.
examples/capture_spike.rs iterated bytes.chunks_exact(4), which clippy flags
as chunks_exact_with_const_size on 1.93 and as chunks_exact_to_as_chunks on
1.99 (the allow name differs between the two, so an allow cannot serve
both). chunks(4).filter(len == 4) visits the same full pixels in the same
order and still drops a trailing partial one; the spike is an example
binary, not the daemon. No behaviour change.
Verified: fake-CC cargo clippy --locked --target aarch64-apple-darwin
--workspace --all-targets -D warnings: clean (whole workspace). Verified on
the Mini: not yet.
docs/dev/core-model.md section 13: what Monitor<P> holds and what stays in
MonitorExt, why the two globals updates are not shared (KDE's plural one
has no border), the serde facts the tests pin (flattened map keys, the
schema order) and the hooks of promote_container, effective_layout_options,
new_floating_window and hide.
Verified: not applicable (documentation).
BEHAVIOUR (Windows runtime): the type of every monitor changes and the
order of the keys of its JSON with it; the content does not. Marked
because it was only type-checked and a unit test of the crate.
komorebi/src/monitor.rs no longer defines Monitor: `type Monitor =
komorebi_core::Monitor<WindowsPlatform>`. WindowsPlatform implements
MonitorPlatform: the id stays a MonitorId written as the same plain
number (the existing hmonitor_json functions), the serial number an
Option<String> (missing when read is None, as before), the fields only
Windows has are WindowsMonitorExtra {name, device_id, workspace_names},
flattened into the JSON, and known_workspace_name reads the names from
them. The three fields are monitor.extra.name, .device_id and
.workspace_names at the 56 places that read or write them (the compiler
found each), and the Monitor literals build an extra. The eight functions
that moved are gone; every other method of the old impl is a method of
MonitorExt, unchanged in body, which each caller imports.
static_config::monitor_config(&Monitor) replaces the From<&Monitor> impl
for MonitorConfig (orphan rule).
Two things the review of this revision found: a flattened map is read
through a buffer that keeps its keys as strings, so workspace_names has a
deserialize_with that parses them (a unit test in the crate pins this with
the same adapter); and the keys of the monitor's JSON now come in this
order: the common fields, then name, device_id and workspace_names. The
state fixtures and the pins compare parsed JSON, so they are unaffected;
a tool that read the text of komorebic state by position would see the
change.
Windows e2e scenarios needed: tiled, stack and float state scenarios with
komorebic state compared to e2e/lib/tests/fixtures/kfw-*.json (the
komorebi-client and the bar parse it), monitor and display tests (hot
plug, monitor reconciliator, work area changes), named workspaces
(workspace-name, ensure-named-workspaces, monitor-index-preference),
move to monitor, and the schema checks.
Verified: Windows clippy --workspace --locked --all-targets -D warnings:
PASS; komorebi-core tests PASS. Not run: Windows tests, e2e.
komorebi-for-kde/komorebi/src/monitor.rs no longer defines Monitor:
`type Monitor = komorebi_core::Monitor<KdePlatform>`. KdePlatform
implements MonitorPlatform: the id is a u32 written as a number, the
serial number a String, no extra fields (NoExtra), the schema of the id
u32. The eight functions that moved are gone; every other method of the
old impl (the constructors new and placeholder, load_focused_workspace,
update_workspace_globals, update_workspaces_globals,
update_focused_workspace, move_container_to_workspace,
add_container_with_direction, assign_windows_to_workspaces) is a method
of the extension trait MonitorExt, unchanged in body, which each caller
imports. Monitor literals get extra: Default::default().
static_config::monitor_config(&Monitor) replaces the From<&Monitor> impl
for MonitorConfig (orphan rule). The JSON pins of Monitor and
MonitorInformation in monitor.rs pass unchanged. No behaviour change.
Verified: KDE cargo test --workspace --no-fail-fast (native, lite env):
875 passed, 0 failed. Not run: VM suites.
komorebi-core gets Monitor<P: MonitorPlatform> (MonitorPlatform extends
WorkspacePlatform), moved from komorebi-for-kde/komorebi/src/monitor.rs
and made generic: the id and serial_number_id are the platform's
associated types (Id with serialize_id/deserialize_id hooks so each daemon
keeps writing its id as the plain number it always did; SerialNumber), and
the fields only a platform has are P::Extra, flattened into the monitor's
JSON (NoExtra for a platform with none). The schema is named Monitor.
The workspace accessors (impl_ring_elements! is not generic) are written
out, and eight functions move with them: focused_workspace_layout,
focused_workspace_name, new_workspace_idx, ensure_workspace_count,
remove_workspaces, remove_workspace_by_idx, add_container and
focus_workspace, the last taking the name the platform last knew for a
new workspace from the known_workspace_name hook (Windows keeps its
names in the extra fields). WorkspacePlatform now needs Clone, Debug and
PartialEq of the platform marker and ContainerWindow needs Debug, because
the derives on the generic structs ask for them.
One serde fact the tests pin: a flattened map is read through a buffer
that keeps its keys as strings, so an extra field with integer keys (the
Windows workspace_names) needs a deserialize_with that parses them; the
Windows converge must carry it.
Tests with stub platforms: the JSON of a monitor with flat extra fields
and the id as a number, a monitor without extra fields, the name hook of
focus_workspace, the schema name and flat properties.
Verified: cargo test -p komorebi-core --features schemars (native): 27
passed, 0 failed; Windows clippy --workspace --locked --all-targets
-D warnings: PASS. Not run: the daemons.
BEHAVIOUR (Windows runtime, none expected; type-checked only): the four
methods are removed from WorkspaceExt and the workspace gets the crate's.
WindowsPlatform implements primary_container_idx (0 for the default
layouts, the primary column of a custom layout, failing as before when it
has none) and keeps the default unmaximize_window and
before_hide_containers, which do nothing, as these methods did nothing
here. effective_layout_options' Custom arm is the crate's fallback arm.
Windows e2e scenarios: promote (promote-focus, promote-window) on default
and custom layouts, float a stacked, monocle and maximized window
(toggle-float), hide on workspace switch (workspaces sweep), layout
options (layout_options and layout_options_rules).
Verified: Windows clippy --workspace --locked --all-targets -D warnings:
PASS. Not run: Windows tests, e2e.
The four methods are removed from WorkspaceExt; the workspace gets them
from the crate. KdePlatform implements the two hooks KDE needs:
unmaximize_window (Window::unmaximize) and before_hide_containers (the
scrolling prelude that hide had inline, with the resize adjustments
returned for the crate to put back). Same behaviour: the bodies are the
ones that moved, in the same order. No behaviour change.
Verified: KDE cargo test --workspace --no-fail-fast (native, lite env):
875 passed, 0 failed (the workspace tests that exercise hide,
promote and new_floating_window included). Not run: VM suites.