Skip to content

debug.capture_backdrop: a patterned window behind a window for capture snapshots - #27514

Merged
teamleaderleo merged 6 commits into
feat-cmux-nextfrom
dep-fleet--debug-capture-backdrop
Oct 30, 2026
Merged

teamleaderleo merged 6 commits into
feat-cmux-nextfrom
dep-fleet--debug-capture-backdrop

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A capture of a translucent window could not show its translucency: debug.window_snapshot composites only the app's own windows, and a capture slot shares its Mac's desktop with other slots, so glass and window opacity showed nothing behind them (a uniform dark backdrop).

debug.capture_backdrop (DEBUG and bench-hook builds only) shows a patterned window of the app itself just behind one of its windows, covering that window's screen:

  • pattern: checker, stripes, image:/absolute/path (read off the main thread), or none (closes it).
  • window / kind: as for debug.window_snapshot (default the key window, else the active main window).
  • Returns pattern, window_number, backdrop_number.

The window it is behind keeps it (an associated object), and floating-overlay discovery skips every capture backdrop, so a snapshot of one window never takes another window's screen-sized backdrop as an overlay over it.

Lifecycle: replacing a backdrop, taking it away (none) or closing its window closes the panel (CaptureBackdropPanel.remove()), so no window-server window outlives it and a screen-sized backdrop never stays on screen after its window is gone.

New observer: each backdrop observes NSWindow.willCloseNotification for its one target window (MainActorObservation, object: target; at most one backdrop per window). It holds the panel weakly, so the window keeping the panel makes no cycle. Stop condition and cleanup: it is dropped in remove() (the backdrop is replaced, set to none, or its window closes) and its token is removed when the panel is released. The arch-ratchet observer_sites +1 carries that reason in plans/cmux-next/arch-ratchet.json.

compositedSnapshotWithOverlays() now composites a window's capture backdrop behind everything else (always last in the window list, whatever the on-screen order), so the window server blurs it under glass as on screen. With no backdrop the snapshot is unchanged. The backdrop is an app window, never the desktop picture, so it goes with the app; nothing outside the app changes.

The capture tooling side (a state's "backdrop": "checker" | "stripes" | "image:<repo path>") is in the private capture tooling repo.

Testing

  • CmuxNextDesignTests.CaptureBackdropPatternTests: pattern names, checker and stripe cells, composite order (backdrop always last, never in front).
  • CmuxNextDesignTests.CaptureBackdropSnapshotTests (display suite, listed in scripts/ci/display-suites.txt): a translucent window's snapshot is uniform without a backdrop and shows the stripes with one; setCaptureBackdrop(nil) takes it away; aSnapshotShowsOnlyItsOwnWindowsBackdrop: two windows, each with its own backdrop, and each snapshot shows only its own.
  • replacingOrRemovingABackdropClosesTheOldPanel: replacing a backdrop closes the old panel (its willClose fires once), none closes the current one, and the window lets go of it.
  • closingTheTargetWindowRemovesAndClosesItsBackdrop: closing the target window removes and closes its backdrop.
  • WindowSnapshotOverlaysTests rerun for the snapshot change.

Observed / Not run

  • Observed: scripts/cmux-next/prepush-gate.py clean; scripts/cmux-next/check-objc-selectors.py 0 failures. Fleet package-test-lane.sh suite on a gui-session-v1 mini at 0b98b4e: CaptureBackdropPatternTests, CaptureBackdropSnapshotTests and WindowSnapshotOverlaysTests all passed. Red: the same suite with the overlay skip removed failed aSnapshotShowsOnlyItsOwnWindowsBackdrop (B's snapshot took A's backdrop as an overlay: floating == 1, an orange stripe pixel). The head after that only replaces the panel's self-retain and close() override with the target window keeping it.
  • Not run: a full pr-capture of a glass surface with the backdrop (follows once this and the tooling side land).

No capture: a DEBUG-only control method and a snapshot path that is unchanged unless a backdrop is set; nothing visible changes in the app. The display tests above check the composite pixels.

Changelog

none

risk:low (DEBUG-only control method; the snapshot path changes only when a backdrop is set)

🤖 Generated with Claude Code

Agent session: agent session

…e snapshots

A capture slot shares its mini's desktop with other slots, and debug.window_snapshot
composites only the app's own windows, so a translucent window or its glass showed
nothing behind it in a capture (the uniform dark backdrop): lifted-row glass
translucency could not be verified. debug.capture_backdrop (DEBUG builds) puts the
app's own patterned window (checker, stripes, or an image file read off main) just
behind a window, covering its screen, and the window's snapshot composites it behind
everything else, so the window server blurs it under the glass as on screen.
`pattern: none` closes it; it goes with the app and is never the desktop picture.

Tests: CmuxNextDesignTests.CaptureBackdropPatternTests (names, pattern cells,
composite order) and the display suite CaptureBackdropSnapshotTests (a translucent
window's snapshot shows the stripes behind it, and does not without the backdrop).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-a7306ef145c005ad24ab
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review f31f3aa: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr27514 -f ref=dep-fleet--debug-capture-backdrop -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); NEW: red check: Package dependency lists sorted is not red on the base 4756282; do not merge until green; see lane-rules

@teamleaderleo

teamleaderleo commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-qqmj05 created for this PR by the cmux coordinator.

@blacksmith-sh

blacksmith-sh Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Found 9 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp3isv7qur/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmp_o_pr96q/summary-missing.json (live):/summary-missing.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
View Logs
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)/
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, next-review): BLOCK at af2187e

  • P2 — CaptureBackdropPattern.swift:115: Backdrops corrupt other windows’ snapshots. With window A above B, A’s screen-sized backdrop sits above B. Because it is an unattached NSPanel, existing floating-overlay discovery includes it in B’s capture, covering B with the opaque pattern despite B having no backdrop. Exclude all capture backdrops from floating-overlay discovery, then explicitly composite only the target’s backdrop. This also prevents a reordered backdrop from becoming full-frame overlay coverage that overwrites captured Chromium content.

…solated pattern

A backdrop covers its window's screen and sits just behind that window, so window
A's backdrop is above window B, and floating-overlay discovery (an app window above B
over its frame) took it as an overlay of B: it covered B in B's snapshot (review of
af2187e). Every capture backdrop is now skipped there; a snapshot composites only
its own window's backdrop, behind it. CaptureBackdropPattern is nonisolated, so the
async control route can parse it off the main actor (the app target did not compile:
"loses global actor 'MainActor'").

Test: CaptureBackdropSnapshotTests.aSnapshotShowsOnlyItsOwnWindowsBackdrop (two
windows, each with its own backdrop; each snapshot shows only its own).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-a7306ef145c005ad24ab
@teamleaderleo
teamleaderleo force-pushed the dep-fleet--debug-capture-backdrop branch from b4f7193 to c893b33 Compare October 30, 2026 00:45
The display tests captured in the same turn the windows were ordered in, before the
window server composited them, so every snapshot was nil (fleet step e4e40db9). They
now flush and capture until the window (and its backdrop) shows, bounded at 5 s, as
WindowScreenshotTests does, and skip on a host that cannot show windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-a7306ef145c005ad24ab
@cursor

cursor Bot commented Oct 30, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

…erride

The panel kept itself alive until an overridden close(), and the crash-class
selector rule wants every new super call probed on a Mac first. The window it is
behind now keeps it (an associated object), so it goes with that window, and
setCaptureBackdrop orders the old one out; no super call is added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-a7306ef145c005ad24ab
@cursor

cursor Bot commented Oct 30, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, router-review): BLOCK at 71131c8

BLOCK

  1. Packages/macOS/CmuxNext/Sources/CmuxNextDesign/Windows/CaptureBackdropPattern.swift:107-110 — Replacing or removing a backdrop only calls orderOut and releases the association; it never closes/removes the independent NSPanel (which also sets isReleasedWhenClosed = false). Repeated pattern updates therefore accumulate stale window-server windows, and closing the target window has no teardown hook, so its full-screen backdrop can remain visible after the target is gone. Cleanly close/remove the old panel during replacement and target-window teardown.

… closes

Replacing or removing a capture backdrop only ordered the old panel out and
dropped it, so repeated pattern changes left window-server windows behind, and
closing the target window left its screen-sized backdrop on screen. The panel
now closes when it is replaced or taken away, and observes its target's
willClose (weakly, removed on teardown) to remove and close itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex-review-box/gpt-6-astra high, lane-place): BLOCK at 71131c8

BLOCK

  1. [P2] CaptureBackdropPattern.swift:114: Retaining the backdrop on its target does not synchronize visibility. Set a backdrop on the palette, then dismiss it: PaletteController.hide() retains and orders out the palette, leaving the screen-sized backdrop visible indefinitely. Minimizing a main window has the same problem. Tie backdrop visibility to the target’s hide/minimize/close lifecycle; the target == nil cleanup cannot handle retained targets.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, router-review): SHIP at f31f3aa

SHIP

No correctness, regression, crash, race, security, or test-blocking defects found.

(router-review gave this verdict for this head; its own --post hit the shared API quota, so the receipt is posted by the PR owner after checking the head has not moved.)

@teamleaderleo
teamleaderleo merged commit 8fb4805 into feat-cmux-next Oct 30, 2026
152 of 163 checks passed
@teamleaderleo
teamleaderleo deleted the dep-fleet--debug-capture-backdrop branch October 30, 2026 06:59
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

lawrencecchen added a commit that referenced this pull request Oct 31, 2026
… capture backdrop is DEBUG/bench only (#28263)

* fix(design): the capture backdrop panel is DEBUG and bench only, as its only callers are

An independent exception review (reward-hacking audit, arch-ratchet tip rises) found that
CaptureBackdropPattern.swift (8fb4805, #27514) ships its CaptureBackdropPanel, its willClose
observer and NSWindow.setCaptureBackdrop in Release, although its only callers
(DebugWindowSnapshot.captureBackdrop, the debug.capture_backdrop route in AppControl+Input) are
`#if DEBUG || CMUX_BENCH_HOOKS`, and its exception said "DEBUG-only". The panel, its view and the
two NSWindow members that make or read it now sit in the same `#if`; Release keeps the two read
helpers that NSWindow+Snapshot uses, as constants (no backdrop: none to skip, nothing behind).
The pattern enum stays (a value type, no observer). Release behavior: unchanged, since nothing
could make a backdrop there.

* arch-ratchet.json: 12 reviewed exceptions for the tip rises an independent reviewer judged needed; 9 unreviewed pending entries removed

Reward-hacking audit (handoff/followups.md "arch-ratchet tip rises (no exceptions)"). An independent
exception reviewer (it wrote none of the code and none of the old reasons) judged 14 candidates at
2391c5e: 12 EXCEPTION-OK, 2 FIX (AppDelegate.swift:514's quit flush, on fix/arch-rise-quit-flush;
the capture backdrop, gated in 2391c5e and then accepted). The reviewer wrote each entry's
receipt itself; each has author, owner and expiry 2026-11-30, one site per entry (allow 1):
global_mutable_statics PaletteScope lock (#28036), CrashWatcher descriptor (#22844);
stateful_singletons KillSwitches.shared (#24212), BlockingViolationTable.shared (f668c89);
observer_sites DesktopWallpaperWatch :144 :147 :148 :149 (#25308), IPhoneHostService wake (#27297),
DebugWindowRecord willClose (#23680), CaptureBackdropPattern willClose (#27514);
blocking_allow_escapes SingleInstanceGuard (#25918), plus its escape_reviews entry (read by the rule
of #28195).

Removed: the 9 PENDING entries with no review that these replace or that cover no site any more
(CrashWatcher, BlockingViolationTable, DesktopWallpaperWatch +7, SingleInstanceGuard, HomeReduceMotion
(a FIX), kill switches +2, PaletteScope +4 (statics gone) and +1, CaptureBackdropPanel). No applied
entry, total or measured_sha changes; no ratchet number is raised. --update still refuses until the
fix PRs land; it is run after them.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — f31f3aab Deployed Oct 30, 2026 by teamleaderleo via namespace autofix #7307
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant