Repository navigation
Conversation
the command palette's fuzzy matcher was the right scorer for a sidebar workspace filter, but two domain packages cannot depend on each other, so it moves down to CmuxFoundation. CommandPaletteFuzzyMatcher stays as a typealias, so no palette call site changes and its reference-scoring tests still pin the same numbers. the filter itself is pure value logic in CmuxSidebar: a prepared index built once per corpus revision, scored per keystroke. fields are tried in priority order and the first hit wins, so a row matching on its title never pays to score its branch, directory and ports (worst-case keystroke 35.9ms -> 10.5ms at 500 all-matching workspaces; 2.8ms on a realistic 200-workspace corpus). rows are filtered, never reordered - a sidebar is spatial and re-sorting by score mid-type moves rows under the pointer. ranking rides along separately for return-to-jump.
hover-reveal for the collapsed sidebar, as a pure (state, event) -> effects machine with no timers, views or clock inside it. the timing policy is a value type so settings configures a policy rather than reaching into the machine. the rules that decide whether a hover panel is bearable are all here and all tested: dwell before revealing so crossing the edge en route to a terminal never flashes the sidebar; grace before dismissing so the pointer can travel the diagonal from edge strip into panel; a drag skips dwell because it has already declared intent; and a hold set (pointer inside, context menu open, keyboard focus, drag in flight) refuses dismissal, which is what stops a context menu from dismissing the panel it was opened from. 21 tests, including stale timers firing in the wrong phase and a hold taken between the grace timer being scheduled and firing.
the filter chrome is split from its text input so the same frame hosts a live TextField in the app and a plain Text anywhere the field is drawn without being editable. match emphasis is a tinted pill behind the run rather than coloured text: recolouring would put a second blue beside the selection fill, which is already the accent, and the two would compete. the tint is scheme-aware because the accent needs more alpha on near-black than on near-white to read the same. a miss is reported by the match count reaching 0 and by an empty state that names the query, with no colour change anywhere. the earlier orange ring introduced a hue the sidebar palette does not have. CmuxSidebarGallery renders these to PNG offscreen via ImageRenderer, no window and no screen-recording permission, in seconds rather than the ~15 minutes a full app build costs. it is deliberately not wired into cmux.xcodeproj. it immediately paid for itself by catching a literal \(queryText) in the empty state and a .link button that renders AppKit's link blue next to the accent.
the float/dock switch is one control in a fixed corner, and both modes host the identical rail at the identical width: switching moves the frame, never reflows the rows, so the row being read keeps its shape across the change. the gloss is the rim, not the fill. a uniform hairline reads as a sticker; catching more light along the top edge and falling off downward is what makes the card read as glass lying over the terminal rather than a rectangle drawn on it. the border is drawn from the panel's own side because the backdrop is whatever the terminal happens to be showing. new-workspace is a full-width footer row rather than a + glyph, so the gesture that picks a workspace is the gesture that makes one. note: ImageRenderer cannot rasterise backdrop blur, so the gallery shows the material as a flat fill. layout, rim and shadow are reviewable offscreen; the blur itself has to be judged in the running app.
rides on the existing retained-AppKit-sidebar path: cmux already keeps the workspace table mounted behind a zero-width shell while the sidebar is hidden, so revealing it is a frame and opacity change with no table to build. that is what makes peek instant rather than merely fast, and it is why the reveal translates a laid-out subtree instead of animating its width, which would re-lay-out every row on every frame. the edge strip is an NSTrackingArea, not a hit-test. hitTest on the terminal host is on the typing-latency path per CLAUDE.md and runs for every keystroke; a tracking area is serviced by AppKit's mouse-moved plumbing and removes itself entirely while the sidebar is docked. it also returns nil from hitTest so it can never eat a click meant for the leading column of the terminal. presentationMode joins VerticalTabsSidebar's == because the toggle's glyph flips on it and would otherwise be skipped by the equality gate; the toggle closure stays out, like every other action bundle.
…lease drag visuals run on a 120hz pointer poll that no appkit drag callback can touch. destination callbacks (planner, overlay, exited) only track the drop plan; the commit stays on the stock release path. frozen frame capture at drag start makes displacement boundaries immovable mid-drag, spring glides use explicit CASpringAnimation because view-backing layers ignore implicit transaction animations, and dragged rows keep full opacity since the row itself is the drag visual (ghost suppressed for single-row drags).
closing a workspace slides its row out and creating one inserts in place instead of reloading every visible cell, which was both the create lag and the visual blink. selection coalescing window drops to 40ms now that switches are cheap.
the peek card lives in its own child panel above the portal-hosted terminal (the only way to draw over it), with real behind-window blur, its own hover tracking, and instant reveal from the titlebar toggle. terminal cursor rects yield to the card while it accepts the pointer so titles stop flashing the ibeam. docking over a revealed card swaps in place with no ghost slide.
the sidebar toggle sweeps the real layout width through the same live resize path the manual divider uses, so pane and terminal move together with no choreography to drift out of sync. window ground is the sidebar material window-wide, the workspace band and terminal draw as one card rounded where it meets the glass, and the last four workspaces stay mounted so recent tab switches are a visibility flip instead of a cold mount.
- settings: sidebar glass tint (one wash shared by docked and floating), peek reveal speed, disable peek, row density, disable switch on drag - window glass on by default: transparent window so the sidebar samples real behind-window vibrancy instead of flat frost - vscode material icon pack in the file explorer, finder/sf fallback - env-gated nav timing lines around switch, create, close, toggle, peek - compact floating panel top inset, dropped the new workspace button
…cker fix sidebar ground is now clear window + compositor blur radius instead of an appkit material, so blur is a real slider (1x-5x). tint slider drives a slate grey wash (default 44%). floating peek card gets the same blur only while it is showing so it never blurs the docked rows under it. files/find/vault sidebar stays on a solid terminal-coloured ground. new sidebar accent setting: blue or flat grey glass pill. toggle-button hover no longer pops the card during a hide (300ms window). close button gets a centred hover circle. sidebar defaults baked (hudWindow fallback, behind-window, always-active) and the upstream defaults-stamping migration is defanged.
dragging a workspace group moved the header row alone while its members stayed put. the freeform lift now carries the header plus its visible member rows as one block, other rows and groups part around the block's real height, and a group can never land inside another group mid-drag. single row drags are unchanged; the layout math is now exact for mixed row heights. commit path is upstream's existing whole-group move.
i removed a filter engine i added as it was mostly for testing, and removed a preview tool that rendered sidebar states to png images without launching the app, which i used as a dev tool for visual iteration. the fuzzy matcher goes back to the command palette where it lived.
…snapping - grab offset captured at native drag start so a group header doesn't jump - slot decided by the lifted block's leading edge, planner fed the same probe, so a tall group can reach the last slots - dragged rows pinned via prepareContent and drawn as a static snapshot so autoscroll can't unload or blank them - no-op drop glides home, real drops settle from where the block was with an ease-out, a re-grab clears any in-flight settle - single workspace drag indents live (top level vs inside a group) - optional active look on a dragged group header (sidebarDragHeaderActiveLook)
neutral translucent fill with a hairline edge, no blue accent; multi-selection is a weaker glass. fill and edge strength tunable via sidebarGlassSelectionFillOpacity / sidebarGlassSelectionEdgeOpacity. tint measured from aside. drops the unused glass pill color.
…pacity rows match terminal background moves into the same card, the sidebar accent row goes (selection is always glass now), search entries follow.
clear, custom color prompt, then the named palette, same as the workspace row's color menu. new defaulted onSetColor action routes to TabManager.setWorkspaceGroupColor.
…catalog, 19 locales) settings search titles use the same keys as their rows. the blur and tint subtitles drop the 0%/100% wording, a bare % reads as a format specifier.
…bar keys listed as not in cmux.json
|
Important Review skippedWe couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe pull request adds floating and edge-triggered sidebar presentation, compositor-glass appearance settings, workspace reorder interactions, material file icons, sidebar customization controls, and related tests and resources. ChangesSidebar Peek and Floating Presentation
Sidebar Glass Appearance
Workspace Reorder Interaction
Sidebar and Resource Updates
Priority: ➖ Normal Estimated code review effort: 5 (Critical) | ~90 minutes Sequence Diagram(s)sequenceDiagram
participant SidebarPeekEdgeTrackingView
participant SidebarPeekController
participant SidebarPeekMachine
participant SidebarPeekPanelHost
SidebarPeekEdgeTrackingView->>SidebarPeekController: forward pointer edge event
SidebarPeekController->>SidebarPeekMachine: apply event
SidebarPeekMachine->>SidebarPeekController: return phase and timer effects
SidebarPeekController->>SidebarPeekPanelHost: update panel presentation
Suggested reviewers: Merge Risk: 🟡 Moderate · up to After switching between floating and docked mode, the sidebar can show an empty or stale workspace list. Background resizes or a fast double toggle can also undo a sidebar show or hide. These issues affect a common interaction and should be fixed before merge. Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (9 errors, 1 inconclusive)✅ Passed checks (15 passed)Full details: Docstring CoverageExplanation Docstring coverage is 38.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 198 functions across 58 files. (2 skipped: 1 unsupported, 1 too large.) Full details: Cmux Swift Blocking RuntimeExplanation The diff adds blocking or timing-based synchronization in shipped Swift code. Resolution Replace the settings debounce and peek dwell/grace Full details: Cmux Swift ConcurrencyExplanation The diff adds a new Combine-backed app-state model in Resolution Migrate the new sidebar state to Swift Observation. Convert Full details: Cmux Swift Package BoundariesExplanation The diff adds independently testable sidebar motion logic to the app target. Resolution Move Full details: Cmux Swift LoggingExplanation The PR adds production app/runtime file logging in Resolution Remove the ad hoc Full details: Cmux Full InternationalizationExplanation The PR adds 31 production strings to Resolution Add a real Khmer ( Full details: Cmux Swiftui State LayoutExplanation The diff introduces new Combine-based SwiftUI state. Resolution Replace the new SwiftUI-observed controllers and added published state with Observation-based models ( Full details: Cmux Architecture RethinkExplanation The PR introduces a 120 Hz polling side channel for reorder rendering. Resolution Make one reorder coordinator or state machine the source of truth for the drag session, preview, drop plan, transform handoff, and cleanup. Feed it authoritative drag begin/move/drop/end and table-apply events, with an explicit generation and terminal transition. Remove the controller-owned 120 Hz timer, the duplicated poll/overlay update paths, and the one-second fallback. If AppKit requires a platform bridge for continuous pointer tracking, isolate that bridge behind the coordinator and emit value snapshots and lifecycle events instead of mutating controller state. Add tests for every terminal path, including drop-before-apply, drag-end-before-drop, table rebuild during a drag, and teardown. Full details: Cmux Swift Auxiliary Window Close ShortcutsExplanation The new user-visible Resolution Assign a stable identifier such as Full details: Cmux No Test Or Debug Seam In Production SourceExplanation The PR adds debug-only observability seams to production source. Resolution Move the debug trigger, ✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Comment |
|
All contributors have signed the CLA ✍️ ✅ |
This comment has been minimized.
This comment has been minimized.
|
Probably yours: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by |
Dogfood tours of
|
…, not cmux.json paths fixes test_every_advertised_path_is_supported
…p fork wording ContentView rendered with matchTerminalBackground off while the catalog, Settings and the other readers said on. the catalog, schema and docs now say off too, which is what the glass sidebar needs. the dead legacy preset migration goes and the diagnostics report the real bgGlassEnabled default.
… of the controller review fixes: - the apply's forced-reload / reconfigure tail hung off the settle check and ran after every structural apply; it is back on the structural chain and the settle runs after it - every structural apply ends the lift (snapshots, hidden cells, transforms, pinned rows), not just the reload and pure-reorder branches, so a drop into a collapsed group or one that changes heights no longer leaves blank cells - a new drag cancels a pending drop hand-off and ends any leftover lift; the grab offset is captured after the duplicate-begin check - the drop commit fallback uses the clock + cancellable task pattern instead of asyncAfter; dismantle clears it and the lift - settle raises rows to zPosition 100 and now drops them back after the glide - the animated insert/remove branches were unreachable behind the pure-edit path; small pure edits now animate there (3 rows or fewer, no height change) - multi-selection glass keeps an Increase Contrast boost file length: the reorder lift and drop code move into SidebarWorkspaceTableController+ReorderLift/+ReorderDrop, the glyph button into its own file, the glass selection colors into SidebarGlassSelection, the row cell's lift helpers into an extension.
…group's own color - only user toggles animate; programmatic setVisible (narrow-window auto-collapse, session restore) applies at once so callers can read isVisible right after. a second hide during the sweep is ignored instead of capturing the half-collapsed width - the peek panel bridge tears its panel down on dismantle and gives the terminal its cursor rects back - Clear Color shows only when the group has its own color (a config color cannot be cleared from the menu) - peek lifecycle modifiers, the toggle hover broadcast and the terminal's cursor yield move into ContentView+SidebarPeek helpers (file length)
…a reveal applies once - the parked docked list keeps applying content (titles, order, cloud identity), so a show slides in current rows. spinners, status pulses and hover pause while it's off screen (#14832's concern) through a table-level rows-on-screen gate, and resume on reveal - VerticalTabsSidebar no longer subscribes to SidebarState it never reads. in its own hosting view every visibility publish cost a full body pass, which is where the triple row projection on reveal came from - isRevealed on the sidebar also unmounts the titlebar double-click/drag strip and minimal mode controls over a hidden sidebar - the reveal's push waits a main-queue turn instead of running in the landing frame - SidebarHiddenPresentationTests waits for the toggle to land on isVisible's own commit (the slide's completion), asserts rows and cloud identity apply while hidden with row animations off, resume on reveal, and at most one projection per row. the provider-scope test moved to its own file unchanged
…t of the landing review follow-ups on 5b2c4c9: - the animator turns the docked table's rows on screen when a show starts and off when a hide lands, so spinners and status pulses slide in moving, as before, instead of waiting for SwiftUI after the landing - the deferred reveal push runs in the default run loop mode, so the landing's event tracking spin can't pick it up, and the rest of the landing update rides the pending push - the hidden presentation test has a one minute limit and checks that rows were added while hidden rather than an exact count
…, same tint - SidebarHeaderGlyphButton gets an opt-in hover fill: a circle on its 16pt bounds in the surface's foreground, 15% on hover, 22% pressed, glyph lifted to 65%. the group header plus and the row close button use it, in both active and inactive rows - the row close button rests at the same secondary label colour as the plus (it was 70% of the selected foreground on active rows) - hover settles from the live pointer on every reveal and conceal, so a click that reflows rows, a drag, or a row scrolling under a still pointer can't strand it
…n place the stock #393939 at 72% applied in both appearances, so in light mode the sidebar stayed a dark glass under dark text. with no light override and the stock hex, light mode now uses #F2F2F2 (the stock opacity maps to 72% too, a chosen opacity is kept). main's light #000000 at 18% assumed a light material under it; the compositor ground is the raw desktop blur. dark is unchanged and a chosen colour wins in both modes. test: stockSidebarTintIsAppearanceAware
… hover around drags - an edgeless soft wash on the row under the pointer (the glass selection's shape, 5% dark / 3.5% light, tunable via sidebarRowHoverFillOpacity[Light]); selection, multi-selection and custom fills win, the header plus draws on top - gated by the sidebarRowHover default (on unless turned off), read where the wash is painted and followed live through KVO: the loaded-cell sweep repaints every row. hover-revealed chrome (close X, header plus) is unaffected - drags: hover is cleared at drag start and before the lift snapshot, suppressed while the drag runs, and re-derived from the live pointer after the drop and after the settle; the sweep covers every loaded row view - the hover code lives in SidebarRowHover.swift and SidebarWorkspaceTableController+Hover.swift, so the cell files and the controller stay inside the length budget
writes sidebarRowHover (UI-only, on by default) from a private row in the section file, so the package interface is unchanged. search entry next to the other sidebar appearance rows. strings in the app catalog in all 19 locales
only in light mode, only while the stock tint is in place (a chosen tint, a light-mode tint, a custom selection colour or matching the terminal opt out); dark is unchanged - ground: #D1D4D5 at 88%, a cool mid-light gray (~#CACDCE over the desktop) - selected row and active group header: a solid white pill with a black 25% hairline and a soft 1 pt drop shadow; text on it goes near-black through the existing contrast path - hover: an edgeless white wash at 62% between the ground and the pill, driven by sidebarRowHoverFillOpacityLight (a black wash, capped at 20%, outside this look) - unselected row titles: black at 69% (~#414141 on the ground) - the glass selection's opacity tuning keys only shape the translucent patch, which this look does not use
…, not solid lands around 250 on the gray ground; the hairline, the soft shadow and the 0.62 hover wash below it are unchanged. the active group header uses the same fill
#expect decomposes allSatisfy(\.isPresentationActive) and types the key path as a throwing function, which failed the test target's compile
black at 7% (was 14%), a barely-there lift; tunable through sidebarSelectionShadowOpacityLight. the hairline is unchanged
… pill showOptimisticAnchorActive (press feedback and the active look while dragging a group) painted label colour at 8% directly, a gray patch in the light look; it now uses the header's own active fill
layers only, no blur: a white fill a touch brighter at the top (48% mid, 10% spread) and a 0.5 pt white inner rim at 55%, like the selection's glass edge; no shadow. tunable through sidebarRowHoverFillOpacityLight, sidebarRowHoverGradientLight and sidebarRowHoverRimLight. the hover layers are gradient layers; dark keeps its flat wash
since the docked list moved into its own unclipped hosting view, a sidebar field's visibleRect came back window-sized, its midpoint fell outside the sidebar, and hiding the sidebar while renaming no longer handed focus back to the terminal. clamp the visible rect to the responder's bounds
dark keeps the translucent white patch; light on the stock tint is white at 90% with a 25% hairline, and a chosen tint keeps the translucent black patch
…port file the reveal projection check reports which known frames ran each pass; the run loop and toggle helpers move to SidebarHiddenPresentationTests+Support to keep the file inside its length budget
…cted binding the docked list lives in its own hosting view, and each ContentView update pushed a fresh `$sidebarSelectionState.selection` binding, which SwiftUI took as a change and re-ran VerticalTabsSidebar's body despite its equatable gate: three full row projections per reveal (CI named the passes: hosting view layout, `_selection changed`). the sidebar now reads SidebarSelectionState from the environment (both hosted environments inject it) through a computed `selection`
|
follow-up round (5b2c4c9..dad39a8) hidden sidebar (ci fix, lucas's option 1)
hover
light mode, matched to aside (stock tint only; dark, a chosen tint, a custom selection colour and match terminal background are unchanged)
fixed overnight from ci
ci on dad39a8: everything this branch touches is green, including checked on a local tagged build; light values measured against aside screenshots in the sidebar lab and the app. dark mode pixel-identical before and after |
the stock light pill was a flat 90% white sticker with a dark 25% hairline. it is now a brighter piece of the hover's glass: 74% white with a top-bright gradient, a 0.5 pt white inner rim, an 8% outer edge and a 5% shadow, all layers on the existing wash layer. the active group header matches, the reorder lift carries it, and dark mode, custom tints and selection colours are untouched. each value is tunable through a sidebarSelection*Light key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
with the stock light look, row titles now take the pill's near-black on every row instead of near-black on the active row and a 69% gray elsewhere, and secondary lines keep their resting colour on the glass pill, so the pill alone marks the active row. dark mode, custom tints and selection colours keep their own contrast rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge-main commit by scripts/merge-main.sh. Merged by scripts/merge-main.sh: origin/main at ef397b8. Resolved conflicts: - Resources/Localizable.xcstrings: xcstrings key-level union - cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py Merge-main-previous-head: 03af860 Merge-main-base: ef397b8
Merge-main commit by scripts/merge-main.sh. Merged by scripts/merge-main.sh: origin/main at cd85f00. Resolved conflicts: - Resources/Localizable.xcstrings: xcstrings key-level union - cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py Merge-main-previous-head: 53d4ead Merge-main-base: cd85f00
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 77b039f. Configure here.
| if tintHex.caseInsensitiveCompare(stock.hex) == .orderedSame { | ||
| resolvedHex = WindowChromeSidebarTintDefaults.light.hex | ||
| if tintOpacity == stock.opacity { opacity = WindowChromeSidebarTintDefaults.light.opacity } | ||
| } |
There was a problem hiding this comment.
Light stock tint ignores empty override
Medium Severity
The new light-mode stock swap only runs when tintHexLight is nil. An empty string, the catalog's stored "no override" value for sidebarAppearance.lightModeTintColor, is treated as an override, so the dark #393939 glass stays in place. Light-mode row titles are black, so the sidebar can become hard to read.
Reviewed by Cursor Bugbot for commit 77b039f. Configure here.


Summary
The sidebar today is a fixed pane with a blue selection pill, and reordering workspaces drags a ghost image around. This brings in the sidebar redesign from my take-home (supersedes #12029, which was opened from my fork):
sidebarSelectionAccentsetting is removed).Defaults change for people who never set these keys: tint color and opacity, window glass, sidebar material and blend mode, compositor glass on. That is what makes the glass look show up out of the box, but it is a visible change for existing users and worth a decision.
sidebar-glass-drag-reorder-group-color-after.mov
Sidebar toggle and peek motion
Austin said opening and closing the sidebar felt laggy. The old toggle swept the real layout width, so every frame relaid out SwiftUI, the splits and every terminal grid (50-75 ms of main thread per frame in debug). Now:
setVisible(narrow window, session restore) drops the slide.sidebar-toggle-glide.mov
Collapse, peek and CMD + B
collapsing with cmd b glides the sidebar out and back on the render server, and hovering the titlebar sidebar button while it's collapsed peeks the floating card with its own glass.
collapse-peek-cmd-b.mov
Hover and light mode
light-mode.mov
Row hover (setting for now)
rows and group headers get a soft hover fill (glassy white wash in light mode). it sits behind settings > sidebar > row hover, on by default while we get second opinions.
row-hover.mov
Addition: file type icons in the Files tab
files and folders in the right sidebar's Files tab now get an icon per file type (swift, ts, json, md, folders and so on) instead of one generic glyph.
Testing
cmux DEVbuilds of this branch, from before the merge with main, ran the floating peek, the dock/float toggle and drag reorder live.macos / macOS compile admission,macos / swift-package-tests, app-host unit tests). On the first push,swift-package-testsfailed in CmuxSettingsUI and CmuxAppKitSupportUI, and compile admission never ran because a guard failed. All three are fixed in 8b472a7 and 5d33457. A package conventions lint failure from the follow-ups is fixed in 02816ae.python3 scripts/verify-local.py --all --swift-changed origin/mainpasses 17/17 (Swift syntax only, not a compile). These also pass:scripts/localization_catalog.py check,tests/test_cmux_schema_parity.py,tests/test_settings_configuration_review_paths.py,tests/test_cmux_config_schema_embed.py,scripts/sync-test-wiring --checkandscripts/wire-app-sources.py --check.asyncAfter, and zPosition reset after a settlecmux DEV sidebar-anim) with the docked sidebar hidden: acmux workspace renamethrough the debug socket loggedworkspace.customTitle.write, then the card'ssidebar.table.rowsBuild,sidebar.row.titlePaintwith the new title andsidebar.table.apply contentChanges=1within 90 ms. The build before the fix logged only the write. The hidden docked list still does no work: one rows build per rename.scripts/swift_file_length_budget.py). Cohesive pieces moved to their own files with no behaviour change.cmuxTests/SidebarWorkspaceReorderLiftTests.swift: a group drag keeps the header grab offset (moved fromSidebarWorkspaceTableTests.swift), a live-drag lift survives a mid-drag close and every row starts from where it stood, and the drag image only shows off the list.cmuxTests/SidebarPeekPanelWindowTests.swift: the keyboard rules of the card's window.SidebarPresentationModePeekPanelTests(CmuxSidebar package): when the card is mounted.cmuxTests/SidebarPresentedWhileHiddenSnapshotTests.swift: a sidebar presented while the docked one is hidden rebuilds its workspace snapshot on rename. Committed before the fix (7533734).AppDelegateShortcutRoutingTests:1207, :1252, :6975). The toggle went to the right window, but the animated hide keepsisVisibletrue for its 170 ms sweep, so the sidebar read as still visible right after the toggle. That also meant a second toggle during the sweep was dropped (it asked to hide again). Fixed in the code:SidebarState.requestedVisibilityis false as soon as a hide is requested,toggle()andsidebarVisibility(windowId:)read it, and a toggle mid-hide now glides the pane back out from where it is. New test:SidebarStateAnimatedToggleTests.hidingClearsReorderIndicator,reorderDragReplansFromStoredWindowPointOnViewportChange). The lift draws the drop preview and never sets an indicator painter, so a drag exit or a hidden sidebar skipped the drop indicator clear and its autoscroll stop. Fixed in the code: retirement also runs for a live accepted drop update.tableRetirementInvalidatesDescriptionLinkAccessibility). With the slide-out on close,didRemoveonly fires after the 220 ms animation, so the removed row kept its actions, link proxies and any open edit until then. Fixed in the code: rows sliding out are retired when the removal starts. Visible side effect: a link-bearing description goes blank as the row starts to slide out.liveDragLiftIsRebuiltAcrossAStructuralUpdateWithoutBouncingpassed on the first run and failed on the second (rows off by 33 pt). The lift rebuild read a presentation copy of each row's transform, and that copy can lag a glide that was just removed. It now reads the model value unless a glide is actually running.SidebarSelectedWorkspaceColorTests). The tests pinned the old blue accent fill. They are replaced bycmuxTests/SidebarGlassSelectionTests.swift, which checks the neutral glass fill and edge, weaker multi-selection, label-coloured text, and that a customsidebarSelectionColorHexstill wins. The multi-selected group header test now expects the glass hairline, and the two link raster tests composite the translucent selection over the dark pane before looking for glyph pixels.WindowAppearanceSnapshotTests). The tools sidebar now sits on a solid terminal backdrop on purpose, so the test checks for that. The tint test reads the left sidebar.BrowserDeveloperToolsConfigurationTests:2023, :2105). Window glass is now on by default, and a glass window clears a blank page so the glass shows through. Main does the same when glass is on, so this is not a regression. The two refresh tests turn glass off for their run, andBrowserGlassWindowBackgroundTestscovers the glass case.WindowOverlayChromeTests(browser content inside the chrome). Compositor glass is on by default and clears the window instead of keeping a native glass root, so the test turns it off to keep checking the native root path.CLI product testsfailed once intests/test_issue_15066_zsh_watcher_sleep.py(zsh watcher timeout; this branch does not touch shell integration) and passed on the next two runs.testTextBoxSubmitSerializesPasteboardRunsAcrossSurfaces(pasteboard, also failing on other PRs) andCLINotifyProcessIntegrationTests.testAgentHookLaunchEnvironmentDoesNotPersistPathOrShelleach failed once on 3c783f9 and passed on 218d22a.ETIMEDOUT). The re-run passed, andmacOS statusis green.cmux DEV sidebar-animcompiles on 218d22a (app target only, the tests compile in CI), with no warnings in the changed files.verify-local.py --all --swift-changed origin/mainpasses 17/17, the Swift file length budget passes, andsync-test-wiring --checkis clean.SidebarToggleSlideMachineTests(press sequences, reversal from the presented offset with velocity carried, stale and repeated landings, reset) andSidebarStateAnimatedToggleTests.programmaticSetVisibleAlwaysReportsTheCommit. The animator itself has no unit tests (it needs a window and portals). Main merged withscripts/merge-main.sh(150f830) since GitHub had the PR as conflicting and no macOS CI had run on d4196a4.SidebarHiddenPresentationTests.visibilityToggleKeepsAppKitTableContainerMountedwaits for the toggle to land onisVisible's own commit and checks the new contract (rows and Cloud identity apply while hidden, row animations off, back on at reveal, at most one projection per row, focus handed back when hiding with a sidebar field focused). CI caught two real regressions from the docked list moving into its own hosting view, both fixed: a projected selection binding re-ran the sidebar body on every push (dad39a8), and a sidebar field's window-sizedvisibleRectbroke the focus handoff (b7b23f9). New tests:stockSidebarTintIsAppearanceAware,lightSelectionIsAsidePillOnTheStockTintAndGlassOtherwise. Reviewed by subagents; light values measured against Aside in the Sidebar Lab and a local tagged build, dark mode pixel-identical.Resources/Localizable.xcstringswith all 19 app locales: the Settings rows, the Group Color menu, and older branch strings that had no translations.THIRD_PARTY_LICENSES.md.Changelog
Added: Floating glass sidebar that peeks in from the left edge, with drag-to-reorder that lifts rows and groups, a Group Color menu, and new sidebar appearance settings
Checklist
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Reworks the sidebar into a floating glass card that peeks in from the left edge, with freeform drag reordering, Group Color, file icons, and a new Settings > Sidebar customization card.
sidebarSelectionAccentis removed.Migration
Users who never set tint, glass, or match-terminal keys now get compositor glass on, match-terminal off, and tint
#393939at 72% — a visible change for existing setups.Written for commit 77b039f. Summary will update on new commits.
Note
Medium Risk
Wide visual and default-setting changes across window chrome and sidebar appearance, plus new compositor blur and peek panel lifecycle, though much of the peek logic is covered by unit tests.
Overview
Ships a compositor-glass sidebar ground and related defaults: when compositor glass applies, the window root goes transparent, CGS sets an explicit blur radius (12–60pt), and the left sidebar paints tint-only over that ground. Match terminal background flips to opt-in (
falseby default); stock tint becomes #393939 at 72% with a light-mode override (#D1D4D5 / 88%) when the stock hex is unchanged. Sidebar material/blend/state and background-glass defaults move to behind-window HUD-style glass; the right tools sidebar always uses a solid terminal-coloured backdrop instead of following workspace glass.Settings > Sidebar gains a customization card: Liquid Glass, blur/tint sliders (debounced), peek reveal speed, disable peek, row density, disable switch-on-drag, plus search anchors. Catalog/schema defaults align with the new glass look.
Adds a testable peek state machine (
SidebarPeekMachine, dwell/grace, holds for menus/focus/drag) and floating-panel UI (glass backdrop, chrome, dock/float toggle, new-workspace footer) with rules for when the peek child window mounts.Infrastructure:
CompositorBlurController.setBackgroundBlur,WindowBackdropPlan.compositorBlurRadius, and backdrop controller wiring; bundled MaterialIcons SVG set + MIT license; minor.gitignorecleanup.Reviewed by Cursor Bugbot for commit 77b039f. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit