Lock screen first pass: click-swallow fix and locked-state skeleton

NO
8d ago
+467-28

First pass on lock-screen support (VISION item 3): investigation, the post-unlock click-swallow fix, and a locked-state skeleton. No settings UI yet (deferred until the Raycast integration exists).

Changes

  • docs/lock-screen.md: how lock/unlock is detected, how the SkyLight and CGS notch spaces interact, what the notch exposed while locked, the click-swallow bug, what we take from upstream #1346, hook points, and a manual test checklist.
  • Click-swallow fix (NotchWindowManager, BoringNotchSkyLightWindow, CGSSpace). SkyLight delegation (flag 7) evicts the window from our notch space, and nothing re-added it. After unlock we now disable SkyLight, re-add the window to the notch space, then swap every window that was ever delegated for a fresh one. The live NSHostingView moves across with a snapshot underneath. Adapted from upstream PR #1346 by @TemujinCalidius.
  • Locked-state skeleton:
    • BoringViewCoordinator.isScreenLocked is the single source of lock state. It's re-checked at launch, on wake and when the user session becomes active again (private/SessionLockState.swift, undocumented key).
    • LockScreenFeature / LockScreenVisibility policy with one Defaults key per feature: music and battery visible; notifications, calendar, shelf and mirror hidden.
    • While locked the notch can't open, windows ignore the mouse, drag detectors stop, and hidden alerts are filtered.

Private API (SkyLight, CGS, undocumented session key) is marked in the code.

⚠️ Needs rebasing onto compact-only (#1)

PR #1 rewrites ContentView into a panel layout. Once it merges, this branch needs main merged in again and its two ContentView hooks re-applied to the panel layout:

  1. liveActivities: coordinator.isVisible(.notifications) / coordinator.isVisible(.music).
  2. Open content: vm.notchState == .open && !coordinator.isScreenLocked.

Verification

  • scripts/dev.sh build: BUILD SUCCEEDED (after merging origin/main at 785298c).
  • Not verified at runtime. The app wasn't launched and the screen wasn't locked. Whether the click-swallow bug actually reproduces, and whether the window swap fixes it, needs a manual test on a real lock screen. See the checklist in docs/lock-screen.md.

🤖 Generated with Claude Code


Summary by cubic

First pass on lock-screen support: fixes a post-unlock click-swallow bug and adds a locked-state skeleton with a per-feature visibility policy. No settings UI yet. The fix is adapted from upstream PR #1346, isn't runtime-verified yet (manual test checklist in docs/lock-screen.md), and needs a rebase onto the compact-only ContentView rewrite to re-apply two hook points.

Click-swallow fix

  • SkyLight delegation evicts the notch window from our CGS notch space and leaves server-side state that makes its transparent margins swallow desktop clicks after unlock.
  • After unlock the window is re-added to the notch space, then swapped for a fresh one; the live NSHostingView moves across behind a snapshot so the notch never goes blank.

Locked-state skeleton

  • BoringViewCoordinator.isScreenLocked is the single lock-state source, seeded at launch and re-checked on wake and session-active via an undocumented session key.
  • Per-feature policy (LockScreenFeature / LockScreenVisibility): music and battery visible; notifications, calendar, shelf, and mirror hidden, one Defaults key each.
  • While locked the notch can't open, open notches close, windows ignore the mouse, drag detectors stop, and hidden alerts are filtered.