Sidebar: hide scrolled rows under the traffic lights (minimal titlebar)

Merged
TE
7d ago
+153-1

With the default minimal titlebar, scrolling the left sidebar list slid rows up under the window's traffic lights, and they stayed visible there. On nightly 1.0.0-nightly.3965802926201 (3a63d6d9) the "Agents" group header pill sat beside the lights.

Cause: since 94f7db0 (minimal titlebar) the sidebar's header row is Metrics.tabStripHeight tall (28 in compact, WindowRootView.applyTokens), so the list starts level with the panes' content. The traffic lights reach about y=22 and the titlebar row ends at Metrics.titlebarHeight (32 in compact). The list's 18 pt top edge fade (ScrollEdgeFadeView) starts at the list's top, so a scrolled row from y=28 down was only partly faded, right below the lights.

Decision (claude-sr2), verbatim: "Miss 6: keep the list starting level with the panes at rest. When the list is scrolled, extend the top mask so content scrolling under the traffic lights is clipped and faded below the lights (y≈titlebarHeight). Don't use max(tabStripHeight, titlebarHeight) at rest."

Change:

  • ScrollEdgeFadeView (CmuxNextDesign) gets a topClip in points. Its gradient's first stop moves down to topClip. While content is hidden above, the top color is clear, so the mask hides everything from the list's top down to topClip and the usual 18 pt fade starts below it. At rest the top color is opaque, so the mask is the same as before. topClip defaults to 0, so the other users (palette, extensions menu, priority panel) don't change.
  • SidebarView sets each scrolling band's clip (the list, and the top item band when it shows) to the part above Metrics.titlebarHeight, but only when the traffic lights are over this sidebar (headerHasWindowControls; a right sidebar gets 0). With the full titlebar the list already starts at titlebarHeight, so the clip is 0.
  • The list doesn't move. There are no sticky headers, the minimal titlebar is still the default, and no component is removed.
  • Motion: no new animation. When the top edge starts hiding content, the clear band appears through the existing Motion.feedback hover crossfade on the mask colors. With animation off it applies in one frame. Changing the geometry (topClip, locations) never animates.
  • Observers: none added. The scroll position comes from ScrollEdgeFadeView's existing clip-view bounds/frame and document frame observers, which its deinit removes. No per-row work: the clip is set in SidebarView.layout, and a scroll only changes the mask's colors, as before.

Test: SidebarTopMaskTests (CmuxNextAppTests), committed before the fix. It reads the mask's gradient the way Core Animation draws it and checks:

  • At rest with a clip, the mask stays opaque at the top.
  • Scrolled, it is clear down to the clip, half-faded in the middle of the fade, and opaque after it.
  • With no clip, the fade still starts at the top.
  • In the sidebar with the minimal header row, each band's clip ends at max(bandTop, Metrics.titlebarHeight) and the band is not moved up.
  • With the full titlebar or no window controls, the clip is 0.

Changelog

Fixed: Sidebar rows no longer show under the window's traffic lights when the list is scrolled with the minimal titlebar.

Observed / Not run

  • Ran python3 scripts/cmux-next/prepush-gate.py: no new violations.
  • Focused suite on the fleet: cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg=Packages/macOS/CmuxNext --arg=SidebarTopMaskTests. Red at 0ccf76674b75 (test only) and green at the head: results pending, to be added here when the steps finish.
  • Not run: the app itself. I didn't look at a scrolled sidebar in a build. The 4 pt clip in compact (28 to 32) comes from the token values, not from a measurement on screen.

No capture: the before/after capture follows from the next nightly (scrolled sidebar state).

Agent session: cmux16
🤖 Generated with Claude Code


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


Summary by cubic

Fixes sidebar rows staying visible under the window's traffic lights when scrolled with the minimal titlebar.

  • ScrollEdgeFadeView gains a topClip that hides content down to the titlebar's bottom and starts the top fade below it; at rest the mask is unchanged.
  • The sidebar sets each scrolling band's clip only when traffic lights are over it (headerHasWindowControls); full titlebar and right sidebar behavior is unchanged.
  • Adds SidebarTopMaskTests covering the mask at rest, scrolled, with no clip, and with full titlebar or no window controls.
  • Logs the fix in the sidebar owner note, since the fix commit reached its branch without a Sidebar-Look trailer.