ARCHITECTURE-UNIFIED.md: the status line, section 1 (a table of where each platform is now), the package paragraph (shims gone, version crate), the oracle paths (schemas/*, docs/*/cli), section 6 (now "done / still open" instead of "what is still the old layout"), the tier-0 row, and the helper tools paragraph (tools/ is committed now). MIGRATION-SUMMARY.md: status line, the three-trees paragraph, the oracle paths, the known issues paragraph, and section 7, which was "Cutover list (not done)" and is now a table of what the cutover stack did and what is open; item 7 of "How to continue". unified-binaries.md: the status banner (with a map from the old directory names to the new ones), the Mac Mini commands (the shim line removed, schema.bar.json under schemas/macos, `komorebi 0.2.0`), and "What is left for the cutover" is now a done / still open list. Still open, as stated in the three: the one flake (flake-one is writing it), CI and release workflows (the old ones are archived in docs/dev/old-ci), the site and repository URLs, COMPARE.md. The 0.1.42 / 0.1.0 / 0.1.1 numbers and the old directory names that stay in narrative sections are history and are marked as such. Not touched: build-and-test.md and flake.md (owned by flake-one). Verified: the relative links of the three files resolve. The statements about what the stack did are taken from the revision descriptions of wip-cutover, not re-measured.
20 KiB
The consolidation of komorebi, komorebi-for-kde and komorebi-for-mac: final-state summary
Status (written at wip-cand8, b6f4c2ae, 2026-10-31; sections 1, 6, 7 and 8 were brought up to date by the cutover stack, wip-cutover, and say so; everything else is as written then, with the paths and version numbers of that time): a summary for a maintainer who was not there.
Gates for candidate 8 were still running when this was written; the results stated below are the ones
that had finished, with the candidate they ran on. Companion documents: ARCHITECTURE-UNIFIED.md
(the map), build-and-test.md (commands, flakes), macos-hand-check-plan.md
(what the owner must run on a Mac), known-issues.md (open decisions).
1. What the repository is now
Three programs (Windows, KDE Plasma, macOS) were three repositories with copies of the same ideas. They are
one repository with one Cargo workspace (one Cargo.lock), shared crates under crates/, and one package
per user-facing program, the platform chosen by cfg(target_os).
| Package (binary) | Directory | How the platform is chosen |
|---|---|---|
komorebi |
komorebi-daemon/ |
main.rs is a cfg_select! calling daemon::run() of komorebi-windows, komorebi-linux or komorebi-macos, which are target-gated dependencies |
komorebic |
komorebic/ |
src/windows.rs, src/linux/, src/macos/; the arguments are in crates/komorebic-core |
komorebi-client |
komorebi-client/ |
src/{windows,linux,macos}.rs behind one Connection |
komorebi-bar |
komorebi-bar/ |
src/windows/, src/macos/; shared part in crates/komorebi-bar-core. KDE has no bar |
Three platform layers remain, because the platform layers are different code, each in its own directory
since the cutover stack: komorebi-windows/ (Windows; it was komorebi/), komorebi-linux/ and
komorebi-hotkeys/ (KDE; with nix/linux/, the NixOS module and the VM suites, and docs/linux/) and
komorebi-macos/ (macOS; with nix/macos/, the nix-darwin module, and docs/macos/). The trees
komorebi-for-kde/ and komorebi-for-mac/ of the first version of this summary no longer exist. The daemon
libraries have no binary; the komorebi package is the binary. The crate map and the tree layout are in
ARCHITECTURE-UNIFIED.md; how to build each is in build-and-test.md.
2. What was shared, and how
| Crate | What it holds | How it was moved |
|---|---|---|
komorebi-collections |
Ring, LockableSequence |
the KDE copy was canonical; Windows and macOS switched to it |
komorebi-protocol |
SocketMessage, payloads, state types |
one enum for all platforms (guard arms for platform-only messages); the wire format is pinned by per-platform goldens generated from the three daemons before the move |
komorebi-config |
StaticConfig and the komorebi.json types, schema generation, and the one window-rule matcher (komorebi_config::rules: should_act, should_act_individual; see rule-matching.md) |
one union type with per-platform schema views; the matcher replaced three copies, each daemon keeps a thin wrapper with its own signature |
komorebi-transport |
the command listener, pause filter, subscriber registry, EventChannel |
the daemons take the window manager lock; the crate does not name a mutex |
komorebi-backend |
the Backend trait (what a daemon asks of its platform), plain-data infos, FakeBackend, contract tests, the id Registry |
derived from the evidence in seam.md; Windows goes through it for the clusters in backend-switch.md; KDE runs on it; macOS has an adapter the daemon does not use yet |
komorebi-core |
the model, generic over the platform window: Container<W>, Workspace<P> (with update_shared, a skeleton with hooks), Monitor<P>, WorkspaceGlobals, and command (the shared commands over a CommandHost: workspace navigation, moves across workspaces and monitors, lock and unlock, focus-workspace with the pointer preamble) |
converge in place, then pure move, then converge per daemon; hooks (WorkspacePlatform, MonitorPlatform, HideSpot) carry what differs; details in core-model.md |
komorebi-settings |
the run-time settings as one immutable snapshot replaced as a whole, instead of process-wide statics (paddings, aspect ratio, layout defaults, display preferences, animation, transparency, window-handling behaviour) | see settings.md |
komorebi-bar-core |
bar configuration, widgets, platform seam, schema views | bar-unification.md |
komorebic-core |
the command line of every platform (clap definitions, docgen), platform enums | the generated docs/cli pages were the oracle |
The state JSON each daemon prints is unchanged by the shared Monitor<P>/Workspace<P> (platform extras are
flattened in, pinned by tests). Native tests of the shared code run on Linux and protect all three daemons
(e2e-to-unit.md says which e2e checks became native).
3. What intentionally stays per platform, and why
- The window manager, the command router and event processing. About 26-37% of the text is shared and the
rest is real behaviour (Win32 cloaking, rule engines with different lists, KWin desktops, AppKit
accessibility).
WindowManager<P>is not built: core-model.md sections 17, 18, 20 and 24 list the blockers per platform; section 21 documents the commands that were shared; section 23 is a design note for a common event vocabulary ("thin events"), not implemented. - macOS
Workspace::update. It differs in a scrolling early return and a wider SkyLight disable/re-enable batch; adoptingupdate_sharedwould change scrolling on macOS and cannot be verified without a Mac (section 22; decision D15 below). - Backends and subsystems: borders, animations, DWM ghosts, KWin bridge and effects, AppKit observers, wallpapers, hotkeys. The trait is the seam; the rules are in seam.md.
- The bar's rendering code (frame margins,
network.rs, the window seams inbar.rs/main.rs): step B4 of bar-unification.md was not done because it cannot be shown mechanical without a screen. - Command line: each platform keeps its own commands and help text; most are common.
- Packaging: unified-binaries.md: one binary, a library per OS.
4. How it was verified
Oracles (checked in files, compared byte for byte)
Wire goldens (komorebi-protocol), the three schema.json and schema.asc.json (now schemas/{windows,linux,macos}/), the two schema.bar.json
(size and hash pinned in komorebi-bar-core), the docs/cli, docs/linux/cli and docs/macos/cli pages (before the cutover: docs/cli of each tree), JSON pins of Container,
Monitor and the state of the three daemons from real fixtures, and derivation drift (nix eval ...drvPath).
Details in ARCHITECTURE-UNIFIED.md section 5.
Gates, by candidate
Each candidate was a merge of the finished stacks, gated once. Results as recorded (host load and flakes noted in build-and-test.md section 6):
| Candidate | Windows e2e (QEMU guest) | KDE VM | macOS (a Mac Mini: compile, unit tests, nix) | nix pre-landing |
|---|---|---|---|---|
| baseline (before the work) | stale markers (4 checks) fail; commands fails about 1 in 10 sweeps |
vm-commands never green; vm-core-e2e once |
n/a | n/a |
5 (core: Container, Workspace, Monitor, update_shared, settings) |
38 scenarios: green apart from the 4 stale markers and flakes (commands 265/1 and config_reload 7/1 from a lock-hold stall, resize 25/1); the bar scenario could not open a window (guest has no OpenGL 2.0), schema byte-equal |
16 of 16 fast suites green, vm-commands green for the first time |
cargo unit tests and clippy green; nix: packages and module checks green after toolchain and source-filter fixes | n/a |
| 7 (shared rule matcher, shared commands, stall fixes) | not run as a whole | not run | cargo green, nix green | Windows clippy green; Windows fmt and deny, and two KDE clippy lints, failed and were fixed in 8; packages built |
| 8 (pointer picks the monitor, bar wgpu fallback, macOS deep dedup, formatting) | in progress at the time of writing: commands 266/0 (first fully clean), config_reload 8/0 |
queued | nix all green, cargo 665 passed, 0 failed, 1 ignored | in progress |
Stress runs of lock-heavy command sequences compared baseline and candidate 5: non-answers of komorebic state
fell from 80-83% to 14-15%, and "border manager stuck" warnings from 21-31 to 3-5 (the stall fixes work; the
stall itself still occurs now and then).
Flake registry (summary; full table in build-and-test.md)
Known: Windows commands stall (about 1 in 10-12 sweeps, cause not found), lifecycle_hidden, resize, four
stale known-failure markers (maximizing.user, placement integer case, unknown_monitor x2, same on baseline),
a runner quirk for focus_follows_mouse. Candidates: daemon start failing with AllowSetForegroundWindow at high
load, hotplug with the recorder. KDE: vm-commands and vm-core-e2e were load-dependent on the old baseline.
5. What is NOT verified
- macOS has never run as a daemon in these builds. Only compile, clippy, unit tests and nix builds ran on a Mac. Every behaviour change that touches macOS (hiding, the registry, the shared Container API, settings, pointer-picks-monitor, bar clicks) is listed in macos-hand-check-plan.md.
- The bar at run time. The Windows
barscenario passes schema and config checks; the guest has only software rendering, so window checks depend on the new wgpu fallback (WARP), which had not run in a guest when this was written. The macOS bar never rendered under these changes. - The KDE
slowVM tier (18 suites: keys, rdp, drag and live resize, lock-sleep, wallpaper, look, ...) was not run; only thefasttier (16 suites) was, on candidate 5. Candidate 8 had not been run on the VM yet. - Windows: no scenario covers transparency or animation; only
config_reloadtouches the settings path. - Behaviour changes marked BEHAVIOUR in revision bodies are landed only after the e2e or VM gate named in the body.
6. Known issues
From known-issues.md: (1) KDE skipped the pointer-monitor preamble for three workspace
commands, now resolved for all platforms by owner decision; (2) Monitor::focus_workspace overwrites a workspace
name; (3) Workspace::remove_container focuses the container next to the focused one rather than the removed
one; (4) a subscriber that stops reading can stall the daemon on macOS (a 1 s write timeout was tried and reverted
because it can truncate a notification). Items 2-4 are inherited behaviour, pinned by tests, left for a decision.
Other open items: the Windows lock-hold stall; docs/linux/dev/COMPARE.md and scripts/linux/compare-gen.py are stale (the
KDE-only justfile this paragraph used to name was merged into the one root justfile by the cutover stack).
7. The cutover: done in the cutover stack, and still open
The stack wip-cutover (on wip-main; jj log -r 'wip-main..wip-cutover') did the mechanical part. Each row
says what the first version of this section proposed and where it stands.
| Item | State now | Open |
|---|---|---|
| shim packages | komorebic-linux, komorebic-macos, komorebi-client-linux, komorebi-client-macos, komorebi-bar-macos deleted |
none |
| version crates and numbers | one crates/komorebi-version instead of version-linux, version-macos; one workspace version, 0.2.0 (the 0.1.42 / 0.1.0 / 0.1.1 of the first version of this section are history) |
none |
| directories | komorebi/ is komorebi-windows/; the KDE and macOS libraries are komorebi-linux/ and komorebi-macos/; komorebi-hotkeys/, spikes/ at the top level; docs/{linux,macos}, nix/{linux,macos}, scripts/linux, schemas/{windows,linux,macos} |
none |
deny.toml, rustfmt.toml, toolchain, .gitignore, licence, justfile |
one of each at the root | none |
| docs | the READMEs and CONTRIBUTING of the ports are docs/linux/ and docs/macos/; paths and links were fixed; the Windows mkdocs site excludes docs/linux, docs/macos and docs/dev |
docs/cli per platform stays until the commands are merged; docs/linux/dev/COMPARE.md is stale and scripts/linux/compare-gen.py with it; the KDE documents still say "this repository" for what is now one of three |
| flakes | the kfde-* and kfm-* flakes were still three when the directories moved (komorebi-for-kde/flake.nix, komorebi-for-mac/flake.nix) |
one root flake is being written by the agent flake-one; the nix commands in the documents name the old attributes until it lands |
| workflows | the files of the old repositories are in docs/dev/old-ci/{linux,macos}/ (inert: under docs/ GitHub does not run them); the Windows .github/ is as it was |
the proposal stands, not decided: one CI that builds the three packages, runs crates/ natively, the nix checks, the Windows e2e on a host with KVM and the KDE VM tiers; one release workflow with one tag and one version; the Homebrew formula template and the macOS docs-site files wait in the archive |
| URLs | unchanged | where the repository and the sites are published is not decided: the old GitHub repositories (LGUG2Z/komorebi-for-kde, LGUG2Z/komorebi-for-mac), komorebi-for-mac.lgug2z.com, the update check of the macOS bar, the $schema URLs of the example configurations and generated schemas |
| tools | tools/ is committed; tools/format-all has one group |
tools/kde-closure-check still reads the old path of root-crates.json |
8. How to continue
- Get the macOS pass done first. macos-hand-check-plan.md. Nothing deeper should land on macOS before it.
WindowManager<P>. Blockers per platform: core-model.md sections 20 (KDE, macOS) and 24 (Windows). The unmanaged-window gate is not behaviour-neutral to move into the router (agate_messagehook is proposed in section 22.1). Rule: gate every step with the e2e/VM suites, one daemon at a time.- Thin events. Design note in section 23 (
CoreEvent<W>, shared handlers for gone, appeared and minimized); risks: side-effect order per arm, macOS focus change carries no window. - Commands.
komorebi_core::commandis partial: sixteen arms and focus-workspace are shared; the remaining arms are per platform (section 21 lists what stays and why). The rule matcher is done (one implementation). - The deeper-dedup boundary. Share a function only when a native test can pin it; where the text differs for
a real platform reason, put the difference behind a hook (as
HideSpotandbegin_passdo) rather than converging behaviour you cannot verify. - Decide items 2-4 of the known issues, then move them into the shared code with the e2e gate.
- Finish the cutover (the open column of section 7: the one flake, CI and releases, the URLs), then regenerate the Windows
docs/cliand the schema docs.
9. The decisions that shaped the design
Rewritten from the decisions log. "Owner" marks a decision the owner made; the others were made to unblock and are reversible as stated.
| # | Decision | Why | Reverse |
|---|---|---|---|
| 1 | One workspace, one Cargo.lock; flakes re-export the nested ones and the root builds them from the whole repo |
a spike showed 859 to 900 packages with no conflicts; the root source can then be shared | keep three workspaces |
| 2 | macOS checks on Linux use a fake C compiler and only type-check | nothing else runs without a Mac | none |
| 3 | The wire oracle is a schema-driven golden generator run on the three daemons before the move | the move must not change what is sent | none |
| 4 | One union StaticConfig with per-platform schema views; per-platform defaults documented in a table instead of converging every doc |
users' files must keep loading; schemas are checked in | one type per platform |
| 5 | komorebic-core generic over the platform, no cfg in the definitions; the CLI docs of each tree are the oracle |
the pages are what users read | per-platform clap structs |
| 6 | Shared transport: the daemon locks the window manager, the crate never names a mutex | different locks per daemon | none |
| 7 | The bar is komorebi-bar-core plus a platform trait, one config enum, schema = per-platform views of one union; macOS accepts and skips Windows-only keys with a warning |
one config file works everywhere | separate bars |
| 8 | Backend is derived from evidence (seam.md), not written from memory of one platform |
a trait written from one platform bakes in its assumptions | none |
| 9 | Settings handle is RwLock<Arc<Settings<P>>> with a writer mutex; the platform part is a type parameter |
no new dependency; one consistent snapshot per use | use ArcSwap |
| 10 | Unified layout: one package each for komorebic, komorebi-bar, komorebi-client; the daemon as three OS-gated libraries behind one thin binary |
rewriting about 1700 crate:: paths and moving KDE tests was the alternative |
merge the libraries later |
| 11 | Shared Monitor<P> keeps each daemon's state JSON (flattened platform extras); the runtime required order in generated schemas may change |
state JSON is pinned by fixtures and user tooling | one superset JSON |
| 12 | Workspace/Monitor move by classifying each difference (cosmetic, same behaviour, real, missing), converging, then moving |
a 25-member trait would hide real differences | per-platform copies |
| 13 | Workspace<P> hook signatures: fallible begin_update, end_update returns a pass, place_window returns a result; the Workspace move is atomic |
review of the first design against KDE and Windows | finer hooks |
| 14 | Windows komorebic visible-windows lists unreadable windows with empty strings |
a shared visible_window_details needs infallible details |
restore the skip |
| 15 | Layout::Custom stays behind a Windows hook in the shared Workspace |
unreachable on KDE; no KDE change | move the arms later |
| 16 | The KDE vm-commands handover failure is a test-precondition bug: fix the scenario, keep the check strict |
an empty workspace cannot recalculate its layout; a check that cannot pass tests nothing | weaker check (rejected) |
| 17 | Freeze new behavioural stacks and stop the model move at the WindowManager boundary until gates catch up |
shared text is 26-37%; gates were the critical path | resume sharing after gates |
| 18 | Revert the 1 s subscriber write timeout; fix the stale bar-running flag, the hwnd writer, dead-window bookkeeping, and add komorebi-core to default members |
the timeout can truncate a notification and does not bound the stall | none (design needed: a bounded queue per subscriber) |
| 19 | Owner: KDE and macOS get the Windows pointer-picks-the-monitor behaviour for the workspace commands | consistency across platforms | per-platform preamble |
| 20 | Owner: explore deeper deduplication where it improves cross-platform testing | tests protect all three | stop at the boundary |
| 21 | Owner: software OpenGL fallback for the Windows bar (eframe wgpu, glow first, retry on failure) |
the e2e guest has no OpenGL 2.0 | remove the feature |
| 22 | macOS keeps its own Workspace::update |
adopting update_shared changes scrolling and cannot be verified without a Mac |
adopt after the Mac pass |
| 23 | Formatting of real sources is applied last, with tools/format-all |
formatting early conflicts with every open stack | none |
| 24 | The rule-list lock order is documented at the statics of each daemon (KDE, macOS, Windows) | two orders deadlocked on reload | none |
| 25 | Constant commit stamp (nix-check, e2e) in checks and e2e builds; only packages.default carries the real commit |
a docs-only commit must be a cache hit | none |
| 26 | Gates are batched into candidates; a rebase invalidates a claim, so the gate is re-run at the tip | one revision compiled only because of a later fix | none |