Skip to content

cmux-next: collages: arrange several pictures as one background - #21789

Closed
teamleaderleo wants to merge 58 commits into
feat-cmux-nextfrom
leo/next-collages
Closed

teamleaderleo wants to merge 58 commits into
feat-cmux-nextfrom
leo/next-collages

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Phase 7 of the backgrounds plan: collages, approved by Leo through the coordinator.

Stacked on #21743, which carries #21731's Settings page provider. Review only 8afeb67.

What changes

  • appearance.background takes collage:<id> (BackdropSelection.collage).
    • The id is letters, digits and dashes only (1–64), so it never names another path.
    • The native BackdropSelectionSetting.accepts and the page's validate.ts both take it. A schema sample and a refused sample (collage:../evil) are added, and the base-autofix regenerates the export.
  • Storage: Backgrounds/Collages/<id>.json holds the record (BackdropCollage: name, fill and layers back to front, each with a source, frame, opacity and corner radius). <id>.png is the drawing, which the window shows like any picture.
  • Drawing: BackdropCollages.render draws the fill, then each layer.
    • Each layer is decoded at about the size it's drawn and fills its frame centered.
    • Rounding and opacity are applied, at 3200×2000.
  • Saving: a saved edit gets a new id, and the old files go. That way the image cache, which is keyed by id, never shows an old drawing.
  • Validation:
    • Layers must be still pictures (art, macOS stills or your images), not videos, the desktop or another collage.
    • Layers must sit on the canvas, with finite numbers, opacity from 0 to 1 and corner radius from 0 to 0.5, and there can be at most 12.
    • A collage where nothing can be read is refused rather than saved as a bare fill.
  • Picker: collages are listed as Your collages, right after your images. Each has ✎ to edit and × to remove; the one in use can't be removed, and its hand placement goes with it.
  • Editor (Settings > Background > New Collage…):
    • add pictures from the grid
    • drag to move, and drag the corner handle to resize; edges snap to the canvas's halves and thirds
    • arrow keys nudge (Shift moves further), and Delete removes
    • Bring Forward, Send Backward, Rounded Corners and Remove Picture
    • fill color and name
    • starter layouts: side by side, three columns, mosaic
    • Save draws the collage natively and sets it as the background
  • Ops: cmux.settings.backdrop.image.collage.save | get | delete, on BackdropImagesPageProvider. The page sets appearance.background itself through the normal settings write. CmuxNextApp is unchanged.

Strings

19 page keys and 3 native keys, all in 21 locales.

Tests

  • CmuxNextDesignTests/BackdropCollagesTests:
    • id safety and round trip
    • save draws 3200×2000, reads back and lists
    • an edit replaces under a new id, and delete works
    • every invalid layer kind is refused, and an unreadable-only collage is refused
    • the catalog lists collages right after your images and never as images
  • CmuxNextPagesTests: collage save, get and delete; a malformed collage is refused; the in-use one can't be deleted.
  • Web:
    • collageLayout.test.ts: snapping, staying on the canvas, minimum size, layouts and restacking
    • hostCards.test.tsx: New Collage adds two pictures, applies Side by Side, saves and sets collage:<id>, the in-use one can't be removed, and an edit saves with replacing
    • all 133 settings tests pass
  • vp lint, vp fmt and tsc are clean.

Verification

  • Swift: not built locally. It'll go in the next next-batch run.
  • Not dogfooded.

Not in this PR (follow-ups)

  • Video layers. The spec's "one video plays at a time" needs the collage drawn live, not as one PNG.
  • Placing a layer by hand inside the window. The editor places layers on its canvas.
  • Rotation.

Changelog

Added: Collages: arrange several pictures as one background, with snapping, layouts, rounded corners and a fill color.

🤖 Generated with Claude Code


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


Summary by cubic

Adds collages as a background: several pictures arranged on one 16:10 canvas, drawn natively to a PNG at 3200×2000 and set via appearance.background = collage:<id>.

Collages

  • The Settings editor adds pictures from the grid, drags and resizes with snapping to halves and thirds, nudges with arrow keys, restacks layers, rounds corners, sets a fill color, and picks starter layouts.
  • Collages live in Backgrounds/Collages/ as a JSON record plus a drawing; a saved edit gets a new id, and old files are removed only after the background switches.
  • Still pictures only; videos, the desktop and other collages are refused as layers.
  • The picker lists collages as "Your collages" with edit and remove (never the one in use); ops are cmux.settings.backdrop.image.collage.save | get | delete.

Carried from earlier phases

  • The subject crop keeps art clear of the sidebar and chat.
  • Placing a background by hand via a drag/zoom overlay (cmux.settings.backdrop.image.adjust), saved per background and removed with the picture.
  • Adding your own image from an https link or file drop, with validation and removal; the page's file reads and disk usage run off the main actor.

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

View guided diff Turn on auto-fix

teamleaderleo and others added 14 commits October 18, 2026 14:55
…t chat

Every wide painting framed the same fixed point (0.5, 0.45), so with the sidebar
and a docked agent chat open the subject often sat under them, and wide or short
windows cut faces off. Each work now names its subject box in art/manifest.json,
and the art zooms, slides or shrinks (over a blurred fill) so that box lies in the
part of the window the columns leave visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…out, not the App

CmuxNextApp may not grow (check-app-size). The sidebar and each workspace's
layout root now tell the window's backdrop where they cover the art
(WindowMaterialView.setCover), and the App is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Background gets Add from a Link (https only, judged by its bytes,
50 MB image / 500 MB video caps), a remove button on each of your images (not
the one in use), what they take on disk, and Show in Finder. A picked, dropped
or linked file must decode or play before it is copied in. Dropping a file on
Settings no longer needs experimental appearance (the page already said to).
The ops live in BackdropImagesPageProvider (CmuxNextPages); CmuxNextApp shrinks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r edge correctly

Review fixes for the subject crop:
- keepsSubject is measured on the final placement, never assumed.
- A shrunk or slid image keeps the subject in the span first (the cover clamp
  overrode it), and the shrink fit counts the subject's height too.
- No vertical fallback that cannot fail; portrait subjects are the head and
  upper body (top 70%%), which fit a 21:9 window.
- The catalog test asserts the subject frame lies in the visible span.
- Only a column that comes or goes springs the art; resizes and live window
  resizes move it at once. Fill filters are made per view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eir name in their folder

Review fixes for Add from a Link:
- The link's name is sanitized (no folders, colons or leading dots), and the
  renamed file must stay in the download's own folder (an encoded ../ climbed out).
- A delegate-driven data task replaces download(from:delegate:), whose task
  delegate never saw progress: the declared length and the running count are
  checked against the cap for the declared type, so a huge body never lands.
- Redirects off https are refused; timeouts are a minute idle, ten in all.
- The temporary folder is removed on every failure.
- Remove refuses the background in use natively, not just on the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Background > Adjust Placement… fades the window's content and puts
an overlay over it: drag moves the picture, scroll or pinch zooms (1x-4x),
Reset goes back to the automatic subject framing, Done or Escape ends. The
placement (the point at the middle of the visible span, and the zoom) is kept
per background in background-placement.json, and every window follows it.
It starts from the framing on screen, so the first drag does not jump.
CmuxNextApp is unchanged: the op rides BackdropImagesPageProvider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re they start

Re-review fixes: LinkDownload keeps its state in a Mutex and is plainly
Sendable (no new unchecked conformance for the crash ratchet). A cancel that
comes before the task exists marks the download, and the task is cancelled as
soon as it is made, instead of invalidating the session first (making a task
on an invalidated session raises). A body that starts like a picture gets the
picture cap whatever type the server declared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ace window, saved in order

Review fixes for Adjust Placement:
- Compile: the store notification's object is compared by identity (the batch
  Swift run stopped on 'sending store risks causing data races').
- Saves run on one serial queue, in order; an unreadable file is kept beside.
- The overlay opens in the workspace window (WindowManager.active), never the
  Settings window; one at a time app-wide; it ends when its window shows
  another background, and fades views added while it is open.
- Keyboard: arrows nudge (Shift: further), + and - zoom, 0 resets.
- A drag past an edge has no dead travel; windows only re-place for their own
  background's change; removing a picture drops its placement; the page's
  'no picture' note clears when the background changes.
- init(coder:) returns nil instead of fatalError (crash ratchet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ace-background

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextDesign/BackdropArtPlacement.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextDesign/WindowMaterialView.swift
#	Packages/macOS/CmuxNext/Tests/CmuxNextDesignTests/BackdropArtPlacementTests.swift
appearance.background takes collage:<id>, a collage saved as
Backgrounds/Collages/<id>.json and drawn at 3200x2000 to <id>.png, which the
window shows like any picture. Settings > Background > New Collage… opens an
editor: add pictures from the grid, drag to move, drag the corner handle to
resize (edges snap to halves and thirds), arrows nudge, layer order, rounded
corners, a fill color and a name, starter layouts (side by side, three
columns, mosaic). Saving draws it natively and makes it the background; your
collages can be edited (a new id each save) or removed (never the one in use).
The page ops ride BackdropImagesPageProvider; CmuxNextApp is unchanged.
Still pictures only for now; video layers are a follow-up.

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

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 22096127-bc5b-417e-b7be-a4edd77db841

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.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

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

Review de6d12f: 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=pr21789 -f ref=leo/next-collages -f platform=macos -f backend_mode=local

Unclear: web / react-apps-check failed: no known signature; failed step: Check webview test shards.

CI failed on de6d12f7df (run 39395662303 attempt 1): 4 code, 1 unknown.
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped.

Job Verdict Why
web / react-apps-check (3) code a web test failed
web / react-apps-check (4) code a web test failed
web / react-apps-check (1) code a web test failed
web / react-apps-check (2) code a web test failed
web / react-apps-check unknown no known signature; failed step: Check webview test shards

Failures:

  • seen on other PRs gallery-coverage.test.ts gallery coverage > every component and page has an entry or an allowlist line: failed (job)
  • seen on other PRs markdown-table-keys.test.ts markdown table keys > Return goes to the cell below and stays in the table: failed (job)
  • seen on other PRs markdown-table-keys.test.ts markdown table keys > Return in the last row adds a row below, keeping the columns' alignment: failed (job)
  • seen on other PRs markdown-table-keys.test.ts markdown table keys > Tab moves on a cell and adds a row past the last cell; Shift-Tab goes back: failed (job)
  • seen on other PRs markdown-table-keys.test.ts markdown table keys > Cmd-A selects the cell's text, then the whole table, then lets the document ta...: failed (job)
  • seen on other PRs markdown-table-keys.test.ts markdown table keys > Escape drops a cell selection to a caret: failed (job)
  • seen on other PRs markdown-table-keys.test.ts markdown table keys > copying cells writes a Markdown table and an HTML table: failed (job)
  • seen on other PRs markdown-table-keys.test.ts markdown table keys > column widths are kept per file and never touch the markdown: failed (job)
  • seen on other PRs mobileCard.test.tsx Mobile is its own category with the allow switch; Accounts has no iPhone card: failed (job)
  • seen on other PRs mobileCard.test.tsx the card shows status, space and phones with their transport, and follows the host: failed (job)
  • ... 17 more in the job logs
Matched log lines
web / react-apps-check (3): (fail) gallery coverage > every component and page has an entry or an allowlist line [0.52ms]
web / react-apps-check (4): (fail) Mobile is its own category with the allow switch; Accounts has no iPhone card [515.24ms]
web / react-apps-check (1): (fail) every page row has exactly one home [1.68ms]
web / react-apps-check (2): (fail) agent pane page > rounds its controls as small-radius rects, not capsules [15.32ms]

Not re-run automatically: web / react-apps-check (3), web / react-apps-check (4), web / react-apps-check (1), web / react-apps-check (2), web / react-apps-check are not machine failures.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

red: cmux-next CmuxNextApp size cap; NEW: green on the base c740963; do not merge until green; see lane-rules
red: cmux-next checks (god files, concurrency, crash safety, l10n); NEW: red check: Checks job structure (+2 more) is not red on the base c740963; do not merge until green; see lane-rules
red: web / Web status; NEW: green on the base c740963; do not merge until green; see lane-rules
red: web / react-apps-check; no finished base run on c740963 to compare; do not merge until green; see lane-rules
red: web / react-apps-check (1); no finished base run on c740963 to compare; do not merge until green; see lane-rules
red: web / react-apps-check (2); no finished base run on c740963 to compare; do not merge until green; see lane-rules
red: web / react-apps-check (3); no finished base run on c740963 to compare; do not merge until green; see lane-rules
red: web / react-apps-check (4); no finished base run on c740963 to compare; do not merge until green; see lane-rules

CI fast guards failed on de6d12f7df (https://github.com/manaflow-ai/cmux/actions/runs/39395662303). It does not block the merge; a red guard merged into main breaks it for every open PR.
The log named no failed step; see the run.

Agents: python3 scripts/ci/guard_attribution.py fix applies the mechanical fixes locally.

…dow comes forward

Re-review fix: the center's clamp had its bounds swapped, so for art that
covers the window every drag or arrow pinned it to an edge. The bounds are
now [middle / size, (middle - view + size) / size], and the test checks the
exact travel of a drag and the stop at the edge. Placing brings the target
window forward again, so from a separate Settings window keys reach it.

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

Copy link
Copy Markdown
Collaborator Author

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

@blacksmith-sh

blacksmith-sh Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Found 33 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp3okd11k8/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpvb17gceq/summary-fail.json (live):/summary-fail.json (live): View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs
src/pages/settings/categories.test.ts/categories.test View Logs
src/pages/settings/categories.test.ts/categories.test View Logs
src/pages/settings/mobileCard.test.tsx/mobileCard.test.tsx View Logs
src/pages/settings/mobileCard.test.tsx/mobileCard.test.tsx View Logs
test/agent-pane-no-capsules.test.ts/agent-pane-no-capsules.test View Logs
test/agent-pane-no-capsules.test.ts/agent-pane-no-capsules.test View Logs
test/composer-location-strip.test.ts/composer-location-strip.test View Logs

...and 23 more test failures. View all on Blacksmith: run 39395662303, run 39395662251

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

teamleaderleo and others added 9 commits October 18, 2026 22:03
The fleet compile found JSONValue (CmuxNextSettings) and revealInBackground
(CmuxNextCompat) used without their imports.

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

Re-review fix: saving no longer deletes the collage it replaces. The page saves
the edit under a new id, switches appearance.background to it, and only then
asks to delete the old one, which the host refuses while the app or any
workspace background (workspace-backgrounds.json) still shows it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sion, cache names

Re-review and fleet-compile fixes: saving never deletes (the page removes an
edited collage's old files after the switch), the record is written before the
drawing lands through a temporary file, a drawing without its record is not
listed, names are read once per id, and list() no longer trips the type
checker's time limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A public initializer's default argument may only use publicly imported API.

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

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

cursor Bot commented Oct 18, 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.

Re-review fix: when workspace-backgrounds.json exists but does not read, a
collage or image is taken to be shown, so removing it is refused rather than
risking a blank workspace. The test covers a workspace showing a collage and
an image, a malformed file, and a file that names neither.

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

cursor Bot commented Oct 20, 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
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 06:49 — with GitHub Actions Inactive
Comment on lines +46 to +48
window.makeKeyAndOrderFront(nil)
window.makeFirstResponder(overlay)
active = overlay

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Placement loses keyboard focus

BackdropAdjustOverlay.begin(in:) gives the overlay keyboard focus without telling the window's focus owner that an overlay opened. In an app window, ShellWindow.makeFirstResponder immediately reports this view as windowOrNone. The focus owner then puts focus back on the selected page or terminal.

The overlay stays visible, but arrow keys, zoom keys and Escape reach the content underneath instead. Register and close this overlay through the window's focus owner, as the palette does.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

return name
}

private static let names = Mutex<[String: String]>([:])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Name cache breaks locking rule

BackdropCollages adds a shared name cache guarded by Mutex. The repository's blocking-runtime rule forbids new manual locks around shared state unless a documented low-level boundary prevents an actor from owning it. This cache has no such boundary or explanation.

Keep cached names on an owned, isolated catalog or store instead of adding another lock-backed global cache.

Rule Used: Flag new blocking or timing-based synchronization in production Swift: semaphores, DispatchGroup.wait, sleeps, Task.sleep, asyncAfter, timers or polling for synchronization, DispatchQueue.main.sync, or manual locks where actor isolation or a real sig... (source)

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 07:10 — with GitHub Actions Inactive
Comment on lines +62 to +65
Self.writes.async {
try? FileManager.default.createDirectory(at: file.deletingLastPathComponent(), withIntermediateDirectories: true)
try? data.write(to: file, options: .atomic)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Saves use an unowned queue

BackdropPlacementStore adds a custom background queue for ordinary file writes. The repository's Swift concurrency rule requires this work to use Swift concurrency instead. Move ordered writes into an owned actor or a stored task chain so the store can await their completion.

Rule Used: Flag new legacy async patterns in cmux-owned Swift where Swift concurrency is the correct shape: DispatchQueue.global for ordinary async work, new Combine app state, completion-handler APIs fully under cmux control, or fire-and-forget Tasks with mean... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

/// `subviews`, so each window update (after every event it handles) looks for new ones.
private var updateObserver: NSObjectProtocol?
/// The one overlay placing now, in any window.
private static weak var active: BackdropAdjustOverlay?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Windows share hidden overlay state

BackdropAdjustOverlay.active keeps app-wide mutable state on the view type. Opening an overlay in one window closes another through this hidden reference. This violates the repository's requirement that runtime state belong to a constructable, injected owner. Move the active overlay to a coordinator owned by the app's window services.

Rule Used: Flag new ambient global state in production Swift: a top-level (file-scope) func used as API, a top-level mutable var or a stub class/struct holding a global flag/once-token, a caseless enum/empty struct used purely as a static func/static let namesp... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +155 to +158
{index === chosen && (
<button
type="button"
className="collage-handle"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Pictures cover the resize handle

A picture in front can cover the selected picture's resize handle. The handle is rendered beside its layer inside the back-to-front loop, and collage-handle has no higher stacking order. Clicking the covered corner selects the front picture instead, so users must move or reorder pictures before resizing. Render the selected handle above all picture layers.

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 07:25 — with GitHub Actions Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

CI / web / react-apps-check (2) is red on 9 of 13 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 07:46 — with GitHub Actions Inactive
Comment on lines +156 to +167
<button
type="button"
className="collage-handle"
aria-label={t("settingsWindow.collage.resize")}
style={{
left: `calc(${(item.x + item.width) * 100}% - 12px)`,
top: `calc(${(item.y + item.height) * 100}% - 12px)`,
}}
onPointerDown={grab("resize")}
onPointerMove={pointer}
onPointerUp={release}
onPointerCancel={release}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Keyboard resizing does nothing

collage-handle is a focusable button, but it only responds to pointer events. Enter, Space and arrow keys do nothing, and the layer's keyboard handler only moves or removes pictures. Keyboard-only users cannot resize a picture. Add keyboard resizing to the handle or provide width and height controls.

Comment on lines +66 to +71
const save = async () => {
setBusy(true);
setRefusal(null);
const result = await store.saveCollage(collage, replacing);
setBusy(false);
if ("id" in result) onClose();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Edits during Save disappear

The editor stays editable while saveCollage() runs. If a user changes the name, layout or pictures during a slow save, those changes update the preview, but Save still writes the earlier collage value and then closes the editor. Disable editing while saving, or keep the editor open when newer changes exist.

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 08:07 — with GitHub Actions Inactive
Comment on lines +384 to +385
const switched = await this.set("appearance.background", `collage:${reply.value.id}`);
if (switched.ok && replacing) await this.request("cmux.settings.backdrop.image.collage.delete", { id: replacing });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Editing resets the saved crop

saveCollage() switches to a new id without copying the old placement, then deletes the old collage and its placement. Anyone who positioned or zoomed the background loses that choice, even when changing only its name. Copy the placement to the new id before switching backgrounds.

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 08:33 — with GitHub Actions Inactive
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 08:45 — with GitHub Actions Inactive
Comment on lines +359 to +366
<button
type="button"
className="icon-button backdrop-remove backdrop-edit"
aria-label={t("settingsWindow.collage.edit", choice.title)}
onClick={() => {
const id = choice.id.slice("collage:".length);
void store.collage(id).then((start) => start && setEditing({ start, replacing: id }));
}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Workspace Edit does nothing

In This Workspace mode, each collage still shows an Edit button, but CollageEditor only renders when !own. Clicking Edit fetches the collage and sets editing, then shows nothing. Hide or disable Edit in that mode, or support opening and saving the editor there.

@teamleaderleo

This comment has been minimized.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

owner: 01a15c08-af2e-7471-8e77-d62753886e4f

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by nx-settings on big-red

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Rebuilt as #25433. The fresh current-base slice starts from 46dd17cbb22a8465e3f864b04998afb23e83c1b2 and carries the native collage model, validation, renderer, actor-owned storage, and focused tests. The catalog/selection bridge and editor remain explicit follow-up work in the replacement PR.

This branch was previously deployed

2 inactive deployments
artifacts — de6d12f7 Deployed Oct 20, 2026 by teamleaderleo
cmux-tui-merge-tree — de6d12f7 Deployed Oct 20, 2026 by teamleaderleo
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