Chrome finds its window and follows it (slice 19.D-β-1)
Kyle entry
The chrome stub from 19.D-α painted at a hardcoded (100,100) and just sat there. 19.D-β-1 makes it a proper docked title bar — it finds the bundle's window, parks itself flush against its top edge, and tracks every move and resize.
Discovery is EWMH-correct. The launcher passes the bundle id into the stub, the stub subscribes to PropertyNotify on the root, then walks `_NET_CLIENT_LIST` and reads `WM_CLASS.res_name` on each entry. The bundle is the one whose instance matches the id. There is an immediate scan before the loop too — without it the stub would race the bundle's MapNotify on a fast machine. Spec-defined, no per-WM heuristics, works on every reparenting WM where a MapNotify on the root sees the WM's frame instead of the client.
Once the bundle is found, the stub picks up its true root coordinates with `XTranslateCoordinates` (the frame extents on KWin/Mutter make raw `XGetWindowAttributes` lie about position), sizes its own window to the same width × 44px, and parks at (bundle_x, bundle_y - 44). Selects `StructureNotifyMask` on the bundle so every ConfigureNotify re-translates and `XMoveResizeWindow`s the chrome.
The non-obvious decision: the chrome is **override-redirect**. The first attempt used WM-managed UTILITY hints with TRANSIENT_FOR and a borderless MOTIF chord. Every reparenting WM I tested took our `XMoveResizeWindow` and reinterpreted it against the frame instead of the client, so the chrome ended up offset by the frame extents (21px x, 39px y on KWin). Override-redirect bypasses the WM entirely and tells the X server "I own this rectangle, place it here." That's exactly what fake decoration is supposed to be.
There's a moonrock-specific workaround in the same loop: `SubstructureNotifyMask` on the root. Moonrock doesn't send the synthetic ConfigureNotify on pure-move that ICCCM requires (this will get fixed in moonrock proper), so the stub also catches the frame's ConfigureNotify on the root's substructure. Harmless on KWin/Mutter — they send both — and lets the chrome track on moonrock today instead of after the moonrock fix lands.
Override-redirect skips the WM's auto-stacking logic too, so the stub also subscribes to `_NET_ACTIVE_WINDOW`. When the active window flips to the bundle, `XRaiseWindow` brings the chrome back above it. Lifecycle: UnmapNotify or DestroyNotify on the bundle exits the stub.
Smoke test on the Legion: kate launched with `kate -name show.blizzard.testqt`, stub launched with `--chrome-stub-test show.blizzard.testqt "Test Qt App"`, then xdotool moved kate around. Geometry log:
The cropped screenshot shows the Aqua title bar with "Test Qt App" centered, the placeholder menu bar (`Test Qt App | File Edit View Window Help`) below it, and kate's own native menu underneath. Three bars stacked, three pixels apart, all the way down.
What's still stub: * The menus are still placeholder text. Real DBusMenu import from the bundle is 19.D-β-2. * Override-redirect chrome doesn't take input focus, so clicking chrome won't activate the bundle. Forwarding is 19.D-β-3. * Bundle invocation still uses a manual wrapper script. The launcher will inject `-name $bundle_id` for Qt and `exec -a $bundle_id` for GTK from the manifest in 19.D-β-2. * Chrome paints at scale=1.0 regardless of host display. XRandR scale probe is 19.D-γ.
AI perspective
The override-redirect call is the slice's real decision. UTILITY hints look like the polite path on paper — they cooperate with the WM, they get free behaviors, they don't lie about ownership. In practice every reparenting WM has its own opinion about where a client should go relative to its frame, and trying to talk it out of those opinions one by one is how you ship a launcher that "mostly works on Mutter but not on KWin." Override-redirect cuts through the negotiation and is the same idiom every fake-decoration shell on X11 uses for exactly this reason. It costs us auto-stacking, and we pay that cost back in 30 lines of `_NET_ACTIVE_WINDOW` listening — a fair trade.
The moonrock pure-move bug is the kind of thing you only find because you ran the smoke test on the actual target. KWin and Mutter both send the synthetic ConfigureNotify on pure-move, and if I'd only tested on a desktop session I'd have shipped a stub that quietly stops tracking on moonrock. The `SubstructureNotifyMask` workaround is a one-liner; the real fix is in moonrock and lives on a separate slice. Worth flagging in the comments so a future reader doesn't strip it out thinking it's defensive paranoia.
The placeholder menus, again, were the right call. Building 19.D-β on top of a chrome that already paints menu items meant the only thing that needed to land in this slice was discovery + docking + tracking. DBusMenu import is its own self-contained problem and deserves its own slice — and now there's a working hallway to drop it into.
======================================================================== CADENCE GOING FORWARD — my recommendation ========================================================================
You asked whether to set a daily-entry rule. My honest answer: daily is wrong for this project.
Most workdays here are slice work — a single named slice landing, maybe two. Some days are pure debugging where the only honest entry is "today I instrumented a watchdog and learned nothing new." A daily rule forces padding on those days, and padding poisons the voice the README is trying to protect ("first person, direct, honest, no fluff"). The blog you'd want to read in a year isn't the one with 365 entries.
What I'd recommend instead:
1. PER-SLICE ENTRY — the natural rhythm. Every named slice that ships gets its own entry. That averages to 3-5 entries per week given the current pace, and the entries write themselves because each slice already has a clear goal, a diff, and a decision.
2. DEDICATED ENTRY FOR LANDMARKS — anything that changes the project's shape (a rename, a framework decision, the SDK freeze, the first AuraFarm launch) gets its own post. These are the entries the public will actually find via search.
3. NO FILLER. If a day's work doesn't change anything readers would care about, no entry. The log gets shorter, the signal gets stronger.
I'll draft each new entry into this file as the slice lands and flag the ones I think are worth posting publicly versus keeping as private notes. You strip the AI PERSPECTIVE block, add your voice on top, and post when you're ready.
If that rhythm starts to feel too sparse — say a stretch of small fixes with no clear narrative — we can adopt a Friday wrap-up entry as a fallback. I wouldn't start with that. Start lean.