Skip to content

Fix iOS toolbar and terminal picker scroll fade - #6641

Closed
austinywang wants to merge 11 commits into
mainfrom
issue-6633-ios-accessory-bar-scroll-glitch
Closed

austinywang wants to merge 11 commits into
mainfrom
issue-6633-ios-accessory-bar-scroll-glitch

Conversation

@austinywang

@austinywang austinywang commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Fixed the original iOS 26 keyboard accessory toolbar fade: scroll-hosted toolbar keys now use a stable flat keycap appearance, while pinned controls can still use Liquid Glass.
  • Fixed the top-right terminal picker/menu fade with many terminal panes: cached menu row structure is keyed on the stable terminal id, so live terminal title/name churn does not rewrite the native menu's backing row list while it is open and being scrolled.
  • Kept terminal picker labels live by rendering display names from the current id -> name map, so name-only terminal updates do not go stale after avoiding row-list rewrites.
  • Added Swift Testing coverage for terminal picker row identity and live display-name lookup, plus a DEBUG preview fixture knob for creating a tall terminal picker menu in simulator captures.

Root Cause

The fresh report was a separate surface from the accessory toolbar. The top-right terminal picker rows carried both id and name, and the row Equatable conformance compared both values. Terminal names can change while the native iOS 26 menu is open, so SwiftUI treated the same row structure as changed and resynced terminalPickerRows during scroll. That churn disturbed the Liquid Glass-backed menu surface and showed up as the top-right menu fading/glitching.

The fix keeps row-list stability tied to terminal ids and moves current names into a separate live lookup used only for display. Structural add/remove/reorder changes still refresh the cached row list, while name-only churn updates labels without replacing the menu rows.

Evidence

Accessory toolbar before/after evidence from the first fix:

Top-right terminal picker fixed-state evidence with 24 terminals:

Validation

  • python3 scripts/swift_file_length_budget.py
  • git diff --check
  • swift test in Packages/iOS/CmuxMobileShellUI was attempted, but SwiftPM fails before compiling tests because the package resolves as macOS 10.13 while dependencies require macOS 12/14.
  • ios/scripts/reload.sh --tag issue-6633-ios-accessory-bar-scroll-glitch --simulator "iPhone 17" --no-launch
  • Localization audit: parsed ios/cmux/Resources/Localizable.xcstrings; checked changed Swift files for new bare user-facing strings; new generated terminal names use existing mobile.preview.terminalNameFormat with English and Japanese localizations.

Closes #6633

@vercel

vercel Bot commented Jun 23, 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 Jul 4, 2026 8:37am
cmux-staging Building Building Preview, Comment Jul 4, 2026 8:37am

@coderabbitai

coderabbitai Bot commented Jun 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new usesStableScrollableAppearance boolean is added to AccessoryActionButton. Built-in and custom buttons in the scrollable accessory bar are created with this flag set to true. Accessory button styling is consolidated into a new AccessoryButtonAppearance struct. The styling pipeline in TerminalInputTextView uses the flag to bypass Liquid Glass configuration and force isStickyLocked = false on iOS 26, preventing luminance flashing during horizontal scroll.

Changes

Stable Scrollable Appearance for Accessory Bar

Layer / File(s) Summary
AccessoryActionButton contract
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/AccessoryActionButton.swift
Adds stored property usesStableScrollableAppearance: Bool and extends init(item:) to init(item:usesStableScrollableAppearance:) with a default of false. Adds an inline comment documenting the iOS 26 Liquid Glass luminance flashing rationale.
AccessoryButtonAppearance struct
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/AccessoryButtonAppearance.swift
New struct encapsulating accessory button styling with configurable background color and corner radius. Provides configuration(armed:sticky:useLiquidGlass:) method that branches on iOS 26 Liquid Glass availability and produces either glass-style configurations with white foreground and conditional system-blue, or fallback plain configurations with custom background handling, corner radius, and conditional stroke.
Button factory and styling in TerminalInputTextView
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift
Replaces hardcoded styling constants accessoryButtonNormalBackground and accessoryButtonCornerRadius with an instance-based accessoryButtonAppearance object. makeAccessoryButton(for:usesStableScrollableAppearance:) overload and custom button factory forward usesStableScrollableAppearance: true into the button initializer. applyAccessoryButtonStyle reads the flag to select between Liquid Glass and non-Liquid Glass configuration via useLiquidGlass parameter, and gates isStickyLocked assignment so stable-appearance buttons always receive false on iOS 26+.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#5510: Modifies accessory button construction and styling flow in AccessoryActionButton and TerminalInputTextView, directly overlapping with this PR's refactoring of button factories and appearance configuration.

Poem

🐇 Hop, hop — no more flicker on the scroll!
A stable appearance now plays its role,
Liquid glass fades where buttons now dwell,
No luminance thrashing when scrolls rebel.
The keycap sits flat, the rendering tame —
iOS 26 scrolling — this bunny tames the frame! 🎉


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swiftpm Lockfiles ❌ Error PR adds cmux-owned Package.swift files with dependencies (Packages/iOS/CmuxMobileTerminal, CmuxMobileTerminalKit) but omits matching package-local Package.resolved diffs, violating swiftpm-package-... Include package-local Package.resolved files for Packages/iOS/CmuxMobileTerminal and Packages/iOS/CmuxMobileTerminalKit in the PR diff.
Cmux Source Artifacts ❌ Error PR adds 28 local tool artifacts: .claude/commands/ (6 files), .claude/skills/ (18 files), .claude/scheduled_tasks.lock, .agents/skills, .cursor/rules/main.mdc, .coderabbit.yaml. Violates source-con... Remove all 28 artifact files from diff or add narrow .gitignore entries for .claude/commands/, .claude/skills/, .agents/, .cursor/rules/, and .coderabbit.yaml.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR directly addresses issue #6633 by fixing the fade/flicker glitch in scroll-hosted toolbar buttons via the usesStableScrollableAppearance flag and disabling Liquid Glass for scrollable items on iOS 26.
Out of Scope Changes check ✅ Passed All changes—AccessoryActionButton signature update, TerminalInputTextView refactor, and new AccessoryButtonAppearance struct—are directly scoped to fixing the scroll fade issue and refactoring styling logic; no unrelated changes detected.
Cmux Swift Actor Isolation ✅ Passed Changes introduce only MainActor-bound UIView/UIButton subclasses and a pure value struct (AccessoryButtonAppearance) with no Codable/Sendable/Identifiable protocols. No async protocols, problemati...
Cmux Swift Blocking Runtime ✅ Passed No blocking or timing-based synchronization primitives found; PR exclusively uses synchronous UI styling updates with flag-based configuration selection.
Cmux Expensive Synchronous Load ✅ Passed Changes are purely UI styling—no expensive synchronous loads (agent sessions, JSONL, transcripts, directory scans) on main actor or interactive paths detected.
Cmux Cache Substitution Correctness ✅ Passed Changes introduce usesStableScrollableAppearance, a transient non-persisted UI property for iOS 26 button rendering; no cache substitution of persistent/history/undo state occurs.
Cmux No Hacky Sleeps ✅ Passed Custom check scope is TypeScript/JavaScript/shell/build scripts only; PR contains only Swift code changes, which are explicitly covered by separate rule.
Cmux Algorithmic Complexity ✅ Passed PR introduces only styling/appearance refactoring with O(1) operations; no nested loops, rescans, sorts, or algorithmic complexity violations detected.
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns: no DispatchQueue, async/await, Task, Combine, or completion handlers. Changes are purely UI styling (boolean property, struct for button configuration).
Cmux Swift @Concurrent ✅ Passed All changes are synchronous UIKit/UI code with no async functions, no nonisolated async work, no @concurrent annotations, and no actor-isolated operations. The modifications only add lightweight UI...
Cmux Swift File And Package Boundaries ✅ Passed PR satisfies swift-file-package-boundaries rules: new AccessoryButtonAppearance.swift is 45 lines with single responsibility; TerminalInputTextView.swift reduces by 10 lines net despite being overs...
Cmux Swift Logging ✅ Passed No Swift logging violations found. The modified files (AccessoryActionButton.swift, AccessoryButtonAppearance.swift, TerminalInputTextView.swift) contain no print, debugPrint, dump, NSLog, or Logge...
Cmux User-Facing Error Privacy ✅ Passed No user-facing error privacy violations detected. User-visible strings are generic labels; debug code is wrapped in #if DEBUG; no vendor/credentials/internal details exposed.
Cmux Full Internationalization ✅ Passed PR contains no user-facing strings, localization keys, or string catalog changes; only adds internal API parameters and configuration logic for iOS 26 UIKit behavior.
Cmux Swiftui State Layout ✅ Passed All PR changes are in UIKit classes (AccessoryActionButton, TerminalInputTextView extend UIButton/UIView; AccessoryButtonAppearance is a plain struct). No SwiftUI Views, @Observable/@published stat...
Cmux Architecture Rethink ✅ Passed PR introduces a small architectural fix without forbidden patterns: immutable usesStableScrollableAppearance flag on AccessoryActionButton with single owner and invariant (scroll-hosted=true, pin...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed All changed files are iOS UIKit view components (UIButton, UIView subclasses, and a configuration struct), not window/panel/controller declarations. No NSWindow, NSPanel, NSWindowController, SwiftU...
Cmux No Test Or Debug Seam In Production Source ✅ Passed The three production source files modified in this PR (AccessoryActionButton.swift, AccessoryButtonAppearance.swift, TerminalInputTextView.swift) contain no new test seams, #if DEBUG blocks, or tes...
Title check ✅ Passed The title clearly matches the main change: fixing iOS toolbar and terminal picker scroll fading.
Description check ✅ Passed The description is detailed and covers summary, root cause, evidence, and validation, though several template sections are not explicitly filled.
✨ 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-6633-ios-accessory-bar-scroll-glitch

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.

@greptile-apps

greptile-apps Bot commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two separate iOS 26 Liquid Glass scroll-fade regressions: the keyboard accessory toolbar (scroll-hosted keys now use flat keycap styling) and the top-right terminal picker menu (row-list identity is now keyed on stable terminal ids, with live name display layered on top separately).

  • Toolbar fix: AccessoryButtonAppearance is extracted into its own type; AccessoryActionButton gains a usesStableScrollableAppearance flag that suppresses .glass() / .prominentGlass() for all scroll-hosted keys, while pinned controls retain Liquid Glass.
  • Terminal picker fix: TerminalPickerMenuRow.== is narrowed to id-only, so name churn no longer triggers syncTerminalPickerRows; live names flow through a liveNamesByID dict computed from terminalPickerLiveRows on every render; onChange is changed to observe terminalPickerLiveRowIDs (ids only) rather than the full row list.
  • Tests + debug fixture: Swift Testing coverage for row identity and live-name lookup is added; the WorkspaceDetailDelayedTerminalPreviewView fixture gains a CMUX_UITEST_WORKSPACE_DETAIL_TERMINAL_COUNT environment knob for creating a scrollable picker in simulator captures.

Confidence Score: 5/5

Safe to merge — both fixes address confirmed root causes with no regressions introduced.

The id-only Equatable change is correctly scoped: structural row adds/removes/reorders still trigger a sync because the ID array changes, while name-only churn is cleanly handled by the live namesByID lookup without touching the cached row list. The toolbar appearance change is equally precise — scroll-hosted buttons opt into flat style via a stable per-button flag, pinned controls retain glass, and the ?? true fallback in applyAccessoryButtonStyle safely suppresses glass for any unknown button type. Tests directly exercise the two key invariants.

No files require special attention.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+MenuState.swift Core fix: id-only Equatable conformance on TerminalPickerMenuRow, plus namesByID helper and displayName(liveNamesByID:) for live label rendering without row-list churn.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift onChange observer narrowed to terminalPickerLiveRowIDs; liveNamesByID passed through to menu content; selection label uses fresh liveRows for name resolution.
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/AccessoryButtonAppearance.swift New file extracting button configuration logic into a standalone value type with useLiquidGlass parameter, cleanly separating styling from button lifecycle.
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/AccessoryActionButton.swift Adds usesStableScrollableAppearance flag with safe default=false; sticky-lock border guarded by this flag on iOS 26.
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift Scroll-hosted buttons (built-in and custom) now pass usesStableScrollableAppearance:true; fallback in applyAccessoryButtonStyle uses ?? true (safe suppression for unknown buttons).
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalPickerMenuRowTests.swift New Swift Testing suite verifying id-only equality, structural-change detection, and live displayName lookup — good direct coverage of the fix invariants.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailDelayedTerminalPreviewView.swift Debug-only fixture extended with CMUX_UITEST_WORKSPACE_DETAIL_TERMINAL_COUNT knob and L10n terminal names; entirely inside existing #if os(iOS) && DEBUG guard.

Reviews (7): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

@austinywang austinywang changed the title Fix iOS accessory toolbar scroll fade Fix iOS toolbar and terminal picker scroll fade Jul 3, 2026
@austinywang

Copy link
Copy Markdown
Contributor Author

Update after the fresh top-right-menu report:

  • Confirmed the new symptom is not the same surface as the keyboard accessory toolbar fix already on this branch.
  • Root cause for the top-right terminal picker: TerminalPickerMenuRow equality included the live name, so title/name churn could rewrite the native menu row backing data while the iOS 26 Liquid Glass menu was open and scrollable.
  • Fix: row equality now uses the stable terminal id; structural add/remove/reorder still updates, but name-only churn no longer resyncs the open menu.
  • Added TerminalPickerMenuRowTests to lock the identity behavior and a DEBUG preview env knob (CMUX_UITEST_WORKSPACE_DETAIL_TERMINAL_COUNT) to generate a tall terminal picker for simulator evidence.

Validation on pushed HEAD 6c571e810a:

  • Passed: python3 scripts/swift_file_length_budget.py
  • Passed: git diff --check origin/main...HEAD
  • Passed: ios/scripts/reload.sh --tag issue-6633-ios-accessory-bar-scroll-glitch --simulator "iPhone 17" --no-launch
  • Attempted: swift test in Packages/iOS/CmuxMobileShellUI; blocked before test compilation by existing SwiftPM platform constraints (package resolves as macOS 10.13 while dependencies require macOS 12/14).
  • Localization audit: parsed ios/cmux/Resources/Localizable.xcstrings; checked changed Swift files for bare strings; new generated terminal names use existing localized mobile.preview.terminalNameFormat.

Terminal picker fixed-state simulator evidence with 24 terminals:

@austinywang

Copy link
Copy Markdown
Contributor Author

Follow-up on structured review finding at 81c3255f10:

The first terminal-picker fix made row equality id-only, but review correctly caught that the existing .onChange(of: liveRows) path also used that equality and could leave cached picker labels stale after a terminal rename.

I fixed that by separating structure from display data:

  • Cached terminalPickerRows still provides stable row ids/order for the native menu.
  • .onChange now tracks only live terminal ids, so structural add/remove/reorder refreshes the cached rows.
  • Menu labels and the toolbar accessibility value render from the current live id -> name map, so name-only changes stay visible without rewriting the menu row list.
  • Added stableMenuRowsCanDisplayLiveNames coverage to the row identity regression suite.

Re-validation after this commit:

  • Passed python3 scripts/swift_file_length_budget.py
  • Passed git diff --check
  • Passed ios/scripts/reload.sh --tag issue-6633-ios-accessory-bar-scroll-glitch --simulator "iPhone 17" --no-launch
  • swift test remains blocked by the existing package platform mismatch before tests compile.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

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

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS: menu/accessory bar glitches and fades when scrolling with more than ~5 items

3 participants