Repository navigation
cmux-next: decode the backdrop painting off the main thread and fade it in - #18105
Conversation
… main actor (red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t in (green) The first window's painting decoded inside its first frame's commit; a 2400 px painting cost that frame about 50 ms. BackdropImageStore now decodes it once per process off the main actor; the window shows the theme's colors until it is ready, then fades the painting in. Later windows take it at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
|
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ade45cf. Configure here.
| fade.toValue = 1 | ||
| fade.duration = 0.2 | ||
| return fade | ||
| } |
There was a problem hiding this comment.
Fade ignores motion-off setting
Low Severity
The painting fade is a hardcoded 0.2s CABasicAnimation and never consults Motion. When motion speed is off, MotionPolicy requires opacity changes to apply in one frame, but the backdrop still animates.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit ade45cf. Configure here.
|
Exact-head Swift test run on
The first two are inherited from feat-cmux-next commits |
|
merge-override: Swift test reds are inherited base failures documented with green Release compile and generated files on this exact head |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37566149610 at bae93f9 failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37566936732 at 53a924d failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37566318678 at 10e4d81 failed: cmux-next checks (god files, concurrency, crash safety, l10n), cmux-next generated files. |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37567938071 at f43f800 failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37567471217 at 2a1b504 failed: cmux-next checks (god files, concurrency, crash safety, l10n), cmux-next generated files. |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37567159145 at abada68 failed: cmux-next checks (god files, concurrency, crash safety, l10n), cmux-next generated files. |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37568316271 at 169c1da failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37568445230 at 3c11c5a failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37568512187 at e8aa5c1 failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37569422881 at 552e7cb failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37569270312 at f60b3d7 failed: cmux-next checks (god files, concurrency, crash safety, l10n), cmux-next generated files. |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37569941713 at 46215de failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37569551101 at 5976099 failed: cmux-next checks (god files, concurrency, crash safety, l10n), cmux-next generated files. |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/37570246034 at e8a5696 failed: cmux-next checks (god files, concurrency, crash safety, l10n). |


Summary
With a background painting set in cmux.json, the first window decoded the painting inside its first frame's commit, on the main thread. The wheat-field painting is 2400 x 1910 (about 18 MB decoded), and drawing it from the first frame cost that frame about 50 ms (#18087).
Now the painting decodes off the main thread and the first frame doesn't wait for it:
BackdropImageStoredecodes each backdrop image once per process with ImageIO (kCGImageSourceShouldCacheImmediately) in a detached task, and shares the result.WindowMaterialView.applyshows a decoded image at once. Otherwise it leaves the art hidden, so the window shows the theme's colors, and fades the painting in over 0.2 s when it's ready. A newerapplycancels a pending load.BackdropArt.imageURLandBackdropSelection.imageURLgive the file to decode.image()is unchanged for the wallpaper picker.This is independent of #18087, and together they give a first frame at base speed in the cmux.json theme.
Numbers
Warm relaunches of Debug fleet builds on one capture mini: a fresh profile, a cmux.json with
Apple System Colors Lightand the wheat-field painting, and an agent chat in workspace 2. Values are ms since process start (first_window_frame_committed), with frames from window captures (sweep4a.py).The last row is a scratch build that also carries launch logging (build 8767ffae). The base's last two values (398, 416) are from the same session as that row.
Testing
Two tests in
BackdropArtTests, committed red before the fix:theFirstWindowsPaintingDecodesOffTheMainActorThenShows: apply shows no painting, then it shows once the load finishes.aLaterWindowShowsTheDecodedPaintingAtOnce.The existing tests that read the painting now wait for the load.
Each run used the same focused command on a fleet runner:
./scripts/ci/package-test-lane.sh suite Packages/macOS/CmuxNext <suites>.theFirstWindowsPaintingDecodesOffTheMainActorThenShows, 2 issues (the painting showed right after apply; nothing was cached)BackdropArtTests,WindowMaterialTests,BackdropTextureTests,WindowKitTests,WindowTranslucencyTests,WindowRootMaterialTests,OneBackdropTests,WindowBackgroundLiveTests,BackdropArtSettingsTestscheck-concurrency.shandcheck-no-godfiles.sh --only swiftpass. No user-facing strings changed, so no localization audit was needed.Not run: a review sub-agent (lane rule: no sub-agents).
Changelog
Changed: cmux-next's background painting fades in after the first frame instead of delaying it.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Moves backdrop painting decoding off the main thread so the first window's first frame no longer waits for it; the painting now fades in over 0.2 s once ready.
BackdropImageStorenow decodes it once per process in a detached ImageIO task and shares the result.imageURLproperties feed the decoder;image()is unchanged for the wallpaper picker.WindowMaterialViewtakes an injectableBackdropImageStore(default.shared) and a test await point (artLoaded()) for the pending load.Written for commit 65f8e9a. Summary will update on new commits.
Note
Low Risk
UI/performance change in window backdrop rendering with no auth or data-path changes; main risk is transient visual behavior (fade, brief theme-only frame) or race on rapid backdrop changes, mitigated by task cancellation.
Overview
Moves backdrop painting decode off the main thread so the first window frame no longer blocks on large bundled JPEGs (~50 ms for the wheat-field asset); the window shows theme colors first, then fades the painting in over 0.2 s when decoding finishes.
Adds
BackdropImageStore: a process-wide cache that decodes each selection once via ImageIO in a detached task and sharesNSImageinstances across windows.WindowMaterialView.applyuses cached bitmaps synchronously when available; otherwise it starts a cancellable load and updates the art layer when ready.BackdropArt/BackdropSelectionexposeimageURLfor decoding; synchronousimage()behavior for the picker is preserved.Tests now
await artLoaded()and add coverage for deferred decode vs. instant display when already cached.Reviewed by Cursor Bugbot for commit 65f8e9a. Bugbot is set up for automated code reviews on this repo. Configure here.