Solid themed toasts, "New Tab" titles, no raw workspace ids

Merged
TE
1mo ago
+1217-824

Toasts in cmux-next were cramped, translucent glass, and a closed New Tab page said Closed “about:blank”. Now every toast uses one solid, themed component, tabs keep their strip names everywhere, and no error shows a raw id.

The component (CmuxNextDesign/Toast)

  • The surface is opaque: the theme's overlay fill (ThemeTokens.opaqueOverlayFill at ChromeTunables.opaqueOverlayLift, the same fill Reduce Transparency uses), pinned on every Mac. Palette.popupSurface isn't on base yet; a comment marks where to swap it in.
  • Corners use the popups' 8 pt radius (PopupStyle.standard), not a capsule, and the toast casts the popups' one subtle shadow.
  • Padding is 8 pt vertical and 12 pt leading, with 12 pt before the action. Undo is a full-strength emphasized text button.
  • CmuxToastCenter already did placement (bottom-center, 24 pt up), stacking (three per window), auto-dismiss on DemandTimer and the hover hold; tests now cover them.

Toasts routed through it

  • Refusal HUD: RefusalHUD, the capsule that said "no workspace ", is now a toast with id refusal.
  • Feed and Tasks rejects: FeedToast is deleted. The Feed reject and the Tasks pane's reject overlay go through a new cmuxToast(id:message:onDismiss:) SwiftUI modifier.
    • onDismiss runs on the next main-actor turn whenever the toast ends, including when a newer toast pushes it out of the stack, so the same reject shows again.
    • It also runs when the view is dismantled, so a reopened panel doesn't replay an old reject.
  • Already on CmuxToastCenter, now with the new look: closed tab, group and workspace close or undo, pin, icon history, tab icon, room deleted, remote forget, keymap, CLI install, cloud move and result, recovery notice, file recovery, browser, bookmark and password imports, classic import, and surface zoom.
  • No web page in webviews/src shows a toast.

Tab titles

  • TabTitleResolver moves the strip's title logic out of PaneController. It covers the New Tab page (or History), agent chats, Home conversations, page and app tabs, and incognito tabs.
  • Three places now use it: the strip, the sidebar's tab rows (SidebarBridge passes it as SidebarMapping's tabTitle) and the closed-tab toast, from both a pane close and a tab-group close.
  • Only a browser tab or a real New Tab page becomes "New Tab". A chat or a page tab keeps its own name, even though its record is titled about:blank.

No raw ids

  • 14 refusals that named an object id now say what happened in plain words, for example "That tab is no longer open.", "That workspace is no longer open." and "That window is no longer open."
  • They cover the workspace, workspace group, tab, pane, a pane's workspace, a tab's content, screen, closed item, column, a pane's daemon handle, open and saved tab groups, a workspace's panes, window and screen group.
  • These messages reach the toast, the action not-found paths and the socket's not_found.
  • The new keys in Refusals.xcstrings and Screens.xcstrings are translated into all 21 locales in each catalog, and the old keys are removed.

Testing

cmux-ci run --class light --script scripts/ci/run-swift-testing-suites.sh --arg Packages/macOS/CmuxNext:

First round

  • Red, at eb65fddc8077c87d9bc59eae7637a0d2d25a81cf: CmuxToastSurfaceTests and ToastCopyTests failed on assertions: a glass material instead of opaque, a 10 pt radius, Closed “about:blank”, and no workspace ws_….
  • The placement and hover-hold tests already passed on base; they pin existing behavior.

Review round

  • Red, at 09c5f377c1bb4d5b59614d3b96a3e48377de4c8d (tests only): the build failed because the new test API (CmuxToastAnchorView.center and dismantle, TabTitleResolver, SidebarMapping's tabTitle) didn't exist yet. These tests never failed on an assertion before the fix.
  • ToastCopyTests now checks four things:
    • a chat, a diff page tab, a New Tab page and a blank browser tab keep their strip names in the sidebar rows and in the group-close toast;
    • 15 refusal reasons never contain a sample id.
  • CmuxToastModifierTests checks that a reject pushed out of the stack, and one left behind by a closed panel, clears its model.
  • Green, at d0dbac151ae71aa0ed61e563782ae880f59cc888: all 141 suites passed, including those three suites and HeaderDiffPlacementTests.

Local checks

  • check-concurrency.sh (1 hit), check-theme-scope.sh (15) and crash_ratchet.py (5 modules) match base; none of the hits are in this change.
  • check-system-blue.py and check-no-godfiles.sh pass.
  • lint-ios-package-conventions.sh flags only the two namespace-type errors already on base (CommandRetentionSetting, AgentPaneWarmup).
  • localization_catalog.py check reports nothing for the new keys.

Not verified: there's no live capture, and the Feed and Tasks panel toasts haven't been checked in a running app.

Changelog

Changed: Toasts are a solid, theme-colored card at the bottom center of the window. A closed New Tab page is called "New Tab", chats and pages keep their names in the sidebar and in toasts, and errors no longer show raw ids.

🤖 Generated with Claude Code