Skip to content

Fix sidebar scroller visibility - #3570

Merged
austinywang merged 2 commits into
mainfrom
issue-3555-sidebar-scroller-always-shown
May 5, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-3555-sidebar-scroller-always-shown

Conversation

@austinywang

@austinywang austinywang commented May 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3555.

Summary

  • Restores sidebar scroller visibility to the real overflow state instead of decorative sidebar chrome.
  • Centralizes workspace sidebar scroll insets and content min-height arithmetic in Swift-5-compatible value types with member-level nonisolated accessors.
  • Adds regression UI coverage for fit-versus-overflow sidebar scroller behavior.

Commit Structure

  1. b31e8199 adds the failing regression test proving the sidebar scroller must hide when workspace content fits and appear after overflow.
  2. b7b416b6 fixes the scroll container invariant and makes the overflow portion of the regression test probe only after a measured dynamic threshold.

Verification

  • Remote-only per task instruction; no local tests were run.
  • PR CI and review feedback are being handled through the iterate-pr loop before the required final tagged launch verification.

Summary by CodeRabbit

  • Improvements
    • Refined workspace sidebar scrolling layout, spacing, and min-height calculation to better handle varying viewport sizes and safe-area insets.
    • Adjusted top/bottom safe-area and drag-indicator spacing for more consistent visual alignment and scrolling behavior.
  • Tests
    • Added a UI test that verifies the workspace sidebar scrollbar appears as the workspace list grows.

Note

Medium Risk
Adjusts sidebar scroll insets/min-height calculations that affect workspace list overflow and drag/drop hit areas, so regressions could impact scrolling or drop targets across window sizes. Adds UI coverage to reduce risk but behavior is still UI-layout sensitive.

Overview
Restores workspace sidebar scrollbar visibility to track real list overflow by centralizing top/bottom scroll insets and using them when computing the scroll content minimum height (instead of implicitly including decorative chrome).

Refactors ContentView.workspaceScrollArea to use the new SidebarWorkspaceScrollInsets/SidebarWorkspaceScrollLayout helpers for safe-area insets and the top drag/drop drop-zone height.

Adds WorkspaceSidebarScrollUITests.testSidebarScrollerVisibilityFollowsWorkspaceOverflow to assert the scroller is hidden when content fits and becomes visible once enough workspaces are created, using a dynamic probe threshold to reduce flakiness.

Reviewed by Cursor Bugbot for commit acf7eac. Bugbot is set up for automated code reviews on this repo. Configure here.

The workspace sidebar now has UI coverage for both sides of the issue: a one-workspace sidebar should not expose a vertical scroller, while an overflowing workspace list should expose one after scroll interaction. The test scopes scrollbar detection to the Sidebar accessibility frame so terminal scrollbars cannot satisfy the assertion.

Constraint: Issue #3555 regressed after sidebar edge-fade chrome changed the scroll container behavior

Constraint: Local tests and local app launch are disallowed before CI per task instructions

Rejected: Source-shape test around safeAreaInset | it would not prove the user-visible scrollbar behavior

Confidence: medium

Scope-risk: narrow

Tested: git diff --check

Not-tested: Local UI tests not run per task instruction
@vercel

vercel Bot commented May 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 5, 2026 8:31pm
cmux-staging Building Building Preview, Comment May 5, 2026 8:31pm

@coderabbitai

coderabbitai Bot commented May 5, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Updates sidebar workspace scrolling to derive top/bottom insets from a new SidebarWorkspaceScrollInsets.workspaceList, compute min content height via SidebarWorkspaceScrollLayout.contentMinHeight(...), adjust ContentView spacer heights and drag-indicator offset accordingly, and add a UI test that asserts vertical scroller visibility when workspaces overflow.

Changes

Sidebar Scroller Visibility

Layer / File(s) Summary
Data Shape & Metrics
Sources/WindowChromeMetrics.swift
Adds SidebarWorkspaceScrollInsets(top:bottom:) with total and static workspaceList; adds SidebarWorkspaceScrollLayout.contentMinHeight(viewportHeight:insets:) returning max(0, viewportHeight - insets.total).
Core Layout
Sources/ContentView.swift
VerticalTabsSidebar.workspaceScrollArea now uses scrollInsets = SidebarWorkspaceScrollInsets.workspaceList; passes SidebarWorkspaceScrollLayout.contentMinHeight(viewportHeight: geometry.size.height + scrollInsets.total, insets: scrollInsets) (viewport + insets) to workspaceScrollContent; sets .safeAreaInset spacer heights to scrollInsets.top and scrollInsets.bottom; updates drag-indicator spacer to scrollInsets.top + 8.
Tests / Validation
cmuxUITests/WorkspaceSidebarScrollUITests.swift
Adds testSidebarScrollerVisibilityFollowsWorkspaceOverflow() and helpers: computes probe start count, creates workspaces up to a cap, reveals scroller via hover/swipe, and determines visibility by enumerating .scrollBar elements filtered by hittability, size, and frame overlap with the sidebar.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • manaflow-ai/cmux#3152: Modifies the VerticalTabsSidebar workspace scroll area and related tests; overlaps on scroll metrics and test behavior.

Poem

🐰 I nudged the top and bottom with care,
Spacers settled tidy, nothing spare.
I hovered, swiped until the bar would show,
When rows exceed the view below. 🥕


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error PR introduces Swift 6 actor isolation mistakes: SidebarWorkspaceScrollInsets applies nonisolated to stored properties without isolation context, violating SE-0313/SE-0434 rules. Remove nonisolated from stored properties workspaceList, top, bottom in SidebarWorkspaceScrollInsets (lines 43, 48-49). Retain it only on computed property total and function contentMinHeight.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description covers the summary, technical changes, commit structure, and verification approach, but is missing the Testing and Demo Video sections from the template. Add Testing section describing how the change was tested (locally or via CI), and explain whether a demo video is applicable for this fix.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix sidebar scroller visibility' clearly and concisely summarizes the main objective of the changeset, which is to restore the sidebar scroller's visibility behavior.
Linked Issues check ✅ Passed The PR addresses all objectives from issue #3555: centralizes scroll insets, updates sidebar scroll behavior, and adds regression UI test coverage for scroller visibility.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing sidebar scroller visibility: metrics refactoring, scroll container updates, and regression test additions are all within the stated objectives.
Cmux Swift Blocking Runtime ✅ Passed PR introduces no blocking or timing-based synchronization primitives in production code. New metric types contain pure value calculations. Test uses allowed polling patterns.
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns. Changes add simple value types for scroll metrics and refactor existing logic. Test-only code uses standard XCTest patterns.
Cmux Swift @Concurrent ✅ Passed PR introduces no new async functions, @concurrent annotations, or concurrency isolation violations. Changes are limited to UI layout refactoring and test additions with no Swift concurrency concerns.
Cmux Swift File And Package Boundaries ✅ Passed WindowChromeMetrics.swift adds pure metrics types (63 lines) without mixing concerns. ContentView.swift refactors without size growth. UI test is focused. No boundary violations.
Cmux Swift Logging ✅ Passed All changes comply with swift-logging.md rules. No logging violations found in new code or modified functions.
Cmux Swiftui State Layout ✅ Passed No violations of SwiftUI state layout rules. Changes use immutable value types and value snapshots, avoid ObservableObject/@published patterns, contain no render-time mutations.
Cmux Architecture Rethink ✅ Passed Sidebar scroller fix centralizes scroll policy with value types. No timing repairs, observers, locks, or split ownership introduced. Permitted correctness fix.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-3555-sidebar-scroller-always-shown

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@austinywang
austinywang force-pushed the issue-3555-sidebar-scroller-always-shown branch from b6ce3e3 to 20f9cf4 Compare May 5, 2026 19:09
@greptile-apps

greptile-apps Bot commented May 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes sidebar scroller visibility by routing the ScrollView's safeAreaInset through the real scroll-container inset (SidebarWorkspaceListMetrics.scrollTopInset) rather than the decorative chrome height, and centralizes the top/bottom inset and content min-height arithmetic into two new value types in WindowChromeMetrics.swift.

  • Sources/WindowChromeMetrics.swift: Introduces SidebarWorkspaceScrollInsets (a small Equatable struct with top/bottom/total) and SidebarWorkspaceScrollLayout.contentMinHeight(viewportHeight:insets:). Both types are backed entirely by existing static let constants, so the static let workspaceList singleton is safe and never stale.
  • Sources/ContentView.swift: Replaces the deleted workspaceScrollTopVisibilityInset wrapper with scrollInsets.top / scrollInsets.bottom at all three call sites; the previously flagged dangling reference on line 10027 is now resolved.
  • cmuxUITests/WorkspaceSidebarScrollUITests.swift: Adds testSidebarScrollerVisibilityFollowsWorkspaceOverflow, which confirms the scroller hides at fit and appears after overflow; revealSidebarVerticalScroller uses hover → short poll → swipe → longer poll, addressing the prior hover/timing flakiness concern.

Confidence Score: 5/5

Safe to merge — the scroller visibility fix is complete, all three previously broken call sites are updated, and no new isolation or layout regressions are introduced.

The change is a focused refactor: one deleted computed property is replaced by a value-type centralizer, and the three usage sites are updated consistently. The static let workspaceList snapshot is built entirely from static let constants so it is never stale. The regression test is well-structured and the hover/timing flakiness raised in the prior review has been addressed.

No files require special attention.

Important Files Changed

Filename Overview
Sources/WindowChromeMetrics.swift Adds SidebarWorkspaceScrollInsets value type and SidebarWorkspaceScrollLayout enum to centralize scroll inset and min-height arithmetic; static let workspaceList correctly captures constant-backed values.
Sources/ContentView.swift Removes deleted workspaceScrollTopVisibilityInset wrapper and threads SidebarWorkspaceScrollInsets.workspaceList through all three scroll-inset call sites; previously flagged dangling reference on line 10027 is resolved.
cmuxUITests/WorkspaceSidebarScrollUITests.swift Adds testSidebarScrollerVisibilityFollowsWorkspaceOverflow regression test; revealSidebarVerticalScroller now uses hover → short poll → swipe → poll sequence, addressing the previously noted hover/timing flakiness concern.

Sequence Diagram

sequenceDiagram
    participant CV as ContentView(workspaceScrollArea)
    participant SI as SidebarWorkspaceScrollInsets.workspaceList
    participant SL as SidebarWorkspaceScrollLayout
    participant GR as GeometryReader
    participant SV as ScrollView

    CV->>SI: let scrollInsets = .workspaceList
    Note over SI: top = scrollTopInset, bottom = bottomScrimHeight
    CV->>GR: GeometryReader { proxy in ... }
    GR->>SL: contentMinHeight(viewportHeight: proxy.size.height, insets: scrollInsets)
    SL-->>GR: max(0, height - insets.total)
    GR->>SV: workspaceScrollContent(minHeight: ...)
    GR->>SV: .safeAreaInset(.top) → scrollInsets.top
    GR->>SV: .safeAreaInset(.bottom) → scrollInsets.bottom
    GR->>SV: drag overlay height → scrollInsets.top + 8
Loading

Reviews (7): Last reviewed commit: "Stop sidebar chrome from forcing scroll ..." | Re-trigger Greptile

Comment on lines +72 to +73
sidebar.coordinate(withNormalizedOffset: CGVector(dx: 0.97, dy: 0.5)).hover()
sidebar.swipeUp()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Hover-then-swipe may be unreliable for scroller visibility in headless CI

macOS overlay scrollbars only appear when the pointer is over the scroll area. sidebar.coordinate(withNormalizedOffset:).hover() simulates this, but in a headless or VNC CI environment the hover event may not trigger NSScroller visibility before swipeUp() is dispatched. The 4-second poll timeout in waitForSidebarVerticalScrollerVisible gives some slack, but if the scrollbar never becomes hittable the test will time-out and emit a false negative. Consider adding a short pollUntil after the hover to confirm at least one scroller is hittable before swiping, or add a known-flaky annotation on this assertion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ff7be2b: the scroller reveal path now hovers first, polls briefly for a visible scroller, then swipes and polls again instead of relying on a single hover-then-swipe timing assumption.

— Claude Code

@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@austinywang
austinywang force-pushed the issue-3555-sidebar-scroller-always-shown branch from 20f9cf4 to 789bb2d Compare May 5, 2026 19:13
@austinywang
austinywang force-pushed the issue-3555-sidebar-scroller-always-shown branch from 789bb2d to cd9ba95 Compare May 5, 2026 19:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxUITests/WorkspaceSidebarScrollUITests.swift`:
- Around line 63-77: The test currently assumes 20 workspaces will induce
overflow; change the logic to programmatically add workspaces until the sidebar
scroller becomes visible or a safe upper bound is reached (e.g.,
MAX_WORKSPACES), using the same keystroke sequence (app.typeKey("n",
modifierFlags: [.command])) and reusing waitForWorkspaceRowHittable after each
addition; stop the loop early when
waitForSidebarVerticalScrollerVisible(sidebar: sidebar, app: app, timeout: 1.0)
returns true, then assert that the scroller is visible (fail if the upper bound
is reached without overflow) so the precondition is deterministic. Ensure
references to workspaceCount are replaced by the loop and the boolean check
using waitForSidebarVerticalScrollerVisible; keep existing helper calls like
waitForWorkspaceRowHittable to verify each new row is hittable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8710b9f3-27fb-43d6-afec-4518e068e95c

📥 Commits

Reviewing files that changed from the base of the PR and between d3695c1 and cd9ba95.

📒 Files selected for processing (3)
  • Sources/ContentView.swift
  • Sources/WindowChromeMetrics.swift
  • cmuxUITests/WorkspaceSidebarScrollUITests.swift

Comment thread cmuxUITests/WorkspaceSidebarScrollUITests.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/WindowChromeMetrics.swift`:
- Around line 42-63: Remove the unsupported `nonisolated` type-level qualifiers
used on SidebarWorkspaceScrollInsets and SidebarWorkspaceScrollLayout: either
update the project Swift toolchain to 6.1+ to keep SE-0449 syntax, or delete the
`nonisolated` keywords on the struct and enum declarations and, if needed, mark
individual members (e.g., the `total` computed property or `contentMinHeight`
method) with appropriate isolation attributes such as `nonisolated` on the
member or `@MainActor` to preserve the intended concurrency behavior; ensure
references to SidebarWorkspaceScrollInsets, its properties (top, bottom, total),
and SidebarWorkspaceScrollLayout.contentMinHeight compile under Swift 5.0 after
the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b076dd7-b134-4856-bf66-199061e404f4

📥 Commits

Reviewing files that changed from the base of the PR and between aa76578 and 14811e6.

📒 Files selected for processing (3)
  • Sources/ContentView.swift
  • Sources/WindowChromeMetrics.swift
  • cmuxUITests/WorkspaceSidebarScrollUITests.swift

Comment thread Sources/WindowChromeMetrics.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/WindowChromeMetrics.swift`:
- Around line 43-49: The stored properties on SidebarWorkspaceScrollInsets are
incorrectly annotated with nonisolated; remove the nonisolated attribute from
the static stored property workspaceList and from the instance stored properties
top and bottom so they are plain stored properties, leaving nonisolated only on
the computed property total and the static method contentMinHeight if present;
update declarations for workspaceList, top, and bottom accordingly in the
SidebarWorkspaceScrollInsets type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 377134d2-3c0c-4260-9ec5-a64fb4f25128

📥 Commits

Reviewing files that changed from the base of the PR and between 14811e6 and b7b416b.

📒 Files selected for processing (3)
  • Sources/ContentView.swift
  • Sources/WindowChromeMetrics.swift
  • cmuxUITests/WorkspaceSidebarScrollUITests.swift

Comment thread Sources/WindowChromeMetrics.swift Outdated
The sidebar edge-fade restore added top and bottom safe-area chrome to the workspace scroll view, but the document baseline still filled the full viewport. That made decorative chrome count as extra scrollable height, so AppKit exposed a vertical scroller even when one workspace fit.

This introduces one scroll-layout policy for the workspace sidebar: safe-area chrome still owns the titlebar/footer clearance, while the document's fit baseline is the visible viewport minus that chrome. Short workspace lists now fit exactly; long lists still overflow through the same ScrollView.

Constraint: Issue #3555 reproduces on 0.64.1 and NIGHTLY after the sidebar edge-fade update

Constraint: Local tests/builds are disallowed before CI by task instruction

Rejected: Force autohidesScrollers in the resolver | the document would still report overflow and the bug would return under always-show scrollbars

Rejected: Remove bottom clearance entirely | bottom rows could disappear behind footer chrome

Confidence: medium

Scope-risk: narrow

Directive: Keep sidebar decorative scroll chrome and document fit height derived from one policy; do not add independent safe-area spacers without updating the fit baseline

Tested: git diff --check

Not-tested: Local build/UI tests not run per task instruction; remote E2E and PR CI pending
@austinywang
austinywang force-pushed the issue-3555-sidebar-scroller-always-shown branch from b7b416b to acf7eac Compare May 5, 2026 20:30
@austinywang
austinywang merged commit b56c9a1 into main May 5, 2026
22 checks passed
@austinywang austinywang mentioned this pull request May 5, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — acf7eacc Deployed May 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sidebar scroller always appears in sidebar after last update

1 participant