Skip to content

iOS: reserve title space for the badged back button - #10790

Open
azooz2003-bit wants to merge 1 commit into
mainfrom
fix-ios-badged-back-reserve
Open

azooz2003-bit wants to merge 1 commit into
mainfrom
fix-ios-badged-back-reserve

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Aug 26, 2026 •

Copy link
Copy Markdown
Collaborator

Dogfood on an iOS 27.0 simulator (build containing #10646, verified by ancestry of its CMUX_GIT_SHA): opening a workspace occasionally showed the terminal picker inside the More "…" from the first frame, and reopening the same workspace cleared it. The screenshots carried the tell: every broken shot had an unread badge on the back button, and the healthy after-reopen shot did not (reopening cleared the unread).

Mechanism: MobileLeadingToolbarTitleWidth reserves a flat backButtonReserve = 44 for the back control, but WorkspaceBackButton folds the other-workspace unread count into the button (chevron + 5pt spacing + an 18pt-minimum mono badge circle, wider for "99+"), rendering ~24-32pt wider than the bare chevron. With a badge present the title over-claimed by exactly that amount, the bar over-committed on the first pass, and the picker folded into More. A collapse born on the first pass never emits the attach-then-detach signature that the #10620 recovery ratchet watches, so it stuck until remount, which is why it read as rare and self-healing.

Fix: the leading reserve now scales with the same unreadCount the button renders: +24pt for a badge, +32pt for the "99+" cap. Deterministic, no measurement or timing involved. Regression tests pin both deltas and the no-back-button case.

Verified on an isolated iOS 27.0 simulator with a staged unread badge (second workspace receiving output): badged mount keeps back, title, Changes, and picker all in the bar.

No user-facing strings changed (localization audit: none needed). HIG reference: Navigation bars (https://developer.apple.com/design/human-interface-guidelines/navigation-bars), controls remain visible rather than overflowing.

🤖 Generated with Claude Code


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

Summary by CodeRabbit

  • Bug Fixes

    • Improved iOS workspace title layout when the back button displays unread-count badges.
    • Added wider spacing for badges showing counts above 99, preventing title overlap.
  • Tests

    • Added coverage for regular and wide unread badges.
    • Verified no extra space is reserved when a back button is unavailable.

Opening a workspace while another workspace held unread folded the
terminal picker into the More menu from the first layout pass (iOS 27
simulator dogfood; sticky until remount because a birth collapse never
produces the attach-then-detach signature the recovery ratchet watches).
The width model reserved a flat 44pt for the back button, but with
unread elsewhere WorkspaceBackButton renders chevron + spacing + a mono
badge circle, ~24pt wider (~32pt at "99+"), and that over-claim was
exactly the fold margin. Reopening cleared the unread, removed the
badge, and hid the bug, which made it look intermittent. The reserve now
scales with the badge deterministically from the same unreadCount the
button renders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d886c567-343f-48b2-859f-cffaf6054f16

📥 Commits

Reviewing files that changed from the base of the PR and between 1fbc8c8 and c9f3005.

📒 Files selected for processing (6)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileLeadingToolbarTitleWidth.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenu.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenuValue.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileLeadingToolbarTitleWidthTests.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/WorkspaceTitleMenuValueTests.swift

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The iOS workspace title width calculation now reserves space for back-button unread badges. Counts above 99 use a wider reserve. The unread count flows from workspace detail data through WorkspaceTitleMenuValue into title fitting, with tests for badge and no-back-button cases.

Changes

Unread Badge Width Handling

Layer / File(s) Summary
Badge-aware width reservation
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileLeadingToolbarTitleWidth.swift
MobileLeadingToolbarTitleWidth stores the unread count and applies standard or wide badge reserves when a back button exists.
Workspace title integration and validation
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenuValue.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenu.swift, Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/*
Workspace title data carries the unread count into title fitting. Tests cover regular badges, wide badges, and counts without a back button.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to c9f30

The change reserves additional title space when the back button shows an unread badge, preventing toolbar controls from being folded into More. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: reserving additional title space for a back button with an unread badge.
Description check ✅ Passed The description clearly explains the problem, cause, fix, and verification. It includes detailed simulator testing and regression-test coverage. The template's Demo Video, Review Trigger, and Checklis…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: The production diff adds only Int state and pure CGFloat width calculations to existing value types. WorkspaceDetailView and WorkspaceTitleMenu are SwiftUI view types, which the rule all…
Cmux Swift Blocking Runtime ✅ Passed PASS: The production diff only adds unread-count data flow and deterministic width arithmetic in MobileLeadingToolbarTitleWidth. It introduces no semaphore, blocking wait, sleep, delayed dispatch, p…
Cmux Browser Automation Off-Main ✅ Passed PASS: The pull request changes only iOS toolbar title-width and related tests. The exact diff contains no browser socket commands, WebKit waits, main processV2Command routing, socketWorkerMethods,…
Cmux Expensive Synchronous Load ✅ Passed PASS. The production diff only adds an Int field, propagates backButtonConfiguration?.unreadCount, and performs constant width arithmetic in MobileLeadingToolbarTitleWidth. It adds no `Restorabl…
Cmux Cache Substitution Correctness ✅ Passed PASS — The diff adds unread-count data only to transient SwiftUI toolbar layout. WorkspaceDetailView derives it from the current store.workspaces collection and passes it to `MobileLeadingToolbarT…
Cmux No Hacky Sleeps ✅ Passed PASS. The pull request changes six Swift files only, including Swift production code and Swift tests. The rule applies to TypeScript, JavaScript, shell, and non-Swift build/runtime scripts; Swift timi…
Cmux Algorithmic Complexity ✅ Passed PASS: The production changes add only constant-time badge-width arithmetic and an integer field propagation. MobileLeadingToolbarTitleWidth.leadingReserve uses fixed branches and constants; it does …
Cmux Swift Concurrency ✅ Passed PASS: The commit adds only synchronous width calculations, stored values, and tests. The complete Swift diff adds no DispatchQueue, Combine, completion-handler, or fire-and-forget Task patterns. Exist…
Cmux Swift @Concurrent ✅ Passed PASS: The diff adds only synchronous width calculations, stored Int state, and synchronous property plumbing. It introduces no async, await, nonisolated async, or @concurrent declaration or …
Cmux Swift Package Boundaries ✅ Passed PASS: The production diff stays inside the existing Packages/iOS/CmuxMobileShellUI SwiftPM target. It changes MobileLeadingToolbarTitleWidth, WorkspaceTitleMenuValue, WorkspaceTitleMenu, and `…
Cmux Swiftpm Lockfiles ✅ Passed PASS. The PR diff changes only six Swift source and test files under Packages/iOS/CmuxMobileShellUI; it does not change Package.swift, Package.resolved, .gitignore, or Xcode project package re…
Cmux Swift Logging ✅ Passed PASS. The pull request adds no print, debugPrint, dump, NSLog, ad hoc file logging, stdout/stderr logging, or Logger declarations. The only NSLog in a changed runtime file existed unchange…
Cmux User-Facing Error Privacy ✅ Passed PASS: The PR changes title-width layout math and unread-count state only. The production diff adds no user-facing error, alert, command output, API error body, or recovery copy. Added comments and reg…
Cmux Full Internationalization ✅ Passed PASS: The production diff changes width calculation and unread-count data flow only. It adds no user-facing Swift text, localization key, string catalog entry, web message, metadata, or rendered copy.…
Cmux Swiftui State Layout ✅ Passed PASS — The diff adds only immutable backButtonUnreadCount data, constants, and pure width calculation. It does not add ObservableObject, @Published, GeometryReader, lazy/list row store referen…
Cmux Architecture Rethink ✅ Passed PASS: This is a small local correctness fix. The new immutable backButtonUnreadCount snapshot comes from WorkspaceBackButtonConfiguration.unreadCount, which is also the value rendered by `Workspac…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The PR changes title-width calculation, workspace menu value plumbing, and tests. The diff adds no NSWindow, NSPanel, NSWindowController, Window, WindowGroup, window identifier, Cmd+W, or close-…
Cmux Source Artifacts ✅ Passed All six changed paths are intentional Swift source or test files under the existing iOS source and test directories. The diff adds badge-aware width logic and regression tests only. No logs, screensho…
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS. The production diff adds no #if DEBUG or other test-build guard, no debug/test-seam-named member, and no visibility-widening wrapper accessor. backButtonUnreadCount is normal internal produc…
Cmux No Ambient Global State ✅ Passed PASS. The production diff adds no ambient global state. backButtonUnreadCount is an instance property on MobileLeadingToolbarTitleWidth and WorkspaceTitleMenuValue; leadingReserve is a private…
Full details: Description check

Explanation

The description clearly explains the problem, cause, fix, and verification. It includes detailed simulator testing and regression-test coverage. The template's Demo Video, Review Trigger, and Checklist sections are not included, but the core description is complete.

Full details: Cmux Swift Actor Isolation

Explanation

PASS: The production diff adds only Int state and pure CGFloat width calculations to existing value types. WorkspaceDetailView and WorkspaceTitleMenu are SwiftUI view types, which the rule allows on MainActor. The new field is read only from the view’s toolbar construction; no async service protocol, Sendable reference type, or background store access was introduced. Both changed value declarations already existed without explicit isolation in HEAD^, and the diff does not worsen that existing debt.

Full details: Cmux Swift Blocking Runtime

Explanation

PASS: The production diff only adds unread-count data flow and deterministic width arithmetic in MobileLeadingToolbarTitleWidth. It introduces no semaphore, blocking wait, sleep, delayed dispatch, polling, main-queue sync, timer, or manual lock. The added tests are deterministic scaffolding, which the rule allows.

Full details: Cmux Browser Automation Off-Main

Explanation

PASS: The pull request changes only iOS toolbar title-width and related tests. The exact diff contains no browser socket commands, WebKit waits, main processV2Command routing, socketWorkerMethods, worker browser router, or policy-test changes. Therefore the browser automation off-main check is not applicable.

Full details: Cmux Expensive Synchronous Load

Explanation

PASS. The production diff only adds an Int field, propagates backButtonConfiguration?.unreadCount, and performs constant width arithmetic in MobileLeadingToolbarTitleWidth. It adds no RestorableAgentSessionIndex.load(), agent-store access, file read, JSON decode, directory scan, syscall loop, or background-loader change. The only matching stat text in the changed file is the existing terminalArtifactStat call and is not added by the diff. The new SwiftUI path therefore does not introduce an expensive synchronous agent-history load.

Full details: Cmux Cache Substitution Correctness

Explanation

PASS — The diff adds unread-count data only to transient SwiftUI toolbar layout. WorkspaceDetailView derives it from the current store.workspaces collection and passes it to MobileLeadingToolbarTitleWidth; the value only changes the title width cap. The diff introduces no persistence, history, undo, or snapshot consumer, and no fresh authoritative read is replaced by a cache. This matches the rule's allowed case for transient, non-persisted UI hints.

Full details: Cmux No Hacky Sleeps

Explanation

PASS. The pull request changes six Swift files only, including Swift production code and Swift tests. The rule applies to TypeScript, JavaScript, shell, and non-Swift build/runtime scripts; Swift timing and blocking primitives are explicitly covered by a separate check. The diff introduces no covered non-Swift delay or timer changes.

Full details: Cmux Algorithmic Complexity

Explanation

PASS: The production changes add only constant-time badge-width arithmetic and an integer field propagation. MobileLeadingToolbarTitleWidth.leadingReserve uses fixed branches and constants; it does not scan or sort a collection. The changed WorkspaceDetailView line only forwards backButtonConfiguration?.unreadCount. Existing compactMap and reduce handling of the small toolbar-item list is unchanged. The added collection-related code is test-only and uses fixed test inputs. No stated algorithmic-complexity failure condition is introduced.

Full details: Cmux Swift Concurrency

Explanation

PASS: The commit adds only synchronous width calculations, stored values, and tests. The complete Swift diff adds no DispatchQueue, Combine, completion-handler, or fire-and-forget Task patterns. Existing async code in WorkspaceDetailView is unchanged; the production view change only passes backButtonConfiguration?.unreadCount ?? 0.

Full details: Cmux Swift `@Concurrent`

Explanation

PASS: The diff adds only synchronous width calculations, stored Int state, and synchronous property plumbing. It introduces no async, await, nonisolated async, or @concurrent declaration or call site. The changed WorkspaceDetailView path reads backButtonConfiguration?.unreadCount while constructing a SwiftUI value and performs no heavy asynchronous work. Existing Task usages are unchanged. Therefore, the Swift concurrency annotation rule has no failure condition to report.

Full details: Cmux Swift Package Boundaries

Explanation

PASS: The production diff stays inside the existing Packages/iOS/CmuxMobileShellUI SwiftPM target. It changes MobileLeadingToolbarTitleWidth, WorkspaceTitleMenuValue, WorkspaceTitleMenu, and WorkspaceDetailView, all of which are iOS SwiftUI layout and composition code. The new logic only calculates title width from the back-button badge count. It is not provider, protocol, persistence, or cross-surface domain logic. The package manifest already defines CmuxMobileShellUI and its test target, and the added tests remain inside that package. This matches the rule's allowed small UI and app-composition cases.

Full details: Cmux Swiftpm Lockfiles

Explanation

PASS. The PR diff changes only six Swift source and test files under Packages/iOS/CmuxMobileShellUI; it does not change Package.swift, Package.resolved, .gitignore, or Xcode project package references. The package manifest has no changed dependency declarations. Its tracked package-local Package.resolved is not ignored, and the package .gitignore contains only .build/. No SwiftPM lockfile rule failure applies.

Full details: Cmux Swift Logging

Explanation

PASS. The pull request adds no print, debugPrint, dump, NSLog, ad hoc file logging, stdout/stderr logging, or Logger declarations. The only NSLog in a changed runtime file existed unchanged in the parent revision. The added Swift code only changes width calculation and tests.

Full details: Cmux User-Facing Error Privacy

Explanation

PASS: The PR changes title-width layout math and unread-count state only. The production diff adds no user-facing error, alert, command output, API error body, or recovery copy. Added comments and regression tests are explicitly allowed by the rule. The new values are used only to calculate leadingReserve and are not used to expose vendor names, provider details, raw messages, identifiers, credentials, tokens, headers, or payloads.

Full details: Cmux Full Internationalization

Explanation

PASS: The production diff changes width calculation and unread-count data flow only. It adds no user-facing Swift text, localization key, string catalog entry, web message, metadata, or rendered copy. The only added "99+" occurrences are developer comments. Tests and fixture updates are explicitly allowed by the rule.

Full details: Cmux Swiftui State Layout

Explanation

PASS — The diff adds only immutable backButtonUnreadCount data, constants, and pure width calculation. It does not add ObservableObject, @Published, GeometryReader, lazy/list row store references, or render-time state mutation. Existing @Bindable, @State, and .onGeometryChange code in WorkspaceDetailView is unchanged. The new workspaceTitleToolbarMenu code only snapshots backButtonConfiguration?.unreadCount into WorkspaceTitleMenuValue.

Full details: Cmux Architecture Rethink

Explanation

PASS: This is a small local correctness fix. The new immutable backButtonUnreadCount snapshot comes from WorkspaceBackButtonConfiguration.unreadCount, which is also the value rendered by WorkspaceBackButton. The width model applies a deterministic reserve for the existing badge states. The diff adds no timing repair, polling, locks, observers, mutable state owner, duplicate action path, or split lifecycle ownership. Regression tests cover badged, wide-badged, and no-back-button cases.

Full details: Cmux Swift Auxiliary Window Close Shortcuts

Explanation

PASS: The PR changes title-width calculation, workspace menu value plumbing, and tests. The diff adds no NSWindow, NSPanel, NSWindowController, Window, WindowGroup, window identifier, Cmd+W, or close-routing code. WorkspaceTitleMenu is an allowed menu/view case. The repository lint also passes and reports 35 registered cmux window identifiers; no auxiliary-window close-shortcut violation is introduced.

Full details: Cmux Source Artifacts

Explanation

All six changed paths are intentional Swift source or test files under the existing iOS source and test directories. The diff adds badge-aware width logic and regression tests only. No logs, screenshots, recordings, temporary or cache directories, build output, dependency checkout, package-manager download, or broad artifact directory appears in the changed paths.

Full details: Cmux No Test Or Debug Seam In Production Source

Explanation

PASS. The production diff adds no #if DEBUG or other test-build guard, no debug/test-seam-named member, and no visibility-widening wrapper accessor. backButtonUnreadCount is normal internal product state with production callers: WorkspaceDetailView reads backButtonConfiguration?.unreadCount, and WorkspaceTitleMenu passes it to the production width calculation. The new leadingReserve helper is private. Tests use @testable import and do not require a production seam.

Full details: Cmux No Ambient Global State

Explanation

PASS. The production diff adds no ambient global state. backButtonUnreadCount is an instance property on MobileLeadingToolbarTitleWidth and WorkspaceTitleMenuValue; leadingReserve is a private instance helper. The two new static let width values are constants, which the rule explicitly allows. The remaining changes pass the value through WorkspaceDetailView and WorkspaceTitleMenu. No new top-level function, mutable global, static-helper namespace, or singleton was introduced.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ios-badged-back-reserve

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.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Verification extended per dogfood request: the badged mount now proven on BOTH runtimes on isolated simulators. iOS 27.0 (iPhone 17): badged terminal mount keeps back+badge, title, Changes, picker in the bar. iOS 26.5 (iPhone 17): badged mount into a browser-restoring workspace (the hardest combination: badge + restore + two trailing items) keeps the full bar with the workspace-name/tab-subtitle pill. No More menu in either case.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Heads-up: #10898 cherry-picked this commit (c9f3005) and then retiered the constants for the new capsule badge shape it introduces (the capsule is wider than the old clipped circle at two-digit and 99+ counts, so 24/32 under-reserves there). If 10898 merges first, this PR is superseded and can close; if this merges first, 10898 will carry the retier as a small conflict resolution.

@teamleaderleo teamleaderleo added area: ios The iOS app and mobile clients S3: minor Wrong behavior with a workaround labels Sep 30, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator

This remains live while #10898 is open; #10898 cherry-picked this fix and retiered the badge constants, so the two PRs should be resolved together.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ios The iOS app and mobile clients S3: minor Wrong behavior with a workaround

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants