Repository navigation
ios: unread-workspace count badge on the back button - #6350
Conversation
On the pushed workspace detail (compact stack), show how many OTHER workspaces have unread activity as an iMessage-style accent pill right beside the system back button, so you can see at a glance what's waiting back in the list. Hidden at zero; caps the glyphs at 99+ while VoiceOver still hears the exact count. - New WorkspaceBackUnreadBadge view (reuses Color.accentColor, like WorkspaceUnreadDot). - Added only in the compact `.navigationDestination` (not workspaceDestination itself, which the iPad split layout also uses and has no back button); the badge coexists with the system back button, so swipe-back is preserved. - Count = workspaces with hasUnread, excluding the one you're viewing. - Localized accessibility label (en + ja) in the app string catalog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds ChangesWorkspace Back Button and Unread State
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (3 errors, 1 warning)
✅ Passed checks (18 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
The badge insert landed between the @ViewBuilder attribute and workspaceDestination, attaching it to the Int-returning unreadWorkspaceCount (error: 'Int' does not conform to 'View'). Move @ViewBuilder back onto workspaceDestination. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
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
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackUnreadBadge.swift`:
- Around line 32-39: The accessibilityLabel property uses a single localization
key that does not handle singular versus plural forms, causing grammatically
incorrect output when count equals 1 (e.g., "1 unread workspaces"). Modify the
accessibilityLabel computed property to conditionally select localization keys
based on the count value: when count == 1 use a singular key like
"mobile.workspace.backUnreadCountFormat.one" with default value "1 unread
workspace", and otherwise use the plural key
"mobile.workspace.backUnreadCountFormat.other" with the existing default value
"%d unread workspaces". Then add both the .one and .other entries to the
Localizable.xcstrings file in ios/cmux/Resources/ with appropriate singular and
plural strings.
🪄 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: fd686226-766a-455f-97d3-e674c7fb5158
📒 Files selected for processing (3)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackUnreadBadge.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swiftios/cmux/Resources/Localizable.xcstrings
| "mobile.workspace.backUnreadCountFormat": { | ||
| "extractionState": "manual", | ||
| "localizations": { | ||
| "en": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "%d unread workspaces" | ||
| } | ||
| }, | ||
| "ja": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "未読のワークスペース %d 件" | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The format string
"%d unread workspaces" has no plural variation, so when count is exactly 1 VoiceOver will announce "1 unread workspaces" — grammatically incorrect in English and likely in Japanese too. Xcode string catalogs support plural rules via a variations / plural block that .xcstrings handles natively.
| "mobile.workspace.backUnreadCountFormat": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "%d unread workspaces" | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "未読のワークスペース %d 件" | |
| } | |
| } | |
| } | |
| } | |
| "mobile.workspace.backUnreadCountFormat": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "variations": { | |
| "plural": { | |
| "one": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "%d unread workspace" | |
| } | |
| }, | |
| "other": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "%d unread workspaces" | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "未読のワークスペース %d 件" | |
| } | |
| } | |
| } | |
| } |
Rule Used: Flag production user-facing text that is not fully... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…al color)
Per dogfood: the count was a separate pill in its own button and tinted blue.
Now it's a single custom back control.
- WorkspaceBackButton (renamed from WorkspaceBackUnreadBadge): chevron + count in
one button ("‹ 3"), primary label color (white on the dark bar / black on
light), no accent-blue pill. Just the chevron when nothing is unread; the
button widens to fit the count.
- Replace the system back button with it (navigationBarBackButtonHidden) in the
compact navigationDestination; pop via compactNavigationPath.
- InteractiveSwipeBackEnabler restores the edge swipe-back that hiding the system
button disables, gated so it only begins when there's a screen to pop.
- Localized "Back" label (en + ja).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per dogfood: bring back a circle background for the count (but monochrome, not blue) and shrink the number. The chevron stays primary; the count is caption2 on a `.primary`-filled circle with a `.systemBackground` numeral, so it reads as a white circle / dark number on the dark bar and inverts on a light bar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ios/cmux/Resources/Localizable.xcstrings (1)
6396-6411:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSplit unread-count localization into singular/plural keys.
mobile.workspace.backUnreadCountFormatis a single format string, so count1becomes “1 unread workspaces” in English. Add.oneand.otherkeys and select by count inWorkspaceBackButton(Line 42-47).💡 Suggested catalog shape
- "mobile.workspace.backUnreadCountFormat": { ... "%d unread workspaces" ... } + "mobile.workspace.backUnreadCountFormat.one": { + "extractionState": "manual", + "localizations": { + "en": { "stringUnit": { "state": "translated", "value": "1 unread workspace" } }, + "ja": { "stringUnit": { "state": "translated", "value": "未読のワークスペース 1 件" } } + } + }, + "mobile.workspace.backUnreadCountFormat.other": { + "extractionState": "manual", + "localizations": { + "en": { "stringUnit": { "state": "translated", "value": "%d unread workspaces" } }, + "ja": { "stringUnit": { "state": "translated", "value": "未読のワークスペース %d 件" } } + } + }Based on learnings: prefer
.one/.otherkeys for unread-count pluralization in Swift localization; this avoids singular/plural mismatch in user-facing strings. As per coding guidelines: full internationalization must be enforced for production user-facing text.🤖 Prompt for 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. In `@ios/cmux/Resources/Localizable.xcstrings` around lines 6396 - 6411, The localization key mobile.workspace.backUnreadCountFormat uses a single format string that produces grammatically incorrect output for count 1 (showing "1 unread workspaces" instead of "1 unread workspace"). Split this key into plural-form variants by creating mobile.workspace.backUnreadCountFormat.one with a singular value and mobile.workspace.backUnreadCountFormat.other with the plural value, then update the WorkspaceBackButton component (around Line 42-47) to select the appropriate key based on whether the count equals 1 or is greater than 1.Sources: Coding guidelines, Learnings
🤖 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.
Outside diff comments:
In `@ios/cmux/Resources/Localizable.xcstrings`:
- Around line 6396-6411: The localization key
mobile.workspace.backUnreadCountFormat uses a single format string that produces
grammatically incorrect output for count 1 (showing "1 unread workspaces"
instead of "1 unread workspace"). Split this key into plural-form variants by
creating mobile.workspace.backUnreadCountFormat.one with a singular value and
mobile.workspace.backUnreadCountFormat.other with the plural value, then update
the WorkspaceBackButton component (around Line 42-47) to select the appropriate
key based on whether the count equals 1 or is greater than 1.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 02ea2116-4982-4a07-8e03-3216e83bc0ec
📒 Files selected for processing (3)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swiftios/cmux/Resources/Localizable.xcstrings
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes 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 a2ad943. Configure here.
| ), | ||
| unreadCount | ||
| ) | ||
| return "\(back), \(unread)" |
There was a problem hiding this comment.
Wrong singular unread label
Low Severity
The new back-button accessibility label always formats the unread suffix with %d unread workspaces, so VoiceOver announces grammatically wrong English when exactly one other workspace is unread (for example, “Back, 1 unread workspaces”).
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a2ad943. Configure here.
| func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | ||
| (navigationController?.viewControllers.count ?? 0) > 1 | ||
| } |
There was a problem hiding this comment.
gestureRecognizerShouldBegin only checks the view-controller count, not whether a push/pop transition is already in progress. UIKit's own delegate for interactivePopGestureRecognizer also gates on the absence of an active transition coordinator; without that check the gesture can begin while a programmatic push is animating (e.g. a deep-link push lands and the user immediately swipes), leaving the navigation controller in an inconsistent state and potentially causing a crash or a corrupted stack.
| func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | |
| (navigationController?.viewControllers.count ?? 0) > 1 | |
| } | |
| func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | |
| guard (navigationController?.viewControllers.count ?? 0) > 1 else { return false } | |
| // Prevent initiating a pop while a push/pop transition is already | |
| // animating; without this guard the nav stack can end up in an | |
| // inconsistent state if a deep-link push lands and the user swipes | |
| // at the same moment. | |
| return navigationController?.transitionCoordinator == nil | |
| } |
- WorkspaceBackButton count font: bold -> semibold (less heavy). - openWorkspace now sends a read receipt: when the Mac supports read-state actions and the workspace is unread, mark it read on open (like opening a thread). This drops it from the unread list and the back-button count. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift (1)
46-56:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse pluralized localization keys for the unread-count accessibility label.
At Line 50,
mobile.workspace.backUnreadCountFormatis a single format string, sounreadCount == 1will produce incorrect singular copy in English (and weak plural handling for other locales). Please switch to plural-aware keys (e.g.,.one/.other) and select by count.Based on learnings, pluralized unread-count strings in Swift should use ICU-style
.oneand.otherkeys (for example,statusMenu.unreadCount.one/.other) to ensure correct localization behavior.🤖 Prompt for 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. In `@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift` around lines 46 - 56, The accessibilityLabel computed property uses a single format string `mobile.workspace.backUnreadCountFormat` that cannot properly handle singular versus plural forms. Replace the current format string lookup with plural-aware localization keys by checking the unreadCount value and selecting between a `.one` key for singular (when unreadCount equals 1) and an `.other` key for plural (when unreadCount is greater than 1), similar to ICU-style pluralization patterns like `mobile.workspace.backUnreadCount.one` and `mobile.workspace.backUnreadCount.other`. Update the String formatting logic to use the appropriate plural key based on the unreadCount comparison before formatting.Sources: Coding guidelines, Learnings
🤖 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.
Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift`:
- Around line 46-56: The accessibilityLabel computed property uses a single
format string `mobile.workspace.backUnreadCountFormat` that cannot properly
handle singular versus plural forms. Replace the current format string lookup
with plural-aware localization keys by checking the unreadCount value and
selecting between a `.one` key for singular (when unreadCount equals 1) and an
`.other` key for plural (when unreadCount is greater than 1), similar to
ICU-style pluralization patterns like `mobile.workspace.backUnreadCount.one` and
`mobile.workspace.backUnreadCount.other`. Update the String formatting logic to
use the appropriate plural key based on the unreadCount comparison before
formatting.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ece47372-9db6-4e5c-8d6a-e6de76da9f97
📒 Files selected for processing (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift (1)
46-58:⚠️ Potential issue | 🟡 MinorPluralization format string will produce grammatically incorrect singular case.
The format string
"%d unread workspaces"will read "1 unread workspaces" instead of "1 unread workspace" for the singular case. TheL10n.string()wrapper andString(format:)approach are acceptable per codebase convention, but the xcstrings entry formobile.workspace.backUnreadCountFormatshould use ICU-style plural variants (.one and .other keys) to handle singular/plural forms correctly across all locales—or update the format string to handle the singular case explicitly.🤖 Prompt for 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. In `@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift` around lines 46 - 58, The accessibilityLabel property uses a format string that doesn't handle singular/plural forms correctly for the unread count. The L10n localization key mobile.workspace.backUnreadCountFormat needs to be updated in the xcstrings file to use ICU-style plural variants with .one and .other keys instead of a single format string, so that singular cases (1 unread workspace) and plural cases (2+ unread workspaces) are grammatically correct. This allows the String(format:) call to properly select the correct plural form based on the unreadCount value.Sources: Coding guidelines, Learnings
🤖 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
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 2624-2626: Remove the `workspace?.hasUnread == true` condition
from the if statement in the setWorkspaceUnread call. The cached hasUnread state
should not gate the mark_read operation as it can be stale and cause the
read-receipt write to be silently skipped. Keep only the
`supportsWorkspaceReadStateActions` check as the precondition for calling
setWorkspaceUnread with id and false, ensuring the mark_read operation always
executes when the capability exists.
---
Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift`:
- Around line 46-58: The accessibilityLabel property uses a format string that
doesn't handle singular/plural forms correctly for the unread count. The L10n
localization key mobile.workspace.backUnreadCountFormat needs to be updated in
the xcstrings file to use ICU-style plural variants with .one and .other keys
instead of a single format string, so that singular cases (1 unread workspace)
and plural cases (2+ unread workspaces) are grammatically correct. This allows
the String(format:) call to properly select the correct plural form based on the
unreadCount value.
🪄 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: 24e8c0fe-285b-46af-bf66-24f5a7dcacfa
📒 Files selected for processing (2)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift
| if supportsWorkspaceReadStateActions, workspace?.hasUnread == true { | ||
| await setWorkspaceUnread(id: id, false) | ||
| } |
There was a problem hiding this comment.
Do not gate mark_read on cached hasUnread state.
On Line 2624, using workspace?.hasUnread == true as a precondition can silently skip the authoritative read-receipt write when local state is stale. In this flow, opening should issue mark_read whenever capability exists.
Suggested fix
- if supportsWorkspaceReadStateActions, workspace?.hasUnread == true {
+ if supportsWorkspaceReadStateActions {
await setWorkspaceUnread(id: id, false)
}As per coding guidelines, “fail when the diff swaps a fresh authoritative read for a cached or opportunistic value in a persistence path without handling cold/stale caches.”
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if supportsWorkspaceReadStateActions, workspace?.hasUnread == true { | |
| await setWorkspaceUnread(id: id, false) | |
| } | |
| if supportsWorkspaceReadStateActions { | |
| await setWorkspaceUnread(id: id, false) | |
| } |
🤖 Prompt for 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.
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`
around lines 2624 - 2626, Remove the `workspace?.hasUnread == true` condition
from the if statement in the setWorkspaceUnread call. The cached hasUnread state
should not gate the mark_read operation as it can be stale and cause the
read-receipt write to be silently skipped. Keep only the
`supportsWorkspaceReadStateActions` check as the precondition for calling
setWorkspaceUnread with id and false, ensuring the mark_read operation always
executes when the capability exists.
Source: Coding guidelines
openWorkspace's read-receipt block pushed the file 8 lines over its budget (5566 vs 5558). The file is already large; accept the small known debt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| final class GestureHostController: UIViewController, UIGestureRecognizerDelegate { | ||
| override func didMove(toParent parent: UIViewController?) { | ||
| super.didMove(toParent: parent) | ||
| navigationController?.interactivePopGestureRecognizer?.delegate = self | ||
| } | ||
|
|
||
| func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | ||
| (navigationController?.viewControllers.count ?? 0) > 1 | ||
| } | ||
| } |
There was a problem hiding this comment.
Gesture delegate not restored on pop, breaking root-list swipe guard
UINavigationController is both the action target and the initial delegate of interactivePopGestureRecognizer. When GestureHostController moves to a parent it replaces that delegate with itself, but didMove(toParent: nil) (called during the pop) just re-sets the delegate to self once more before the VC deallocates. Since delegate is a weak reference, it becomes nil once the VC is released. After returning to the root list the gesture recognizer has no delegate, so gestureRecognizerShouldBegin defaults to true and the gesture can begin on a screen with nothing to pop — leading to a visual glitch or an undefined-state pop attempt.
| final class GestureHostController: UIViewController, UIGestureRecognizerDelegate { | |
| override func didMove(toParent parent: UIViewController?) { | |
| super.didMove(toParent: parent) | |
| navigationController?.interactivePopGestureRecognizer?.delegate = self | |
| } | |
| func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | |
| (navigationController?.viewControllers.count ?? 0) > 1 | |
| } | |
| } | |
| final class GestureHostController: UIViewController, UIGestureRecognizerDelegate { | |
| private weak var previousDelegate: UIGestureRecognizerDelegate? | |
| override func didMove(toParent parent: UIViewController?) { | |
| super.didMove(toParent: parent) | |
| if parent != nil { | |
| let recognizer = navigationController?.interactivePopGestureRecognizer | |
| previousDelegate = recognizer?.delegate | |
| recognizer?.delegate = self | |
| } else { | |
| navigationController?.interactivePopGestureRecognizer?.delegate = previousDelegate | |
| previousDelegate = nil | |
| } | |
| } | |
| func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | |
| (navigationController?.viewControllers.count ?? 0) > 1 | |
| } | |
| } |
…r + ios chat top-level) - v1 debug/test control dispatch cut over to ControlCommandCoordinator (manaflow-ai#6343) - iOS workspace read/unread from terminal menu, chat button promoted (manaflow-ai#6362) - iOS unread-workspace count badge on back button (manaflow-ai#6350) Adapter: stub v2DebugSidebarVisible / v2DebugIsTerminalFocused / v2DebugSimulateTerminalFileDrop call sites with .err(unimplemented). Bodies moved into ControlCommandCoordinator upstream; v2 ctl path not used by cmux_term workflow.
Merge brings in the back-button unread badge (#6350) and mark read/unread + chat-top-level toolbar (#6362). Resolved the swift-length-budget conflict to a single WorkspaceDetailView entry. Also in this branch update: - Add "Rename Workspace" to the terminal-icon picker menu (gated on supportsWorkspaceActions), presenting the shared WorkspaceRenameSheet and forwarding the new name via store.renameWorkspace. Sheet attached across the terminal / chat / browser panes so it works from any mode. - Increase the glass title pill's vertical padding (5 -> 9) so its glass background height matches the back button and other bar-button glass items. Padding-based (not a fixed height) so the multi-line chat header pill grows. - Bump WorkspaceDetailView length budget to 839. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>


What
On the pushed workspace detail screen (compact / iPhone), show how many other workspaces have unread activity as a small accent pill right next to the system back button, so you can see at a glance what's waiting back in the list.
How
WorkspaceBackUnreadBadgeview: an iMessage-styleColor.accentColorpill with the count, reusing the same accent asWorkspaceUnreadDot. Hidden at zero; caps the glyphs at99+while VoiceOver hears the exact count..navigationDestinationclosure, not inworkspaceDestinationitself (the iPad split layout reuses that and has no back button). It's atopBarLeadingitem that coexists with the system back button, so the back chevron and the swipe-back gesture are preserved.store.workspaceswithhasUnread, excluding the workspace you're currently viewing.mobile.workspace.backUnreadCountFormat(en + ja) added to the app string catalog.Split out from the liquid-glass-header work (#6300) per request, as its own PR. Independent files (
WorkspaceShellView, new badge view, catalog), no overlap with that PR.Verification
dev.cmux.ios.unread) compiled clean and installed to a physical iPhone via the cloud builder; macOS app rebuilt on the same tag.Localization audit: one new user-facing string (the accessibility label) added with en + ja; the badge glyphs are a number (not localizable). No other user-facing strings changed.
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Note
Low Risk
iOS-only navigation and read-state UX; Mac sync is gated on existing capability flags and only fires for unread workspaces.
Overview
On iPhone compact navigation, the workspace detail screen replaces the system back button with a custom
WorkspaceBackButtonthat shows a chevron plus how many other workspaces are unread (e.g. “‹ 3”), capped at 99+ with full counts in VoiceOver. The count excludes the workspace you’re viewing; iPad split layout is unchanged.InteractiveSwipeBackEnablerrestores edge swipe-back after hiding the system back control. Opening a workspace from the list now marks it read on the Mac when read-state actions are supported and it was unread, so the badge and unread list stay in sync.New en/ja strings for Back and the unread-count accessibility label.
Reviewed by Cursor Bugbot for commit c244013. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Replaces the system back button on iPhone workspace detail with a custom back button that shows how many other workspaces have unread activity (e.g., "‹ 3"). Keeps edge swipe-back; iPad split view unchanged.
New Features
WorkspaceBackButton: chevron + count in one control; count on a small monochrome circle withcaption2semibold monospaced digits; hidden at 0; caps at "99+" while VoiceOver reads the exact count..navigationDestination; replaces the system back button and pops viapopCompactStack;InteractiveSwipeBackEnablerrestores swipe-back.mobile.workspace.backandmobile.workspace.backUnreadCountFormatlocalizations (en, ja).Bug Fixes
@ViewBuilderthat attached to theInthelper; now correctly applied toworkspaceDestination.MobileShellComposite.swift(+8) to accommodate the read-on-open block and keep CI green.Written for commit c244013. Summary will update on new commits.
Summary by CodeRabbit
Release Notes