Skip to content

Restore windows to their macOS Spaces without switching Spaces - #6

Open
jeanmonet wants to merge 7 commits into
openprose:mainfrom
jeanmonet:codex/native-space-restoration-tahoe
Open

Restore windows to their macOS Spaces without switching Spaces#6
jeanmonet wants to merge 7 commits into
openprose:mainfrom
jeanmonet:codex/native-space-restoration-tahoe

Conversation

@jeanmonet

@jeanmonet jeanmonet commented Aug 23, 2026

Copy link
Copy Markdown

Dependency

Depends on #5 and should be merged after it. Until then, GitHub's diff against main also contains #5's scratchpad/session work; after #5 lands, this PR reduces to the six Space-restoration commits from 2068df2 through 7e7082d.

Both branches are rebased onto upstream v0.5.1.

Summary

  • restore each independent window group to its saved macOS Space on macOS 26
  • keep the user's active Space unchanged during cold launch, even when restored windows belong elsewhere
  • surface a restored window already on the current Space during cold launch, or create a nonpersistent empty window there when none exists
  • make ordinary running-app Space changes completely passive: no hidden window creation, ordering, activation, delayed callbacks, or session write
  • handle deliberate Dock/Finder reopen through AppKit's native reopen event and otherwise follow native macOS app switching
  • make Margin's durable session the single source of truth instead of competing with AppKit state restoration
  • gate the native WindowServer integration to the tested macOS major version and fall back safely when it is unavailable

Implementation notes

macOS exposes no supported API for assigning a window to a specific Space. This implementation therefore isolates the necessary private WindowServer/SkyLight calls in MarginPrivateSupport, verifies every move, and enables that backend only on macOS 26. The adapted yabai code is credited in NOTICE.

Cold launch starts as an accessory application and becomes a regular foreground app only after a current-Space window is prepared. Remote restored windows are ordered without activation, preventing Launch Services or AppKit from pulling the user into another Space.

Once Margin is running, it does not observe active-Space changes. AppKit's explicit applicationShouldHandleReopen delegate path is the only running-app event that may surface a window. This deliberately removes the previous transparent key-window prearming workaround, which could race a user's click into another app and steal focus 100 ms after a Space transition. Launch Services activates a running app before delivering its reopen event, so guaranteed current-Space Dock reopening would require persistent hidden preactivation window machinery; this PR instead follows native macOS activation behavior.

Verification

  • rebased cleanly onto upstream v0.5.1; git range-diff reports all six Space-restoration commits unchanged
  • manually validated cold launch with two Margin windows in different macOS Spaces: both returned to their saved Spaces and launch left the active Space unchanged
  • reproduced the background focus defect in the previous build: an inactive Margin window remained marked main and the active-Space handler created/keyed transparent windows immediately and after 100 ms
  • exercised three genuine cross-Space Firefox → Margin → Firefox cycles with the patched installed build; Firefox remained frontmost at both 250 ms and 750 ms after every switch, Margin never reclaimed focus, and deliberate Margin activation remained stable
  • idle patched process: 0% CPU, zero sampled energy impact, five sleeping threads, approximately 46 MiB resident after settling
  • scratchpad file count and SHA-256 hashes were unchanged across installation and activation testing
  • make smoke — passed on exact rebased head 7e7082d; bundle, CLI, document inspection/comparison, and app-window launch checks passed
  • make benchmark-matrix — passed all five cases on exact rebased head; worst visible-window p95 was 440.371 ms against 500 ms, and worst target-ready p95 was 1,145.982 ms against 1,250 ms
  • strict code-signature verification — passed
  • make check-version — passed at 0.5.1 (11)
  • git diff --check — passed
  • all affected Swift sources parsed successfully
  • make test — unavailable locally because the machine has Command Line Tools rather than full Xcode/XCTest; focused test coverage for the surrounding Space/session policy remains in the branch

Release handling

The upstream base is now v0.5.1 (app version 0.5.1, build 11). This PR intentionally does not invent a second version bump: it adds its user-facing note under Unreleased, and the maintainer can choose the next release version after deciding the merged release contents. Given the feature scope, that may be 0.6.0 rather than another patch version.

Compatibility and safety

  • Existing scratchpad and session persistence remains authoritative and is not duplicated by AppKit restoration.
  • Ordinary Space changes do not allocate windows, change key/main state, activate Margin, schedule delayed callbacks, or perform persistence work.
  • Startup work is benchmark-isolated, smoke-tested, and measured.
  • The native backend fails closed outside the explicitly supported macOS major version.
  • Private support code is isolated and its adapted upstream license is included.
  • User-facing behavior, native activation tradeoff, and release implications are documented.

Signed-off-by: jeanmonet <15112920+jeanmonet@users.noreply.github.com>
Signed-off-by: jeanmonet <15112920+jeanmonet@users.noreply.github.com>
Signed-off-by: jeanmonet <15112920+jeanmonet@users.noreply.github.com>
Signed-off-by: jeanmonet <15112920+jeanmonet@users.noreply.github.com>
Signed-off-by: jeanmonet <15112920+jeanmonet@users.noreply.github.com>
Signed-off-by: jeanmonet <15112920+jeanmonet@users.noreply.github.com>
Signed-off-by: jeanmonet <15112920+jeanmonet@users.noreply.github.com>
@jeanmonet
jeanmonet force-pushed the codex/native-space-restoration-tahoe branch from 16ccacc to 7e7082d Compare August 28, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant