PROGRESS.md

Progress

Updated: 2026-11-10 on branch docs/265-done, based on main at f79eb93d

This file records in-flight state and the handoff between sessions. Product decisions live in decisions/, architecture in docs/adr/, and tasks in GitHub Issues. Update it at the end of every session and in any PR that changes what is done or next.

Objective

Reach a working v1 app that manages windows through the bundled engine, as defined in release scope. The app foundation, engine integration, configuration, default shortcuts, shortcut customization, and gesture controls are merged. The current milestone is the open v1 work, which the v1 blocker label lists. Drag insertion passed Sam's hand checks on 31 October 2026, and the stack limit and stack overflow await their hardware checks. The public release path and the complete acceptance runs follow.

A display left on an empty desktop after a replug

#265 reported that after a replug of the main external display, the built-in display showed an empty desktop while its windows sat in another Space. On 10 November a software replug reproduced it on main abe13aa1, once on each display (verify-miri run 20261110-200106). macOS moves the windows into a Space it does not show, and a frame write cannot move a window between two Spaces of one display. paneru#158 adds ecs/shown_space.rs: after a display change that moved a display's Spaces, the engine focuses a window in the Space that holds them, macOS switches the display, and focus goes back. Sol approved it in 2 rounds, and Sam merged it as 87d7a81, whose tree matches the tested head af10d64. This branch pins 87d7a81. On af10d64, run 20261110-212943 switched the external display back in 0.7 s and returned focus to Notes. Not checked live: the built-in display case on the fix (a mock replay covers it), a cable replug, and three or more displays. Seen 3 times in the fix runs and not filed: when the main display changed by arrangement, the restore left two busy Helium windows in their old Spaces, on the other display. It did not recur in 13 display changes on main f79eb93d. #419 landed the pin on main as f79eb93d. On that build, run 20261110-215223 switched the external display back in 0.67 s after a software replug, and Sam's cable replug kept every window on its display and in its order. #265 closed on 10 November.

A window lost while it slides back after a drop

#377 reported a dropped window that stopped partway back to its column under load. The retained traces show that the engine had not stalled the slide: it had dropped the live window. When macOS reports that a window left a Space, the engine keeps the window only if a fresh read of its AX role succeeds. A busy app leaves that read unanswered within 0.25 s (kAXErrorCannotComplete), and the engine counted that as a closed window. This is the same cause as the lost windows in verify-miri run 20261109-052022. paneru#156, merged on 10 November 2026, counts only a read the app answers with an error as a closed window. Branch fix/377-engine-pin pins its merge f238891, on top of the #410 pin 578e228. In verify-miri runs 20261110-172230, -172758, and -174326, on the fix commit 50f56bc without paneru#157, none of 39 loaded drops of T3 Code stopped partway or lost the window. 37 drops ended at the slot, and 2 ended at the top of the column 2 pt and 4 pt wider, because T3 Code widens itself during a drag and the engine kept that width. Sam chose to track the wider column as #414, not against #377. Only 30 of the 39 drops ran at a load average above 10, so the 40-drop criterion stays unticked. Next: Sam merges #413, and a loaded run on main completes the 40 drops.

A new window wider than its display

#410 closed on 10 November 2026 after verify-miri run 20261110-180854 on main at 547f9777, engine 578e228. T3 Code relaunched after an update 2560 points wide, and the engine had put it in the built-in display's strip at that width, so 520 points ran past the right edge. paneru#157, pinned by #412, narrows a new window that joins the active strip to the tiling area. #411 added V fixture --width --display --background and the new window width recipe. Sam relaunched T3 Code on the fix build, and it fit the built-in display. Not narrowed: a window that joins the strip after the above-ordinary level check in recheck_window_level, and a window that opens during a cross-display focus handoff, which can get the previous display's limit. Neither was observed.

Bug reports, shortcut illustrations, and localization

Sam picked the design of #352, #351, and #350 on 9 November 2026. The decision record on branch design/350-351-352 holds the picks, and each issue body carries the acceptance criteria they change. #352 closed on 10 November 2026 after #398 merged as 766ea76b. #394 tracks simpler public issue forms before the public release. #399 merged on 10 November 2026 as 015bfd0d, and moves every interface text in Sources/Miri and Sources/MiriCore into Localization/Localizable.xcstrings, which holds only English so far. make strings fails on text that is not localized where it stands, and verify-miri relaunch --language starts Miri in another language without changing the Mac's. The 8 actions named "monitor" now say "display". Branch feat/351-shortcut-illustrations adds a preview panel to Settings > Shortcuts, pinned above the list as Sam chose on 10 November 2026. It draws a before and after sketch of the action under the pointer or keyboard focus, plays the move once, and shows a caption that VoiceOver reads. The scenes live in Sources/MiriCore/ShortcutSceneCatalog.swift, and ShortcutSceneTests checks them against decisions/keybinding reference.md. #401 merged as 3454d61d, and Sam confirmed the pictures and Reduce Motion on 10 November 2026. #403 made the panel follow keyboard focus and VoiceOver, and #351 closed on 10 November 2026. Branch feat/350-translations adds the translations for all 22 languages of #350. Claude Opus 5.5 drafted them from the glossary in TRANSLATION.md, GPT-6.1 Sol checked them, and each is marked needs_review until a native speaker reviews it. make strings now fails on a missing translation, and verify-miri cutoff with relaunch --double-length checks every Settings page for cut-off text. Next: Sam merges the PR, an agent verifies #350 on main, and Sam changes the Mac's language once as the hand check that criterion 2 names. #407 merged as 04ae4c7a, so the Settings window no longer loses 32 points of height at each launch.

Desktop at the shared edge while columns slide back in

#316 closed on 9 November 2026 after verify-miri run 20261109-144222 on main at 62bfb3d9, engine 0f4affb. macOS shows a new photo panel a frame late, so a panel made while photos slid left the shared edge, or the space between columns, bare for that frame. paneru#146, pinned by #385, shows the next hidden column out behind the nearest hidden column and behind each sliding photo. paneru#148, pinned by #392, shows every hidden column at a shared edge at rest, so no panel is made while photos move. Behind a hidden stack, whose slices have gaps, the farther columns still show only while they slide. In 24 rounds, main at 1417553f showed 9 edge exposures over 8 pt and 1 gap of 438 pt between columns, and main at 62bfb3d9 showed none of either. Across 72 rounds of engine 0f4affb or its identical PR head, one sample showed 24 pt between columns for 7 ms, when an existing photo panel moved one sample after the window ahead of it. Sam accepted that lag on 9 November 2026. The real Helium window case from the issue report was not rechecked.

macOS 15 support

Sam chose a macOS 15.0 minimum on 8 November 2026 in #369. Branch feat/369-macos-15 lowers the minimum, fixes the SDK version that build-app.sh records, names the Accessibility pane of each macOS version, and records what stays unverified. A Tart virtual machine named sequoia15 on macOS 15.7.7 ran the Swift and engine tests, make e2e, the disk image install, and every verify-miri recipe. Trackpad gestures, multiple displays, display changes other than a resolution change, and sleep and wake need a real macOS 15 Mac. No one has run Miri on macOS 26. #376 tracks those hardware checks and a make e2e run on Sam's Mac.

Hidden extra strips

#364 closed on 8 November 2026 after verification on main at a482ada3, engine de88217. The engine keeps one strip per macOS Space: paneru#139 makes the virtual workspace commands do nothing and merges any second strip at restore and at runtime, and paneru#140 lays a strip out again after a runtime merge. #366 pinned that engine. On Sam's Mac, display rearrangement, a display unplug, display sleep, and system sleep each left one strip per Space and no hidden window. macOS gives the built-in display's Space a new number after sleep or a replug, and the engine follows it. #367: verify-miri empty-display hides each app with a managed window on the built-in display, and end shows those apps again. #371 merged, and #367 closed on 8 November 2026 after a recheck on main at be4d2615 (run 20261108-161750). #374: an app hidden before the engine started, such as Beeper, had its windows tiled as invisible columns, and focusing one showed the app again. paneru#142 marks a window hidden when it spawns if its app is hidden, and #378 pins it at 4b640f7. #374 closed on 8 November 2026 after a recheck on main at 021d26a7 (run 20261108-175327). #380: the #374 fix skipped float rules and minimized state for a window that spawned while its app was hidden. paneru#144 sorts each hidden window into minimized, floating, or tiled when the app shows. #382 pins 981f93c, which also carries the #314 engine work. #380 closed on 8 November 2026 after a recheck on main at 43975990 (runs 20261108-191820 and 20261108-191906).

Show in Dock

#349: Settings > General has a Show in Dock switch, on by default, stored as [miri].show_in_dock. The bundle sets LSUIElement, so Miri starts with no Dock icon and becomes a regular app only when the setting is on. The Welcome to Miri window offers the same switch in a Dock section (#358). AppModel.settings applies the policy on every new value, so launch, the switch, and a config.toml edit all take the same path. #355 reached main at 1b2dd8b9. verify-miri run 20261108-021834 on that commit passed criteria 1, 2, 3, 4, 6, and 7, and the issue records the results. An Accessibility press on the switch lets Settings drop behind, while a real mouse click keeps it in front. A prototype showed that only the deprecated activate(ignoringOtherApps:) keeps the front after an Accessibility press, so VoiceOver users may see Settings drop behind. Next: Sam turns the switch off, logs out, and logs back in, which checks criterion 5. When the Dock icon stays hidden, an agent closes #349.

Settings window redesign

Branch feat/109-settings-rehaul rebuilds the Settings window from the Paper design for #109. The Paper file "miri" holds the design. Its weekly agent quota ran out on 7 November 2026, so only saved readouts and screenshots of it were used. The window is 880 by 640 points, with a sidebar of Setup, General, Layout, Shortcuts, Gestures, and Diagnostics, and Miri's colors in light and dark mode. Every control and message of the old tabbed window kept its behavior. Animate window movement moved to Layout, and Restart Engine to Diagnostics and the sidebar button. The update settings in the design are left out until #82, and the under-pointer swipe until #212. Live runs on 7 November 2026 showed every page in light and dark mode, the running, restarting, failed, and waiting states, every control that writes config.toml except Launch at login, shortcut recording, and the kept-default Use button. make check passed on f149ca2f. Not yet seen live: Launch at login, and a confirmed gesture test, which needs Sam's trackpad. Branch feat/109-menu-bar-rehaul, stacked on it, restyles the menu bar menu and the gesture test sheet. The menu keeps Restart Engine, Copy Diagnostics, and the failed and restarting menus. Setup Checker opens Settings on Setup. The gesture test sheet answers its question "Did the strip follow your fingers?" with Yes and No, a UI council change that Sam can revert. A live run on 7 November 2026 checked the running, waiting, and failed menus, both Setup entries, and the sheet while it waits and while it asks for confirmation. Branch feat/109-ui-polish (#343), stacked on the menu branch, polishes the whole UI through eight rounds of a design council (GPT Sol and Claude Sonnet). Both rated every aspect 9 out of 10 on 531c9ac9. It rebuilds Set Up Miri and Copy Diagnostics in the Settings design, and changes contrast, state names, the type scale, keyboard and VoiceOver support, and copy across Settings and the menu. Captures came from the real app with -managementStopped YES, and the waiting state with a stand-in process named yabai; the running, restarting, and failed states and the gesture sheet were not recaptured on this branch. make check was not run on this branch; swift test and make lint pass. The eight optional round 8 nits are done too: menu caption order, "Stopped by you" in the status line, the Check Again timer, plain text instead of LiveText outside the gap steppers, red glyphs for blocking checks, help on the disabled Test Gesture button, a stronger field border, and a spoken shortcut in the recording announcement.

V1 completion audit

The v1 audit for #83 maps 98 v1 requirements on main at 6edff137, engine ee89647. 84 are built, 6 are partly built, and 8 are not built. 0 of 105 acceptance checks pass, because no release candidate is frozen. verify-miri run 20261106-090237 found that new windows keep their own width instead of 50% of the display, filed as #305. CI on 6edff137 failed once on the flaky engine test slice_photos::a_press_while_a_revealed_photo_waits_never_shows_its_window_beside_it, then passed on rerun. #83 closed on 6 November 2026 after the audit landed in #306. The audit is a snapshot of 6edff137. The owning documents and issues hold the current state. Next: the audit's Remaining blockers table, and the stale known limitations rows that the audit lists.

Reopened windows stay on the desktop in use

#323: reopening Settings or Copy Diagnostics switched the display back to the desktop where that window was last open, and a saved frame kept it on its old display. #353 gives each WindowPresenter window .moveToActiveSpace and centers a hidden window on NSScreen.main when its frame is on another screen, which is triage option 1, and reached main at 7c2b9a28. verify-miri runs 20261108-012140 and 20261108-014840 failed all 3 criteria on main 91865afa, and run 20261108-014458 passed all 3 on the fix. #323 closed on 8 November 2026 after verify-miri run 20261108-020409 passed all 3 criteria on a build of 7c2b9a28. Set Up Miri was not driven, because it shares show with the two windows that were.

Where a crossing focus lands

#332: a plain ⌃⌥← or ⌃⌥→ that crosses to a touching display now focuses the destination's column nearest the crossed edge, so the opposite press returns. When that column is a stack, the window that last had focus in it gets focus, as Sam decided on 8 November 2026. Before, the crossing focused the destination's last-focused window, which could sit in the middle of its strip. paneru#150 holds the engine change and merged into miri/v0.5.2 as 7be4df9. #396 pinned it and reached main at 632efc42. #332 closed on 10 November 2026 after verify-miri run 20261110-004217 on 632efc42, with the displays side by side, focused X on ⌃⌥→ and returned to C on ⌃⌥←. Run 20261108-225330 on main 3dc50c83 focused Y on ⌃⌥→ instead. Sam also hand-checked both directions on the #396 build. A stacked edge column was not checked on hardware, and an engine test covers it. #333: ⌃⌥N now visits the displays in screen order, left to right by left edge and then top to bottom, and wraps from the last to the first. Before, it took the first other display in the engine's storage order, so four displays could skip one. paneru#149 holds the engine change and merged into miri/v0.5.2 as f1c14be, and branch fix/333-pin-screen-order pins it. #333 stays open after the pin merges, because its three-display check waits for hardware.

Command-Shift Left and Right stay on their display

#331: Sam decided on 7 November 2026 that ⌃⌥⌘⇧← and ⌃⌥⌘⇧→ move the column to the first or last place on its own display and never cross to another display. The defaults now bind those chords, and the H, L, A, and D chords, to window_swap_first and window_swap_last, at default bindings version 6. Settings no longer lists the four actions that moved or focused to an end or else to the next display. An upgrade removes those defaults only where the user kept them unchanged, and a custom key on a removed action stays under Other Engine Bindings. paneru#137 makes a swap to the end that the column already holds do nothing, instead of swapping inside its stack. #354 pinned its merge b5ae4bd and reached main at a35bb5f4. #331 closed on 8 November 2026 after verify-miri run 20261108-140758 on a35bb5f4 passed both directions and the Settings check, against run 20261107-013236 on main 54e4e5f4, which crossed. Of the rest of the 7 November shortcut decisions, #332 and #334 are closed, and #333 waits for its three-display check.

Pointer stays after a click

#339: with mouse_follows_focus = true, a click on a window moved the pointer to the window's centre. paneru#138 moves the pointer only when a Miri command changes focus, as Sam chose on 7 November 2026. #341 pinned engine 5f01554, the merge of paneru#138, and reached main at 13cd4aaa. #339 closed on 7 November 2026 after verify-miri run 20261107-201447 passed all 5 criteria on a build with the same tree as 13cd4aaa. Starting the engine no longer moves the pointer onto the first focused window.

#360: Sam moved the switch for this key into v1 on 8 November 2026. #361 adds Move the pointer to windows that Miri focuses to Settings > General, off by default, writing [options].mouse_follows_focus. It reached main at 14d500e0. #360 closed on 8 November 2026 after verify-miri run 20261108-031207 passed all 6 criteria with real clicks on the switch, on a build with the same tree as 14d500e0. VoiceOver finds the switch and toggles it with VO-Space. On 8 November 2026 Sam pressed VoiceOver's ⌃⌥→ on a real keyboard with a main build at ab3ca230 running, in Settings and in a managed window, and reported no conflict with Miri's ⌃⌥ arrow shortcuts.

Engine CI flake in a slice photo landing test

#329: slice_photos::the_column_ahead_waits_for_a_slow_window_landing_under_its_photo failed at random under CI load. The defect was in the test only, so the engine Miri ships did not change. Each test pass ran about 4 engine frames per 20 ms of real sleep, so the slow app's 40 ms write left only about 10 ms before the 250 ms landing timeout. paneru#135 holds the slow app's writes through each pass and lands them after it, so the result no longer depends on the machine's speed. It reached main through the engine pin 5f01554 of #341, so #335, which pinned only 762eb7e, closed as superseded. #329 closed on 8 November 2026 after the test passed 50 of 50 runs, 16 at once, at pin 5f01554, and main CI on bff3ed5b passed it. To force a flaky test of this kind, use the mock's write holds: real-time delays flipped the old test only about half the time.

Drops on another display that macOS tiles

#123 closed on 6 November 2026 after Sam's hand drags on main at 471737fb, engine a20f56e3. A title-bar drop on another display now stays where the drop put it, with any macOS tiling option on (#310). Two engine causes were fixed in paneru#130, delivered by #321. A pointer in a display's menu bar now counts as on that display and picks the column below it. A drag also counts as a drag when macOS gives a tiled window its earlier size back as the drag starts, but not when the press is on a resize edge. Before the fix, 4 of 5 such drops ended back on the source display. After it, every hand drop held, including menu-bar fill, Option-drag, and same-display undo.

Columns slide back when a window leaves a strip

#320 closed on 6 November 2026 after Sam's hand drags on main at 471737fb. A strip now keeps its scroll when a column leaves it, so the columns past it slide into its space, and a strip wider than its display never keeps empty space past an end (paneru#131, #322). A scroll that a dropped window's arrival caused stays when that window was not the strip's last column, as known limitations records.

Window-snapping app warning

#277 adds a Window-snapping apps setup check that warns, without blocking start, while Rectangle, Loop, or Magnet runs. On main at 6f398e2b, verify-miri run 20261105-230335 showed that Rectangle 1.100's Maximize action moves a tiled window and can swap its column with its neighbor, and that the setup checker said nothing. Runs 20261105-230759 and 20261105-234236 on the fix showed the warning naming each running app, a normal start, and a passed row after the app quit. In the second run, Loop, Magnet, and Rectangle each moved a tiled window, and Miri put it back within about 30 to 45 ms. In Sam's hand check, run 20261106-000259, Loop's Globe trigger resized a tiled window 14 times, and Miri restored it each time within 25 to 50 ms. Magnet's edge snapping placed Sam's own tiled windows, and Miri then moved them back to their columns. A drop that kept the window's size slid back in 0.4 to 0.6 s, Miri's normal drop animation. A drop where Magnet changed the size took 0.5 to 7.5 s, and once Miri kept Magnet's half width (run 20261106-122455). #314 found two engine causes, and Sam chose fixes A1 and B1 on 6 November 2026. The fix is paneru#134, merged on 8 November 2026 as ec85ffc and delivered by the Miri PR on branch fix/issue-314. A size change after a drop now counts as a snapping app's tile, and frames an app reports while Miri moves the window count as the app catching up. In verify-miri run 20261107-010533, drops with Magnet's own shortcuts returned in 0.34 to 0.60 s at the column's full size. On the merged engine ec85ffc (run 20261108-ec85ffc), 11 of 12 drops returned in 0.14 to 0.80 s, and 1 stalled for 5 s off its slot and then kept Magnet's 1280 width, as #347 describes. paneru#141 fixes that stall in two ways, and paneru#143, merged as d0b532f, fixes two cases its review found. Once Miri is done writing and the app has held a frame for 200 ms, Miri writes its frame once more, and a write the app leaves unanswered (kAXErrorCannotComplete) is sent once more with every part of it. On the re-assert commit 8011f0e (run 20261108-reassert3), the first 28 of 41 drops returned to the exact slot in 0.4 to 2.3 s with no 5 s stall. The 29th stopped partway home, and a traced run (20261108-trace2) showed two causes: lost last writes, which the retry covers, and a window that stopped partway, which is #377. The section A window lost while it slides back after a drop gives its cause. On d0b532f with trace lines (run 20261108-guarded, load average 7 to 9), 5 drops returned to the exact slot in 0.9 to 1.6 s, two of them after the retry resent a write T3 Code left unanswered. In the 6th, Magnet put its third on the built-in display, and Miri kept T3 Code there at Magnet's width. Under a load average of 19 or more, Magnet applied its shortcut more than 1 s after release, and Miri kept its width as the app's own. paneru#145, merged as 31be78d, is Sam's choice for that case: while Miri resizes a window it just dropped, a size outside the sizes between its size before the drop and Miri's new size counts as a snapping app's tile, so the window returns to the drop's place. In verify-miri run 20261108-xdisplay (load average 4 to 8), 20 of 20 drops returned to the exact slot on the 27-inch display in 0.33 to 1.44 s, 19 of them after macOS had moved the window to the built-in display during the drag. #314 closed on 7 November 2026, and the check on main found #388: a dropped window that reached its slot's origin while the app was still resizing it kept a narrower width. paneru#147 makes Miri wait while the app's frame still changes, and write the slot again when the app stops short of it. It merged into miri/v0.5.2 as 24fef41, which branch fix/388-pin-engine pins. In verify-miri runs 20261110-034914, -035139, -035535, and -040113 on that pin, 17 of 20 valid quarter drops returned T3 Code to its full 1676 by 1394 frame, and none came back narrower. The other 3 ended on the built-in display, which is #390, so #388's 20 of 20 criterion stays unticked. Next: Sam merges #406, and the 20 of 20 check runs again after #390 is fixed. #277 closed on 6 November 2026 after verify-miri run 20261106-131827 on main at 5eb9366f: the row named Rectangle and Magnet, management still started, and the row passed after both quit.

Picture-in-Picture resizing

#249 and #250 closed on 5 November 2026, after verify-miri runs 20261105-220252 and 20261105-222020 on main at a5853509 with engine ee89647. A Chrome Picture-in-Picture window now resizes from every corner as it does with Miri off: 3 of 3 top-left, 20 of 20 bottom-left, and 20 of 20 bottom-right scripted drags were correct, and Sam's hand drags passed. Chrome's own resize of that window lags after anything outside Chrome sets its frame, so the engine must never write to it. A new window without a full-screen button therefore waits untiled for its 1 s level check, and tiles then if it stays ordinary.

Stacks when macOS moves their windows to another Space

#144 closed on 5 November 2026, after verify-miri run 20261105-190223 on main at 37abded6 with engine 200109e. A two-window stack stayed one column when Sam closed and opened the lid, and when the built-in display was mirrored onto the 27-inch. On main before the fix, a real lid close split the stack after one native read that listed both windows. A lid open where macOS moves one stack window alone is proven only by an engine test, because macOS moved both windows together in every later hardware cycle.

Columns leaving a shared edge

#159 closed on 5 November 2026, after verify-miri run 20261105-173442 on main at 5c575480 with engine 4dc453f. With Show window contents on edges next to another display off, a column that leaves across a shared edge now slides in 3 of 3 presses, then tucks. It tucks at about its midpoint, which known limitations records.

Plain horizontal shortcuts cross touching displays

#104 is implemented on main 61026f2c, with engine PR #123 merged as 6b5dbc5. Keyboard dispatch adds horizontal step intent while wheel, Lua, and direct engine commands keep their previous behavior. A fresh press at the strip end crosses only a touching horizontal display with positive vertical overlap. Plain moves arrive at the crossed destination edge, so the opposite press returns the whole column to the source end. This extends ADR 0005's earlier dedicated-command-only rule. Command-layer move behavior and arrival after the destination active column stay unchanged.

Signed main 61026f2c stayed on its source display after plain focus and move at both touching edges in verify-miri run 20261104-203946. Signed fix 82779a3 crossed focus and move both ways, returned immediately with whole stacks and focus retained, and preserved width and maximize behavior in runs 20261104-205815 and 20261104-210106. Outer-edge commands stayed on their display. Runs 20261104-211336 and 20261104-213022 confirmed repeat confinement in both directions, including local destination movement after the fresh crossing. The last run recorded east focus crossing to interior fixture-4 before repeats reached last fixture-3, with both focus observations agreeing. Vertical stack controls and Command-layer arrival after an interior active fixture retained their behavior. These are synthetic HID observations on two horizontally touching displays, not physical-keyboard or alternate-topology results. Evidence stays outside the repository under the named verify-miri run directories.

Cleanup restored the previous build, engine, session, and all eight recorded strip positions. Exact unrelated frames and focus did not restore. Three runs stopped at setup preconditions before the affected feature action, and their results remain inconclusive. An earlier delegate activated an app after releasing the lock, which violated the test procedure. Later runs performed every drive before end and made no post-lock activation.

Two keyboard-dispatch regressions failed before the fix and passed afterward. All 193 Swift tests, 42 library plus 1182 binary developer-engine tests, 56 library plus 1278 binary default-feature tests, strict lint, the signed build, native selftests, and packaging passed separately. Sol-loop approved the app and engine behavior diff in one round with GPT-6.1 Sol on high, with no findings. The Opus comment and cross-model trail audit failed after repeated API errors and supplied no verdict. make verify-engine-lifecycle passed seven rules and three mutation checks on main and fix. Its 196-state process model does not cover display crossing. The first make check stopped at the unchanged unpublished engine-pin guard. After normal engine publication and repinning, the complete make check passed on app 563e74a1 with engine 6b5dbc5, including both native selftests and DMG packaging. The app changes are in PR #291. Next: verify the landed build before issue closure.

Direct monitor focus shortcuts

Sam chose direct horizontal monitor focus on 4 November 2026 while reviewing #104. Version 5 assigns Control-Option-Command plus Left, H, or A to window_focusdisplay_west, and Right, L, or D to window_focusdisplay_east. The edge-focus actions remain listed in Shortcuts without default chords. Column-move shortcuts keep their existing behavior. The migration transfers only exact offered defaults and preserves customized, disabled, conflicting, unversioned, and unsupported bindings. An existing direct-monitor value keeps both itself and its edge-focus predecessor. Stop registration now refreshes after a successful configuration migration, so a chord released by an edge default works without another reload. All 203 Swift tests pass, including the new default-transfer, customization, disabled-action, conflict, restart, and Stop-registration checks. Strict Swift lint, documentation checks, and make lint pass. Sol-loop approved the migration in two GPT-6.1 Sol high rounds after a canonical-alias destination check fixed the sole finding. Both empty and customized suffix aliases preserve the edge predecessor, and the regression failed before the fix. The comment audit found no added comments or suppressions. The separate lifecycle model passes all seven rules and three mutants, but cannot prove shortcut registration or monitor focus. The approved source 554a4fc9, with engine 6b5dbc5, passes the full locked make check, including 203 Swift tests, engine tests, both native selftests, and packaging. In verify-miri run 20261104-225455, one synthetic Command-layer arrow press crossed from an interior fixture on display 1 to the last-focused interior fixture on display 3, and the opposite arrow returned directly. Both engine and macOS focus agreed after each arrow and each H, L, A, and D alternative. A plain right-arrow control moved locally between fixture columns. The rendered Shortcuts rows showed all six monitor chords and both empty edge-focus actions. The earlier candidate migrated the actual version 4 configuration to version 5 without changing unrelated values, and the final build preserved that configuration. An earlier fixture-setup attempt was inconclusive before a valid proof key and does not count as a shortcut result. The final build remains running from this checkout's .build/app/Miri Developer.app. Cleanup restored eight recorded strip positions and released the lock, but five native frame rectangles and engine focus differed from the pre-gate capture, and one previously recorded window was absent. No physical-keyboard or alternate-topology result is claimed. Next: check PR #291 CI, then verify the landed build before issue closure.

Accept shortcuts sent by AppleScript

#279 is verified on landed main 418e0962, with engine 0e9659cd, and closed. Sam merged paneru#122 and the Miri pin in #288. AppleScript sends its key event into the session stream after the engine's HID listener. Diagnostic run 20261104-183311 captures the key at session, annotated-session, and PID taps, but not HID. Its flags 0x2c0021 already include left Control and Option device bits. The generic-modifier fix alone failed all three earlier live trials. The final fix adds a session key-down listener beside the unchanged HID path, with shared tap ownership, cleanup, and recovery. Handled HID keys are consumed before session delivery and cannot dispatch twice. Keyboard bindings also accept generic modifier flags while retaining device-side distinctions.

On the fresh signed main build, verify-miri run 20261104-195010 moves focus from fixture-2 to fixture-1 in three of three exact AppleScript trials, in both the engine and macOS. The HID control passes, and one HID shortcut moves fixture-3 to fixture-2 without skipping to fixture-1. The fixture images and JSON agree. Cleanup restores the previous build/session, stops all test processes, and passes the layout comparison for all eight recorded windows without an exception. The live-test lock is free. The earlier landed-main attempt stopped during fixture setup and does not count as a passing shortcut test. All 34 input tests pass on the landed checkout. The full CARGO_INCREMENTAL=0 CARGO_PROFILE_DEV_DEBUG=0 make check gate passes on integration 0fefc13, including 56 harness tests, 193 Swift tests, 42 engine library tests, 1174 engine binary tests, native selftests, and packaging. Landed main has exactly the same complete source tree as CI-green PR head 55bbe671, where all six CI jobs passed. Default engine tests pass 56 library and 1270 binary tests, and both feature configurations pass strict Clippy and formatting. Sol-loop approves the final engine behavior in two rounds with GPT-6.1 Sol at high effort, with no findings or open IDs. The reviewed engine diff remains one file, +133/-68, and usage was not exposed. make verify-engine-lifecycle is skipped because its Swift process-lifecycle model cannot detect keyboard event delivery and no covered code changed. All four acceptance boxes are ticked, and the landed-main verification comment records closure. No physical keyboard, trackpad, other automation-app, permission-attribution, or exact pixel-frame restoration result is claimed. No agent merged a PR, changed a guard, or bypassed a push check.

Native Space cleanup comparison

The failed setup run for #279 exposed a cleanup-helper false positive for a floating window listed in two native Spaces before and after restoration. The checker retained only the last membership for a window ID. PR #289 prefers exact window ID, Space, and row, while retaining a same-ID fallback to report a genuinely lost membership. The unchanged-membership regression fails before the fix in both workspace iteration orders. All 19 helper tests and 58 harness tests pass after the fix, and a captured-state replay reports no differences. Ordinary cleanup completed without an override, although that floating window had closed by the final check. Sol approves the separate helper diff in one GPT-6.1 Sol high round, with no findings or open IDs. All six CI jobs pass at helper head 182ca136, and the normal pre-push hook passes. The helper PR stays open for Sam to merge, with current CI and review status on the PR.

Supervisor test timing

Verification for #276 captured four supervisor assertions failing after 30.91 seconds, when polling expired before queued timer work completed. The two tests now subscribe to state changes before stop or crash and await their target state, with a one-minute Swift Testing time limit. Their kill, completion, current-state, and recovery assertions remain intact. A fresh baseline run passed all 192 Swift tests, so this session did not reproduce the captured failure. After the change, all nine focused supervisor tests and all 192 Swift tests pass, as do make lint swift-lint and make pre-push. This separate test-only change leaves product code and the lifecycle model unchanged. Sam merged #285, and the fresh landed-main gate below passes with these waits.

Setup advice for desktop swipes

#276 is verified on landed main e120875c, with engine 1bcbb2e, and closed. Sam merged the advice in #286 after the separate test-only #285. Turning the macOS gesture off also stops desktop swipes, while three fingers keeps desktop swipes and leaves four fingers for the strip. The previous agent reproduced the old advice on main 481d52ff in verify-miri run 20261104-130849 and committed the failing regression before the fix. The regression failed both desktop-swipe assertions before the change. Sol GPT-6.1 at high reasoning effort approved the change in one round with no findings. The fix-build verify-miri run 20261104-182024 and fresh landed-main run 20261104-190310 show every sentence in the native Setup page with four-finger value 2 and three-finger value 0. The visible text wraps without clipping. Fresh CARGO_INCREMENTAL=0 CARGO_PROFILE_DEV_DEBUG=0 make check exits 0 on landed main, including 56 harness tests, 193 Swift tests, 42 library plus 1172 binary engine tests, the signed build, native Accessibility observations, fixture recovery, and DMG packaging. The landed-main run restored the original build and session, confirmed all nine recorded window positions, and released the lock after selecting the recorded first Helium native tab. Cleanup did not independently compare window frames. The Settings helper returned 1 because it looked for General-page status while Setup was open, and the native Setup control and exact text were verified directly. Accessibility attribution remains unresolved, and no physical swipe test ran. make verify-engine-lifecycle does not apply because its model covers engine process transitions and cannot check advice text. All three acceptance boxes are ticked, and the landed verification comment records closure. No issue-specific work remains.

Preserve focus during an engine restart

#248 is verified on landed main 929a2c10, with engine 1bcbb2e, and closed. On current main at bc022f09, engine af52f84, verify-miri run 20261104-155648 moved both macOS and engine focus from Settings to fixture-2 after Restart Engine completed session restoration. The startup system explicitly focuses and raises the first active-row window even when it restores a saved session. Engine commit 40d154e records two failing regressions before the fix and a passing first-launch regression. Engine fix 6f01709 in paneru#121 preserves native focus when an enabled saved session exists, and keeps the first-launch focus request when none exists. Sam merged the engine PR as 1bcbb2e, which has exactly the same source tree as the verified fix, and the companion Miri change pins that merge. The three focused regressions and all 42 library plus 1172 binary tests pass with the Miri feature configuration. Default-feature tests pass 56 library and 1268 binary tests, and both configurations pass strict Clippy and formatting. On the signed fix bundle, verify-miri run 20261104-161343 kept Settings focused after its Restart Engine button, and kept fixture-2 focused after the actual status-menu Restart Engine item. All 11 recorded regular window ID and owner pairs kept their order after each restart and the delayed checks. The run restored the previous build, session, and all eight pre-existing window positions, then released the live-test lock. make native passes, including 192 Swift tests, the signed build, Accessibility selftests, fixture recovery, and DMG packaging. make check passes with the merged engine pin, including compatibility, all lint checks, tests, the signed build, native selftests, and DMG packaging. make verify-engine-lifecycle passes all seven rules and three mutation checks, but its process-state model does not cover window focus or raises. An abandoned verification run from #279 was recovered with Sam's approval to retain Space 5144 because its recorded Space 5080 no longer exists. The subsequent main-build runs restored all eight surviving windows to their saved strip positions. All three acceptance criteria are ticked with fix-build and landed-main evidence in the issue. Sol-loop approved the exact behavior diff in one round with GPT-6.1 Sol at high effort and no findings. The configured Claude comment and decision-trail review failed after repeated API errors and produced no verdict. The engine PR is merged. Sam merged the companion Miri pin in #283 as 929a2c10, whose tree matches the integration tree that passed make check. On a freshly signed landed-main build, verify-miri run 20261104-171657 preserved Settings focus through its restart and fixture-2 focus through the actual status-menu restart, including delayed checks beyond restore grace. All 121 recorded regular layer-zero window ID and owner pairs retained identical order in both immediate and delayed comparisons for each restart. All three startup-focus regressions pass on landed main, including first launch without a saved session. Cleanup restored the previous build, session, and all eight original windows by Space, display, row, and strip position after correcting two exchanged Helium columns. The lock is free and verify-miri doctor passes. Cleanup did not independently compare pixel frames. The landed-main verification comment records the evidence and closure. No issue-specific work remains.

Keyboard slides in v1

Sam chose the v1 keyboard slide on 4 November 2026 in #278. A keyboard slide keeps the exponential ease-out. A spring that keeps its speed when a keyboard target changes is deferred to v2. Swipe settling keeps the spring it already uses.

First step of a keyboard slide

#280 now expects the ease-out from #278, with a first step of at most one 16 ms frame, about 20% of the move. Sam chose that bound on 6 November 2026. The fix is in paneru#126, merged on 6 November 2026, and #311 pinned the engine at its merge commit b2a14d3. Idle pump sleep counted as animation time in two passes, because Time is sampled before the pump sleeps, so each of a slide's first two steps could cover 37% of what was left. The engine now leaves that sleep out, and the pass that starts a move counts at most one 16 ms frame. On verify-miri run 20261106-094155, the largest engine first step fell from t 0.373 on main to 0.201, and the largest first visible frame in 10 untraced swaps was 19.6%. Settling slowed by a median 11 ms in the engine, and the window server settle reached 447 ms, as main did at 444 ms the same day. Sam therefore made the settle and shared-edge criteria regression checks against main on 6 November 2026, and both hold. Desktop shows beside a column while it changes width, up to 74 pt on main and 78 pt on the fix, which #308 tracks. Sam's hand check on 6 November 2026 confirmed that with Reduce Motion on, a keyboard swap moves in one step. In Low Power Mode, verify-miri run 20261106-120817 kept every engine first step at t 0.201 or less over 10 traced swaps. Sam reworded the first-frame criterion to the engine's first step, because an app that draws late can show several steps at once. #280 closed on 6 November 2026, after verify-miri runs 20261106-123306 and 20261106-123623 on main at 34cca407. On landed main, the engine's first step stayed at t 0.199 or less over 30 traced swaps, and the median settle was 375 ms against 370 ms for the pre-fix build in the same session.

Mission Control drag to the other display

#143 is fixed in paneru#118 and paneru#119, merged to main in #272 with the engine pin at af52f84, and closed. A window you drag in Mission Control onto the other display's desktop now joins that desktop's strip and stays there. macOS 27 reports the drop's new Space about 300 ms before the window's new frame, so the engine refused the report and then wrote the window back to its old strip. The engine now reads every Space again when a native move carries a window onto another display, and when Mission Control closes. On main at c356ad7a, verify-miri run 20261103-110506 sent the window back on every drop. With the fix, run 20261103-111539 kept it where each of Sam's drops put it, across both displays and both desktops of the built-in display. On main at 49ff99fc, run 20261103-115304 kept the window on the built-in display after each drop. The last open #89 checkbox is now ticked.

System Settings window after a display rearrangement

#262 is fixed in paneru#116 and #268, on main at d77f8d46 with engine ecd584a, and is closed. After you rearrange the displays in System Settings, the System Settings window keeps its strip. A window with its centre and the middle of its top edge on no display now counts as on its strip's display, and the engine puts it back. On main at d77f8d46, verify-miri run 20261103-004356 kept every strip through 4 drags. A traced build of the same engine kept every strip through 20 more drags (run 20261103-005228), and the issue comments record each run. One drag back on a fix build still moved System Settings into the other strip, in 1 of 36 drags, with no known cause. Known limitations records it.

Strips when a display moves

#156 is done and closed on 2 November 2026: moving a display beside another no longer moves windows between the two displays' strips. #260 pinned engine 6cb0330, the merge of paneru#115. When the arrangement changes, macOS moves the windows the engine parks past a display's edge and files each one in the Space of the display that now holds its centre. The engine's display-change read took that report, and display homes kept the move because the window's own Space still existed. Now the engine records the displays present when a burst of display events begins, and during that burst a report never moves a window between two of them. A display plugged in during the burst still gets the windows macOS files on it. On a main build at 5f1ab7e3, verify-miri run 20261102-181845 moved display 3 to the left of, above, and to the right of the built-in display, and windows changed strips at every move. On the fix commit 118d277, run 20261102-184144 kept both strips' windows identical at every move. The engine test moving_a_display_beside_another_keeps_every_window_in_its_strip fails without the fix. On a build of main at 7227f856, verify-miri run 20261102-190025 kept every strip's windows the same after all 4 display moves. Not verified: a display moved by hand in System Settings, the built-in display moved instead of the external one, and a burst that adds a display and moves another at once.

Focus on an empty display

#253 is done and closed on 2 November 2026: ⌃⌥⌘ toward a display with no window moves focus there on the first press. #258 pinned engine 53226d6, the merge of paneru#114. On a build of main at ecc6a719, verify-miri run 20261102-180341 pressed ⌃⌥⌘← toward the emptied built-in display 6 of 6 times from T3 Code and 4 of 4 times from a fixture, and ⌃⌥⌘→ came back each time. On macOS 27.0.1, WindowManager puts an "Event Shield Window" over each display at the desktop-icon level, in front of Finder's desktop window. The engine took the first window at that level, so ⌃⌥⌘ toward an empty display brought WindowManager to the front, which showed the desktop of the display that had focus and left focus there. The engine now takes only Finder's desktop window. A probe that made the engine's macOS calls kept focus on the 27-inch display 3 times out of 3 with the shield window, and moved it to the built-in display 3 times out of 3 with Finder's window. On a main build at de3e051f, ⌃⌥⌘← from T3 Code toward the built-in display, emptied with a virtual workspace, failed 2 of 4 times. Sam checked by hand on the first fix build, engine 362988a, with the built-in display on an empty native Space, and the first press moved focus there from T3 Code and from Finder. Not verified: another macOS version, a third display, and a Mac with Finder's desktop turned off, which now gets the "did not switch" message. #177 did not reproduce on main on 2 November 2026 and waits on needs-info.

Stack overlap with real minimum heights

#124 is done and closed on 2 November 2026: a stack overlaps only when its windows' real minimum heights do not fit the column. The engine change in paneru#111 sums each window's known minimum height, with a top bar for each window of unknown minimum, instead of 200 points per window. #252 pinned engine 5f22d8e, which holds paneru#111 and paneru#112, and added verify-miri fixture --min-height. On a build of main at 0d878f80, verify-miri on display 3, a 1042 point column, stacked two plain fixtures with an 800 point minimum fixture. The rows were 117, 808, and 117 points with no overlap, where the main build before #252 gave 1042, 808, and 200 with overlap. With a 300 point minimum on one plain fixture, the stack overlapped after the refusal, and the frames held 4 seconds later. Not verified: real apps such as the #93 case, and a hardware check by Sam.

Column widths across a relaunch

#153 is done and closed on 2 November 2026: a Miri relaunch keeps each column's width. On quit, exit cleanup returns each tiled window to its frame from when that engine started, and the next engine read column widths from those frames. The engine change in paneru#112 saves each tiled window's width as a share of its strip's viewport, and session restore gives the window that width again. #251 pinned its tested commit 7b27b40, not the merge 5f22d8e, which also holds the #124 engine change from paneru#111. #252 then moved the pin to 5f22d8e, which holds both changes. Before the fix, a main build at 70ad7f0f brought a column back at 638 points after it was widened to 842. On a build of main at 4c30e66b, verify-miri run 20261102-165439 resized a single column and a stacked column twice, with a relaunch after each resize, and both kept 631 and then 956 points. make verify-engine-lifecycle passes, and its model does not cover window frames or session restore. Not verified: real apps with their own size limits, a second display, a Dock on the side, and a display change between quit and relaunch.

Engine lifecycle model check

verification/engine-lifecycle/ holds a TLA+ model of engine start, stop, restart, and quit. make verify-engine-lifecycle checks it in about 3 seconds, and .claude/skills/verify-engine-lifecycle/SKILL.md says when to run it and how to keep it in step with the code. The model found four lifecycle bugs on 2026-11-01. #242, #240, and #241 fixed them, and all seven rules now hold on main. A live run on Miri Developer built from main at f37f425b passed every acceptance criterion on 2 November 2026, and #234, #235, and #236 are closed. #243 makes verify-miri end and renew refuse a run that another thread owns, after one agent's end closed another agent's live run. On main at 1dfed270 the check exited 3, because the #109 rehaul moved the Start, Stop, and Restart buttons into files the model did not cover. #345 models those buttons, covers their files, and adds a make lint test that fails when a file with a lifecycle control is not covered. #346 merged as 2ec9aad4, and #345 is verified and closed on 8 November 2026. Nothing is in flight here.

Scroll position after a restart

#69 is done and closed on 2 November 2026: a restart keeps each strip where it was scrolled. #256 pinned engine d169943, the merge of engine PR #113. The session file now saves each row's view: one window in the row, and where its column sat on the display. A restart puts that column back in the same place, so a row on a display without focus no longer starts at its first column. A verify-miri run on 2 November 2026 restarted the engine from the menu bar with seven fixture windows in a scrolled row on display 3 and focus on display 1. On main at 70ad7f0f, every fixture in that row moved to x 2551. On the fix build at engine f5a3866, every fixture kept its x, and a restart from Settings did the same. Engine PR #112 then resized restored columns after the scroll was measured, which put a row too far over when macOS handed the windows back at another width. Engine a358a36 measures each column at its saved width, and a menu bar restart on that build at 16:52 kept every fixture's x and width, after exit cleanup had handed them back 8 points narrower. make verify-engine-lifecycle still holds all seven rules, but its model covers only start, stop, restart, and quit, so it cannot show this fix. A row with a maximized column is not tested yet, and docs/limitations.md keeps it as unverified. A restart also moves focus away from the window or app in use, which #248 tracks. On a build of main at cc46a347, a menu bar restart with seven scrolled fixtures on display 3 and focus on display 1 kept every fixture's x and width, and the restore matched all 13 windows. Nothing is in flight here.

Logs from the engine failure view

Branch fix/issue-220 adds Show Logs in Finder next to Restart Engine in the failed state of the menu and Settings > General, for #220. A verify-miri run on 2 November 2026 killed the test engine three times on main at e8d69276 and on the branch. On main, both views showed only Restart Engine. On the branch, both views showed the new button, and each press opened Finder with engine.log selected. Finder came to the front from Settings. From the menu, a mouse click posted at the HID event tap showed that Finder stayed behind the current app, because Miri was not active. showLogsInFinder() now calls NSApp.activate() first, as WindowPresenter.show does, and a rerun showed Finder in front from the menu twice and from Settings.

Configuration error in the menu

Branch fix/issue-221 makes the menu bar menu show the configuration error and its line, for #221. ConfigurationIssue.menuSummary puts the line number before the message and cuts the text to 80 characters, so a cut keeps the line. Live runs on 2 November 2026 with a broken config.toml showed "Configuration error. Open Settings for details." on the main build at e8d69276, and "Configuration error on line 88: Expected ] to close the table header." on the fix build. A long key name showed as "Configuration error on line 88: Expected = after the key miri_window_gap_settin…". verify-miri cannot open the menu bar menu yet, so these runs read it with a scratch Accessibility tool outside the repository.

Live verification skill

Branch chore/verify-miri-skill adds the project skill .claude/skills/verify-miri/. Its verify-miri command takes the live-test lock, backs up the window session, swaps in a test build, and puts back Sam's build and session afterwards. It drives shortcuts at the HID event tap, presses Settings controls through Accessibility, and opens AXFixture windows as tiled columns. Evidence stays in ~/.cache/miri/verify-miri/runs/, outside the repository. AXFixture gained the opt-in options --label, --resizable, and --activate, and the selftests still use its default window. A run on 2026-11-02 at main 38b851c8 proved that ⌃⌥←, ⌃⌥→, ⌃⌥H, and ⌃⌥L move focus between two fixture columns, in both the engine and macOS, and make e2e passed in the same run. The feature map also lists recipes for the Start, Stop, and Restart controls, setup checks, and shortcut customization, which no verify-miri run has driven yet. Next: drive those recipes with verify-miri and record each result in features/README.md.

#263: verify-miri begin now records every strip of Sam's build by window ID, and end reports each window that the restore left in another strip or position, keeps the lock, and exits 1. The engine's session restore puts a saved strip in the Space that now holds most of its windows, so a run that moves a majority of one strip's windows to the other display leaves the whole strip there, with the same matched count. On a main build at 9c5b9230, run 20261103-013706 with the old script ended with exit 0 in that case, and run 20261103-013756 with the new script reported both windows and passed after send-cmd put them back.

Initial setup

#219 is implemented on branch feat/219-initial-setup. On first launch Miri opens a Set Up Miri window with the window management status, any start blockers, and the strip swipe controls from Settings > Gestures. Setup selects four fingers when no finger count is chosen. The swipe stays off until a confirmed gesture test, or turns on at once when a matching saved test exists. Closing the window saves initialSetupDone in user defaults, and quitting with the window open does not. Launch with -initialSetupDone NO to show setup again. swift build, make swift-test, make lint, make swift-lint, and make build passed. A live run of the app bundle on 1 November 2026 showed setup on a real first launch, changed Off to four fingers, and reported this Mac's four-finger macOS conflict with the test disabled. Quitting with setup open left it pending, Done finished it, and the next launch showed no setup. With three fingers, which have no conflict on that Mac, the test from setup passed and turned the swipe on after confirmation. Engine focus commands moved the strip in that run, not a trackpad, so it is not trackpad evidence. Sam then ran the test from setup with three fingers on the trackpad on 1 November 2026: the strip followed, and the status read "On, verified by user test". AG-3 with four fingers still needs a Mac with no four-finger macOS conflict.

Status rows and VoiceOver

The fix for #228 is implemented on branch fix/228-live-status-labels. On macOS 27, LabeledContent keeps the first accessibility value of its content, so VoiceOver kept reading an old status after the status changed. A new LiveText view restates its string as its accessibility value, and the Status rows in Settings, Set Up Miri, and the strip swipe controls use it, as do the gap stepper labels. swift build, make swift-test, make lint, make swift-lint, and make build passed. On 1 November 2026, Accessibility API reads on the main build showed a stale outer value in the gap, management status, and swipe status rows, and the fix build kept all three current through each change. VoiceOver itself was not run.

Windows missing from the expected desktop

#85 is fixed in engine PR #110, merged as ffe0673, which this branch pins. Its code is identical to the tested head 654c554. The 25 October report had another cause, an extra hidden row in one Space, which #139 fixed on 28 October. After an engine restart, session restore placed windows where Mission Control listed them but the desktop did not show them. Restore parked the selected row of every Space that was not showing, and only an activation on the display with focus unparked it. Restore also rebuilt each strip under its saved Space ID, although macOS can renumber Spaces while the windows stay put. On 1 November, the built-in display's Space 3097 had become 3234 for an unknown reason, so 5 of Sam's windows were restored into a parked strip for a Space that no longer existed. Restore now places every selected row in its viewport, and maps each saved Space to the Space that holds most of its windows.

Live on Miri Developer, the same script ran on engine 68365bc and on the fix, from the same saved session. It launches Miri twice, then picks the other display's second Space in Mission Control while focus stays put. Before the fix, the 5 restored windows and one throwaway Finder window per display were 0% on screen. With the fix, all of them showed, and 3 rounds of Space switches on each display passed.

On landed main at bbe31c28 with engine ffe0673, the script passed twice, and Sam's hand checks with Control-Arrow passed 14 switches on both displays. #85 closed on 1 November. Sam chose not to pursue one unreproduced failure from their first hand check. Synthetic Control-Arrow presses do not switch Spaces on macOS 27, so that path and switching to a window on a hidden Space are not yet checked by hand. The scripts are in /tmp/miri85 (may be gone).

Dock changes while the engine runs

#215 is fixed in two engine PRs. This branch pins 8b585cc, the merge of the second. Engine PR #106, merged as 036c73b with engine PR #105's #87 tests, re-reads every display on NSApplicationDidChangeScreenParametersNotification. Before it, the engine read each display's Dock position only at startup or after a display change, so a Dock that showed, hid, or moved later left windows laid out for the old Dock. A changed Dock position moves that display's strips with its usable area, through the same layout step as an edge padding change, so a maximized column seen there fills the new area. macOS can publish a Dock change late or never: after a size change every process read the old work area for 7 s, and a hiding killall Dock could leave the old Dock area reserved. Engine PR #108, merged as 8b585cc, therefore also reads the Dock's own icon-list frame and auto-hide preference 0.5 s and 2 s after the Dock reports a preference change or restart. macOS's work area wins while the two agree within 8 points on the same side. Live on 1 November, windows on the 2560x1440 display went from 1402 to 1324 points tall when the Dock showed at its bottom, and back when it hid, without a restart. With the Dock moved to the left of the built-in display, a maximized window there went from 2040 points wide at x -2044 to 1962 wide at x -1966, beside the Dock. With 8b585cc, a Dock at size 0.7 gave 1282-point windows while macOS still reported the old area, and a hiding killall Dock gave full-height windows while macOS kept the stale area. Sol approved engine PR #106 after 3 rounds and #108 after 2. #87's remaining criteria passed live on 1 November: Window > Zoom, Make column full width, a Dock known at startup, and a built-in display at 2x beside a 1x display.

Pointer drop target

#203 makes the pointer pick a drag's drop target, as Sam chose on 1 November after trying drags with the trackpad. It replaces the #72 rule, where the dragged window's centre picked the column. Over the dragged window's own column a drop only reorders its stack, so the #72 grab near the end of the title bar still works. Engine PR #102 carries the change, and #208 pins it. On main 97672bc7 with engine 68365bc, the 213 engine drag tests pass, and the two own-stack tests fail when the own-column exception is removed. On 1 November, synthetic Finder drags ran on Miri Developer built from that main, twice, and passed 5 of 5 both times:

  • A window grabbed near the right end of its title bar and dropped with the pointer over another column's right quarter became a new column. A control on the old build had joined that column, because the centre was over its middle.
  • A window grabbed near the left end and dropped with the pointer over another column's middle near the bottom joined that column at the bottom.
  • The #72 case, a stacked window grabbed near the right end of its title bar and dragged straight down, reordered its stack.
  • A stacked window dropped over the neighboring column's near quarter left its stack. A synthetic diagonal drag of that window sometimes never started, so the passing drag first moved straight down (#71).
  • A window grabbed near the right end on the 27-inch display and dropped with the pointer over the right quarter of a column on the built-in display became a new column after it there. Its centre was over that column's middle, so the old rule would have joined it.

In the second run, the engine's drag overlay was on the display under the pointer during all five holds. Two hold screenshots, kept local, show the insertion line next to the pointer. In the first case a vertical line sits on the gap at about x 1019, with the pointer at x 989. In the second case a horizontal line sits at the bottom of the target column. On 1 November Sam made the same five drags with the real trackpad on Miri Developer with engine ffe0673, and all five landed under the pointer, so #203 is closed.

CI speed

#184, now closed, tracked making CI fast enough for agent iteration, one PR per change. On 30 and 31 October the macOS job took 8 to 15 minutes, and runners started within 4 s. The engine test binary alone took 403 to 620 s, and every run compiled the engine dependencies from nothing three times. GitHub's macOS runner gives Bevy one compute thread, against four on Sam's Mac, so the engine tests run far slower there.

  • #185 caches engine build artifacts, saved only from main. A warm run took Clippy from 103 s to 28 s and the engine build from 176 s to 46 s.
  • #186 skips the macOS job for documentation-only pull requests, verified in CI with throwaway #187.
  • #188 adds a pre-push hook for the fast checks. Run make setup or git config core.hooksPath .githooks once to turn it on.
  • #191 treats Swift warnings as errors.
  • Optimizing engine dependencies in the dev profile cut the local engine tests from 415 s to 99 s, in #194.
  • The macOS job is split into four parallel jobs: Swift, engine lint, engine tests, and the app build with selftests and packaging. The engine tests job is the slowest.
  • Measured on 1 November over the GitHub Actions API: 89 green runs from 28 to 31 October took a median of 9.9 minutes, p90 14.3. The 19 green runs after #194 took a median of 5.2 minutes, p90 7.0. Documentation-only pull requests take 12 s.
  • The engine tests job was then the critical path, a median of 299 s, with 182 s spent in the 1146 tests. Bevy's multi-threaded executor woke its threads on every frame, so 39% of the suite's CPU time was kernel time.
  • Production already ran its frame schedules on the single-threaded executor, but the test apps did not. Engine PR #107 makes both call one function, so the tests match production. On Sam's Mac with 3 test threads, the suite went from 68 to 90 s to 26 to 28 s. #217 pins its merge commit 68365bc. In CI there, the 1152 engine tests ran in 38.5 s, against 182 s, and the whole run took 2.75 minutes.

Rust 1.99

#182 moves rust-toolchain.toml and README.md to Rust 1.99.0. This branch pins 1612ae2, the merge of engine PR #97, with no change from the tested tree. Clippy 1.99's assert_is_empty lint failed 16 engine test assertions written after the earlier fixes 9878143 and c87ec14, and #97 rewrites them as Clippy suggests. No production code changed. On 31 October, Miri Developer built with 1.99.0 restored all 14 windows, answered query state, and moved focus with send-cmd.

Upstream Paneru sync

#137 is implemented in engine PR #96, merged as 3cb2fa0 with no change from the tested tree. This branch pins 3cb2fa0. Sam decided on 1 November 2026 that this sync was a one-off, so the release-tag rule in ADR 0003 stays. It brings the 8 upstream main commits up to 425d37a that miri/v0.5.2 lacked, as a merge commit so later syncs keep upstream ancestry. Sam chose on 31 October to take all 8. With Miri's settings, the visible changes are lower idle CPU (cc55f94) and a stray-tab check that no longer queries every app each second (425d37a). The paging swipe, the virtual workspace fix and the overlay fix change nothing by default, because Miri never turns on paging, virtual workspaces or the focus border. Upstream 425d37a could leave a Ghostty stray-tab column for good, so the engine keeps rechecking while an overlapping column exists. Live on 31 October, nobody touching the Mac, the engine used 6.92 s of CPU per idle minute at e7742b8 and 2.83 to 3.48 s at the tested head. Not checked live: a Ghostty stray tab folding, and a slow app's focus retry, which can now fire about 125 ms late when nothing else wakes the engine.

Held edge shortcuts

#169 is implemented in engine PR #88, merged with no change from the tested head cd5fdb5. This branch pins 378093f, the merge of #88 and engine PR #89, whose files match the combined build tested live below. Sam decided on 30 October that a held edge shortcut stops at the strip's end and only a fresh press crosses to another display. The engine's event tap ran every key auto-repeat as a new press, so holding ⌃⌥⌘⇧← for one repeat moved the column to the first position and then onto the next display. The tap now reads the auto-repeat flag, and a repeated edge command reaches the strip's end but never crosses. Sam then decided that a held key whose fresh press crosses does nothing more, because its repeats carried focus or the column on to the far end of the new display's strip. The fresh press now records whether it crossed, and the repeats of that hold do nothing after a cross. Live on 30 October, a held ⌃⌥⌘← or ⌃⌥⌘→ from a strip's end crossed once and stayed on the window a single press lands on, and a held ⌃⌥⌘⇧← took a Finder window across once and left it where it arrived. On 30 October, with the built-in display left of the external display, the release build carried focus onto the built-in display after one fresh ⌃⌥⌘← press and 3 synthetic auto-repeats. The build from this branch stopped at the first column, a fresh press then crossed, and 8 more repeats stayed put. No window moved. The keybinding reference now records the rule, and this settles the held-key question for #79.

Empty display focus from Finder

#168 is implemented in engine PR #89, merged with no change from the tested head 2c83c97. This branch pins 378093f, the merge of #89 and engine PR #88, whose files match the combined build tested live below. It therefore carries #88's parser hashes in scripts/check-compat.py, the same as #172. Display focus on a display with no window brings Finder to the front with that display's desktop as key window. When Finder was already in front, macOS ignored that request, so ⌃⌥⌘ toward an empty display did nothing from a Finder window. The engine now fronts its own window-less process first in that case, which makes the desktop request a real switch without a pointer move. On 28 and 30 October, live probes with the same macOS calls confirmed the cause and the fix: fronting any window-less process first, the running engine included, moved the menu bar to the target display in both directions, and the old calls never did. On 30 October, the combined build passed the end-to-end key press with the built-in display below the external display and emptied through a second virtual workspace. From a Finder window, ⌃⌥⌘↓ moved the menu bar and focus to the empty display, pressed once or held, and ⌃⌥⌘↑ came back. With the built-in display left of the external display and emptied, a held ⌃⌥⌘← from a Finder window in the first column crossed once to it. The build without the fix did nothing in both cases, and ⌃⌥⌘↓ from another app still worked. This settles the Finder note from the #79 checks. On 31 October, merged main at 845a13a passed the same key presses with ⌃⌥⌘↓, J, and S, and the issue's last criterion was built in engine PR #94. When macOS ignores the desktop focus, the engine now logs a warning and flashes "macOS did not switch to the other display" if the display is not active 500 ms after the request. A local build with #89's fix turned off showed the message and the log line from a Finder window, and the real build showed neither. Engine PR #95 keeps flash messages opaque until their last 300 ms, because the old linear fade left this message hard to read. It reached main with #181, whose engine pin 3cb2fa0 contains it. #168 was verified on main at dd1cfd3 and closed on 31 October.

Focus after moving a window to another display

#183 is fixed in engine PR #98, merged as 51d3925 with the tested tree. A window that moves to another display keeps its focus, so macOS sends no focus event, and the display used to return to the window used there before. The engine now records the moved window for its new strip when focus follows the move, and a move that leaves focus behind changes neither display's memory. On 31 October, the live repro passed on the fixed build: a Finder window moved to the built-in display, and focus came back to it after a visit to the other display. Drags across displays and virtual-workspace moves were not checked.

Arrow, HJKL, and WASD shortcuts

#79 is done and closed. #119 merged it as 45d73f0, with the engine half from engine PR #67. The keybinding reference records the version 4 table and its upgrade rules. A declined review nit remains: when several bindings hold an upgraded default's new chords, the Settings notice names only the first.

On 31 October, Miri Developer built from main at 48b104e with engine e7742b8 passed every #79 check live with real key events on all three key sets. A version 3 copy of Sam's configuration upgraded to the same version 4 bindings Sam has, and the engine bound 71 distinct chords, none with Fn. With the built-in display left of the external display, as Sam uses it, focus and moves crossed both ways, a single column and a two-window stack reached each strip end and crossed whole, and nothing crossed further left. A held edge shortcut stopped at the strip's end, and a fresh held press crossed once and stayed, as the held edge section above describes. With the displays stacked for the session and the built-in display emptied through a second virtual workspace, the within-strip actions, both edge stages, display focus and moves, insertion after the active column, ⌃⌥N, two- and three-window stacks, and an empty destination all passed. Focusing the empty display worked from a Finder window too. When the destination's last-focused window had closed, display focus went to its most visible window. Settings > Shortcuts listed all three chords for every directional action and the four replaced actions with no shortcut. No window outside the test changed display or order, and the pointer never moved.

Windows return to their display

#142 is implemented in engine PR #75, merged as f20b00e. Its tree matches the tested head e1705af. main pins 5075a4a, which also carries engine PR #76 for #103 and engine PR #77, a test deadline. macOS moves Spaces between displays when a display disconnects, and on replug it hands the main display every Space that existed before, the built-in display's own Space included. The engine followed the Spaces, so the built-in display's windows stayed on the external display. The engine now records each display's columns and stacks on the first event of a display change, before macOS destroys any Space, in src/ecs/display_homes.rs. After each reconcile pass it moves back every window whose Space macOS moved to another display or destroyed, in its old order and stack. Windows that never left keep their place, a window opened meanwhile stays where it was opened, and a window moved to another Space on purpose stays there. ADR 0008 and the display section of the release scope now describe this behavior.

On 28 October on Miri Developer, a software unplug of the external display (SLSConfigureDisplayEnabled, app-only) reproduced the bug on engine c1f2b5e: the built-in display's windows ended up in a hidden Space of the external display. On engine fa1f098, three unplug cycles with stacks on both displays returned every window to its display, order, and stack, and a window opened while unplugged stayed on the built-in display. One cycle also hit the hardware variant: macOS destroyed the built-in display's only Space while it held windows, and they still came back with their stack. Turning the built-in display off in software could not be undone in software, and only a sleep and wake brought it back. After that wake, the engine dropped six windows whose AX elements died, which is #147. Later on 28 October Sam ran a cable unplug, a lid close, and a sleep on engine e1705af, with the layout recorded every 4 s. The cable unplug returned every window to its display, order, and stack. After the lid and the sleep, the windows the engine still managed came back correctly, but #147 dropped others: their AX elements died and the engine gave up after 1 s. #147 is fixed in engine PR #104, merged as 589b44b. Its tree matches the tested head 83c0b05, and this branch pins it. While the screen is locked, every AX read from another process fails: each window element answers "invalid element" and each app's window list is empty. macOS moves windows between Spaces on wake while the lock screen is still up, and the engine closed each moved window 1 s later, still locked. The engine now waits for the unlock before it starts the 1 s wait for a window's AX element. On 1 November on Sam's Mac, a sleep and wake on Sam's daily build dropped the 3 windows on the built-in display, and a screen lock with no sleep made every AX read fail until the unlock. On the fix build, a sleep and wake kept all 16 windows, and the 2 windows on the built-in display kept their order on its new Space. A lid close with the external display connected, which does not sleep, kept all 14 windows on the daily build. Stacks across a lid close or a sleep were not checked in these runs. Later on 1 November, a lid close and a sleep and wake with stacks on both displays kept every stack, and #142 closed. Off-screen windows during a lock may still drop through two other engine paths, which #210 tracks.

Side-by-side displays

#103 is done and closed on 3 November 2026, with all 6 criteria ticked: a column hidden off screen stays on its own display, and its strip keeps it, with the displays side by side. It first closed and then reopened the same day, because 3 criteria were only partly checked. #156 fixed the last criterion that failed outright, a display move that moved windows between strips. verify-miri run 20261103-021450, on a build of main at c356ad7a, moved the built-in display below, left, right, beside only the lower part of the 27-inch display, and below again. In all 20 steps, no window had its centre on another display, and every window stayed in its strip. The same script on main at e1f74751, before #148, failed at launch and at every side-by-side step (run 20261103-021135). With an HP 840 G9 AiO beside the built-in display, runs 20261103-125245, 20261103-125446, and 20261103-125716 on main at a1d0e5ac saw an engine slice panel on the left and the right shared edges. With a maximized window focused, no panel shows, which matches free edges, where the maximized window covers the slivers. Sam swiped with four fingers on each display, side by side, and only that display's strip moved. Sam decided that the System Settings window that moved strips once in 36 Arrange-sheet drags stays a known limitation under #262. Not verified: three displays. The closing comment links the evidence for each criterion.

#103 is fixed by engine PR #76. The engine hid an off-screen column by leaving a sliver at mid-height and hanging the rest of the window past the display edge. With another display beside that edge, the window lay mostly on it, so macOS moved it into that display's Space and drew it there. On a display arrangement change, macOS first pulled such a window back onto its own display, the engine slid it back across the other display, and the engine's topology read then adopted it into that display's strip. position_layout_windows in src/ecs/layout.rs now checks the frame a window shows. If its center, or more of it, lies on another display, the window tucks below its own display's bottom edge. A frame that no tuck improves, such as one above a display directly below, stays as before. Free sides keep the mid-height sliver, so arrangements with the displays above and below each other keep their behavior. Sam chose the bottom corner on 28 October.

Engine PR #76 hung the window past the bottom corner on the side it hangs past, and sat it flush inside that side when a display was beyond the corner. Flush, the whole width of the window showed along the bottom edge, and Sam rejected that bar. Step 1 of #150 changes the tuck on engine branch miri/150-free-corner-hiding, at 6fda494. The window now hangs past a free bottom corner of its own display with 1 point of its width showing. A free corner has no display beyond it. The engine tries the corner on the side the window hangs past first, then the other bottom corner. Only when neither corner is free does the window sit flush, as before. Harness tests in src/tests/side_by_side.rs cover each case, and the two corner tests fail on 5075a4a. Sam ran the same change as prototype engine fa76e14 on 28 October with the displays side by side: no bar showed at the bottom, and every window stayed in its Space. The step 1 build passed the side-by-side live check on 28 October, including scrolling to both ends without moving windows between Spaces.

Step 2 of #150 is on engine branch miri/150-edge-slice-geometry, at ed67edd. When a column tucks, the engine now records an EdgeSlice on each of its windows in src/ecs/shared_edge.rs. An EdgeSlice holds the sliver the window would show with the other display moved away, in screen points and in the window's own points. For each display and side, showing keeps every front window of the hidden column nearest the edge in strip order. The engine logs that set at debug level as showing edge slices: [...] each time it changes. Harness tests in src/tests/shared_edge.rs compare each slice with the sliver of the same layout with the other display below. They also cover a hidden stack, the nearest column only, both sides of a middle display, free edges, stacked displays, an inactive strip, and scrolling the column back. On 28 October, the logged rectangle (0,38)-(5,1432) matched the same window's real sliver with the displays stacked. Step 3 adds the Settings switch and the Screen Recording prompt, in #152.

Step 4 of #150 is on engine branch miri/150-edge-slice-photos. The engine reads [options] shared_edge_slices, which is off when absent. With the switch on and CGPreflightScreenCaptureAccess true, each showing slice gets a borderless, non-activating panel at its place, one level below the dim overlay. src/ecs/slice_photos.rs holds the photo state of each slice, and src/slice_screen.rs draws the panels and takes the photos with ScreenCaptureKit. The first photo is taken when the slice shows, and the panel stays out of sight until it arrives. After that, a photo is taken every 2 seconds while the panel's occlusion state is visible, and the panel repaints only when the photo's pixels changed. A window gone or a failed photo closes its panel, and a refused photo closes every panel until the config or the permission changes. The engine logs each step at debug level, with lines that start edge slice. Harness tests in src/tests/slice_photos.rs cover the first photo, the refresh, repaint, stale photos, the occlusion cut, the switch and permission gates, and the closing cases. The prior live session on 28 October verified the granted switch, successful engine captures under Miri Developer's Screen Recording grant, a panel at the logged rectangle, matching edge pixels, and updates about every 2 seconds. Photos stopped with the switch off, under fullscreen, during display sleep, and while locked. The initial CPU diagnosis found repeated window enumeration, so engine 14a08e4 reuses each slice's capture filter. That engine also cancels unwanted pending photos and defers photos of moved panels while unseen. Sol approved those fixes in round two. On 29 October, with a release engine built from 14a08e4 and one slice showing, the engine used a median 1.77% of one core with the switch on (1.67% to 1.87%, 5 runs of 30 seconds) and 0.80% with it off (0.67% to 1.03%, 5 runs, alternated). A slice whose window changed every second measured the same, 1.77% to 1.87% over 3 runs. Each run took 15 photos, and the engine looked windows up 4 times for 83 photos. What limits the remaining 1 point of a core is not yet known.

Step 5 of #150 is on engine branch miri/150-slice-input, which this branch pins. The event tap holds back a press on a painted panel, in src/platform/slice_tap.rs, so the window beneath the panel never gets it. The engine then focuses the panel's window and scrolls it into view the way a focus command does, in src/ecs/slice_input.rs. The press lands on the pressed row, at least 12 points inside the window edge that faces the slice, because the outer 3 points are the resize border. In a live test, a press on the outer points reached no view in Helium or an AppKit window, and two quick clicks widened the window through the macOS double click on a window edge. Once the window rests on a display and the window server's hit test at that point returns it, the engine posts the press again at the HID level, tagged with its press id. The tap lets that press through, then the user's drags and release, so a drag goes on in the app. A release during the slide is held back, and the tap posts it right after the press. A press that the user dragged and released during the slide is dropped when it arrives, and the window stays focused and revealed. In a live test of a quick click, the tap saw the posted press before the user's earlier release. The release then reached Helium and an AppKit view at the panel, about 1190 points from the press, so a button would see a cancelled click. The tap now stamps each event with its time, the event timestamp for the user's events and the tap's own reading of the same clock for a posted press. A drag or release that the user made before the posted press went through now reaches the app moved as far as the press. A Sol review found that a second press during the slide reached the window under the pointer once the panel had closed, and the first press was then dropped. While a press waits for its window, the tap now drops further presses with their drags and their release, so a fast double click during the slide acts as one click. A second review found that this check went by the order in which the tap saw events, not by when the user made them. A second press made before the posted press went through, but seen after it, still reached the window under the pointer. The tap now notes when the posted press went through, and also drops a press the user made before that time. The tap now also keeps each dropped button on its own, so a held left press that expires no longer lets through the drags and release of a dropped right press. After 1.5 seconds without arrival, by event time in the tap and by frame time in the engine, the tap and the engine drop the press, and focus and the scroll stay. Hover over a panel reaches only the panel, and the tap drops scroll over a panel. The engine's window lookups treat a point under a panel as having no window, so focus-follows-mouse and click handling ignore the window beneath. The engine logs each step at debug level: edge slice press swallowed, revealing, arrived, delivered, gave up, expired, dropped, edge slice release moved with its press, and edge slice gesture passed through. Harness tests in src/tests/slice_input.rs drive the engine through the tap's own state machine, with the window server mocked, and fail when the hold, the arrival check, the release, the late release, the second press, the give-up, or the hover and scroll cuts break. On 28 October, a build at engine c99fbe8 delivered quick clicks with releases 30 to 150 ms after the press, double clicks, and 20-move drags to an AppKit test window and to Helium, and nothing reached the window beside the slice. On 29 October, Miri Developer built from this whole stack at engine e30ba61 ran with the displays side by side. Turning the switch on drew two slices on the shared edge, one per display, with the test window's playing video in its slice. Quick clicks of 30, 80, and 150 ms, a double click, and a 20-move drag on that slice reached the test window 12 points inside its edge, as one press and release at one point. A second press 20 or 30 ms after the first was dropped in 3 of 3 runs, once when the tap saw it after the window arrived but before the delivery, and the first press still reached the window as one click. Turning the switch off closed both panels. The give-up after 1.5 seconds is checked only by harness tests. Those checks used software clicks only, which carry nanosecond timestamps. At the HID tap, real trackpad and mouse events carry mach ticks, and the tap read them as nanoseconds. On Sam's M3, every real press after one delivered slice press was then dropped until Miri quit. Engine a0c88bf, merged as 863558c from paneru#83, reads each user event's timestamp on the engine's clock. On 29 October, a build at a0c88bf delivered 10 of 10 real trackpad clicks on slices, and no press was dropped. The same log showed a quick click's release landing as far again from the press, because posting the press had already moved the pointer. Engine 9e20d4a, from paneru#84, moves an early drag or release only while it is still on the panel. On 29 October, a press on a slice with its release at the moved pointer reached the window at (836, 700) for both, in 3 of 3 runs, where the previous engine released at (1670, 700). Revealing a tucked column used to pop it into place, and pushing a column off a shared edge made it vanish until a new photo arrived. Engine ec7fc7c, from paneru#85, lets photos stand in for windows wherever the real window cannot show at a shared edge. A revealed window's photo slides in with the row and the real window takes over under it, and a column pushed off the edge slides out under its photo. Each window keeps its last photo, so a later crossing paints at once, and every hidden column's photo follows its place in the row. On 29 and 30 October, Sam confirmed a slice click slides smoothly, and burst captures of rapid keyboard presses on Sam's M3 showed no bare edge. The first crossing after the engine starts now waits up to 1.5 s for its first photo, since the #199 fix in paneru#101. On 6 November, verify-miri runs on main at 6edff137 with engine ee89647 did not reproduce #166, which closed that day with every criterion verified. After a fresh engine start, a fixture window, a stack of three fixtures, and a stack of three Helium windows each slid under a photo on their first crossing, 280 to 380 ms after the photo was asked for. Each stack slid back in under its photo, and a second crossing used the kept photo. Half-second stalls after some focus changes are #162, and short gaps between ordinary columns are #163, described below.

On 28 October on Miri Developer, Sam's built-in display moved from below the 27-inch display to its left. On engine 8b450d6, Grok moved from the 27-inch strip into the built-in strip, which parked it back over the 27-inch display. Moving the display back put Grok at the end of the 27-inch strip. On engine cc9955a, the same move tucked Grok into the 27-inch display's bottom-left corner, in its own strip and Space. Scrolling the 27-inch strip to its last and first columns kept every window in Space 515, and moving the display back left every strip and order unchanged. macOS keeps about 32 points of a tucked window above the bottom edge, so the visible corner is about 5 by 32 points.

Window contents on shared edges

#150 step 3 adds the Show window contents on edges next to another display switch to Settings > Layout, off by default. It writes [options].shared_edge_slices. Turning it on calls CGRequestScreenCaptureAccess, and without the permission the switch stays off with a note and an Open Screen Recording Settings button. Each setup recheck calls CGPreflightScreenCaptureAccess and turns a saved switch off with the same note when the permission is gone. ADR 0002 now allows this one Screen Recording use. This branch sits on top of steps 1, 2, 4, and 5, so the switch turns on photos and input that already work. On 28 October, the step 3 build showed the macOS prompt naming Miri Developer.app, kept the switch off with the note after Deny, and showed no second prompt on retry. Its button opened the Screen & System Audio Recording pane, which listed one Miri Developer.app entry. After Sam granted that entry, the switch stayed on and wrote the key, with no prompt. Not checked yet: a grant revoked while the switch is on.

#196 fixes a blank photo of Notion on the shared edge, which showed the desktop instead. Notion draws nothing while its window is parked, so a photo of the parked window is 99.95% clear. Two engine faults let such a photo through. A frame that moved focus ran about 200 ms, and the 250 ms photo wait counted from that frame's start, so Notion parked about 50 ms after the photo was asked for. The engine also painted and kept any photo, so a blank refresh replaced a good one. The wait now starts in the frame after the ask, and a photo with at least 99% of its sampled pixels clear leaves the panel and the kept photo as they were. On 31 October, with Notion beside a 920 pt gap on Sam's built-in display, the base build showed 96.9% desktop in the gap on 2 of 2 crossings, and the fixed build showed 0% on 6 of 6, both 1 s and 6 s after Notion parked. #199 fixes the same gap right after each Miri start. The first photo after the engine starts took 1.02 s while the engine restored 16 windows, so Notion tucked before it and the photo came out blank. A window heading for a tuck now waits up to 1.5 s for its photo, and a blank first photo of a window still in view asks for another. On 1 November, after each Miri restart with no crossing, main showed 96.9% desktop in the gap on 2 of 2 restarts, and the fix showed Notion on 6 of 6, at every sample from 1 s to 10 s. A first photo took 778 ms on one of those restarts.

Gaps between columns while the row scrolls

#163 is partly fixed by engine PR #87 and engine PR #90, merged into miri/v0.5.2 at 5bee78b and 986c53c. Engine PR #87 writes one app's windows from the back of the motion to the front, and keeps Helium's AXEnhancedUserInterface off for a whole motion. That removed most gaps between windows of the same app. Gaps between windows of different apps stayed, because each window eased to its target on its own while apps take a move at different speeds: Spotify at once, Helium 15 to 40 ms later, and up to a few hundred ms later right after a focus change. Engine PR #90 paces the row at its slowest window. While a row animates, no window gets closer to its target than the column behind it, as the window server shows that column. Sliding photos at the shared edge are held the same way. A window whose app took every move of a whole motion in time paces from where the engine puts it, so a row of quick apps moves together. A window that shows no progress for 250 ms stops pacing the row, so a hung app cannot stall it. The shown frames are read on a thread of their own, on a window server connection of their own: on the main connection the read waited for the engine's own drawing and froze the engine for 500 ms at a time. On 30 October, 4 alternating rounds of 12 presses on Sam's 27-inch row, with fast-release engines on top of #162, measured desktop between different apps at 359 ms per burst without pacing and 0 ms with it. The row starts and finishes about as fast as before, and quick apps pause more often mid-motion: 12 pauses over 40 ms per burst instead of 7. The gaps left come from windows that park or un-park, which snap without an animation, so #163 stays open for them. Engine PR #91, merged into miri/v0.5.2 at e7742b8, fixes two cases that #90 left. When focus moves from one app to another, pacing forgets what it saw of both apps' windows, because an app can turn slow right after a focus change. In 3 sessions of 10 tries that switch apps and reverse at once, desktop showed in 1 of 10 tries each time before this fix and in none after it. A photo sliding in at the shared edge now closes only once the window server shows its window at rest, or after 250 ms. Before, it closed as soon as the app took the move, and the bare desktop flashed where the photo had been. The slice panels also take their places before the frame's window writes, so the window ahead no longer moves a frame before the photo grows beside it. With shared-edge photos on, photos now slide columns across the shared edge both ways, so parking and un-parking no longer snap there. Engine PR #128, merged into miri/v0.5.2 at ee3e3b5, fixes two gaps left at a shared edge, with tests that fail before each fix. The column ahead of a slow window now waits while that window leaves for its tuck, and while it lands under its photo. On 6 November main showed the landing gap in 2 of 7 rounds with a slow stand-in app, and an engine log showed the column ahead moving on while the window server still showed the slow window at its tuck. The fix is not yet checked live: the stand-in stopped taking its moves in later runs, and a real Helium window never showed the gap, even on main. A live check needs a reliable app that shows each move about 60 ms late. A fast window next to the shared edge also went missing for a few frames, about 110 ms over 24 rounds on both builds, for a cause not yet found. Engine PR #125 fixed a slice photo test that failed about 1 run in 100 under load. #163 closed on 6 November after a live check on main at 159d9419: in 24 rounds, desktop showed beside the slow window 0 times, against 5 on the build before. A photo that shows a frame late at the shared edge remained, as #316, which paneru#146 fixes at the edge. Engine PR #129, merged at 6c15fd6, fixes the engine test the_command_and_a_double_click_toggle_one_state, which had failed since engine PR #126. The maximize command and the system that ends width runs had no order, and #126 changed the order Bevy picked, so the command's restore lost its anchor. Quick apps pause a little more often mid-motion: Spotify paused 4 times over 40 ms per burst instead of 2. A focus move between two windows of one app still resets nothing, and one burst of 4 showed 33 ms of desktop between two Helium windows. The recordings and frame captures were removed for privacy and stay local.

One strip per native Space

#89 is fixed by engine PR #73, merged as ba198d9. Its tree matches the tested head ade3f05. main pinned c1f2b5e, which includes ba198d9. Mission Control can drag a window to an inactive Space, and the engine learns of it only when that Space activates or a display topology read runs. On a topology read, place_extracted_strip in src/ecs/display.rs reused the Space's first row only when that row was empty. Otherwise it opened a new virtual row parked at the display corner, and v1 has no UI for that row. Windows from a removed display took the same path. The engine now appends the moved windows, with their columns, to the end of the Space's selected strip, and opens a strip only when the Space has none. The engine's workspace popup flashed the row number on every native Space switch, which read as Miri making extra desktops. The engine now defaults workspace_popup_status to false, as it does for focus_follows_mouse. Miri cannot write that key into existing configurations, because they define only [decorations.menu], and Miri refuses to add keys to the implicit [decorations] table.

On 27 October on Miri Developer, a Finder window dragged in Mission Control to the other Space, followed by a 1-point app-only display origin change, landed alone in a new row 3 on engine 1fd95b1. Focusing it hid the Space's other windows and flashed "3". The same steps on engine fca146c appended the window to the end of the destination Space's visible strip, and focusing it changed no row. On engine 4e26369, four native Space switches showed no number, and Settings showed no configuration error. Rows that older builds saved stay in the session until their windows move. On Sam's Mac, virtualmovenum moved Spotify and one Helium window back into their Spaces' visible rows. On 28 October Sam passed the original Mission Control drag by hand, and the same drag followed by sleep and wake. Unplugging and replugging the external display left the built-in display's windows on the external display, as on main. #142 tracks the planned restoration that Sam decided, and #143 and #144 track the cross-display drag and broken stacks.

Apps busy at launch

#63 was partly fixed in engine PR #74, merged as c1f2b5e. That fix admits an app that blocked its main thread for more than 5 s right after launch, once it answers. An app that answers but is busy again by the next registration waits 1, 2, 4, 8, 16, then 30 s between tries.

Engine PR #109, merged as e8e62c5, fixes the rest. This branch pins e8e62c5, whose tree matches the tested head 6956ac3. Before it, a window whose subrole read or window-id read timed out was rejected as not a window, and the launch scan ended once the app's other windows answered. Focus discovery also cached a window it could not read while its app was busy, so later focuses of that window did nothing. Timed-out Accessibility calls now return Error::CannotComplete. The launch scan lists an app again on each tick, until its 10 s launch timeout, while any window did not answer. Focus discovery caches a window only when its app answered.

On 1 November, live fixture checks on Miri Developer gave these results, with the scripts in /tmp/miri63 (may be gone).

Checkmain with engine 68365bcFix
One window's subrole read sleeps 350 ms for 2 s2 of 9 windows unmanaged at 8 s0 of 18
One window's title read sleeps 600 ms, so the next window-id read times out2 of 12 with only the subrole fix0 of 30
One window answers late for 14 s, then is focused at 12 s and 16 s4 of 12 unmanaged at 19 s0 of 12
Six apps block 300 of every 350 ms for 10 s1 of 360 still unmanaged at 16 s0 of 360

The retry runs on the engine's main loop. While one window kept timing out for 14 s, the engine answered state queries in a median 109 ms for about 10 s, against 22 ms on main, which instead stalled once for 1.2 s. With six busy apps, query stalls over 100 ms totalled 3.6 to 5.6 s per launch, against 4.3 to 7.4 s on main.

Freeze after a keyboard focus change

#162 is fixed by engine PR #86, merged into miri/v0.5.2 at 8b026d6. After many focus changes, Helium's focus notifications name the macOS caret indicator, a CursorUIViewService window that gets a new id on each text-field focus. The engine tracks no such window, so auto_discover_unmanaged_focused_windows in src/ecs/systems.rs asked every app for its focused window and its window list on the main loop. Each question waits up to the 250 ms Accessibility timeout, so one busy app froze the row for about 500 ms, and the animation then took one capped step. The engine now asks the window server which process owns the window, and asks only that app, or no app when the engine tracks none. Harness tests in src/tests/interaction.rs fail on ec7fc7c.

On 30 October on Miri Developer with a debug engine, a test app that blocks its main thread 950 ms of every second reproduced the freeze on ec7fc7c. In 16 Control-Option-Left and Right presses on the 27-inch row, a discovery scan took up to 714 ms, and 4 frames took 250 ms or more, the longest 733 ms. On the fix, the same presses gave no frame of 150 ms or more, the longest 118 ms, and each discovery took 0 to 5 ms without asking any app. On ec7fc7c, scans that met no busy app still took 35 to 124 ms after each focus change into Helium. Frames of 40 to 120 ms still follow most presses on the debug engine, from other per-press work. Sam's 29 October freeze is the same mechanism by inference: two 250 ms timeouts made the 490 ms gap, and the resolver timed out on a busy app during it.

Re-tiled window slot

#125 is implemented in engine PR #69, merged as d829a65. Floating a tiled window now records its slot, and tiling it again returns it there while its row is on screen. For a floated window, that slot wins over a rule's index. Minimized windows keep their old behaviour. Its tree matches the tested PR head 10ea677, and main's engine pin 1fd95b1 includes it. This branch removes its entry from Known limitations.

On 27 October, a live check with three Finder windows ran on Miri Developer. On the maximize-app build, a floated window that popped right re-tiled after its neighbour. On this branch's build, the same geometry returned the window to its slot. Sol approved the engine change in round 2 of sol-loop, after round 1 found the rule index case. Later on 27 October, a build of main with engine 1fd95b1 passed the same check: B popped from x=490 to x=522, right of its neighbour A, and re-tiled between C and A.

Engine panic when stopped during startup

#121 is verified on main at 7499990 and closed. The fix is engine 1fd95b1, the merge of engine PR #71. The ECS used to get Config from an Event::InitialConfig copy in the event channel, and a stop during startup queued Event::Exit ahead of it. The first frame then ran without Config and panicked with "Resource does not exist". The engine now inserts the config at build time, and Event::InitialConfig is gone. A clean exit then saved the empty startup layout over the session, so the engine now drops StateFile when an exit arrives while startup gathers processes. A probe launched the real engine with temporary XDG folders and its own Mach service name, then sent SIGINT 25 to 100 ms after launch. Before the fix, 10 of 10 runs panicked. With the fix, 12 of 12 runs exited with code 0 and kept a seeded 11-column session. The real app on Sam's desktop, quit with AppleScript, exited without a panic in 8 of 8 runs. A quit that reached the engine before session restore saved an empty session (13 columns to 0) in 4 of 4 runs. That interval runs from about 0.1 s to 0.7 s after the engine starts, and #134 tracks it. A quit after session restore kept all 13 columns in 5 of 5 runs, and the next start restored 15 windows with none missing. #134 is now fixed, so that row is gone from Known limitations.

Session kept when the engine exits before startup finishes

#134 is fixed by #136, merged as f8cffa0 with engine 8b450d6 from engine PR #72. The exit save wrote the half-built startup layout, which has strips but none of the windows startup is still discovering. The engine now skips the exit save while Initializing exists. On 28 October, Miri Developer built from main at f8cffa0 kept a 9-column session byte-identical for AppleScript quits 0 to 300 ms after the engine started, and saved all 9 columns on a quit at 2 s. #134 is verified and closed, and this branch removes its row from Known limitations.

Known limitations inventory

#91 is implemented on this branch as Known limitations. The README and SUPPORT.md link to it. The audit filed #120 to #125 for confirmed defects that had no issue, and Sam chooses each fix. #120 is fixed and closed, so the page no longer lists it. Add each newly confirmed limitation to that page, and remove an entry when its fix is verified.

main pins engine 0d023e2, the merge of engine PR #70. Docker Desktop runs as a menu-bar app until its dashboard opens, and then it becomes a regular app. The engine dropped such an app when it was not regular at engine start, or 5 seconds after its launch, so the dashboard was never tiled. The engine now keeps the process, and its activation-policy observer starts a new launch watch when the app becomes regular. Sol approved engine PR #70 in round 1 of sol-loop. The pin also brings engine PRs #67, #68, and #69, which open PRs #127 and #129 pin on their own. Engine PR #67 added the focusedge and moveedge verbs, so EngineCommand.swift now mirrors them and check-compat.py records the new parser hashes.

The test a_menu_bar_app_that_becomes_regular_later_is_tiled fails before the fix and passes after it. On 27 October, a live check on Miri Developer closed the Docker dashboard, restarted Miri, and opened the dashboard from Docker's menu. The main build at 7bbecc5 never tracked the dashboard. The fixed build tiled it as a full-height column and focused it. #133 is verified on main at b8887ef and closed.

Windows of busy apps on a hidden Space

#68 is fixed on main. The fix is engine PR #68, merged as 23bcf1b, which main's engine pin 0d023e2 includes.

The startup brute-force scan stopped after 250 ms and at element 0x7fff. An app numbers its elements in creation order, so a busy app's newest window sits past both limits. On 27 October a new Helium window got element 32890, and the scan gave up at element 15616. The scan now runs until every window resolves or 0x4000 ids in a row own no window element, with a 2 s ceiling.

On 27 October, /tmp/miri68/repro.sh placed Helium windows 55561 and 62288 on a hidden Space and relaunched Miri Developer. Engine 3fee523 left both unmanaged with restore missing=2. This branch managed both on 4 of 4 relaunches, and restore reported missing=0. Engine start to initialization went from 0.75 s to about 1.2 s on Sam's Mac.

On 27 October, a build of main at b8887ef with engine 0d023e2 passed the same check on 3 of 3 relaunches. Helium windows 64426 (element 33795), 55561, and 63045 on a hidden Space were managed, and restore reported missing=0. A Helium window minimized before the relaunch joined its strip when it was restored, without focus. #68 is closed, and this branch removes its entry from Known limitations.

Engine left running after Miri dies

#33 is implemented on this branch. The fix is engine 4158e8f, the merge of engine PR #61, which the engine pin 3fee523 from main includes. Sol approved engine PR #61 in round 2 of sol-loop, after round 1 found that an exit during engine startup was lost, which aa10128 fixes. Miri passes its PID to the engine in PANERU_PARENT_PID. The engine watches that process with a kqueue NOTE_EXIT filter and exits through its normal cleanup when the process ends. Miri also treats SIGTERM as Quit, so kill stops the engine and recovers windows. At launch, Miri stops any miri-engine whose parent is launchd, then recovers windows, because such an engine holds the Mach service.

Unit tests cover the parent watch, the environment, and finding and stopping an orphan.

On 27 October, /tmp/miri33/live-check.sh ran against the real app, with the azerty-digits build as the build before the fix. After SIGTERM on that build, its engine survived with parent PID 1. The fixed build stopped that orphan at launch. SIGTERM on the fixed app ended the app within 400 ms, together with its engine. SIGKILL on the fixed app ended its engine within 600 ms. The first run found that the SIGTERM handler deadlocked, because it called terminate inside a main-queue callout, and the fix is on this branch. That run also showed that an engine interrupted during its startup can panic with "Resource does not exist", which #121 tracks.

Maximized column

#106 keeps a maximized window in its row, so focus moves and swipes scroll past it. Maximizing a window records the rules Sam chose on 26 October. Engine 3fee523 and every later pin include engine PRs #57, #58, #60, #63, and #64. They make a maximized tiled window a display-wide column, make maximize the one toggle with fullwidth as its older name, and add [options].maximized_window_side. They also restore a maximized window before width, stack, float, and drop actions, and keep maximize in the saved session across an engine restart. The pin also brings the Picture-in-Picture level fixes from engine PRs #56 and #59, and engine PRs #53, #55, #61, and #62.

This branch builds the app part. Layout settings has a Maximized stacked window opens picker, and the ⌃⌥F default is now window_maximize, labeled Maximize or restore window. A file that binds window_fullwidth keeps that binding, and Settings lists it under the same action. After a restart, the engine still focuses the first column, so a maximized window in another column comes back partly in view until it is focused, like any wide column. An unfocused Lua SetWidth stores its ratio but never applies it, for any window. This predates #106.

On 27 October, this branch's build at engine 3fee523 ran a live check on the 2560 by 1440 main display and the built-in display, with throwaway Finder windows and synthetic input. First, the build from main reproduced the defect: a maximized window filled 0,30 2560x1410, and after a focus move its neighbor showed on top of it at its slot. With this branch, a maximized window filled 0,30 2560x1410, and focus moves east and west scrolled past it 3 of 3 times with no overlap and no change of size. ⌃⌥F, through Sam's window_fullwidth binding, and a title-bar double-click toggled the same state, and restore returned the exact earlier slot. On a floating window, both filled the usable area and restored the exact floating frame. A stacked window popped out to the right, and to the left after the Settings picker was changed to Left of its stack, and restore put it back in its stack slot. Grow restored first and then grew from the earlier width, a stack command restored first, and a new window opened beside a maximized window, which stayed maximized. Display moves filled the built-in display and then the main display again, and no native full-screen Space appeared. After the engine was killed, the session file held the maximize with width share 0.5, the restarted engine showed the window maximized, and the toggle restored its 1276 point width. Synthetic title-bar drags of the maximized window did not register, so the drag rules and the pointer edge resize are checked only by engine tests. The acceptance ledger rows stay Pending until a frozen release candidate runs them.

AZERTY digit shortcuts

#31 is implemented on this branch, which pins engine 28f550f from engine PR #53. On AZERTY only the keypad types digits without Shift, so the engine bound ctrl+alt-1 to keypad 1. A key name now matches a main key's character, then a US position, then a keypad key's character, then a fixed key. The digit names therefore press the top row, and keypad1 and the other keypad names still press the keypad. KeyLayout.keyCode(for:) follows the same order, so Settings labels ⌃⌥1 as ⌃⌥& on AZERTY, which is the key's unshifted character.

On 26 October, a probe test that calls the engine's own generate_virtual_keymap ran with the French layout selected. Engine d7a4c3f resolved 1, 2, and 0 to keypad codes 83, 84, and 82, and the fix resolved them to top-row codes 18, 19, and 29. The fixed app's engine, started under French, logged the same top-row bindings for ⌃⌥1, ⌃⌥2, and ⌃⌥0. Synthetic key presses were unreliable in that run with both builds, so no press result counts as evidence.

macOS Fill near the screen bottom

#108 is fixed on this branch, which pins engine 594a6c5, the merge of engine PR #52. After macOS Window > Fill, a tiled window now returns to its slot instead of keeping the Fill height past the display's bottom edge.

On 26 October, a throwaway Helium window in its slot at 1352,38 1200x1394 on the 2560x1440 display ended at 1352,38 1200x1410 after Fill, 8 points past the bottom. The engine harness reproduced it with the mock's sticky bottom, which models macOS ignoring a small shrink near the screen bottom. The tile restore had turned off the size check that lowers and regrows a window in that case, and then kept the frame as the app's refusal. With engine 7bcd7d6, 3 of 3 Fills returned the same window to 1352,38 1200x1394, and a Fill on the built-in display left it at its slot. These are automated observations driven by menu clicks through Accessibility, not physical input.

Title-bar maximize

Engine e6c8ebc, the merge of engine PR #43, built this for #88, and the branch now pins the later engine d7a4c3f. The release scope records the rules. A double-click on a window's top bar fills the usable area of its display, and the next one restores it. The window keeps its place in the layout while maximized, so its neighbors never move. #106 replaces this model with a maximized column that stays in its row.

All of #88's code is on main through #97, #99, and #116, and ⌃⌥F and the double-click send the same ToggleMaximize trigger. Engine PR #65 adds a test that a maximized window on an unplugged display stays maximized and restorable, which no test covered.

On 27 October, the build of main at fd16a22, with engine 1fd95b1, passed the #88 criteria as #106 revised them, except the gaps below. Since the earlier check on 3fee523, that engine adds display moves from engine PR #67, the re-tile slot from #69, and the hidden-Space scan from #68. A tiled Finder window filled 0,30 2560x1410 and restored its exact frame. Its neighbors kept their sizes, scrolled aside while it was maximized, and returned to their exact frames on restore. Four cycles that mixed the double-click and window maximize returned the same frames, and focus moves scrolled past the maximized window. A stacked window returned to its stack slot, and with its stack mate closed it became a full-height column at its old width. With stack_limit = "fit", a window maximized out of a full 7-window stack returned as a column right after the stack once the stack was full again. A floating window filled the usable area and returned to its floating frame by both triggers. movedisplay south filled the built-in display at 302,1486 2048x1286, the restore there kept the width share, and a move back north restored on the large display. The window stayed maximized while its display switched to another native Space and back. System Settings filled the height at its 740 point width limit, a throwaway Helium window passed 2 cycles on its tab strip, and AXFullScreen stayed false. A double-click on Finder's view switcher left the window alone, a title-bar drag reordered the row, and the close button closed the window. These are automated observations on real windows, driven by synthetic mouse events and engine commands, not physical input. A follow-up run on the same build covered the rest. A window maximized out of a 3-window stack rejoined at the clamped slot after a mate closed, and a stacked window moved to the built-in display restored as a column there. A maximized floating window dragged to the built-in display filled it and restored at its earlier size and offset. A maximized tiled window dragged to empty space on the large display filled it and joined its layout. Sam unplugged and replugged the large display by hand, and the maximized window on it stayed maximized and restored at its earlier width. Not checked on this build: physical mouse input, and side-by-side displays, which wait for #103. #88 is verified and closed, and the issue comment records each criterion.

On 26 October, current main with engine 6325638 reproduced the defect on the primary display. A double-click on a Finder title bar let macOS zoom Finder to 868 points wide, and the engine took that as the column's width, which moved T3 Code 156 points left. With the candidate, Finder, a stacked Finder window, a Finder window on the built-in display, a Helium window, and a floating Helium window each filled the usable area and returned to their exact frames. The other windows kept their frames throughout. A double-click on Finder's back button left the window alone. These are automated observations on the real windows, driven by synthetic mouse events, not physical input.

macOS ignores a small shrink that leaves a window's bottom edge near the bottom of the screen, but it lets a window grow into that zone. A restore therefore first ends 100 points above the slot and then grows into it. The same rule can also stop the layout from shrinking a newly opened window that touches the screen bottom into its slot. That layout case exists without this branch and may explain #58.

Maximized windows across displays

Engine ab3ce39, the merge of engine PR #49, which follows engine PR #44, built this for #87, and the branch now pins the later engine d7a4c3f. A maximized window that a display command moves, or that a title-bar drag drops on another display, fills that display's usable area. A new column that arrives from another display is never wider than the destination's tiling area.

On 26 October, the build of feat/title-bar-maximize with engine e6c8ebc reproduced the defect. A maximized Finder window moved south by movedisplay south stayed at 0,30 2560x1410 on the large display, while the engine placed its slot on the built-in display. Dragged by its title bar to the built-in display, it ended at -6,1505 800x1278, partly off the display. A Helium window on the built-in display had kept a frame 8 points wider than the tiling area from an earlier move, with its left edge off the display.

With engine 39de6ed, movedisplay north and movedisplay south made a maximized Finder window fill 0,30 2560x1410 and 182,1486 2048x1286, and it kept focus. That engine found the dropped window under the pointer, and its title-bar drag failed live, because macOS dropped Finder 18 points below the pointer. Engine 4f07509 chose the display by the window's centre, and drags in both directions then filled the same frames. Review found that a low drop within the large display could put the centre on the built-in display, while macOS keeps the window on the display under the pointer. Engine 7e937a1 therefore remembers the pressed window and uses the display under the pointer at the release. With it, title-bar drags down and up filled the same frames, and a low drop within the large display left Finder at 0,885 2560x1410, although its centre was over the built-in display. A drag within one display left the window where it was dropped, and the next double-click restored its slot. These are automated observations on real windows, driven by synthetic mouse events and engine commands, not physical input. Engine 0ce219c adds only a test for side-by-side displays, and that test fails without the fix. Later on 26 October, with the same fix, a throwaway Helium window passed the same display-command, drag, low-drop, and restore checks with the same frames. After macOS Fill, movedisplay south gave Helium 1264,1486 958x1270, inside the built-in display's tiling area. Sam checked the stacked arrangement by hand, with a real mouse, and steps 1 to 5 passed. With the displays side by side, the off-screen columns moved to the other display, so the side-by-side check could not run (#103). Sam also found that a drag of a maximized window showed no drop line, which a drag after a restore did show. Engine PR #49 shows the line while a maximized window is over another display, and the drop puts the window in that display's layout at the line. Over its own display the drag stays a free move with no line. With engine 1b55f53, a maximized Finder window dragged within the large display moved to 0,586 2560x1410, and the layouts did not change. Dragged onto the built-in display, it filled 234,1486 2048x1286, kept focus, and joined the built-in display's layout after the Helium column. The next double-click restored it to its new slot at 1636,1494 638x1270. The synthetic run could not show the line clearly, because the built-in display's full-width column puts it at the display edge. Sam then dragged a maximized window by hand on 26 October and confirmed that the drag works. Not checked live yet: a horizontal display arrangement, which waits for #103.

macOS Fill on a tiled window on the large display leaves it at 1352,38 1200x1410, so its bottom edge is 8 points below the display. Sam's build from keyboard-focus-pointer does the same, so this branch did not cause it.

Picture-in-Picture windows

#77 is implemented on this branch, which pins engine d7a4c3f. That pin includes engine PR #50 for this issue and engine PR #51, which stops a flaky SIGABRT in the engine tests. It also includes engine PRs #44, #48, and #49, which merged into the fork branch first. A window that its app keeps above the normal window level now floats when it opens, unless a rule sets floating = false. Such a window also takes keyboard focus once when it opens, because Chrome opens its Picture-in-Picture window without focus. The float toggle and tile overrides work on it as on any other automatically floating window.

On 26 October, the running build at engine 58e5c7d reproduced the defect with a throwaway Chrome instance. The Chrome video window and the Document Picture-in-Picture window both sit at window level 3 with the macOS all-Spaces tag. Miri tiled both, stretched them to the column height, and left focus on the previous app. With the candidate, both floated and took keyboard focus once, and Chrome became frontmost. Focus then moved to T3 Code and stayed there, and the video window stayed above every ordinary window. On the primary display, the video window belonged to both native Spaces and stayed visible above Notes after a Space switch and back. Closing it left the Chrome source window's tile unchanged, and an engine restart with the window open floated it without taking focus. The macOS system Picture-in-Picture window, owned by PIPAgent at level 19, is the one Safari and IINA use. Miri does not manage it, and it ignored the keyboard even when made frontmost, so it cannot take keyboard focus. IINA's player window, which Sam's IINA keeps on top with alwaysFloatOnTop, now floats too. QuickTime Player and IINA hide their player window during system Picture-in-Picture, and its Restore button brings the same window back. On 26 October at engine d7a4c3f, QuickTime came back at the same 640 by 360 frame in its old column, and the whole display layout matched the layout before Picture-in-Picture. IINA, which floats, came back at the same frame. For the fallback, QuickTime sat alone in virtual workspace 2, entered Picture-in-Picture, and the display switched to workspace 1. After Restore, it appeared on the shown workspace 1 at its old size, fully visible at the right edge of the display. Sam accepted the system Picture-in-Picture focus limit and chose that a Picture-in-Picture window stays on the display where it opened. These are automated observations driven through the Chrome DevTools protocol and Accessibility, not physical input. Private local evidence is in /tmp/miri77/ on Sam's Mac.

A recheck on main 12323e5 found a race in criterion 2 and criterion 4. In 1 of 8 trials, the first Picture-in-Picture window in a fresh Chrome profile tiled at 2013 by 1402 and did not take focus. Chrome shows that window at the normal level and raises it to level 3 between 20 ms and 90 ms later, and the engine read the level only once. Engine PR #56 reads a newly tiled window's level again each frame for one second. If the level rises, the engine floats the window, gives back the frame it opened with, and focuses it once. A native test app that raises its window 150 ms after it opens tiled on the old engine. With engine aba75ea, raises after 60, 150, 300, and 600 ms all kept the window floating at its own frame. The engine query leaves out that test app's floating-level window, before and after the change, but it lists Chrome Picture-in-Picture windows. A Sol review found that a window arriving without a size could be tiled before its level was known. Engine PR #59 leaves such a window untiled until its level check ends, so a raised window floats at the frame its app gave it. Both fixes reached main with the engine pin 3fee523 from #116. On 27 October, the build at engine 3fee523 floated and focused 3 of 3 first-time Chrome Picture-in-Picture windows, and the test app kept its frame for raises after 150, 300, and 600 ms. Private local evidence is in /tmp/miri77b/ on Sam's Mac.

Keyboard focus and pointer separation

#90 is implemented on this branch, which pins engine 58e5c7d, the squash merge of engine PR #41. Its tree is identical to the tested d4340b7. The engine now defaults focus_follows_mouse and mouse_follows_focus to false. Centre column, display focus, and display moves move the pointer only when mouse_follows_focus is on. Display focus on a display with no visible window focuses that display's Finder desktop, so its menu bar becomes active without a pointer move. [options].focus_follows_mouse = true is a hidden key that turns pointer-driven focus back on.

On 26 October, the running build at engine 4ba2bf0 reproduced every reported symptom with synthetic input and throwaway Helium windows. Pointer movement focused the window under it, and keyboard focus, Centre column, display focus, display moves, a new window, and red-button closes moved the pointer. The candidate build kept the pointer in place in all of those cases, and pointer movement no longer changed focus. Closing a background window kept the focused window, and closing the focused window changed focus once to its neighbour, in columns and in a three-window stack. An empty display, made with an empty virtual workspace on the built-in display, became the active display with Finder frontmost and no false focused window. The hidden key restored pointer focus, and the original configuration was restored byte-for-byte afterwards. These are automated native observations with synthetic events. On 26 October Sam closed a background window and the focused window with their red buttons, using a mouse and the trackpad, and reported that it works as expected. Sam also closed windows in an overflowing stack and reported that it works as expected. Private local evidence is in /tmp/miri90/ on Sam's Mac.

Constrained window width and tile override

This branch pins engine 6325638 for #93, which merges engine PR #42. The release scope records both accepted rules. A stacked window records the widest and narrowest widths it refused. A member at its widest no longer sets the column width, so it keeps its own size at the left edge and the rest of the column stays empty. A column also reserves a member's refused minimum width. Tiling a window with the float toggle while a floating rule matches it records an override in the engine's tile-overrides.json. The override survives an engine or Miri restart while that window stays open, and floating the window again or closing it ends the override. A window is known by its window number, process ID, and process start time.

On 26 October, current main reproduced the defect on the primary display. System Settings, which accepts no more than 740 points of width, sat above Spotify, which accepts no less than 800. The column took 748 points, so T3 Code covered the right 52 points of Spotify. With the candidate, the column reserves 808 points, Spotify stays fully visible, and the desktop shows to the right of System Settings. Unstacking, restacking, reordering, a width step, a second stacked column of two Helium windows, and a move to the built-in display and back all kept that spacing. Miri Settings stayed tiled after an engine-only restart, and floating it again or closing it removed its override entry. These are automated observations on the real windows, driven through the engine's command socket, not physical keyboard input.

Automatic floating of fixed-size windows

This branch pins engine ae05362. A window whose size macOS reports as not settable now floats when it opens, unless a rule sets floating = false. The float toggle tiles such a window with the same override as a rule-floated window. A window that opens floating now also takes focus. A floating window that is minimized, including one minimized when the engine starts, floats again when it is restored instead of tiling. Before, macOS reported its focus before the engine tracked it, so the float toggle acted on the previous window.

On 26 October, synthetic key presses drove the hotkey path on the primary display. A fixed-size test app, 420 by 332 points, floated when it opened and took focus. Control-Option-T tiled it and wrote its override. It stayed tiled after a full Miri restart, a second press floated it and removed the override, and quitting the app removed the override again. Stacked under Spotify with Control-Option-], it kept its 420 by 332 size at the column's left edge. System Settings stacked onto Spotify with Control-Option-] in an 808-point column, and Ghostty started past Spotify's right edge. Moving Settings to the top and moving focus within the stack by key kept that spacing. No physical keyboard or mouse input was used.

Fixed-size windows in stacks

This branch pins engine 3bdae94, which merges engine PR #46 and engine PR #47. On 26 October, Sam chose to give a fixed-size window's spare stack height to the other windows. A window that cannot be resized now keeps its own height in a stack, and the spare height goes evenly to the resizable windows. In a stack of only fixed-size windows, they stack from the top and the rest of the column stays empty below the last one. A window that opened floating now gets its padding when it is tiled, so it lines up with its column. Quitting an app now ends the tile overrides of its windows, as closing them does.

On 26 October, on the primary display with synthetic key presses: Calculator stacked under Spotify left Spotify 994 points with no gap or overlap. Digital Color Meter as a third member gave Spotify 777, Calculator 408, and Digital Color Meter 217 points of the 1402-point column. After a Miri restart, all three sat at the same left edge with 8-point gaps. Quitting Calculator emptied tile-overrides.json. These checks ran before the last engine review fixes, which tests cover. #101 tracks a false minimum height that a resizable window records when it refuses a small shrink at the bottom of the screen.

Refused width after a display move

This branch pins engine f9c4837, the merge of engine PR #54. A window moved to a narrower display gets a width scaled by the two displays' widths, and the strip scrolls for that width. A window that refuses the narrower width keeps its own, and the strip now scrolls again so the focused window stays on the display.

On 26 October, main at 9bfa927 left two moved windows partly past the right edge of the 2048-point built-in display until the next focus change. A window with an 1100-point minimum width was asked for 886 points and kept 1108, so 214 points stayed off the display. A fixed-size 600-point window tiled with ⌃⌥T was asked for 486 points and kept 608, so 114 points stayed off the display. With engine 4216682, which has the same files as f9c4837, both windows ended at x=2323, inside the display. These are automated observations with synthetic key presses, not physical input.

False minimum height at the screen bottom

This branch pins engine 8639fdb for #101, which merges engine PR #48. macOS ignores a small shrink that leaves a window's bottom edge near the bottom of the screen. The engine took such a kept height as the window's minimum, so a window stacked under it overlapped. The size check now lowers the window and grows it back once, and only a height the window keeps after that counts as its minimum.

On 26 October, Font Book was resized through Accessibility to end at the screen's bottom edge, and Miri was restarted. The previous engine logged that Font Book and T3 Code kept 1410 and 1418 points when asked for 1402, and recorded those heights as minimums. The candidate engine brought both to the asked height with no refusal logged. A window on another macOS Space, which Accessibility does not list, still keeps its height.

Bracket directions for stack shortcuts

#92 is implemented on this branch. An opening bracket now means left and a closing bracket means right for joining and popping out. ⌃⌥[ joins the column on the left, and ⌃⌥] joins the column on the right. The pop-out shortcuts already followed this rule and did not change. Default-set version 3 moves an upgraded file's join bindings only while they keep the exact value Miri wrote and the new chord is free. A blocked move keeps the old chord, and the Shortcuts settings page reports it. The engine's default configuration has an empty [bindings] table, so it adds no bracket bindings of its own.

On 26 October, the running pre-change build from the PR #76 worktree reproduced the old direction: ⌃⌥[ joined the middle test window into the column on its right. The branch build then upgraded Sam's real version 2 configuration: only the version and the two join lines changed, after a byte-identical backup. On three throwaway Helium windows, synthetic key events showed each join and pop-out direction, a join from inside a stack, a repeated join, and no-ops at both strip edges and for a pop-out outside a stack. Focus stayed on the moved window, and all three windows stayed visible. A fresh install wrote the new defaults at version 3. A customized version 2 file kept both join bindings, and Settings showed the kept-chord notice and the actual chords. These are synthetic key events in the real app, not physical key presses. Sam's configuration and the previously running build were restored byte-for-byte afterward. Private local evidence is in /tmp/miri92/.

Drag target and empty display fixes

This branch pins engine 4ba2bf0, which merges engine PR #39 for #72 and engine PR #40 for #73. Engine PR #39 implements the drag rule in release scope. Engine PR #40 implements the expected result in #73, which keeps focus where it is. Each change has tests that fail without it, and Sol approved both. Live on Miri Developer on 26 October, a stacked Finder window grabbed at the right end of its top bar and dragged straight down showed the bottom insertion line and reordered the stack. Closing three Finder windows that filled the primary display, with focus on the built-in display, left Grok Bot and Ghostty in view and focus unchanged. On 27 October, engine PR #66 added the missing #72 tests: a stack member grabbed near its left edge, and an edge hold that follows the pointer when the window is grabbed off-centre. Each new test fails when its rule is broken. On 27 October, Sam's real-mouse check passed the left-edge grab. Sam's right-edge grab dragged straight down moved the window to the built-in display, because that display sits directly below the primary display and the pointer crossed onto it. The same day, an agent ran synthetic drags with two Finder windows on Miri Developer built from engine 3fee523, with the primary display above the built-in display:

  • A right-edge grab and a left-edge grab, each released over the lower window of its own stack on the primary display, showed the bottom insertion line and reordered the stack.
  • A right-edge grab dragged past the primary display's bottom edge became a new column on the built-in display.
  • A drag from the built-in display into a primary-display column showed one insertion line, on the primary display only, and joined that column.

Synthetic drags do not replace Sam's real-mouse check (#71). Later on 27 October, Sam's real-mouse right-edge grab, released over the lower window of its own stack on the primary display, reordered the stack. Sam ran every real-mouse check on Miri Developer built from main at 7bbecc5 with engine 3fee523. The faint white insertion line over white windows belongs to #86. #72 can close after this PR merges, because the engine tests for the left-edge grab and the edge hold land with it. #74 tracks engine window_writes tests that fail under heavy machine load.

Unknown-key configuration warnings

The remaining #26 warning gap is implemented on this branch. Miri validates binding names with the same keyboard-layout resolver it uses for recorded shortcuts. Unknown characters now produce the existing configuration error in Settings and the menu bar, and Miri leaves the invalid file unchanged. Valid US, German, and French layout characters remain accepted. The resolver compares UTF-8 bytes to match Rust, so a decomposed Unicode spelling does not match a composed character by mistake. This extends Sam's original engine and app repair in PR #27, without changing its engine pin or commits.

On 26 October, the signed current-source baseline reproduced the missing Settings warning for ctrl+alt-?, while engine 03a7420 logged invalid key '?'. The candidate showed the warning and preserved the invalid file byte-for-byte. Replacing it with the supported ctrl+alt-= cleared the warning. Sam manually confirmed the candidate's menu-bar configuration-error message. The original configuration was restored byte-for-byte after testing. These are automated native Settings observations and a manual menu-bar observation, not a fresh physical keyboard test. The issue retains Sam's earlier physical named-typo confirmation. Private local evidence is in /tmp/miri26-evidence/ and the check logs in /tmp/miri26-*.log. Sol approved the final behavior in two GPT-6.1 Sol rounds on high effort. Its one Unicode finding is fixed, with a failing regression before its repair. All 148 final Swift tests pass. The full make check passed, including strict lint, 41 library and 803 engine tests, granted permission observation, native fixture recovery, and DMG packaging. After the one-line Unicode repair, the final Swift lint, signed build, granted permission observation, native fixture recovery, and DMG packaging passed again. The final signed app also showed the ? warning and preserved the invalid configuration byte-for-byte, then the original configuration was restored. A fresh synthetic keyboard probe was inconclusive because Computer Use and the engine reported different focused windows. It is not additional physical-key evidence. The issue stays open until the landed revision passes its acceptance checks.

macOS tile restoration fixes

Engine 03a7420 merges engine PR #38 for #58 on top of engine PRs #36 and #37. When the engine detects a same-display fill or tile, a window dragged onto the menu bar or a screen edge returns to its column from before the drag, with its own size and focus. The engine undoes its own drop when macOS tiles the dropped window within one second. It also restores a fill that macOS finishes after the restore or puts back after the hold, and it matches move and resize notifications that name no window ID, which Helium sends often. Each change has a regression test that fails without it. Live on Miri Developer with Helium, the old engine moved the window to a new column after an edge tile 3 of 3 times and kept the fill size in 3 of 3 fills; the fix passed 32 of 32 mixed tries. A report that a drag macOS does not fill leaves the window 8 points up against the menu bar did not reproduce. Sam's hand check on 6 November 2026, with engine ee3e3b5, returned the window inside the outer gap, so #122 closed as not reproduced. A drop onto another display that macOS then tiles is not undone.

Stack resize and engine restart fixes

This pin also brings engine c2d7d38, which merges engine PR #36 and engine PR #37. They fix five #40 defects that a live rerun on 25 October reproduced on 4890749. Engine PR #36 keeps a height step off windows outside the step, keeps an even split beside a learned minimum, and lets a grow step pass a neighbour at its minimum. Engine PR #37 keeps windows on a hidden native Space managed after a restart, keeps each window on its strip's display at exit, and shows every display's restored strip inside the display padding. Each defect has a regression test that fails without its fix. The live rerun on Miri Developer passed every case on the built-in display with Helium, T3 Code and Ghostty, and three Restart Engine runs restored all windows that the scan reached. Evidence is in /tmp/miri-e2e-20261025/ on Sam's Mac and is not published.

Two gaps remained open. #68 is now fixed on main through engine 23bcf1b. #69 is fixed on main through engine d169943, which the scroll position after a restart section describes.

Stack sizing and order fixes

Engine PR #34 merged as e4137a4 and fixes the #42 mixed-app ordering. AXRaise puts a background app's window just below the frontmost app's focused window, so raise_in_order now checks each raise against earlier entries only and puts the focused window back in front after a raise of another frontmost-app window. Engine PR #35 merged as 4890749 and fixes the #40 fresh-window overlap. The size check read a Chromium-style late resize as the old full height and stored it as a minimum, so a window still taller than requested is now read again after 500 ms first. An app that applies a size more than 500 ms late can still have its old height learned. Each PR has a regression test that fails without its fix. Both pass 52 library and 878 binary tests with defaults, 41 and 788 without, strict Clippy, and rustfmt. Greptile scored both 5/5, and one Sonnet sol-loop round approved both with nits only.

A signed Miri Developer build with candidate engine 07b55ae, both fixes on 896d887, ran native fixture checks on 25 October. The #42 mixed column A1, B1, A2 passed 13 of 13 order checks, against 9 of 13 on base 896d887. The #40 gate fixture showed no stale minimum with a 300 ms delay, a 1000 ms delay, or dropped writes, and the fresh-window repro passed 3 of 3 runs. Evidence is in .audit/fullsend-stacks-20261025/. Sam's trial build was restored afterwards. Two of Sam's windows, Spotify and a wide Helium window, ended in a virtual workspace that the restored trial engine does not show, so it parks them at the corner.

This branch pins 4890749, which also brings in engine PR #30, 896d887, the per-app background window writes. The merged engine's non-test source is identical to the natively tested candidate 07b55ae. Only the test mock differs. The sol-loop nit commit for #34, a variable rename and two comment changes, missed the merge and is still pending. On 25 October, make check passed with the pin: 144 Swift tests, 41 library and 789 engine tests, strict lint, the signed build, observed-granted permission observation, fixture recovery, and DMG packaging.

Native issue verification

PR #65 is merged, and agents now verify and close issues without a release wait. The 24 October native audit used a fresh signed build from the tree-identical 34f2f6d head and the published engine pin f831216. It confirmed #39, #44, and the bounded retry in #48. Their issue comments record the native observations, passing regression tests, and limits. #26 was reopened because a native check confirmed the missing Settings error for an unresolved single-character binding. The earlier named-typo repair still works.

The mature-window lane for #40 passed equal splitting, minimum-height redistribution, retained manual sizing, and membership removal. The fresh-window lane failed, so #40 stays open for triage. #33 and #58 reproduced their app-death and height-restoration failures. One busy-launch cohort admitted all 18 windows, which does not clear the intermittent discovery defects in #59 and #63. The remaining keyboard, gesture, overflow-ordering, and drag criteria still belong to their open issues. No release-candidate acceptance row was passed by this audit.

All test apps and engines exited, the original Miri support folder was restored byte-for-byte, and existing user-window frames were unchanged. The private local evidence is in .audit/native-issue-verification-20261024/. It includes unrelated window titles in baseline captures and must not be published wholesale. Only sanitized observations were posted to GitHub. No product source or engine pin changed. A manual checklist is prepared in .audit/native-issue-verification-20261024/manual-checklist.md for Sam's combined hardware results. It covers seven open-issue scenarios and four optional physical checks for closed issues. The confirmed code gaps in #26, #31, and #33 need no repeated manual reproduction.

Sam's follow-up reports a pass for #35, correcting an initial reference to #33. His #40 observations match adjacent-pair height resizing, equalization after removal, and horizontal row selection with the destination's last row as a fallback. Those observations do not resolve the earlier fresh-window failure, so #40 remains open. Overlap is expected only when the windows' minimum heights cannot fit, under #42's top-bar and focus-ordering rules. The supplied #42 recording shows wallpaper and a cursor without application windows, so it cannot verify those rules. No issue was closed from this follow-up.

The autonomous follow-up used the same signed merged-build artifact and engine f831216 in .audit/stack-repro-20261024/. Fresh flexible and constrained #40 cases passed with animations enabled and disabled. Five flexible native fixture windows also split without overlap, and removing a constrained sixth member restored their equal shares. The earlier fresh-window failure remains unexplained, so #40 stays open.

The #42 mixed-app column A1, B1, A2 reproduced the wrong native order on every return to A2 in three focus walks. Five paired trials then changed the wrong order A2, A1, B1 to A2, B1, A1 with a direct native raise of B1. A2 remained the focused/frontmost window at both endpoints. The source-derived explanation is the overly broad exclusion of frontmost-app windows in raise_in_order, which skips B1's raise. The native counterexample disproves an unavoidable platform limit for this fixture case, but it does not directly trace that engine branch. Independent review excluded the distinct-app comparison because its third window was on another display. No product fix was applied, and #42 stays open. The previous trial build at engine 5daaa0f and its original configuration were restored after testing. On reload, the trial app removes the derived swipe.gesture.fingers_count entry, while the original user-facing settings remain unchanged. Final verification found no changed existing window frames. The independently reviewed observations are posted on #40 and #42.

Stack ordering and window admission repairs

Engine PR #32 merged into miri/v0.5.2 as 50f49fc for #42. It keeps an overlapping stack of windows from different apps in order after focus changes. AXRaise returns before a background app reorders its window, and it places that window just below the frontmost app's windows. The engine now raises only windows that need to move, waits up to 50 ms for each raise to show, and waits up to 500 ms for the focused window to show in front. Native focus walks with four fixture apps failed 7 of 12 steps on 9ac7ec7 and passed 96 of 96 on the fix. While Finder is frontmost with no window, the previously focused stack window can remain in front instead of returning to its overlapped place. The bounded focus-first experiment on 24 October used four native fixture apps and returned focus to Finder after activating each app in stack order. The captured run left the wrong order without pauses and restored D1, C1, B1, A1 with 50 ms and 150 ms pauses. The helper at /tmp/miri-42/ff.swift sets AXFrontmost for each app and then Finder. It does not issue a separate AXRaise, so this records activation with the running engine rather than an isolated focus-and-raise implementation. The workaround would take focus from Finder while cycling through the apps. No engine change is included, and the Finder limitation is recorded in release scope. Sam limited this investigation to one workaround attempt, so it ends here. These results come from the prior session's captured output, not a fresh native run. Two windows of one app with another app's window between them still cannot be fully ordered.

Engine PR #33 merged as f831216 for #59. macOS can announce a launch before NSRunningApplication knows the process, and the engine now looks it up again instead of dropping the process after 5 s. An app that shows no window within 10 s now stays managed, so a later window enters the layout. The float command now toggles a window only when macOS reports it focused in the frontmost app. Native launch probes missed none of 320 ordinary windows, a window shown 12 s after launch was managed after 13 s, and the float key ignored an unmanaged focused window. An app whose observers cannot register for 5 s is still dropped, and an app that keeps its main thread blocked can wait for a later event.

Both engine PRs passed two Sol rounds and scored 5/5 in Greptile. This branch pins f831216.

Previous acceptance repairs

Engine PR #31 merged into miri/v0.5.2 as 9ac7ec7ef6bd4ecd60bca4019e40a22730b25cf5. Miri PR #61 merged as 3898f6fd8e3f27bc6b4f7eab0a3e43d43c2797a9 and pins this published revision. It keeps size-refusing stack windows on the display where the engine placed them. It also exposes the eligible focused survivor after removal and preserves focus when a background window is destroyed. The visibility request waits for scrolling or drag placement to finish and then checks the current focus. Keyboard focus placement and workspace restoration retain control. Issue #60 tracks the close and visibility failure.

Sol approved the source and tests after five bounded review rounds. Greptile reviewed the final engine revision at 5/5, and both PR checks passed. Independent native verification returned PASS+NOTES on engine 1cbb987 before its tree-identical squash merge. The verifier reproduced BASE moving a constrained window to the built-in display, stealing focus after background close, and leaving a focused survivor at x=-32. The candidate retained the external display and focus, and corrected the survivor to x=8. Native AX close, SIGTERM, graceful quit, and synthetic modifier-scroll checks passed within their recorded scope. Physical touchpad behavior and exact native swipe timing remain unverified. The engine passes 41 library and 759 binary tests without defaults, and 52 library and 849 binary tests with defaults. Formatting and strict Clippy pass in both configurations. Miri's full make check passes with the published pin. This includes compatibility, 144 Swift tests, 41 library and 759 engine tests, strict lint, the signed build, permission observation, window recovery, and DMG packaging. Sol approved the three-file Miri integration and verified parser compatibility and the minimum-height wording. Independent verification of the final Miri artifact returned PASS+NOTES on 1fcfa57, whose tree matches the merged revision. The signed app and read-only DMG match completely, and fresh native closes preserve focus and final visibility. All integration PR checks passed before merging. Both PR watchers are stopped, and the verified app remains running with one engine child. The DMG inspection left a nonblocking installation prompt that computer use could not inspect because it timed out. No installation occurred.

The macOS tiling setup check already merged in PR #57 as 601b056. The keybinding reference now distinguishes a 200-point layout slot from its 192-point native window with default gaps. The overlap-order retry prototype failed 6 of 12 native focus steps and was removed from the shipping diff. Overlap ordering and the documented stack-focus switch remain unresolved under #42. Issue #59 tracks intermittent window discovery and remains unfixed. Issue #58 separately tracks macOS tile height. These changes do not complete frozen-release acceptance. The evidence and decision trail remain in .audit/acceptance-pickup/ in the repair worktree.

Done

  • #222 is closed, delivered by #239. The app bundle renders AppIcon.icns from assets/icon.svg, and the menu bar shows the filled window while the engine runs and the outlined window while it is stopped. Miri Developer draws the mark in yellow, and the released Miri will use icon.svg unchanged. macOS's built-in SVG renderer does not draw the drop shadows in icon.svg, so the app icon has none. On 2 November 2026 a build of main at 7b95cbc0 showed the icon in Finder and System Settings > Accessibility, and both menu bar images on dark and light menu bars. The light bar needed a light wallpaper as well as Light appearance, because macOS colors the bar from a saturated wallpaper.

  • chore/live-test-lock moves the live-test lock from agent memory into the repository. scripts/live-lock.py takes, renews, checks, and releases /tmp/miri-live.lock, and an expired lock no longer blocks other runs. make e2e holds the lock while the selftests run, and AI-CONTRIBUTIONS.md says when a run needs it.

  • chore/pr-evidence adds an acceptance evidence table to the PR template, with one row per acceptance criterion. AI-CONTRIBUTIONS.md now says how to capture window evidence without showing the desktop. The pre-push hook runs scripts/check-pushed-media.py, which rejects new commits that add a recording, or an image outside assets/ and Sources/.

  • chore/agent-owned-issue-closure removes the GitHub Actions issue lifecycle and its dedicated harness tests and fixtures. Agents verify every acceptance criterion on landed code, post evidence, and close completed issues under the issue lifecycle. Partial or unverified issues remain open, and release distribution acceptance stays separate. The lifecycle notes below are historical evidence for the retired automation. No live issue or label was changed.

  • branding/window-mark replaces the README's pink M logo with the blue and violet window mark. assets/ also contains running and stopped menu bar SVGs and usage notes. SVG parsing and a browser preview passed for all three assets, including the menu bar marks on light and dark backgrounds. make lint passed on current main plus these assets, including all 18 harness tests. make native was skipped because this change contains only SVG assets and documentation. The Paper UI screens remain unexported because Paper Desktop was closed.

  • The v1 specification and product decisions are merged (#3).

  • The Accessibility permission prototype is merged (#4). Enabling only the Miri entry authorizes both the app and its engine in the development build.

  • The development harness is merged (#10), tracked by #9.

  • PR #11, the Miri Developer identity, merged into chore/repo-harness after #10 had already merged that branch. Its commits never reached main, so feat/v1-app-core carries them.

  • feat/v1-app-core adds the first production app slice:

    • MiriCore, a tested library with a TOML 1.0 parser that edits single values without touching the rest of the file.
    • Miri-owned config.toml with the accepted defaults and derived engine keys for gaps, presets, animation, and floating Settings.
    • An engine supervisor with an isolated environment, bounded logs, and a three-crashes-in-five-minutes restart limit.
    • App-side window recovery after every engine exit, verified live against the fixture window.
    • A setup checker, redacted diagnostics with a preview, and the menu bar and Settings UI.
  • The engine fork change samanyudas/paneru#5 hides the engine's menu bar item through [decorations.menu].visible, which Miri sets to false. It also creates a missing PANERU_CONFIG file in place instead of falling back to standalone Paneru's files.

  • The engine fork change samanyudas/paneru#6 makes stacking move only the focused window, in both directions, and adds pop-out to the left. It also stops a stack command next to a fullscreen column from dropping windows out of the layout.

  • Sam decided on 15 October 2026 that stacking moves only the focused window, and deferred Q6 to the final pre-release check.

  • PR #12, the first production app slice, merged into main on 15 October 2026 with #13 still open.

  • samanyudas/paneru#7 merged into miri/v0.5.2 on 16 October 2026 as engine 0341353. It fixes #14: Stop and Quit no longer move floating, minimized, or hidden windows.

  • Sam decided on 16 October 2026 that a current floating rule wins over a saved tiled placement (#15). samanyudas/paneru#8 merged into miri/v0.5.2 on 16 October 2026 as engine 39def42, which does this. A live run on the built-in display confirmed that a floating rule now wins after a restart.

  • The issue lifecycle job that labels issues a merged PR references failed with HTTP 403 on every merge until #18. Its token lacked pull-requests: read. A harness test now checks each workflow job's scopes against its API calls.

  • The release, timed-close, and reopen stages of the issue lifecycle have not run live, because no stable release exists. A harness test runs their real job scripts against a fake gh. A dispatched run of the close job passed on main on 16 October 2026. No issue carried please confirm close, so that run proved only that the job starts with its permissions and its script parses.

  • On 16 October 2026, make check passed on main, and a live headless Claude session showed the guard block a push to main, a force push, a PR merge, a release, tccutil reset, and defaults write.

  • #13 shrank a window in 1 of 35 runs, across the built-in display and a 2560 x 1440 external display. The engine requested only height changes in every run. After a screen lock, three windows were at exactly half width, and no engine had run since their last check. macOS tiling is the main suspect, but Sam may have moved them, so this is not confirmed. #13 was closed as completed on 16 October 2026 after these runs.

  • feat/default-bindings writes the accepted default shortcuts to [bindings] from one table in Sources/MiriCore/Bindings.swift. A new install gets the full set. An existing file gets each default whose engine command it does not bind under any alias, with only the default's free chords. [miri].default_bindings_version records the offer, so Miri never re-adds a binding the user deleted. A live run on 16 October 2026 sent all 43 bound chords as synthetic key events, and the engine ran each command. The engine's behavior differed from the accepted behavior only where the engine mapping records it, such as a numbered jump into a stacked column focusing the top window instead of the last-focused one. Some actions stay unbound or behave differently, as the engine mapping records.

  • PR #27 merged into main on 18 October 2026 and pins engine 3455e37 on miri/v0.5.2, the squash merge of samanyudas/paneru#11, which follows samanyudas/paneru#9 and #10. The engine adds five-percentage-point resize steps (#22), directional display focus and move (#23), and column jumps that return to a stack's last-focused window (#24). It drops a binding whose key name it does not know, instead of binding the A key (#26). Vertical focus and swap no longer cross to another display at a column's edge. Display focus preserves the selected virtual workspace and skips background native tabs. The Lua display commands reject positional directions. The merge also brought window-recovery fixes that preserve window state and geometry when an Accessibility handle is replaced. Miri's default set is now version 2. An upgrade replaces the four version 1 resize bindings only where they still have Miri's value. Miri reports a binding the engine cannot read, and owns ⌃⌥Q for Stop (#25). On 16 October 2026 the built app upgraded Sam's real version 1 file as designed, and the engine parsed all 51 bindings in it.

  • samanyudas/paneru#10, merged as 0010611, fixes a Rust 1.98.1 Clippy float_cmp error that 67debbc introduced in a recovery test. On 17 October 2026, make lint, Swift lint, 69 Swift tests, the app build, and both end-to-end checks passed at 67debbc, which has the same source apart from that test.

  • A live run on 15 October 2026 on the built-in display only confirmed Start, 8-point gaps on all sides and between windows, Stop, crash recovery and restart, the three-crash limit, manual restart, and Quit.

  • A 16 October 2026 hardware run of the PR #9 engine used an external 2560 x 1440 monitor above the built-in display. Sam confirmed horizontal growth and the South move, North focus, and South focus shortcuts. The recorder saw Fixture 0 move between displays and grow from 500 to 756 points, which matches two 128-point steps. Sam paused the run before the East and West check, which PR #27 later completed. Evidence is in .build/artifacts/pr9-hardware-evidence/. Fixture test helpers remain in .build/artifacts/pr9-hardware-runtime/ and are reusable for native checks.

  • PR #27 also fixes default upgrades, exact configuration backups, and Stop ownership for usable and rejected engine bindings. Its description records the full verification, and the append-only decision trail is .audit/pr27-full-stop-20261017.tsv. Sam physically confirmed width spacing, stacked-height growth, stack jump returns, ordinary Stop, Stop after a rejected reload, and the mistyped-key check. Sam physically confirmed all four horizontal and all four vertical display actions at engine 6e572ea, whose tree matches 3455e37. The horizontal check had the laptop left of the external monitor with top edges aligned. The vertical check had the external monitor above the laptop with left edges aligned. Both physical checks used fixture animation speed 1000, and separate native replays passed at speed 14 with Fixture 0 and TextEdit. The confirmations are .audit/pr27-hardware/horizontal-6e572ea-confirmation.json and .audit/pr27-hardware/vertical-6e572ea-confirmation.json.

  • PR #29 pins engine a15682f, the merge of samanyudas/paneru#12, which fixes #28. The engine now builds a native frame from its rounded origin and size, so odd widths and heights stay exact. A native gap check on 18 October 2026 used four fixture windows with 4-point window and edge padding, then resized the leftmost window to 629 points. Engine 3455e37 recorded width 636, and the native gap to its neighbour was 7 points. Engine a15682f recorded native X=8 and width 629 as x=4 and width 637, and every native gap stayed 8 points.

  • feat/shortcut-settings adds the Shortcuts settings page. It records a chord through an event tap at the head of the HID event stream, so it also sees chords the engine binds. Each action lists several chords, and each chord has its own remove button. The page rejects a chord that another Miri action, Stop, or an unlisted engine command already uses, and the file stays unchanged. It warns before it takes a shortcut that macOS uses. That list is the live System Settings shortcuts merged over built-in defaults, plus standard app shortcuts. Reset to Defaults backs up the file, replaces [bindings] with the default set, and restores ⌃⌥Q for Stop. Stop is customizable in [miri].stop_shortcut. When the keyboard layout changes, Miri registers Stop again and restarts a running engine, because the engine reads the layout only when it starts. A default-binding upgrade never takes a chord that presses the same key as a Stop chord or a user binding under the active layout. Key names resolve through the active layout in the engine's order, so AZERTY, QWERTZ, and Dvorak record the key the engine binds. Unit tests pin key names and round trips on the US, French, German, and Dvorak layouts. On 18 October 2026 a built app ran with management stopped and a temporary home folder. Synthetic HID key events showed the rejection messages, the macOS conflict alert, adding a Stop chord, and the unusable-key message. They also showed that Escape and switching apps cancel recording, and that reset writes a backup. A second run on 18 October 2026 used the same temporary home folder with the engine running. While recording, the page caught ⌃⌥T and showed "⌃⌥T is already used by Float or unfloat focused window.", and the engine did not act. A recorded Stop chord, ⌃⌥⇧P, stopped management when pressed. With French, German, and Dvorak each selected, the recorder wrote ctrl+alt+shift-a, -q, and -' for the key at the US Q position. A freshly started engine ran each recorded binding. With the engine running, switching to French and back to Australian restarted the engine each time, with process IDs 5840, 6259, and 6268. On AZERTY the default digit shortcuts, such as ⌃⌥1, reached the keypad digits, because the top row types symbols without Shift. #31 later moved them to the top row. GPT-6.1 Sol reviewed the branch in four rounds and approved it after the layout restart and the upgrade fixes.

  • feat/gesture-controls is phase 3a, the strip gesture controls without pointer-versus-focus targeting. Settings has a Gestures tab with Off, Three fingers, and Four fingers (recommended), and Reverse swipe direction. [miri].strip_gesture defaults to Off, and [miri].reverse_swipe sets [swipe.gesture].direction. GestureState.evaluate turns the swipe on only for a confirmed user test that matches the finger count, direction, macOS version, engine revision, and readable macOS gesture settings. A confirmed macOS conflict always keeps it off, and automatic detection never turns it on. Miri writes [swipe.gesture].fingers_count only while the swipe is on or a test runs, sets vertical = false, and removes the legacy [options] swipe keys. The gesture test polls miri-engine query state --json, needs the strip to slide both ways, fails on a Space switch, and saves a result only after the user confirms it. The result lives in user defaults, and Report a macOS Gesture Change deletes it. Unit tests cover the state table, the file keys, the result's JSON, and the movement detector. Changing the finger count, direction, macOS version, engine revision, or a readable macOS gesture setting deletes the saved result, even while the swipe is Off, so changing back needs a new test. The four-finger setup check no longer reports a conflict when macOS keeps the four-finger value at 2 while full-screen swiping uses three fingers, as on Sam's Mac on 18 October 2026. On 18 October 2026 the built app showed the Gestures tab on screen with the swipe Off by default and both test buttons disabled, and its config had direction = "Natural", vertical = false, and no fingers_count. No live trackpad check has run. GPT-6.1 Sol reviewed the branch in three rounds and approved it. #33 tracks the engine helper outliving a dead app, which would keep a test swipe active until the next launch.

  • PR #37 records the strip settling design, and PR #41 pins engine 491508e, which implements it. A swipe settles on the column nearest the center where the flick would stop, and focus reveals only the hidden part of a column. The engine also keeps a column's anchor during preset width cycling (#39), and Quit and Stop return windows to their frames from before Miri started. A live check on Sam's Mac started and quit the engine with nine windows open, and all nine returned to their starting frames. No real trackpad check of settling has run.

  • PR #45 records the stack rules Sam accepted on 19 October 2026 in release scope. PR #46 pins engine 579bc0b, which gives stacked windows equal heights and stops a window with a minimum height from overlapping the next one (#40).

  • PR #47 makes CI tolerate the open -W race in both selftests and retries hdiutil in DMG packaging.

  • PR #50 pins engine 4b4705a, which asks a slow app for focus again before it floats the window (#48).

  • On 20 October 2026 a status audit compared main at e516edd with the v1 documentation. Sam settled the open #42, #43, and #44 questions on 20 October 2026, and the branch docs/drag-and-overflow-decisions records them in release scope. Dragging to reorder columns and dragging between displays moved into v1. The branch docs/reconcile-audit-20261020 corrects the stale status claims, adds the acceptance ledger, and updates #42, #43, and #44.

  • PRs #51 and #52 changed only documents, but each listed Refs #42, Refs #43, and Refs #44, so the issue lifecycle labelled all three fixed (pending release). The labels were removed on 20 October 2026, and one wrong bot comment remains on each issue. fix/refs-vs-part-of adds Part of #N for decisions, docs, and partial work, which never labels an issue, and stops Related to #N from labelling one. A harness test reproduced the Related to labelling before the change and passes after it.

  • feat/stack-overflow pins engine db5bba3, the merge of samanyudas/paneru#21, which implements stack overflow (#42). When a stack's minimum heights exceed its column, each lower window overlaps the window above it only below that window's top bar. The engine measures each top bar through Accessibility when a stack first overlaps, and again when the window loses focus or resizes. A window with no measurable top bar gets 52 points. When any Accessibility read fails, the engine keeps the last measured height, or 52 points if it has none, and tries again after a growing wait. The focused window comes to the front, and it returns to its place when focus leaves it. The engine also lays out a stack with more windows than fit at the 200-point minimum, which it used to leave out of the layout. Engine unit and harness tests cover the overlap, the fallback, the refresh and retry, the spill past the bottom, the window order, and the dropped column. A read-only Accessibility probe measured a 96-point top bar on Helium, the only app with a standard window open. No real desktop or hardware check has run, so acceptance check S-5 stays pending.

  • feat/stack-limit delivers the stack limit (#44) with samanyudas/paneru#20, which adds the engine option [options].stack_limit. It holds a whole number from 1 or "fit", defaults to 5, and is capped at what fits in the column at 200 points on the active display. A Stack command into a full column moves the window out of its own stack into a new column between the two. A window that is already its own column stays put. Lowering the limit leaves existing stacks unchanged, and the engine reads the limit on every Stack command, so a hot reload applies to the next one. The engine rejects 0, negative numbers, and other values with a parse error, and so does Miri, which reports options.stack_limit. effective_stack_limit(config, column_height) in the engine's src/ecs/layout.rs holds the cap, for reuse by drag insertion (#43). Settings has a Windows per stack picker on the Layout page, with Fit the display and the numbers from 1 to what fits on the main display. PR #54 pinned engine 9065569, the merge of samanyudas/paneru#20 into miri/v0.5.2. On 20 October 2026, make native passed on feat/stack-limit with 141 Swift tests, 40 engine library tests, and 427 engine binary tests. Its permission selftest reported observed-granted. Attribution unresolved., and its recovery selftest recovered the fixture window. Before the engine merge, make lint passed except make compat, which rejected the unpublished pin. Settings was not opened during that run. No hardware check has run, and the acceptance check is S-7 in the acceptance ledger.

  • PR #54 merged into main as 6a85fcf on 21 October 2026 after Greptile scored its final head 87178e9 at 5/5 and CI passed. Independent verification passed 142 Swift tests, strict Swift lint, and make lint, including 19 harness tests and compatibility. A native fixture rendered the production Layout settings view with an isolated model and configuration file. The previous commit kept a stale capacity of 6 after a simulated display notification, while the merged view immediately showed 3 and preserved the configured count of 5. This did not change physical displays or run the engine. This branch merges that result and keeps engine db5bba3, which contains both stack limit and stack overflow.

  • feat/drag-insert-integration starts from main at 49661a5 and pins engine merge ffff464 from samanyudas/paneru#29, merged into miri/v0.5.2 on 23 October 2026. This brings in the landed drag insertion and feedback changes from engine PRs #22, #23, and #28, together with part C from #29. Part C adds cross-display drops, outer-edge hold scrolling, and restoration after macOS tiles a managed window (#43). Native membership uses fresh window frames and preserves held drags, pending engine placement, and native tab groups. The engine also replays a held tab group's skipped layout when a press ends without a drop. The regression failed before the fix for lost release, pointer movement, and resize cancellation, and passes after the fix. The app also recognizes the engine's showmenu and hidemenu binding keys and rejects extra words, matching the updated engine parser. The compatibility guard still requires the engine pin to be published on miri/v0.5.2. The verified merge satisfies that ancestry requirement. Mock engine tests cover the drag behavior, including a neighboring display with a native fullscreen Space. Standalone engine ee94515 passes all thirteen final native fixture lanes on two physical displays on 23 October 2026. These include both keyboard-resize lanes, Window menu Half, Quarter, and Fill, same-display gestures, and nine consecutive cross-display runs. Immediate Grow produces width 1920 after an app resize to 1600, replacing the previous wrong 1280. Cross-display runs preserve width ratios, equal 643-point stack shares, insertion feedback, refusal, and complete return geometry. All fourteen unrelated window frames stay unchanged in every lane, and strict foreground checks pass. Every owned process exits and the original frontmost app returns. Sam disabled Vorssaint's Focus Follows Mouse setting before these final runs. The final runs use the existing engine focus behavior. These checks use three owned AppKit windows and Sam-authorized synthetic CGEvent input, with Helium hidden and its session preserved. Successful drops now retire earlier native resize snapshots for every native-tab member. A fixture-driven native Half-sized resize followed by a drag now keeps its accepted destination. Its frame stays at (0,30), 1280 by 705, instead of returning to the earlier slot. Sol approved the full engine change after two rounds, with the stale snapshot finding resolved. Physical diagonal-edge behavior has source and mock coverage only. The integration pins verified merge ffff464, whose tree exactly matches the reviewed and natively tested ee94515. Acceptance check S-6 stays pending.

  • feat/setup-tiling-conflicts adds the macOS tiling check that Sam decided for drag insertion (#43). The setup checker now reports Drag windows to screen edges to tile, Drag windows to menu bar to fill screen, and Hold Option key while dragging windows to tile when they are on. macOS stores these values only after a change, so an absent value counts as on, the macOS default. The check never blocks window management. On 23 October 2026 the built app showed Passed with all three off, and named the two options turned on with defaults after Check Again. With macOS tiling on, a dragged window that macOS filled or tiled returned to its column origin and width within 0.4 seconds, but kept the macOS height of 1410 points instead of 1394.

Next

Each phase lands as its own PR or PRs. Record every acceptance result in the acceptance ledger.

  1. Finish the remaining v1 features.
    • Add pointer-default and focused-strip gesture targeting to the engine, the configuration, and Settings (#212).
    • Run the stack overflow (S-5) and stack limit (S-7) hardware checks, and record them against the frozen release candidate.
    • Verify every Strip gestures checklist item and the settling behavior on a real trackpad.
  2. Build the public release path, in parallel with phase 1. Integrate Sparkle updates, then production signing, notarization, the release DMG, and the Homebrew cask. Verify install, upgrade, and Accessibility attribution on the released bundle.
  3. Freeze one release candidate, then run the complete app and multi-monitor acceptance checklists on that bundle with Accessibility granted.
  4. Define the engine memory-stability rule, complete the endurance period, and have Sam decide Q6 at the final check before publishing v1.

Blockers

  • Developer ID enrollment is pending and blocks signed, notarized releases.

Last verification

The app implementation at 540cc57, with engine 48df35a, passed strict Swift and engine lint, the signed app and helper build, and 142 Swift tests. Its engine passed 41 library and 707 binary tests without default features, and 52 library and 794 binary tests with defaults. The first native target failed recovery while the desktop was locked. After Sam unlocked the desktop, make e2e passed both permission observations and fixture recovery. Permission attribution remains unresolved. Those app checks do not verify the newer standalone engine repairs.

On 23 October 2026, standalone engine ee94515 passes 52 library and 841 binary tests with defaults. The no-default run passes 41 library and 751 binary tests. Formatting and strict Clippy pass in both feature configurations. Sol approves the full engine change in round two, with source and mock evidence kept separate from native results. All thirteen final native fixture lanes pass under the environment conditions above. Greptile scored engine ee94515 and integration d4102fe at 5/5. Engine #29 merged after Sol approved, both bot checks passed, and all review threads were resolved. Sol approved the merge-commit repin in integration round two, with no findings.

The integration repin to published merge ffff464 passes make check on 23 October 2026. This includes docs, 19 harness tests, ShellCheck, actionlint, compatibility, strict Swift and engine lint, the signed build, 142 Swift tests, and 41 library plus 751 binary engine tests. Its native selftests report observed-granted. Attribution unresolved. and successful fixture recovery. DMG creation, verification, mounting, signatures, and the embedded engine revision check also pass. The parser hashes and publication guard remain unchanged. The integration remains draft until fresh CI and bot review finish for the repin.

On 24 October 2026, the issue-closure implementation passes make docs harness-tests actions shell and make lint with engine pin f831216 unchanged. The baseline harness passes all 19 tests, and the remaining nine tests pass after removing the retired automation's ten tests. The tracked-instruction search leaves legacy lifecycle guidance only in historical evidence and the new policy's legacy-label explanation. make native was intentionally skipped for this workflow and documentation change. Its skipped targets are make doctor, make swift-lint, make engine-lint, make build, make test, make e2e, and make package. make check was not run because it includes those native targets. No native app or desktop operation ran, and no live issue or label changed. The integrated branch also passes make lint and git diff --check. Independent source review approved 743802f against b2e6d12 with no findings. The comment pass found no deletion candidates or refactor flags.