Skip to content

cmux NEXT sidebar: workspace rows run full length; the hover close is a top-right dog-ear - #23783

Merged
teamleaderleo merged 12 commits into
feat-cmux-nextfrom
sidebar-row-dogear
Oct 23, 2026
Merged

teamleaderleo merged 12 commits into
feat-cmux-nextfrom
sidebar-row-dogear

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Leo-ask: ask-c8936f0e52

Leo, on the dog-ear as it shipped in the sidebar-dogfood build:

"the dog ears you straihgt up didnt understand i wanted the hoverable area to just... not have it on the top right corner. a smaller top right corner not higlighitng bc the button itself chopped off that top right. tht's what i mean. anywyas current ismaybe "OK" but eh"

Before that (2026-10-21) he asked for the row's length back ("so we can have more content per sidebar row"), for the hover action to sit "like dog ears on a book on the top right", and for the title not to reshape on hover.

What changed (this round)

  • The folded triangle is gone. The close (or the menu, on Home, which can't close) sits in a small square on the row's top-right corner, and the row's hover or selection fill leaves that square out. The highlight looks like the row with its corner cut away by the button. The x stays where it was.
  • The cut is in the fill, not in the row. While the corner is cut, the row draws its fill as a shape layer under its content, clipped to the row's corner radius. Otherwise the fill stays on the backing layer as before. The row gets no mask. The button draws only its glyph (textSecondary, textPrimary under the pointer): no fill, no box. The fill colors are the theme's translucent hover and selection fills, so it stays glass. No accent and no animation were added; the shaped fill shows and clears at once, like the hover fill.
  • Clicks: the close takes exactly the cut as drawn, including the rounded fillets that reach a few points past its square, in every look. A click on the fill, even right next to the cut, belongs to the row. Under a pointer that isn't moving, the x lights as soon as it appears. Changing the look in Debug Settings redraws the rows already on screen. The tabs chevron, the session tree's fold and the audio button still draw over the corner and keep their clicks.
  • Unchanged from the earlier rounds: rows run to the list's inset (rowTrailingTrim 0). The title keeps its full width with or without hover. The unread badge and keycap keep their slots. An expanded row's Pin stays at the end of the name's line, clear of the badge and the corner.

Gallery versions

These are the real list views drawn offscreen on the fleet (WorkspaceRowCloseNotchGalleryTests, step 1ba69b92265428b3dca070a4 on aws-m4pro-3). Each sheet shows a hovered row under the selected row, and the selected row hovered, in dark and light (Soft Blue / Soft Blue Light). They are kept as Debug Settings choices under sidebar.closeNotch ("Row close corner"), the same pattern as sidebar.allChats.design.

  1. Snug rounded cut (default, the pick): about 0.6 of the old dog-ear's side, with the x centered and the cut's corners at half the row's radius.
  2. Snug square cut: same size, square corners.
  3. Rounded cut at the size of the old hit box (the dog-ear's full square).
  4. Snug rounded cut with a hairline (Palette.separator) along its edge.

Why snug rounded: it is the "smaller top right corner" Leo described. The cut is barely larger than the x, so it reads as the button taking its own corner rather than as a new shape. Its rounded inner corners match the row's own radius family. The square cut looks like a rendering bug next to the rounded row. The hit-box cut takes noticeably more of the row and reads as a tab or notch. The hairline adds a border, and rows are fills-only.

close corner snug
close corner square
close corner hitBox
close corner hairline

Native capture (fleet capture mini)

This is a hovered row in the real app at the head (the x sits in the corner its fill leaves out):

Capture steps-318fcfd6cc at 90fc220e3655 (fleet build, capture mini, text checked for private data)

Light Dark
light dark

Earlier rounds, still in this PR

  • Rows run to the list's inset again. SidebarStyle.rowTrailingTrim is 0. Pinned rows and list blocks read the same token, so every row still ends at one x. Takes back cmux-next sidebar: hover follows the pointer, row pills stop short of the edge, the row card stands off its row #22733: Leo asked for the row length back on 2026-10-21. Only its rowTrailingTrim part is taken back; its hover-follow and hover-card changes stay.
  • One hover action: the close on a row that can close, the menu on one that cannot (Home). The ⋮ menu no longer shows on hover for closable rows. It is still on right-click and on VoiceOver's custom action, opened under the corner. Identifiers are unchanged (cmux.sidebar.workspace.<id>.close / .more).

Tests

  • WorkspaceRowDogEarTests, rewritten for the cut:
    • the close is the row's top-right snug square, smaller than the old dog-ear, takes a click in the corner and not beside or below it, and closes on press;
    • the hover fill leaves the corner out (the backing layer is clear, the shaped fill keeps the row's radius and is translucent), and the row fills whole again after hover;
    • every sidebar.closeNotch look cuts its own size, keeps the x at the same point, and draws the hairline only in the hairline look;
    • in every look, a point in the drawn cut outside the close's square closes, and a point just outside the cut selects (Codex review of 90fc220);
    • changing sidebar.closeNotch relays out a row already on screen through SidebarTunablesWatch;
    • the close shows lit when it appears under a still pointer, and unlit when the pointer is off it;
    • plus the existing row-length, title-width, controls-under-the-corner, Home menu and expanded-pin tests.
  • WorkspaceRowHoverMotionTests expects the one corner button.
  • WorkspaceRowCloseNotchGalleryTests renders the gallery when CMUX_TEST_REQUIRE_GUI=1 (the fleet's opt-in); it is skipped otherwise.

Observed / Not run

  • Fleet compile-check --app at c80b62d: passed (job 35ad40b6f78c49c0088d2346).
  • Fleet suites at c80b62d: WorkspaceRowDogEarTests, WorkspaceRowHoverMotionTests, SidebarRowHoverGapTests and SidebarRowBadgeStackCapTests (it shares SidebarTunablesWatch), all passed (step 137a4a1f79fb29b72aec0851).
  • The gallery and the native capture above were taken at 1bde45a and 90fc220. The later commits change the hit area, the look watch and the pointer refresh, not the drawing.
  • python3 scripts/cmux-next/prepush-gate.py: no new violations.
  • Not run: the full CmuxNext suite.

Changelog

Changed: Sidebar workspace rows use their full width again. On hover, the close sits in the row's top-right corner, which the highlight leaves out.

Agent session: cosmic-gray

Takes back #22733: Leo explicitly asked for the row length back; this PR restores full-width rows while preserving the hover-follow and hover-card changes.

teamleaderleo and others added 2 commits October 21, 2026 14:52
…se or menu is a top-right dog-ear

Rows end at the list's inset again (rowTrailingTrim 0, taking back #22733's
trim). The x and the more button no longer sit in rounded boxes at the end of
the title: one dog-ear folds over the row's top-right corner (the close, or the
menu on a row that cannot close), takes clicks only on its fold, follows the
row's corner radius and draws translucent theme fills. The title keeps its full
width on hover; the unread badge and keycap keep their slots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 16cd759: 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=pr23783 -f ref=sidebar-row-dogear -f platform=macos -f backend_mode=local

Not verified: macOS jobs did not run: compile and app tests were skipped on 16cd7591da; ci-status is green without them.

CI passes on 16cd7591da (run 39913297412 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

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 checks (god files, concurrency, crash safety, l10n); no finished base run on cba27b0 to compare; do not merge until green; see lane-rules

Tour of 16cd7591da (run)

The path-selected recorder failed for core-after, core-before; see the run artifact and failed manifests.

Path-selected interactions

Interaction Changed paths
core Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Logic/SidebarTunables.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Store/SidebarTunablesWatch.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/NSView\+HoverReveal.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/SidebarCloseNotch.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/SidebarDogEarButton.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/SidebarRowView.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/SidebarStyle.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceRowView\+DogEar.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceRowView\+Expanded.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceRowView\+Rendering.swift
Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceRowView.swift
Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/WorkspaceRowCloseNotchGalleryTests.swift
Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/WorkspaceRowDogEarTests.swift
Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/WorkspaceRowHoverMotionTests.swift

Core UI (before): FAILED — 16 ok, 3 failed, 1 unavailable

tour

Video (mp4)

# Step Result Command
1 Launch ok
2 New workspace ok cmux workspace new --name Tour
3 Type in the terminal ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "echo hello from the cmux-next tour\r"
4 A command reports progress: tab loading spinner ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "clear; printf '\\033]9;4;3\\007'; sleep 900\r"
5 Split right failed: exited 1 cmux action run splitRight
6 Split down failed: exited 1 cmux action run splitDown
7 New terminal tab failed: exited 1 cmux action run newSurface
8 Browser tab ok cmux tab new-browser --url https://example.com
9 Hide the sidebar ok cmux action run toggleSidebar
10 Show the sidebar ok cmux action run toggleSidebar
11 Notification ok cmux notify --title Tour --body "Hello from CI"
12 Notifications panel ok cmux action run showNotifications
13 Agent chat ok cmux agent new-chat
14 Agent workspace ok cmux workspace new --name Agent
15 Agent starts ok cmux workspace name:Agent screen current pane current tab current terminal current write --text "clear; printf '\\033]7501;state=working\\033\\\\'; sleep 8; printf '\\033]7501;state=done\\033\\\\'; sleep 900\r"
16 An agent works: row working spinner ok cmux workspace new --name Notes
17 The agent finishes: done mark in the spinner's place ok
18 Session tree: keycaps on the first rows ok cmux settings set sidebar.sessionTree true
19 Session tree back off for later tours ok cmux settings set sidebar.sessionTree false
20 Profile menu unavailable: exited 1 (the menu shot needs a screen capture route) cmux action run sidebar.profileMenu

Failed steps:

5. Split right
Split right

6. Split down
Split down

7. New terminal tab
New terminal tab

Screenshots (12)

1. Launch
Launch

2. New workspace
New workspace

3. Type in the terminal
Type in the terminal

4. A command reports progress: tab loading spinner
A command reports progress: tab loading spinner

8. Browser tab
Browser tab

9. Hide the sidebar
Hide the sidebar

11. Notification
Notification

12. Notifications panel
Notifications panel

13. Agent chat
Agent chat

16. An agent works: row working spinner
An agent works: row working spinner

17. The agent finishes: done mark in the spinner's place
The agent finishes: done mark in the spinner's place

18. Session tree: keycaps on the first rows
Session tree: keycaps on the first rows

### Core UI \(after\): FAILED — 16 ok, 3 failed, 1 unavailable

tour

Video (mp4)

# Step Result Command
1 Launch ok
2 New workspace ok cmux workspace new --name Tour
3 Type in the terminal ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "echo hello from the cmux-next tour\r"
4 A command reports progress: tab loading spinner ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "clear; printf '\\033]9;4;3\\007'; sleep 900\r"
5 Split right failed: exited 1 cmux action run splitRight
6 Split down failed: exited 1 cmux action run splitDown
7 New terminal tab failed: exited 1 cmux action run newSurface
8 Browser tab ok cmux tab new-browser --url https://example.com
9 Hide the sidebar ok cmux action run toggleSidebar
10 Show the sidebar ok cmux action run toggleSidebar
11 Notification ok cmux notify --title Tour --body "Hello from CI"
12 Notifications panel ok cmux action run showNotifications
13 Agent chat ok cmux agent new-chat
14 Agent workspace ok cmux workspace new --name Agent
15 Agent starts ok cmux workspace name:Agent screen current pane current tab current terminal current write --text "clear; printf '\\033]7501;state=working\\033\\\\'; sleep 8; printf '\\033]7501;state=done\\033\\\\'; sleep 900\r"
16 An agent works: row working spinner ok cmux workspace new --name Notes
17 The agent finishes: done mark in the spinner's place ok
18 Session tree: keycaps on the first rows ok cmux settings set sidebar.sessionTree true
19 Session tree back off for later tours ok cmux settings set sidebar.sessionTree false
20 Profile menu unavailable: exited 1 (the menu shot needs a screen capture route) cmux action run sidebar.profileMenu

Failed steps:

5. Split right
Split right

6. Split down
Split down

7. New terminal tab
New terminal tab

Screenshots (12)

1. Launch
Launch

2. New workspace
New workspace

3. Type in the terminal
Type in the terminal

4. A command reports progress: tab loading spinner
A command reports progress: tab loading spinner

8. Browser tab
Browser tab

9. Hide the sidebar
Hide the sidebar

11. Notification
Notification

12. Notifications panel
Notifications panel

13. Agent chat
Agent chat

16. An agent works: row working spinner
An agent works: row working spinner

17. The agent finishes: done mark in the spinner's place
The agent finishes: done mark in the spinner's place

18. Session tree: keycaps on the first rows
Session tree: keycaps on the first rows

Tours are data in scripts/cmux-next/media/tours: add a step or a tour file to show your feature.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by sidebar-rows on cosmic-gray · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by sidebar-rows on cosmic-gray · session

@blacksmith-sh

This comment has been minimized.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (claude-subagent): BLOCK at f8cc39c

I reviewed this by reading the code only. I did not compile or run anything; Swift compile and tests were still pending in CI when I wrote this.

Blocking

  1. On an expanded row (sidebar.density expanded, with the App's workspacePin wired), the hover Pin now draws on top of the unread badge or keycap, and part of it sits under the close fold.
    • Where: WorkspaceRowView+Rendering.swift:143-159.
    • Why it overlaps the badge: pinSlot is trailing - control ..< trailing, computed before the badge/keycap slot is taken off trailing. That is the badge's slot. Before this PR the badge and keycap hid whenever the x or menu took that slot. Now setHoverShown(badge, badge.state.isUnread) and setHoverShown(keycap, ...) keep them visible. They are also not in the occlusion fade loop at :223. So a hovered expanded row with an unread count shows the pin glyph over the count.
    • Why it reaches the fold: with no disclosure or count, the pin box is about [w-24, w-6] x [1, 19] at compact size, and the dog-ear square is [w-16, w] x [0, 16]. The pin glyph's top-right corner (about (w-10, 5)) is inside the fold triangle. closeButton comes first in interactiveSubviews (WorkspaceRowView.swift:156), so a click on the upper-right part of the pin closes the workspace.
    • Fix: lay the pin out after the badge/keycap slot and clear of the corner. Compute it after if slot > 0 { trailing -= slot + Metrics.space2 }, with maxX = min(trailing, full.maxX - SidebarStyle.dogEarSide - Metrics.space1); the time label then fades under it through overlayStart, as before. If you want the pin to keep the old x slot, hide badge and keycap while the pin shows (showMore && isExpanded && isPinned?() != nil) and still keep its box out of the fold.
    • Test: add a case to WorkspaceRowDogEarTests for an expanded row with unread and a pin provider, hovered. Assert that pinButton.frame overlaps neither the visible badge nor the fold.

Non-blocking

  • The disclosure chevron's box (only with showWorkspaceTabs) also reaches the fold at its top-right, so a click there closes instead of toggling tabs. The glyph itself is clear of the fold. Consider keeping hit boxes out of the fold, or giving visible row buttons precedence in SidebarRowView.hitTest.
  • At compact size the glyph is tiny: dogEarSide = 16, so it renders at (16 * 0.4).rounded() = 6 pt. Check it in a capture.
  • NSButton's cell sends the action on mouse-up anywhere in the square. A press inside the fold that is released below the crease still closes.
  • draw builds a new tinted NSImage on every draw. That is minor, but you could cache it per tint.
  • Stale docs:
    • WorkspaceRowView.swift pinButton: "on hover beside the menu button".
    • NSView+HoverReveal.swift header: "the unread badge they replace".
  • The PR has no capture yet ("No capture: ..." is in the body).

Checked and fine

  • APIs and isolation:
    • NSBezierPath calls.
    • The nonisolated isFlipped / wantsUpdateLayer overrides.
    • The multi-statement performWithTheme closure, which infers Void.
    • The internal NSImage.tinted, which lives in the same module.
    • The [(SidebarDogEarButton, Bool)] tuple loop.
    • label didSet with init-time assignment.
    • OverflowButton.glyph is public.
  • Fold hit test: points outside the fold fall through to the other row buttons or the row.
  • The ears hide again (and isPointerOnFold resets) on recycle and when hover clears.
  • zPosition puts the fold above the trailing marks.
  • The .close / .more identifiers still apply through the [(NSButton, String)] loop.
  • The VoiceOver menu action still anchors to moreButton.frame, which is the corner, set every layout.
  • Tests:
    • New WorkspaceRowDogEarTests: bitmap orientation is right (colorAt is top-left and the view is flipped). The sampled points sit clear of the glyph, the crease and the curve at radius 6/7. The frames and trailingOcclusion asserts look right, and fadesNextHover is never true for workspace rows, so moreButton.isHidden holds.
    • SidebarMenuAnchorTests.aWorkspaceRowMoreButtonOpensItsMenuUnderTheButton still passes: sendAction ignores isHidden, and the anchor is the corner frame.
    • SidebarOverflowGlyphTests still passes because the glyph is the same vector .actionMoreVertical.
    • Gutter and pinned-width tests read rowTrailingTrim.
    • No other test asserts that the badge hides on hover or that ⋮ shows on hover.
  • Takes back #22733: is in the body.

… badge and the dog-ear

Review follow-ups: the pin's slot is taken after the badge or keycap slot and
ends before the dog-ear's square, so it never covers the badge (which now stays
on hover) and a click on it never lands on the fold. The dog-ear is a little
larger (controlSize + space1) so its glyph reads at compact size, its tinted
glyph is cached per color, and two stale doc comments are updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / nightly-guards / workflow-guard-tests / release-notary is red on 14 of 20 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

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / cmux-next localization debt ratchet is red on 20 of 20 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

Copy link
Copy Markdown
Collaborator Author

Review (claude-subagent): SHIP at 1ea173e

I reviewed this by reading the code only. I did not build or run anything. When I wrote this, the compile and swift test checks were still queued.

The earlier block is fixed

  • WorkspaceRowView+Rendering.swift:155-161: the pin slot is now computed after trailing -= slot + space2, so it sits left of the unread badge or keycap slot. It is also clamped to full.maxX - dogEarSide - space1.
  • At compact size the pin is at about [w-40, w-22] with no badge, or [w-46, w-28] with a count, and the dog-ear square is [w-20, w]. So the pin no longer covers the badge and no longer reaches the fold.
  • The time label, the row badge and the text still fade under the pin through overlayStart, as before.

The new commit and test look right

  • anExpandedRowsPinClearsTheUnreadBadgeAndTheDogEar should compile. Every API it uses exists with the types it expects:
    • SidebarWorkspace(id:title:kind:unread:), with arguments in declaration order.
    • SidebarModel.rowBadgeStyle is a SidebarRowBadgeStyle?.
    • workspacePin is an (isPinned:toggle:) tuple.
    • DesignSettings() has a public init, and sidebarSections.density = .expanded is settable.
    • SidebarView(model:design:), contextMenuProvider, renderNow(), and the internal list, rowViews, isExpanded, badge and pinButton, which @testable reaches.
  • The test's assumptions hold when traced through the code:
    • renderNow() → applyListPreferences sets model.expanded. baseRowContent then builds WorkspaceRowContent(expanded:), so isExpanded is true.
    • .trailing makes badgesLead false, so .count(3) goes to badge.
    • wire sets onMore, because the provider is set before the rows exist. It also sets isPinned, which returns false (not nil), so the pin shows.
    • setHoverShown(badge, true) keeps the badge's alpha at 1.
  • The test fails at f8cc39c, where the pin slot was the badge's slot, and passes here.
  • The setup matches SidebarTrailingColumnTests: no window, then renderNow, layoutSubtreeIfNeeded, reload.
  • The tint cache in SidebarDogEarButton.draw compiles:
    • Assigning the unlabeled tuple to (color:image:) is allowed.
    • flatMap ... ?? tinted gives an NSImage.
    • tinted is internal to the module.
    • The cache is reset when renderGlyph rebuilds the image.
    • Inside performWithTheme the palette returns concrete theme RGB values, so a theme change misses the cache, as it should.
  • The doc comment fixes in WorkspaceRowView.swift and NSView+HoverReveal.swift match the code.

Non-blocking

  1. The dog-ear is now controlSize + space1 = 20 pt on a 24 pt compact row, and it reaches further into the trailing cluster.
    • With sidebar.showWorkspaceTabs on (off by default), the hover chevron's box is [w-24, w-6] x [3, 21]. Its upper-right third is inside the fold, and the top-right corner of the 8 pt glyph is about 1 pt inside it.
    • closeButton comes first in interactiveSubviews (WorkspaceRowView.swift:156), so a click slightly up and right of the chevron closes the workspace.
    • Fix: keep row buttons' hit boxes clear of the fold, or let a visible row button win over the ear in SidebarRowView.hitTest.
  2. Nothing fades under the fold any more. Before this PR, the x's overlayStart faded the marks it covered. Now only the pin does, and the pin shows only on expanded rows.
    • On a compact row, the end of the name-line text sits under the fold on hover. That text is a one-line agent status (trailingStatus) or, with showTimestamps on, the time. The close glyph ([w-10, w-2] x [2, 10]) touches the text's last character.
    • The same happens to the top-right of a .trailing count badge.
    • The title running under the fold is intended and tested. For these marks, either do the same and check it in a capture, or fade marks whose frame meets the fold.
  3. The tint cache holds one tint, so it is rebuilt each time the fold switches between lit and unlit. This is fine as it is.
  4. "cmux-next localization debt ratchet" and the nightly-guards checks are red. The PR adds no strings or catalog changes, so they look like base reds; I did not confirm that on base.

…he row's end controls

Codex review of #23783 (xhigh) found two ways the dog-ear closed a workspace the user did not mean
to close:

- NSButton tracked the press over the dog-ear's whole square, so a press on the fold dragged below
  the crease and released there still closed. The dog-ear now tracks the press itself: a release
  fires only on the fold after a press that began on it, and the fold unlights while the pointer
  is off it.
- On a 24 pt row the tabs chevron, the session tree's fold or the audio button at the row's end sits
  partly under the dog-ear's square, and the dog-ear hit-tested first, so a click on the chevron's
  corner closed the workspace. The row now hit-tests the dog-ear last and draws those controls over
  the fold, so what shows on top is what a click presses.

Tests: aPressDraggedOffTheFoldDoesNotClose, aControlUnderTheDogEarKeepsItsClicksAndDrawsOverIt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray
…robed super calls

Codex re-review of #23783 at 8819d4b: a press held while the dog-ear was disabled still fired on
release. The release now fires only on an enabled dog-ear, and always clears the press.

The crash-class selector rule flagged super.mouseDragged and super.mouseUp on NSButton (no probe in
objc-selector-table.json). The dog-ear only reaches them with no press of its own, and then NSButton's
tracking loop already took the drag and the release, so it ignores them instead of calling super.

Test: aPressDraggedOffTheFoldDoesNotClose covers a press disabled mid-click and the click after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex xhigh, correctness first): SHIP at f016b52

  • Reviewer: Codex (xhigh) through acpmux, read-only, with the full PR diff against base, the PR's title and body, and the touched files. Three rounds:
    • At b5ddcce: FIX. (1) NSButton tracked the press over the dog-ear's whole square, so a press on the fold dragged below the crease and released there still closed the workspace. (2) On a 24 pt row the tabs chevron, the session tree's fold or the audio button at the row's end sits partly under the dog-ear's square, and the dog-ear hit-tested first, so a click on the chevron's corner closed the workspace.
    • Fixed in 8819d4b: the dog-ear tracks its own press (a release fires only on the fold after a press that began on it), and the row hit-tests the dog-ear last and draws those three controls over the fold. Tests: aPressDraggedOffTheFoldDoesNotClose, aControlUnderTheDogEarKeepsItsClicksAndDrawsOverIt.
    • At 8819d4b: FIX, one should-fix: a press held while the dog-ear was disabled still fired on release. Fixed in f016b52 (release fires only when enabled and always clears the press; covered in the same test). The same commit drops the super.mouseDragged / super.mouseUp calls that the crash-class selector rule flagged as unprobed on NSButton.
    • At f016b52: SHIP. No blocker or should-fix finding.
  • Fleet compile: cmux-ci compile-check --ref f016b52dbb933612ad7612523ea4d8609e942030 --app passed (job 443286fb22aa5d76740d99f2).
  • Not run: the package tests. The fast tier's test build is red on base (CmuxNextAgentPaneTests/AgentPaneTabRowHolesTests.swift:103, HoleRecordingPage does not conform to PageChromiumPage), so no CmuxNext test, including the new dog-ear tests, runs until that is fixed.
  • The expanded row's hover pin is pre-existing (cx-13w3, cx-13w3: an expanded row's hover pin runs Pin/Unpin Workspace #19475); this PR only moves its slot to the end of the name's line.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next crash-class rules / cmux-next crash-class rules is red on 11 of 14 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 added the hold cmux-next batch queue: keep this PR out of the batch (docs/ci/cmux-next-batch.md) label Oct 22, 2026
…, no dog-ear fold

Leo on the dog-ear: "i wanted the hoverable area to just... not have it on
the top right corner. a smaller top right corner not higlighitng bc the
button itself chopped off that top right."

The close (or the Home row's menu) no longer draws a folded triangle. It
sits in a small square on the row's top-right corner that the row's hover
or selection fill leaves out, so the highlight reads as the row with its
corner chopped off by the button. The x keeps its place. The fill is a
shape layer under the row's content while the corner is cut (no mask on
the row), and goes back to the backing layer otherwise.

Debug Settings `sidebar.closeNotch` keeps the gallery's versions: snug
rounded cut (default), snug square cut, a rounded cut the size of the old
dog-ear, and the snug cut with a hairline. WorkspaceRowCloseNotchGalleryTests
renders them all, light and dark, on the fleet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray
@blacksmith-sh

blacksmith-sh Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Found 8 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp_76kkt1k/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmphkmassts/summary-fail.json (live):/summary-fail.json (live): View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
test_base_passes (main.NoticesPairTests.test_base_passes)/
test_base_passes (main.NoticesPairTests.test_base_passes)
View Logs
test_targets_outside_the_app_go_to_the_mac_canary (main.SwiftCanaryFleetCoverage.te
st_targets_outside_the_app_go_to_the_mac_canary)/
test_targets_outside_the_app_go_to_the_mac_canary (main.SwiftCanaryFleetCoverage.te
st_targets_outside_the_app_go_to_the_mac_canary)
View Logs

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next / cmux-next checks (god files, concurrency, crash safety, l10n) › Checks job structure is red on 12 of 14 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

Copy link
Copy Markdown
Collaborator Author

cmux-next / cmux-next checks (god files, concurrency, crash safety, l10n) › No fleet host names in gallery fixtures is red on 11 of 14 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

Copy link
Copy Markdown
Collaborator Author

cmux-next / cmux-next checks (god files, concurrency, crash safety, l10n) › No new SwiftUI (macOS UI is AppKit) is red on 10 of 10 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.

…ows a look change and a still pointer

Codex review of 90fc220 (BLOCK):
- The rounded cut's fillets reach past the close's square, and a click
  there selected the row. The close's frame now covers the whole cut
  (SidebarCloseNotch.cutFrame) and it takes exactly the points the row's
  drawn fill leaves out (cutContains), in every sidebar.closeNotch look.
- sidebar.closeNotch was read only in layout, so shown rows kept the old
  cut. SidebarTunablesWatch observes it with the other row tunables and
  configure hands it to the row, which lays out again on a change.
- Shown under a pointer that does not move, the close got no enter event
  and its x stayed dim. It reads the pointer when it unhides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray
…awn cut; the gallery draws each look

Codex review of c80b62d (BLOCK):
- Only the first hit test checked the cut; NSButton's own tracking fired
  on a release anywhere in the frame, which reaches onto the row's fill
  past a rounded cut. The close tracks the press itself: it fires only on
  a release in the cut after a press there.
- The gallery set each look and drew rows in the same turn, before the
  tunables watch dropped its cached values, so every look could draw the
  snug cut. It refreshes the watch after each set and requires each row
  to carry the look it is drawing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

@teamleaderleo teamleaderleo left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review (codex xhigh, correctness first): SHIP at 16cd759

@teamleaderleo teamleaderleo removed the hold cmux-next batch queue: keep this PR out of the batch (docs/ci/cmux-next-batch.md) label Oct 23, 2026
@teamleaderleo
teamleaderleo merged commit cd84bc6 into feat-cmux-next Oct 23, 2026
154 of 160 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/39913961585 at a62351d failed: cmux-next fast tier, cmux-next perf gate.
Those jobs last passed at 3b82593. This pull request is one of 3 merged since: #23889, #23783, #24352.
If the failure is in your change, fix forward on feat-cmux-next. Pull requests run only the tiers their paths reach (docs/ci/cmux-next-tiers.md); label a batch PR full-ci to run them all before merging.

@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

@github-actions

Copy link
Copy Markdown
Contributor

The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/39922732738 at ab42b3c failed: cmux-next fast tier.
Those jobs last passed at 3b82593. This pull request is one of 3 merged since: #23889, #23783, #24352.
If the failure is in your change, fix forward on feat-cmux-next. Pull requests run only the tiers their paths reach (docs/ci/cmux-next-tiers.md); label a batch PR full-ci to run them all before merging.

teamleaderleo added a commit that referenced this pull request Oct 23, 2026
* test(cmux-next sidebar): a workspace row runs full length and its hover action is a top-right dog-ear (failing)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* fix(cmux-next sidebar): workspace rows run full length; the hover close or menu is a top-right dog-ear

Rows end at the list's inset again (rowTrailingTrim 0, taking back #22733's
trim). The x and the more button no longer sit in rounded boxes at the end of
the title: one dog-ear folds over the row's top-right corner (the close, or the
menu on a row that cannot close), takes clicks only on its fold, follows the
row's corner radius and draws translucent theme fills. The title keeps its full
width on hover; the unread badge and keycap keep their slots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* fix(cmux-next sidebar): an expanded row's hover pin clears the unread badge and the dog-ear

Review follow-ups: the pin's slot is taken after the badge or keycap slot and
ends before the dog-ear's square, so it never covers the badge (which now stays
on hover) and a click on it never lands on the fold. The dog-ear is a little
larger (controlSize + space1) so its glyph reads at compact size, its tinted
glyph is cached per color, and two stale doc comments are updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* cmux NEXT sidebar: the dog-ear fires only on its fold and yields to the row's end controls

Codex review of #23783 (xhigh) found two ways the dog-ear closed a workspace the user did not mean
to close:

- NSButton tracked the press over the dog-ear's whole square, so a press on the fold dragged below
  the crease and released there still closed. The dog-ear now tracks the press itself: a release
  fires only on the fold after a press that began on it, and the fold unlights while the pointer
  is off it.
- On a 24 pt row the tabs chevron, the session tree's fold or the audio button at the row's end sits
  partly under the dog-ear's square, and the dog-ear hit-tested first, so a click on the chevron's
  corner closed the workspace. The row now hit-tests the dog-ear last and draws those controls over
  the fold, so what shows on top is what a click presses.

Tests: aPressDraggedOffTheFoldDoesNotClose, aControlUnderTheDogEarKeepsItsClicksAndDrawsOverIt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* cmux NEXT sidebar: a dog-ear disabled mid-press does not fire; no unprobed super calls

Codex re-review of #23783 at 8819d4b: a press held while the dog-ear was disabled still fired on
release. The release now fires only on an enabled dog-ear, and always clears the press.

The crash-class selector rule flagged super.mouseDragged and super.mouseUp on NSButton (no probe in
objc-selector-table.json). The dog-ear only reaches them with no press of its own, and then NSButton's
tracking loop already took the drag and the release, so it ignores them instead of calling super.

Test: aPressDraggedOffTheFoldDoesNotClose covers a press disabled mid-click and the click after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* cmux NEXT sidebar: the row's hover fill leaves the close's corner out, no dog-ear fold

Leo on the dog-ear: "i wanted the hoverable area to just... not have it on
the top right corner. a smaller top right corner not higlighitng bc the
button itself chopped off that top right."

The close (or the Home row's menu) no longer draws a folded triangle. It
sits in a small square on the row's top-right corner that the row's hover
or selection fill leaves out, so the highlight reads as the row with its
corner chopped off by the button. The x keeps its place. The fill is a
shape layer under the row's content while the corner is cut (no mask on
the row), and goes back to the backing layer otherwise.

Debug Settings `sidebar.closeNotch` keeps the gallery's versions: snug
rounded cut (default), snug square cut, a rounded cut the size of the old
dog-ear, and the snug cut with a hairline. WorkspaceRowCloseNotchGalleryTests
renders them all, light and dark, on the fleet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* cmux NEXT sidebar: the row's corner close is a VoiceOver button at the snug cut's size

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* cmux NEXT sidebar: the corner close takes exactly the drawn cut, follows a look change and a still pointer

Codex review of 90fc220 (BLOCK):
- The rounded cut's fillets reach past the close's square, and a click
  there selected the row. The close's frame now covers the whole cut
  (SidebarCloseNotch.cutFrame) and it takes exactly the points the row's
  drawn fill leaves out (cutContains), in every sidebar.closeNotch look.
- sidebar.closeNotch was read only in layout, so shown rows kept the old
  cut. SidebarTunablesWatch observes it with the other row tunables and
  configure hands it to the row, which lays out again on a change.
- Shown under a pointer that does not move, the close got no enter event
  and its x stayed dim. It reads the pointer when it unhides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* cmux NEXT sidebar: the corner close fires only on a release in the drawn cut; the gallery draws each look

Codex review of c80b62d (BLOCK):
- Only the first hit test checked the cut; NSButton's own tracking fired
  on a release anywhere in the frame, which reaches onto the row's fill
  past a rounded cut. The close tracks the press itself: it fires only on
  a release in the cut after a press there.
- The gallery set each look and drew rows in the same turn, before the
  tunables watch dropped its cached values, so every look could draw the
  snug cut. It refreshes the watch after each set and requires each row
  to carry the look it is drawing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16040-fd0b-7dd0-9487-1a3a198d9f5c&host=cosmic-gray

* docs: add ergonomic cmux-next preview workflow

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a1675a-0d00-7500-aa2e-61664f715df3&host=cosmic-gray

* cmux NEXT: make close-notch preview artifacts reviewable

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16759-6434-7f61-9180-62dc0965c7b2&host=cosmic-gray

* cmux NEXT: add ergonomic preview wrapper

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16758-4b24-7c41-b817-18aff1fb02a5&host=cosmic-gray

* docs: align preview wrapper contract

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16747-6774-7840-9abc-3147d6b51232&host=cosmic-gray

* test(next): cover Home dog-ear VoiceOver label

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a166c1-b197-7073-8526-9f5eba16f64f&host=cosmic-gray

* docs: clarify hosted close-notch preview command

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16747-6774-7840-9abc-3147d6b51232&host=cosmic-gray

* fix: serialize preview process startup

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16747-6774-7840-9abc-3147d6b51232&host=cosmic-gray

* fix(next): make browser previews use real checkout state

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16746-f280-7d72-9922-f6c3c758ca81&host=cosmic-gray

* fix: route gallery entries through hash links

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16747-6774-7840-9abc-3147d6b51232&host=cosmic-gray

* docs: scope native preview recipe claims

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a16747-6774-7840-9abc-3147d6b51232&host=cosmic-gray

* test(next): reject galleries from another preview process

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a166c1-b197-7073-8526-9f5eba16f64f&host=cosmic-gray

* fix(next): use only the managed preview gallery URL

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a166c1-b197-7073-8526-9f5eba16f64f&host=cosmic-gray

* fix(next): publish gallery metadata before preview readiness

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a166c1-b197-7073-8526-9f5eba16f64f&host=cosmic-gray

* test(next): run preview wrapper contract in CI

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a166c1-b197-7073-8526-9f5eba16f64f&host=cosmic-gray

* test(next): reject foreign preview index readiness

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a166c1-b197-7073-8526-9f5eba16f64f&host=cosmic-gray

* fix(next): authenticate preview index readiness

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a166c1-b197-7073-8526-9f5eba16f64f&host=cosmic-gray

* fix(next): avoid curl pipefail on preview ownership check

Agent-Session: https://cmux.com/deeplink/agent?harness=codex&session=01a166f4-abed-7bc3-922b-15af1bc7b48e&host=cosmic-gray

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lawrencecchen added a commit that referenced this pull request Oct 23, 2026
… own hover x (red)

Lawrence 2026-10-23: "need to revert styling back to original, for the X button, ... and text
needs to be as left as possible (how it originally was)". A row with no icon must start its title
at the row inset, where its group header's name starts (no empty icon slot, c779988), a
depth-1 member must not be indented, and the hover close must be the row's x at the end of the
title's line (before #23783's corner close).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lawrencecchen added a commit that referenced this pull request Oct 23, 2026
… stay full length)

Reverts cd84bc6 (Leo #23783, the corner dog-ear / close-notch close) except rowTrailingTrim,
which stays 0. Lawrence 2026-10-23: "need to revert styling back to original, for the X button".

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

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 16cd7591 Deployed Oct 23, 2026 by teamleaderleo via namespace autofix #1738
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant