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

open5
main
feat/lock-screen-first-pass
opened7d agobyKKainoa Newton
View on GitHubTwo-way sync enabledPR last imported 7d ago

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.

Written for commit c149a72bba0e1a8f48e5eb449badfa2008d6b699. Summary will update on new commits.

View guided diff Turn on auto-fix

Checks

9 successful Ā· 1 skipped
All checks passed
c149a72

Added 5 commits

  • Document the lock-screen code path and upstream PR #13468558b72
  • Add a locked-state skeleton and per-feature lock-screen policy91127b8
  • Replace SkyLight-delegated notch windows after unlocka4dd096
  • Merge remote-tracking branch 'origin/main' into feat/lock-screen-first-passc149a72
  • Merge branch 'main' into feat/lock-screen-first-passf6d1a69
Synced from GitHub

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

āš™ļø Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 40dae153-d83a-4c06-99e2-86ecac906fd1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • šŸ” Trigger review
  • Autofix Ā· Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

ā¤ļø Share

Comment @coderabbitai help to get the list of available commands.

Ccubic-dev-aiBotreviewed Ā· 3 inline threads7d ago
Synced from GitHub

3 issues found across 12 files

Prompt for AI agents (unresolved issues)
text
1
2Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
3
4
5<file name="docs/lock-screen.md">
6
7<violation number="1" location="docs/lock-screen.md:50">
8P3: This section is stale after the fix: it says the current unlock path leaves windows out of the CGS space and never replaces them, but the implementation now reasserts membership and replaces delegated windows. Mark lines 50–54 as the pre-pass behavior or rewrite them to describe the current path.</violation>
9</file>
10
11<file name="boringNotch/BoringViewCoordinator.swift">
12
13<violation number="1" location="boringNotch/BoringViewCoordinator.swift:73">
14P2: `setScreenLocked` clears only music sneak peeks, so a battery peek already visible when the screen locks can bypass the battery lock-screen policy. Clear every shown sneak peek whose mapped feature is hidden.</violation>
15</file>
16
17<file name="boringNotch/managers/NotchWindowManager.swift">
18
19<violation number="1" location="boringNotch/managers/NotchWindowManager.swift:65">
20P2: Lock teardown is not persistent: later display or settings callbacks can recreate global drag monitors because `setupDragDetectors()` has no locked-state guard. Return after cleanup when `isScreenLocked` is true.</violation>
21</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

……@@ -53,48 +55,151 @@ final class NotchWindowManager {
62+ // Nothing opens over the lock screen (BoringViewModel.open refuses),
63+ // so close what's open and stop all pointer and drag input.
64+ ([primaryViewModel] + contexts.values.map(\.viewModel)).forEach { $0.close() }
65+ cleanupDragDetectors()
Ccubic-dev-aiBoton line 657d ago

P2: Lock teardown is not persistent: later display or settings callbacks can recreate global drag monitors because setupDragDetectors() has no locked-state guard. Return after cleanup when isScreenLocked is true.

Prompt for AI agents
text
1Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At boringNotch/managers/NotchWindowManager.swift, line 65:
2
3<comment>Lock teardown is not persistent: later display or settings callbacks can recreate global drag monitors because `setupDragDetectors()` has no locked-state guard. Return after cleanup when `isScreenLocked` is true.</comment>
4
5<file context>
6@@ -53,48 +55,151 @@ final class NotchWindowManager {
7+ // Nothing opens over the lock screen (BoringViewModel.open refuses),
8+ // so close what's open and stop all pointer and drag input.
9+ ([primaryViewModel] + contexts.values.map(\.viewModel)).forEach { $0.close() }
10+ cleanupDragDetectors()
11+
12 if !Defaults[.showOnLockScreen] {
13</file context>
docs/lock-screen.mdline 50Open thread
……@@ -0,0 +1,140 @@
47+
48+### Does our code have it?
49+
50+Very likely yes, though it can't be confirmed without locking a real machine. Our lock path is byte-for-byte the code #1346 was written against:
Ccubic-dev-aiBoton line 507d ago

P3: This section is stale after the fix: it says the current unlock path leaves windows out of the CGS space and never replaces them, but the implementation now reasserts membership and replaces delegated windows. Mark lines 50–54 as the pre-pass behavior or rewrite them to describe the current path.

Prompt for AI agents
text
1Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/lock-screen.md, line 50:
2
3<comment>This section is stale after the fix: it says the current unlock path leaves windows out of the CGS space and never replaces them, but the implementation now reasserts membership and replaces delegated windows. Mark lines 50–54 as the pre-pass behavior or rewrite them to describe the current path.</comment>
4
5<file context>
6@@ -0,0 +1,140 @@
7+
8+### Does our code have it?
9+
10+Very likely yes, though it can't be confirmed without locking a real machine. Our lock path is byte-for-byte the code #1346 was written against:
11+
12+- The same `delegateWindow` (flag 7) and the same `undelegateWindow`.
13</file context>
boringNotch/BoringViewCoordinator.swiftlines 73–77Open thread
……@@ -49,6 +49,34 @@ final class BoringViewCoordinator: ObservableObject {
74+ for (uuid, state) in sneakPeekStates where state.show && state.type == .music {
75+ sneakPeekStates[uuid]?.show = false
76+ }
77+ }
Ccubic-dev-aiBoton lines 73–777d ago

P2: setScreenLocked clears only music sneak peeks, so a battery peek already visible when the screen locks can bypass the battery lock-screen policy. Clear every shown sneak peek whose mapped feature is hidden.

Prompt for AI agents
text
1Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At boringNotch/BoringViewCoordinator.swift, line 73:
2
3<comment>`setScreenLocked` clears only music sneak peeks, so a battery peek already visible when the screen locks can bypass the battery lock-screen policy. Clear every shown sneak peek whose mapped feature is hidden.</comment>
4
5<file context>
6@@ -49,6 +49,34 @@ final class BoringViewCoordinator: ObservableObject {
7+ if expandingView.show, let feature = LockScreenFeature(expandingView.type), !isVisible(feature) {
8+ expandingView.show = false
9+ }
10+ if !isVisible(.music) {
11+ for (uuid, state) in sneakPeekStates where state.show && state.type == .music {
12+ sneakPeekStates[uuid]?.show = false
13</file context>
suggestion
1 for (uuid, state) in sneakPeekStates {
2 guard state.show,
3 let feature = LockScreenFeature(state.type),
4 !isVisible(feature) else { continue }
5 sneakPeekStates[uuid]?.show = false
6 }