cmux NEXT sidebar: a workspace opened into a named group appears straight in its group, never loose first
Leo, 2026-11-03: "likewise, i hate it when you guys make new worksapces or whatever and they appear for a split second and it's just bloop and then it goes into presumably whichever group... idk how we wanna solve that it's just weird how it flickers into some top level group or whatever then goes away"
leo-named-fix: land on push.
What happened
acpmux new --group / cmux agent open --group run agent.openSessionWorkspace. The daemon's workspace.create lists the new workspace with the loose rows; the app's join (AgentSessionWorkspace.join, a sidebar-document move or group.create) runs only once the daemon reports the workspace (WindowManager.applyPendingPlacements). So the sidebar drew a pass with the row at top level, then a pass with it in its group: two sidebar renders of two daemon states (created, then grouped).
Regressing PR: #30583 removed the first stop (Cmd-N's slot in the window's current group) but left this one, since the join has always followed the create (#24162 / #22809 made the join a post-create move).
Fix (mechanism b: a bounded hold)
GroupJoinHold/GroupJoinRows(CmuxNextSidebar, pure logic + oneDemandTimer): a workspace opened with--groupis held. While a pass lists it outside every group, the pass leaves its row out; the first pass that lists it in a group releases it, so it draws once, in its group, and no other row moves. Bound: 2 s from its first loose listing (a slow create spends none of it), then it shows where it lists; a one-shot deadline re-runs the pass so it shows even with no other change. A create that fails or is closed releases it (PendingWorkspaceRows.remove). Only while the sidebar document is active (the join path that moves an unlisted row).SidebarEdits.group(named:accepting:in:machine:): the join reads the window's rows, which now leave the held row out, so a named group takes it by its machine (the samecanPlacerule a drag uses).SidebarEdits.removing(_:from:)moved out of the App (pure); App Swift lines ā17.
Mechanism (a) (the create carrying the group) would need workspace.create to take a document group placement in its commit; the hold covers every create that does not.
Spec: plans/cmux-next/sidebar-owner.md ("A new row appears once, in its final place").
Tests
GroupJoinHoldTests (CmuxNextAppTests): every intermediate pass of a grouped create (create, passes between, join) never draws the row outside a group, and no other row is left out; the limit's fallback shows a row whose join never lands; a slow create is held from its first listing; a released create is not held; a held row's group is found by its machine. Red on base: the type does not exist there, and without the hold the create's pass draws n loose.
Observed / Not run
- Ran:
scripts/cmux-next/prepush-gate.py --base origin/feat-cmux-next --head HEAD: no new violations. - Not run yet: the Swift suite and compile (this host has no fleet client); they run in PR CI. Not tried in a DEV build yet.
Changelog
Fixed: A workspace an agent opens into a sidebar group appears straight in that group instead of flashing at the top level first.
Agent session: agent session
š¤ Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith Ā· Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the sidebar flicker where a workspace opened with --group (via acpmux new or cmux agent open) appeared at the top level briefly before jumping into its named group. The row is now held out of the sidebar while the daemon lists it loose, and it draws from the first pass that lists it in its group.
- The hold is bounded at 2 seconds from the row's first loose listing; a join that never lands shows the row once the limit passes, and a failed or closed create releases the hold.
- The join picks the named group by the workspace's machine, since its held row is absent from the listing.
GroupJoinHoldTestscovers intermediate passes, the limit fallback, slow creates, released creates, and machine-based group lookup.