Skip to content

cmux-next: smoother dialogs, AA contrast for every dialog button (macOS, web, iOS) - #27638

Merged
azooz2003-bit merged 35 commits into
feat-cmux-nextfrom
feat-cmux-next-alert-polish
Oct 31, 2026
Merged

azooz2003-bit merged 35 commits into
feat-cmux-nextfrom
feat-cmux-next-alert-polish

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Polishes every cmux-next confirmation dialog (macOS native, about 100 call sites through CmuxDialogCenter), the web pages' ui/Dialog, and the iOS alerts: smoother open and close, and every dialog button at WCAG AA.

Measured with a new DEBUG tool, debug.dialog_strip: the window and its overlay panel read back to back from the window server (about 21 ms apart) while a dialog opens and closes, with per-frame card and scrim progress.

Found on the base build (5 runs per fixture, dark; 3 per fixture, light):

  • Close flash: the card lit up a lighter grey for the whole fade, because its overlay panel gives key back as the fade starts and the Liquid Glass took its inactive frost. The close's first frame was 1.2 to 1.7 times further from the open dialog than the closed state is. Seen in 9 of 10 dark closes.
  • Entrance flash: one frame of the whole dialog and scrim at full opacity, then the fade from zero. Seen in 7 of 10 dark opens. There were two causes: attaching the panel displayed its layers at full opacity, and Motion.set committed the model value and its animation in separate transactions.
  • Flat motion: the card only faded, while the palette grows in. The scrim shared the card's 120 ms fade, then the inactive-window tint dimmed again on its own 180 ms token, so the window darkened in two steps.
  • Contrast: destructive titles were 3.36:1 (light) and 3.11:1 (dark) on the opaque card, and 2.1:1 over the glass; the theme's danger is fit only to 3:1. Key hints were 4.13:1. The default button's fill differed from Cancel's by 1.06:1 (light) and 1.12:1 (dark): Return's button did not stand out. These are the base readings of CmuxDialogLightContrastTests.

Changes

  • Motion (WindowOverlayHost+Present): the card grows from Motion.panelOpenScale on the appear spring and shrinks toward panelCloseScale on fadeOut, as the palette does. Under Reduce Motion only the fades remain; with animation off the dialog shows in one frame and completions still run. The scrim eases on the scrim token, the one the inactive-window tint uses. Card and scrim are transparent before the panel attaches.
  • Motion gate (Motion+AppKit): every spring, fade and timed set commits its model value and its animation in one transaction. This fixes the one-frame flash for every gated layer animation, not only dialogs.
  • Glass: the dialog card keeps the key-window glass (keepsActiveLook, as the window backdrop does).
  • Buttons (CmuxDialogButtonLook):
    • Default (Return): the solid accent fill with on-accent ink, as the web's .primary.
    • Destructive: a solid danger fill with ink fit to 4.5:1, as the web's .danger. The tinted alternative (red text on a faint red wash, also fit to 4.5:1) stays in code as CmuxDialogButtonLook(filledDestructive: false) for the design call; there is no Debug toggle, so the defaults guard sees no new default.
    • Cancel and normal: the selection gray.
    • Every title and key hint is fit to 4.5:1 over both card surfaces (glass over the scrimmed window, and the Reduce Transparency surface).
    • The focus ring moves just outside the button so it shows on a solid fill. It had been clipped away: clipsToBounds.
  • Web (ui/popupSurface.css): the dialog grows in and shrinks out with the same scales; the backdrop eases on the standard (scrim) step; Reduce Motion (system or the app's data-cmux-motion=reduced) keeps only the fades.
  • Overlay panel detach (WindowOverlayHost): the panel leaves its window only after the frame that draws it empty has committed (AfterFrameCommit; a re-attach in between cancels it). Before, it was ordered out with its last dialog still on its surface, and the next attach showed that for a frame (2 in 10 opens when the panel attached fresh; none while it stayed attached). An attach is never hidden, so page planes, the agent cursor and first overlays show at once.
  • iOS (HIG Alerts, checked: "Always place the default button on the trailing side…"; "Use the destructive style to identify a button that performs a destructive action people didn't deliberately choose"):
    • Text-entry alerts (Rename, Rename Pane, Rename Screen, Rename Tab Group, Set Tab Group Icon, New Group, Create Team, Join, Send, Set, Open, Save, Create) had Cancel drawn bold. Their confirm is now the preferred action (.keyboardShortcut(.defaultAction): bold, Return), as is Message Not Unsent's Try Again.
    • Destructive confirmations keep their role and are never the default.
    • iOS alerts are system alerts, so their animation and colors stay the system's.
  • DEBUG tool: debug.dialog_strip {open, close, hold_ms, scale, dir}.

Capture

Viewed after image: yes
Visual verdict: Yes, the after stills read as a clean native dialog next to ChatGPT and Claude, with a solid default button, a clearly destructive red button, every label at 4.5:1 or better and a focus ring that clears its neighbors.

No capture: cmux-ci capture for this PR built the head (jobs f369165f4e5b and 8fc7292a788f), but no capture mini could be leased. cmux14, cmuxs-mac-mini-3, cmux9s and cmux10s rejected SSH authentication ("verify the fleet key and owner"), the others stayed busy, and the Blacksmith fallback found no runner (manaflow-ai/cmuxterm-hq run 41141384309).

Real-app captures from the tagged DEV builds are in the PR comments and the visual reviews: before/after stills of both dialogs in light and dark, the focus ring, and frame-by-frame open/close strips from debug.dialog_strip. The last real-app stills are from 1dcfb3f. Since then only the focus ring color has changed, now fit to 3:1 on the card as it renders, plus non-visual code. CmuxDialogLightContrastTests checks that against the light card color measured in that capture, rgb(223,223,223); it is red at 44f77e2 (2.63:1) and green at the head.

Performance

metric before after delta harness or run link
Main thread to present, median (Close Workspace) 17.0 ms 26.0 ms +9.0 ms debug.dialog_strip, tagged DEBUG builds alertbase vs alertpolish, same window and display, 10 dark runs per dialog
Main thread to present, max (Close Workspace) 27.0 ms 33.8 ms +6.8 ms debug.dialog_strip, tagged DEBUG builds alertbase vs alertpolish, same window and display, 10 dark runs per dialog
Main thread to close, median (Close Workspace) 13.2 ms 23.9 ms +10.7 ms debug.dialog_strip, tagged DEBUG builds alertbase vs alertpolish, same window and display, 10 dark runs per dialog
Open settles, median (Close Workspace) 131.5 ms 149.0 ms +17.5 ms debug.dialog_strip, tagged DEBUG builds alertbase vs alertpolish, same window and display, 10 dark runs per dialog
Close settles, median (Close Workspace) 134.5 ms 124.5 ms -10.0 ms debug.dialog_strip, tagged DEBUG builds alertbase vs alertpolish, same window and display, 10 dark runs per dialog

These laptop numbers are not a controlled A/B; read them as unresolved. The branch app's state drifted from the base app's: it gained a browser workspace with a live page and a second window, which the base app never had. Across the branch's own batches the median present time ranged from 8.5 ms (an earlier build) to 26 ms (the later ones, in the drifted app), against 11.5 to 17 ms on base. DEBUG builds with the recorder running are also not budget figures. The controlled measurement is the fleet run of CmuxDialogLatencyBench (dialog build and layout, where this PR adds CPU: the per-button color fit) at the merge base and at the head; results below. No fleet STRESS bench of the present path was run.

Not finished at merge: the CmuxDialogLatencyBench runs (CMUX_DIALOG_BENCH=1, merge base vs head) were still running on the fleet when this merged. Their numbers will be added as a PR comment. Until then there is no controlled perf evidence either way.

Owners

PL-motion-cef (Overlay/WindowOverlayHost*.swift) and the Performance Lead (Motion/): Motion.set now commits its model value and animation in one transaction, and the overlay panel detaches one commit later, after it has drawn empty.

Design decision for Lawrence (#13742)

Filled (shipped) vs tinted (in code, not exposed). Posted with a recommendation for filled on #13742.

Changelog

  • Changed: Dialogs open and close more smoothly, and their buttons are easier to read: the button Return presses is filled, and destructive buttons are a solid red.

Verification

Regression tests, red then green (cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg=Packages/macOS/CmuxNext --arg=<suites>, Xcode 26.6):

Commit Suite Red at its test commit Green at head
test(dialogs): destructive and key-hint ink, distinct default, grow-in, key-window glass DialogFadeTests 2 failures: no grow spring; card glass keepsActiveLook false passed
same CmuxDialogLightContrastTests 7 failures: destructive title 3.36:1 light, 3.11:1 dark; key hint 4.13:1; default vs Cancel fill 1.06:1 / 1.12:1 passed
test(dialogs): panel attaches with the dialog transparent DialogFadeTests opacity 1 at attach passed
test(dialogs): focused button draws an unclipped ring CmuxDialogLightContrastTests clipsToBounds true passed
test(overlays): panel leaves only after its empty frame; re-attach cancels DialogFadeTests the panel left in the same turn its content did passed

At the head (a56a777), 22 suites pass: WindowOverlayHostClose, AgentCursorGlassBlur, AgentCursorTagClick, FullScreenTitlebarChild, DialogFade, CmuxDialogLightContrast, FloatingGlassStyle, FloatingSurfacesStayGlass, DialogKeyHint, HoverCardPanelMotion, MotionCategoryPolicy, MotionCommitProbe, MotionMasterSwitchSurface, MotionPathValueKind, MotionPathValues, MotionScale, MotionScreen, PagePopupMotion, PopupGrowMotion, PopupReduceMotion, ThinScrollerFade and CmuxToastSurface. Motion.set and the panel detach affect every gated layer animation and every overlay, so the motion, popup, hover card, toast, agent cursor and full-screen titlebar suites are included. App overlay suites also pass: FullScreenTitlebarPageMode, PageGeometryPost, StripScrollbarAbovePage and PopupSurface. AgentCursorGlassBlurTests caught a regression in an earlier detach version, fixed in 5007c02.

Live measurement, debug.dialog_strip on two tagged builds (base alertbase, branch alertpolish), same window on the same display:

Base Branch
Opens with a full frame before the fade 5 of 30 0 of 28
Dark closes where the card turns grey 19 of 20 0 of 28
Largest close overshoot (dark) 1.65 1.02
Destructive title contrast, light / dark (captured) 3.81 / 2.92 5.63 / 5.64

Observed / Not run

Observed:

  • Red/green fleet runs (above), prepush-gate.py clean, scripts/web-lint.sh clean.
  • Tagged builds alertbase and alertpolish, driven through the debug socket on the agent display only, never focused. 70 recorded opens and closes, light and dark, plus full-resolution stills.
  • iOS: cmux-next-ios-sim.sh build at the head built, installed and launched in an iPhone 17 Pro simulator (iOS 26.5).
  • Two correctness-first review subagents ran (see the PR comments). The first It found 3 web CSS defects (the backdrop close fade was cut off; the Home palette slid sideways under reduced motion because of transform; sheets snapped to a scale) and 4 minor native ones (debug timer lifetime, Cancel's hover hue in accent themes, the focus-ring gap, multi-surface ink fit), all fixed. The second reviewed those fixes and the base merge. It found that hiding a freshly attached panel also hid page planes and delayed first overlays, so the fix moved to the detach (2ede0f5, 5007c02).

Not run:

  • The web browser test popup-reduce-motion (updated for scale). Freestyle needs a key this Mac doesn't have, the receipts and gallery hosts don't resolve from here, and the fleet only runs the full webview suite.
  • An iOS alert on screen: no UI test reaches one. The bold preferred action is SwiftUI's documented behavior.
  • No fleet perf STRESS bench. Present and close main-thread times in DEBUG are in the same range as base.
  • No Cloud path: this change touches no Cloud code, so the tag uses the local backend.
  • The panel-detach path live: in the tagged builds every window keeps a visible Chromium page, so the panel never detached during the strips. The detach is covered by its regression test only.

🤖 Generated with Claude Code

azooz2003-bit and others added 17 commits October 29, 2026 14:28
…strip)

DEBUG builds only. Reads the active window and its overlay panel (where
dialogs draw) back to back while a debug.dialog fixture opens and closes,
and reports per-frame card and scrim progress, intermediate frames,
settle time, the largest progress step and the main actor's present and
close time, with a PNG per frame. Evidence for the dialog motion work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A recorded frame is the window's backing pixels times the scale, so on a
2x display the card rect (points times scale) was half size and off the
card. Derive the pixels per point from the first frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n, grow-in and key-window glass

Regression tests for the alert polish, red on the base:
- a dialog's destructive title and every key hint read at 4.5:1 on their
  fill over the card (the theme's danger is fit only to 3:1; over the
  dialog glass it measured 2.1:1 with debug.dialog_strip);
- the default button's fill differs from Cancel's at least 3:1 (it was
  a gray 3 to 4% denser);
- the card grows in from Motion.panelOpenScale on the appear spring,
  only fades under Reduce Motion, and a closed card starts whole again;
- the card's glass keeps the key-window look (the close lit it up grey).

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

Every cmux dialog (CmuxDialogCenter, about 100 call sites) draws through
CmuxDialogView on WindowOverlayHost. Measured with debug.dialog_strip
(window-server frames, before):

- the card only faded, flat; it now grows from Motion.panelOpenScale
  about its center on the appear spring, as the palette opens (fade only
  under Reduce Motion), and shrinks toward panelCloseScale on fadeOut;
- the scrim eases on the scrim token, the one the inactive-window tint
  under it uses, so the window darkens in one step, not two;
- the close lit the card up grey for the whole fade: its overlay panel
  gives key back as the fade starts and the glass took its inactive
  frost. The card's glass keeps the key-window look (keepsActiveLook,
  as the window backdrop does);
- the destructive title read 2.1:1 over the glass (the theme's danger is
  fit to 3:1). CmuxDialogButtonLook fits every ink to 4.5:1: the default
  button is the solid accent fill with on-accent ink (the web pages'
  .primary), a destructive one a solid danger fill (the web's .danger;
  Debug Settings 'Filled destructive dialog button' off shows the tinted
  variant), cancel and normal the selection gray. The focus ring moves
  just outside the button so it shows on a solid fill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page dialog (ui/Dialog) only faded, and its backdrop shared the card's
120 ms fade. As the native dialog now does: the dialog grows from 0.97
while it fades in and shrinks toward 0.98 as it fades out; the backdrop
eases on the standard (scrim) step. Reduce Motion (system or the app's
data-cmux-motion=reduced) keeps only the fades; popup-reduce-motion
covers both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text-entry alerts (Rename, New Group, Create Team, Join, Send, Set, Open,
Save, Create) paired a Cancel button with an unmarked confirm, so iOS drew
Cancel bold and Return pressed nothing. The confirm is now the preferred
action (.keyboardShortcut(.defaultAction): bold, Return), and Message Not
Unsent's Try Again its preferredAction. HIG Alerts: place the button people
are most likely to choose as the default. Destructive confirmations keep
their role and are never the default. The iOS alerts are system alerts:
their animation and colors are the system's, and the root tint is the
label color, so their buttons already read at full contrast.

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

Red on the base: in about half the opens the window server showed the
whole dialog and scrim for one frame before the fade (debug.dialog_strip,
base build too), because attaching the panel displays its layers at full
opacity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Attaching the overlay panel (syncPanel) displays and commits its layers
at once, so in about half the opens the window server showed the whole
dialog and scrim for one frame, then the fade began from zero: a flash.
The card and scrim go transparent, with no implicit animation, before
the panel attaches; a dialog shown again mid-close keeps what is on
screen.

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

Red at the previous head: the solid default button showed no focus ring
in the tagged build (debug.window_snapshot).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ring layer, added in init, did not survive AppKit giving the button
its backing layer, and the button could clip it. It joins the current
layer on layout and the button no longer clips, so keyboard focus shows
on the solid default and destructive buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Motion.set wrote the model value and added the animation in two
transactions. With none already open (a socket, timer or task turn),
each commits to the render server on its own, so a frame could show the
new model value before the animation that starts from the old one. A
cmux dialog flashed in at full opacity for one frame, then faded in from
zero: 1 open in 5 after the panel-attach fix (debug.dialog_strip,
window-server frames). Every spring, fade and timed set now runs in one
outer transaction, which fixes the class for every gated layer
animation, not only dialogs.

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

Red at the previous head: ordering the panel in showed its old surface
(the last dialog at full opacity) for a frame before the new dialog's
fade (debug.dialog_strip: 2 in 10 opens with a fresh attach, 0 in 20
with the panel already attached).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ordering the overlay panel in shows its window surface at once, and that
surface still holds what the panel last drew on screen, often a dialog at
full opacity, before this turn's commit reaches it: one frame of an old
dialog and scrim, then the new one fading in from zero (debug.dialog_strip:
2 opens in 10 when the panel attached fresh, 0 in 20 while it stayed
attached). The panel orders in transparent and shows after two display
frames (FrameClient on its display link, no timer); a window on no screen
shows it at once. An attached panel is untouched.

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

From the correctness review:
- Cancel and normal buttons keep the selection fill's hue on hover and
  press (an accent theme's selection is tinted; pressedFill is gray).
- The focus ring leaves a 2 pt gap, so it reads apart from the accent
  default button's fill.
- readable() repeats until the ink reads on every card surface (a move
  for one surface could lower another).
- debug.dialog_strip keeps its pause timer alive across the await.

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

From the correctness review of the dialog motion:
- the grow-in is the scale property, so a dialog placed with a transform
  (Home palette and forms: translateX(-50%)) keeps its place, and gets
  the grow-in too; under reduced motion it no longer slides sideways;
- a sheet (also .ui-dialog) only fades again;
- the backdrop's close matches the dialog's: Base UI unmounts both when
  the dialog's transitions end, which cut the longer backdrop fade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x-next-alert-polish

# Conflicts:
#	cmux-next-ios/CmuxMobileKit/Sources/CmuxMobileWorkspaces/TabMoveAndGroupSheets.swift
…pending reveal on teardown

After merging feat-cmux-next:
- Rename Pane, Rename Screen, Rename Tab Group and Set Tab Group Icon
  (new in #27084) make their confirm the preferred action, as the other
  text-entry alerts do here (HIG Alerts). The old Rename Tab Group alert
  in TabMoveAndGroupSheets is gone on the base.
- A window that closes while its overlay panel waits for its first frames
  stops that reveal (the base now closes the panel on teardown).

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

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

@azooz2003-bit azooz2003-bit added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Correctness review (subagent, correctness-first)

Reviewer verdict on 7d5567c3e734: REVISE. Every finding was fixed afterwards:

# Finding Resolution
1 Web: the backdrop's 180 ms close fade was cut off part way, because Base UI unmounts the portal when the dialog's own 80 ms transitions end. The backdrop close now matches the dialog's (9cf5483).
2 Web: under data-cmux-motion="reduced", transform: none (0,3,0) beat .lg-palette/.lg-form's translateX(-50%), so the Home palette and forms slid about 310 px sideways. Grow-in uses the scale property, which never conflicts with a page's transform (9cf5483).
3 Web: sheets (also .ui-dialog) snapped to 0.98 and lifted off their edge on close. .ui-sheet is excluded from the scale; sheets only fade (9cf5483).
4 Web: .lg-* dialogs never got the grow-in. Fixed by the scale property (9cf5483).
5 Debug: debug.dialog_strip's pause timer could be freed before it fired in an optimized build. withExtendedLifetime (556cfe2).
6 Cancel and normal buttons changed hue on hover in accent themes. The same selection fill, denser on hover (556cfe2).
7 The focus ring on the accent default button barely read. A 2 pt gap (556cfe2).
8 readable() could leave an ink under 4.5:1 on an earlier surface. Repeats until every surface reads (556cfe2).

The reviewer checked these and found them fine: fade-out cleanup (OverlayCountdown fires exactly once on every path), a dialog presented again mid-close (same window or another), Reduce Motion and animations-off end states, hideForEntrance on every path, the outer CATransaction in Motion.set (nesting only; no caller's behavior changes), Swift 6 isolation, DEBUG-only code guarded, and the iOS changes.

After the fixes, all 18 dialog, motion and popup suites pass at the head. The base merge (4892940) brought in teardown changes to WindowOverlayHost; cbc90c4 stops a pending panel reveal when the window closes.

@blacksmith-sh

blacksmith-sh Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Found 5 test failures on Blacksmith runners:

Failures

Test View Logs
src/agent-session/acpmux/conversation/MessageGroupRow.test.tsx/MessageGroupRow.test.tsx View Logs
src/agent-session/acpmux/conversation/MessageGroupRow.test.tsx/MessageGroupRow.test.tsx View Logs
test/workflow-tasks.test.tsx/workflow-tasks.test.tsx View Logs
test/workflow-tasks.test.tsx/workflow-tasks.test.tsx 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

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

azooz2003-bit and others added 3 commits October 29, 2026 15:23
…ach cancels the detach

Replaces the fresh-attach reveal test. Red at the previous head: the panel
attached transparent (alpha 0) for two frames and left in the turn its
content did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de it on attach

Review of c646e47: hiding a freshly attached panel for two frames
also hid the page planes (focus ring, inactive-pane dim) and the agent
cursor it carries when the first browser page appears, and delayed every
first overlay in a window by two frames.

The stale surface comes from the detach, so the fix goes there: the
panel leaves its window only after AfterFrameCommit, once the frame that
draws it empty has committed (a re-attach in between cancels the detach).
Any later attach shows at once, with no hidden frames, no display-link
client and no timer. Teardown stays synchronous.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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 59993c3: 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=pr27638 -f ref=feat-cmux-next-alert-polish -f platform=macos -f backend_mode=local

Gallery before/after for 59993c3: 16 states rendered, 0 images differ, 2148 more touched states not rendered. Images are in the run's artifact, not on this public PR.

gh run download 41140482950 -R manaflow-ai/cmux -n gallery-advisory-27638-59993c3e4c56f815761f0899a1f0055486edd1f1 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.header-tools/native-row-off Apple System Colors Light unchanged 0% own popupSurface.css
agent-pane.header-tools/native-row-off Apple System Colors unchanged 0% own popupSurface.css
agent-pane.header-tools/native-row-tools Apple System Colors Light unchanged 0% own popupSurface.css
agent-pane.header-tools/native-row-tools Apple System Colors unchanged 0% own popupSurface.css
agent-pane.header-tools/unified-compact Apple System Colors Light unchanged 0% own popupSurface.css
agent-pane.header-tools/unified-compact Apple System Colors unchanged 0% own popupSurface.css
agent-pane.header-tools/unified-roomy Apple System Colors Light unchanged 0% own popupSurface.css
agent-pane.header-tools/unified-roomy Apple System Colors unchanged 0% own popupSurface.css
agent-pane.header-tools/unified-tight Apple System Colors Light unchanged 0% own popupSurface.css
agent-pane.header-tools/unified-tight Apple System Colors unchanged 0% own popupSurface.css
agent-pane.header-tools/idle Apple System Colors Light unchanged 0% own popupSurface.css
agent-pane.header-tools/idle Apple System Colors unchanged 0% own popupSurface.css
agent-pane.header-tools/panels-open Apple System Colors Light unchanged 0% own popupSurface.css
agent-pane.header-tools/panels-open Apple System Colors unchanged 0% own popupSurface.css
agent-pane.header-tools/panel-open Apple System Colors Light unchanged 0% own popupSurface.css
agent-pane.header-tools/panel-open Apple System Colors unchanged 0% own popupSurface.css
ui.combobox/closed Apple System Colors Light unchanged 0% own popupSurface.css
ui.combobox/closed Apple System Colors unchanged 0% own popupSurface.css
ui.combobox/open Apple System Colors Light unchanged 0% own popupSurface.css
ui.combobox/open Apple System Colors unchanged 0% own popupSurface.css
ui.combobox/keyboard Apple System Colors Light unchanged 0% own popupSurface.css
ui.combobox/keyboard Apple System Colors unchanged 0% own popupSurface.css
ui.combobox/tab-completion Apple System Colors Light unchanged 0% own popupSurface.css
ui.combobox/tab-completion Apple System Colors unchanged 0% own popupSurface.css
ui.combobox/escape Apple System Colors Light unchanged 0% own popupSurface.css
ui.combobox/escape Apple System Colors unchanged 0% own popupSurface.css
ui.combobox/disabled Apple System Colors Light unchanged 0% own popupSurface.css
ui.combobox/disabled Apple System Colors unchanged 0% own popupSurface.css
ui.combobox/inline Apple System Colors Light unchanged 0% own popupSurface.css
ui.combobox/inline Apple System Colors unchanged 0% own popupSurface.css
ui.context-menu/closed Apple System Colors Light unchanged 0% own popupSurface.css
ui.context-menu/closed Apple System Colors unchanged 0% own popupSurface.css

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Probably yours: MessageGroupRow.test.tsx, workflow-tasks.test.tsx fails here but not on main's latest full suite.

CI failed on 1f03732f54 (run 41142473578 attempt 1): 1 code, 1 unknown.

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

Failures:

  • new in this PR MessageGroupRow.test.tsx a run with a message going out starts open, names its target and ticks only while it goes: failed (job)
  • new in this PR workflow-tasks.test.tsx a running workflow keeps its elapsed clock live before agent records arrive: failed (job)
Matched log lines
web / react-apps-base-check (4): (fail) a run with a message going out starts open, names its target and ticks only while it goes [11.61ms]

Not re-run automatically: web / react-apps-base-check (4), 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: web / Web status; a job it gathers is red on this change (its line here); do not merge until green; see lane-rules
red: web / react-apps-base-check (4); NEW webview test reds: test/new-tab-no-entry-menus.test.ts > on a New Tab, every surface it opens appears at once (reduce); test/new-tab-no-entry-menus.test.ts > on a New Tab, every surface it opens appears at once (no-preference); do not merge until green; see lane-rules
red: web / react-apps-check; a job it gathers is red on this change (its line here); do not merge until green; see lane-rules

Shipped in:

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Default changes

No defaults change at 1f03732f5436.

azooz2003-bit and others added 2 commits October 29, 2026 21:27
Visual review of #27638 (real-app capture at ed9cff0): the focus
band of a solid button was drawn inside its bounds, so the focused Save
looked 3 px smaller on every side than its neighbors. Red at ed9cff0.

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

From the visual review of #27638: the focus gap was a band inside the
button's edge, so a focused solid button (Save, Don't Save) looked 3 px
smaller on every side and its edges no longer lined up with its
neighbors'. The gap is now a 1 pt band just outside the fill, then a
1.5 pt ring: 2.5 pt past the edge, still 1.5 pt clear of a button 4 pt
away (the neighbor test holds). A translucent fill shows no gap band; its
ring sits in the same place.

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

Copy link
Copy Markdown
Collaborator

Review (codex gpt-6-astra high fast, drain lane): SHIP at c45470c

No blocking findings.

azooz2003-bit and others added 3 commits October 30, 2026 15:01
…ext-alert-polish

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextDesign/Tunables/ChromeTunables.swift
Visual review of #27638 (real-app capture at c45470c): the light
focus ring measured 2.73:1 on the card, under WCAG's 3:1 for a focus
indicator. Red at the previous head (no fitted ring color).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Visual review of #27638 (real-app capture at c45470c): the light
focus ring was the theme's focus ring as is, rgb(134,134,134) on the
card's rgb(223,223,223), 2.73:1, under WCAG's 3:1 for a focus indicator.
On a gray Cancel it is the only sign of focus. CmuxDialogButtonLook now
fits it to 3:1 on both card surfaces, as it fits titles to 4.5:1; dark
(3.81:1) and an accent ring that already reads keep their color.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
azooz2003-bit and others added 2 commits October 30, 2026 15:18
…enders

Real-app capture at 1dcfb3f: the light ring still measured 2.66:1.
It was fit to an estimate of the glass card (about rgb 241), but the
real light card renders as the scrimmed window, rgb(223,223,223). Red at
1dcfb3f.

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

Real-app capture at 1dcfb3f: the light focus ring still read 2.66:1.
Inks were fit to an estimate of the glass card (the veil over the
scrimmed window, about rgb 241), but Liquid Glass shows little of its
veil and the light card renders as the scrimmed window, rgb(223,223,223).
cardSurfaces now includes the scrimmed window, so every title, hint and
the focus ring are fit on it too, and the focus gap takes its color.

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

The defaults guard flagged the new Debug Tunable (dialog.filledDestructive,
on) and, through a local variable named `color` (also a Tunable helper's
name), CmuxDialogButtonLook.readable and minimumContrast. The toggle only
compared two styles for the design call; filled was already what ships.
The tinted alternative stays as CmuxDialogButtonLook's
`filledDestructive: false`, covered by its contrast test, for
#13742. The local is renamed. Nothing anyone gets
changes.

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

Copy link
Copy Markdown
Collaborator Author

Correctness review 3 (subagent): commits after 61ef179, through the base merges

Verdict: SHIP, with two low findings, both fixed in ed9cff0:

  1. A panel that starts to leave now has its mouse routed again in the same turn, so the empty panel stops taking clicks before it orders out.
  2. The focus gap takes the color the card actually shows: the opaque surface under Reduce Transparency, otherwise the scrimmed window.

Checked and fine: pending-detach re-adoption and reorder; AfterFrameCommit runs in common modes, including live resize and menus; window close while a detach is pending, with the base's teardown changes; focus ring and gap z-order and hidden states; the new tests under .closesItsWindows.

Visual review (real-app captures)

  • ed9cff0, NEEDS-HUMAN-TASTE: a focused solid button looked 3 px smaller. Fixed in c45470c; the gap now sits outside the fill.
  • c45470c, FIX: the light focus ring read 2.73:1, under 3:1. Fixed in 1dcfb3f and 03a4d78. The ring is now fit to 3:1 against the card as it really renders, the scrimmed window (rgb 223 in light); the earlier fit used an estimate of the glass. Red at 44f77e2: 2.63:1.

Defaults guard

The Debug toggle dialog.filledDestructive is removed (59993c3). Filled was already the shipped style; the tinted alternative stays in code for the design call in #13742. A local variable named color matched a Tunable helper's name and is renamed. defaults_guard.py check now finds no default changes.

…keups

fast gates on 59993c3: CmuxNextDesignTests imported CmuxNextWakeups
(for AfterFrameCommit) without declaring it, which the base's package
dependency rule now refuses. The test waits on the same signal with a
one-shot CoreFoundation before-waiting observer instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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.

@azooz2003-bit
azooz2003-bit merged commit 0b0968d into feat-cmux-next Oct 31, 2026
223 of 247 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-cmux-next-alert-polish branch October 31, 2026 00:01
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Dialog latency bench (after merge)

CmuxDialogLatencyBench (CMUX_DIALOG_BENCH=1, fleet, Xcode 26.6) measures from "show this dialog" to laid out. It covers dialog build and layout, where this PR adds work: the per-button colour fit.

run native median native p95 n
head 1f03732 6.10 ms 12.94 ms 40
merge base a94df42 not measured not measured -

The merge-base run failed during checkout (could not lock config file .git/modules/ghostty/config), so there's no before number from the same harness. Not a controlled A/B. The head's 6.1 ms median sits under the 10 ms action budget. A base run on a working worker would complete the comparison.

lawrencecchen added a commit that referenced this pull request Oct 31, 2026
…(build skew of #28266 and #27638)

#28266 turned GlassPanelView(keepsActiveLook:) into GlassPanelView(activeLook:)
while #27638 added Glass.makePanel/makeOverlayPanel(keepsActiveLook:) for the
dialog card. Both merged, and feat-cmux-next stopped compiling
(Glass.swift:79 extra argument 'keepsActiveLook').

Glass.makePanel, Glass.makeOverlayPanel and OverlaySurfaceView now take
activeLook: ActiveLookScope?. CmuxDialogView takes it from CmuxDialogCenter,
which passes the dialog window's backdropActiveLook. An app-scoped dialog
(no window) follows the key state.

Compiled: 9b4cea495b0bc379ed0794d1
@teamleaderleo

Copy link
Copy Markdown
Collaborator

Base red from this PR: new-tab-no-entry-menus.test.ts ("on a New Tab, every surface it opens appears at once") has failed on feat-cmux-next since 0b0968d. The dialog's scale and the backdrop's fade took --cmux-motion-standard directly, so New Tab's no-entry rule (--ui-popup-open: 0s in newtab/noEntry.css) no longer stopped them. In #28718 they take a new --ui-dialog-open token, which noEntry.css sets to 0s. Dialogs elsewhere keep this PR's grow-in.

teamleaderleo added a commit that referenced this pull request Oct 31, 2026
…5592) and dialog grow-in (#27638) (#28718)

* webviews: fix the react-apps-check base red from Settings > Menus (#25592) and dialog grow-in (#27638)

feat-cmux-next's react-apps-check (1) and (3) went red again after #28601,
from two newer landings:

#25592 (Settings > Menus, 451c25b) added CSS that fails four checks:
- check:design-tokens / check-design-tokens: 12 new off-scale spacing and
  size literals. Spacing is now on the scale (--ds-space-1/2/4, or 0 for the
  1px row gaps). The three icon and dot sizes (16px icon, 12px twisty, 6px
  edited dot) are not control heights; they go in the baseline.
- check:layers / check-layers.test "this checkout passes": the dragged row's
  raw z-index: 2 is var(--layer-raised).
- popup-one-hairline: the dragged row cast var(--ui-popup-drop), a popup
  token, with no hairline. A lifted row is not a popup, so it casts
  var(--elevation-2), the same drop that --ui-popup-drop resolves to.
- gallery-control-surfaces (pages.settings-menus): hovering a row's
  icon button lit the whole li.menu-row. The row's hover wash now skips
  while one of its own icon buttons is hovered; the button lights alone.

#27638 (smoother dialogs, 0b0968d) gave the dialog's scale and the
backdrop's fade the --cmux-motion-standard step directly, so New Tab's
no-entry rule (--ui-popup-open: 0s) no longer stopped them
(new-tab-no-entry-menus: dialog [opacity, scale]). They now take
--ui-dialog-open, which noEntry.css sets to 0s like --ui-popup-open.
Exits keep --ui-popup-close.

check-design-tokens.ts and check-layers.py: fail at the base, pass here.

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

* webviews: Settings > Menus hover exclusion keeps a dragged row's opaque fill

Review of 20d7aed (router-review, P2): :not(:has(...)) raised the hover
rule to (0,4,0), over .menu-row[data-dragging] (0,2,0), so a row dragged by
its grip took the translucent hover fill. The exclusion is in :where(), so
the rule weighs (0,1,0) and the dragged row's fill wins again.

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

* webviews: ContextMenusCard's two oxlint findings carry reasoned disables (web lint (fast) base red)

web lint (fast) fails on every feat-cmux-next push whose range reaches
ContextMenusCard.tsx (#25592): jsx-a11y/no-autofocus at the rename field
(its suppression was a biome-ignore, which oxlint does not read) and
jsx-a11y/prefer-tag-over-role at the separator line. Both now use the
oxlint-disable-next-line form with a reason, as AgentHarnesses.tsx and
DevicesCard.tsx do: the rename field opens on the person's Rename, and the
divider sits inside the draggable row's flex body, where an <hr> would bring
its own box.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

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

Labels

media-tour Run the cmux-next UI tour (cmux-next-media.yml) on every push risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants