Skip to content

Add mark-read/unread + clear notifications to workspace group menu - #6535

Merged
azooz2003-bit merged 6 commits into
mainfrom
feat-workspace-group-mark-read
Jun 21, 2026
Merged

azooz2003-bit merged 6 commits into
mainfrom
feat-workspace-group-mark-read

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jun 21, 2026 •

Copy link
Copy Markdown
Collaborator

Workspace groups are a superset of their member workspaces, but the group header's right-click menu only offered group-config actions (rename, pin/unpin, edit config, docs, ungroup, delete). It was missing the basic notification actions that every normal workspace row has, most notably mark as unread.

This adds three items to the group context menu, between Pin/Unpin and Edit Group Config:

  • Mark Group as Read
  • Mark Group as Unread
  • Clear Latest Notifications

Each operates over all member workspaces (anchor included) and reuses the existing TerminalNotificationStore mutation path (markRead/markUnread/clearLatestNotification) that the per-workspace menu uses, so there is one shared behavior path rather than duplicated logic. Each button is disabled when not applicable, using canMarkWorkspaceRead/canMarkWorkspaceUnread and a latest-notification check, matching the per-workspace menu's disabled rules.

The header view stays within the sidebar snapshot boundary: the new inputs are plain Bool value snapshots plus action closures (closures hold the store weakly), added to the view's Equatable so unread-state changes correctly invalidate the row.

Localization: added workspaceGroup.contextMenu.markRead, workspaceGroup.contextMenu.markUnread, and workspaceGroup.contextMenu.clearLatestNotifications to Resources/Localizable.xcstrings with translations for all locales used by the analogous per-workspace strings (en, ja, ko, zh-Hans, zh-Hant, de, es, fr, it, da, nb, pl, ru, th, tr, uk, ar, bs, pt-BR).

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Add notification controls and quick-create to the workspace group header menu. Plain actions target the group’s anchor; mark-all items update member workspaces and skip the anchor.

  • New Features

    • Added: New Workspace in Group, Mark Group as Read/Unread, Clear Latest Notifications, and Mark All Workspaces in Group as Read/Unread.
    • Mark-all resolves members at action time and guards each workspace before mutating to avoid stale IDs and manual-unread side effects.
    • Reuses TerminalNotificationStore; buttons disabled via canMarkRead/canMarkUnread/hasLatestNotifications and canMarkAllRead/canMarkAllUnread; header stays snapshot-safe with Bool inputs and weak closures; Equatable extended; localized strings added (incl. km).
  • Refactors

    • Moved drop zone/action/policy/delegate types into SidebarWorkspaceGroupHeaderDropDelegate.swift to keep the view file under the size budget; no behavior change.

Written for commit 83b9dda. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added workspace group context menu actions: Mark Group as Read/Unread, Clear Latest Notifications.
    • Added bulk controls to mark all workspaces in a group as read/unread, with items enabled/disabled based on current state.
    • Added support for drag-and-drop of workspace tabs onto a workspace group header.
  • Localization
    • Added new localized strings for the added context menu actions across multiple locales.

Workspace groups only exposed group-config actions (rename, pin, ungroup,
delete) and lacked the notification actions every normal workspace row has.
Since a group is a superset of its member workspaces, its header context
menu now offers Mark Group as Read, Mark Group as Unread, and Clear Latest
Notifications, each operating over all member workspaces (anchor included)
and disabled when not applicable, reusing the same TerminalNotificationStore
mutation path as the per-workspace menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 21, 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 Jun 21, 2026 10:20pm
cmux-staging Building Building Preview, Comment Jun 21, 2026 10:20pm

@coderabbitai

coderabbitai Bot commented Jun 21, 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

Run ID: 36c2cb85-947e-40cd-943e-5f9a5d250673

📥 Commits

Reviewing files that changed from the base of the PR and between c1667a2 and 83b9dda.

📒 Files selected for processing (5)
  • Resources/Localizable.xcstrings
  • Sources/SidebarWorkspaceGroupHeaderDropDelegate.swift
  • Sources/SidebarWorkspaceGroupHeaderView.swift
  • Sources/VerticalTabsSidebar+WorkspaceGroups.swift
  • cmux.xcodeproj/project.pbxproj
💤 Files with no reviewable changes (1)
  • Resources/Localizable.xcstrings

📝 Walkthrough

Walkthrough

Workspace group headers now support five new context menu actions for managing notification and read state: Mark Group as Read/Unread, Mark All Workspaces in Group as Read/Unread, and Clear Latest Notifications. The implementation adds state properties and action closures to SidebarWorkspaceGroupHeaderView, computes their values from workspace notification capabilities in VerticalTabsSidebar+WorkspaceGroups, and extracts drag-and-drop logic into a dedicated SidebarWorkspaceGroupHeaderDropDelegate file. Localization strings are provided across ~19 locales, and the project is updated to include the new delegate file.

Changes

Workspace Group Header: Notifications and Drag-and-Drop

Layer / File(s) Summary
Header view model contracts and context menu UI
Sources/SidebarWorkspaceGroupHeaderView.swift
Adds five state properties (canMarkRead, canMarkUnread, hasLatestNotifications, canMarkAllRead, canMarkAllUnread) and five action closures; updates Equatable to include all new properties; restructures context menu with "New Workspace in Group" button and five conditionally-disabled actions for marking read/unread and clearing notifications; removes drag-and-drop helper types previously in this file.
Wiring computed state and action closures
Sources/VerticalTabsSidebar+WorkspaceGroups.swift
Computes five notification capability flags and hasLatestNotifications from anchor and non-anchor member workspaces; passes computed values into SidebarWorkspaceGroupHeaderView; provides action closures that mark anchor workspace and bulk-update non-anchor member workspaces via notificationStore, each guarded by nil checks, resolving member IDs dynamically from tabManager.tabs and filtering by workspace-specific mark predicates.
Drag-and-drop delegation for workspace group header
Sources/SidebarWorkspaceGroupHeaderDropDelegate.swift
New file implementing drop handling for dragging workspaces onto group headers. Defines center-drop detection zone, validates drop policy (requires payload, matching workspaces, center targeting; rejects pinned/group-anchor cases), routes drop lifecycle events through group-header logic or underlying tab reorder delegate, and provides helpers for center-drop updates, no-op edge-drop consumption via SidebarDropPlanner, and drag state cleanup.
Localization strings for context menu actions
Resources/Localizable.xcstrings
Adds five new workspaceGroup.contextMenu keys with manual extraction state and translated stringUnit values across ~19 locales for mark read/unread, mark all read/unread, and clear latest notifications.
Build configuration and project setup
cmux.xcodeproj/project.pbxproj
Updates Xcode project to include SidebarWorkspaceGroupHeaderDropDelegate.swift: adds PBXBuildFile, PBXFileReference, and includes file in Sources group and build phase.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#4989: Workspace-group header drag-and-drop handling via SidebarWorkspaceGroupHeaderDropDelegate and related drop-delegate logic for dropping/reordering group headers.
  • manaflow-ai/cmux#6052: Same-area refactor of SidebarWorkspaceGroupHeaderView and group-header drop handling using SidebarRowHeightStore for drop/anchor sizing.

Poem

🐇 Five new actions bloom on headers true,
Mark read, mark unread, and clear the queue!
Drag-and-drop logic finds its own way,
While nineteen voices all have their say,
The sidebar grows tidier every day! 🌿✨

🚥 Pre-merge checks | ✅ 21 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a clear summary of changes and rationale, but the Testing section is missing and demo video is not included as required by the template. Add a Testing section documenting how the changes were tested and what was verified, and optionally include a demo video link showing the new menu items in action.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (21 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding mark-read/unread and clear-notifications actions to the workspace group context menu.
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 PR introduces properly isolated code: pure enums, @MainActor drop delegate, weak-captured closure access to @MainActor notificationStore executed from MainActor SwiftUI context menu, no actor isola...
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization patterns detected. PR adds UI controls with synchronous closures calling existing notificationStore methods, without semaphores, sleeps, DispatchQueue.main.sync, locks,...
Cmux Expensive Synchronous Load ✅ Passed PR adds notification actions to workspace group context menu using lightweight TerminalNotificationStore methods (in-memory mutations + background-dispatched notification removal), with no Restorab...
Cmux Cache Substitution Correctness ✅ Passed Boolean snapshots (canMarkRead, canMarkUnread, hasLatestNotifications, canMarkAllRead, canMarkAllUnread) are used only for transient UI disabled states in context menu buttons, not persisted via Se...
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift code, localization files, and Xcode project files. The check applies to TypeScript, JavaScript, shell, and build/runtime scripts only; Swift code is explicitly out of scope (...
Cmux Algorithmic Complexity ✅ Passed All algorithmic operations are linear-time with bounded collections: render-time button-state checks iterate group members O(m), action handlers do one-pass batch scans O(n), and drop handlers are...
Cmux Swift Concurrency ✅ Passed No legacy async patterns introduced. New closures are SwiftUI Button callbacks performing synchronous mutations on MainActor-isolated objects with proper weak references; uses no DispatchQueue, Com...
Cmux Swift @Concurrent ✅ Passed All Swift changes are synchronous with no async/await, nonisolated async, or @concurrent violations. Three new files contain only synchronous functions operating within proper actor isolation (Main...
Cmux Swift File And Package Boundaries ✅ Passed All modified/new Swift files are under the 400-line single-responsibility or 800-line limits; SidebarWorkspaceGroupHeaderView (372 lines) is pure UI rendering, SidebarWorkspaceGroupHeaderDropDelega...
Cmux Swiftpm Lockfiles ✅ Passed PR modifies only localization, Swift source files, and Xcode build file references; no SwiftPM dependency, Package.resolved, or .gitignore policy changes apply.
Cmux Swift Logging ✅ Passed All Swift code changes pass logging compliance: no print, debugPrint, dump, or NSLog in production code; only 1 allowed cmuxDebugLog call properly guarded by #if DEBUG; no MainActor Log...
Cmux User-Facing Error Privacy ✅ Passed The PR adds user-facing menu items and localized strings that are safe and generic (e.g., "Mark Group as Read", "Clear Latest Notifications") without exposing vendor names, internal providers, cred...
Cmux Full Internationalization ✅ Passed PR adds 5 new user-facing strings (markRead, markUnread, markAllRead, markAllUnread, clearLatestNotifications) to workspace group context menu, all using String(localized:defaultValue:) API with ma...
Cmux Swiftui State Layout ✅ Passed PR adds Bool snapshots and weak-capture closures to header view within LazyVStack; no new ObservableObject/@published, render-time mutations, or store references in row subtrees.
Cmux Architecture Rethink ✅ Passed PR adds notification actions to workspace group headers using value snapshots, weak action closures, and the single shared TerminalNotificationStore path—no timing, observers, duplicate owners, or...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds notification actions to an existing sidebar View component and DropDelegate helper; introduces no NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup requiring close-shortc...
Cmux Source Artifacts ✅ Passed All 5 changed files are hand-written source code, localization catalogs, or project configuration—explicitly permitted under the PASS criteria in source-control-artifacts.md. No build artifacts, te...
Cmux No Test Or Debug Seam In Production Source ✅ Passed No test or debug seams found. The #if DEBUG block gates legitimate debug logging (cmuxDebugLog) which is real product behavior, not test-only state exposure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat-workspace-group-mark-read

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.

@greptile-apps

greptile-apps Bot commented Jun 21, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds notification actions (mark read/unread, clear latest, bulk mark-all) to the workspace group header context menu, and also inserts a "New Workspace in Group" shortcut. The drop-delegate logic previously co-located in SidebarWorkspaceGroupHeaderView.swift is extracted into its own file with no behavioral change.

  • Five new context-menu items are added: "Mark Group as Read/Unread" and "Clear Latest Notifications" targeting only the anchor workspace, plus "Mark All Workspaces in Group as Read/Unread" targeting non-anchor members; buttons are disabled via canMarkRead/canMarkUnread/hasLatestNotifications/canMarkAllRead/canMarkAllUnread, mirroring the per-workspace menu's disabled rules.
  • The header view stays snapshot-safe: all new inputs are plain Bool values passed at render time, action closures hold notificationStore and tabManager weakly, and all five new fields are included in the Equatable conformance so unread-state changes correctly invalidate the row.
  • Localization for five new string keys is complete across 20 locales (en, km, ja, ko, zh-Hans, zh-Hant, de, es, fr, it, da, nb, pl, ru, th, tr, uk, ar, bs, pt-BR); the km entries previously flagged were fixed in 7671684 and are present in this head.

Confidence Score: 5/5

Safe to merge — changes are narrowly scoped to the group header context menu, reuse existing notification store mutation paths, and carry no side effects on other surfaces.

All five new menu actions use guarded per-member iteration (no unconditional mutation on already-read/unread workspaces), Bool snapshots keep the .equatable() boundary intact, closures hold stores weakly, and localization is complete across all 20 supported locales. The previously flagged issues (missing km locale, unconditional markUnread on all members) were both addressed in commit 7671684 and are reflected in this head.

No files require special attention.

Important Files Changed

Filename Overview
Sources/VerticalTabsSidebar+WorkspaceGroups.swift Adds render-time Bool snapshots for all five new enabled/disabled inputs and constructs action closures with correct weak references; onMarkAllUnread guards each member individually with canMarkWorkspaceUnread(forTabIds: [id]) to avoid setting the manual-unread flag on already-unread workspaces.
Sources/SidebarWorkspaceGroupHeaderView.swift Five new Bool inputs and five new action closures added; all five Bool fields included in the Equatable conformance; context menu extended with the six new items (including New Workspace in Group) in the correct order with appropriate disabled modifiers.
Sources/SidebarWorkspaceGroupHeaderDropDelegate.swift Pure refactor — drop-delegate types extracted verbatim from the bottom of SidebarWorkspaceGroupHeaderView.swift into their own file; no behavioral change.
Resources/Localizable.xcstrings Five new string keys added, each with 20 locale entries matching the locale set used by sibling workspaceGroup.contextMenu.* strings; km entries are present for all five keys.
cmux.xcodeproj/project.pbxproj Correctly registers the new SidebarWorkspaceGroupHeaderDropDelegate.swift in both the file references section and the app target's Sources build phase.

Reviews (4): Last reviewed commit: "Split drop-delegate types out of Sidebar..." | Re-trigger Greptile

Comment on lines 176214 to 176576
}
}
},
"workspaceGroup.contextMenu.markRead": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Mark Group as Read"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "グループを既読にする"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "그룹을 읽음으로 표시"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "将分组标记为已读"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "將群組標為已讀"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Gruppe als gelesen markieren"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Marcar grupo como leído"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Marquer le groupe comme lu"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "Segna gruppo come letto"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "Marker gruppe som læst"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "Merk gruppe som lest"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "Oznacz grupę jako przeczytaną"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Отметить группу как прочитанную"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "ทำเครื่องหมายกลุ่มว่าอ่านแล้ว"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "Grubu Okundu Olarak İşaretle"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "Позначити групу як прочитану"
}
},
"ar": {
"stringUnit": {
"state": "translated",
"value": "تعليم المجموعة كمقروءة"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "Označi grupu kao pročitanu"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "Marcar Grupo como Lido"
}
}
}
},
"workspaceGroup.contextMenu.markUnread": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Mark Group as Unread"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "グループを未読にする"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "그룹을 읽지 않음으로 표시"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "将分组标记为未读"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "將群組標為未讀"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Gruppe als ungelesen markieren"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Marcar grupo como no leído"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Marquer le groupe comme non lu"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "Segna gruppo come non letto"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "Marker gruppe som ulæst"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "Merk gruppe som ulest"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "Oznacz grupę jako nieprzeczytaną"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Отметить группу как непрочитанную"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "ทำเครื่องหมายกลุ่มว่ายังไม่อ่าน"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "Grubu Okunmadı Olarak İşaretle"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "Позначити групу як непрочитану"
}
},
"ar": {
"stringUnit": {
"state": "translated",
"value": "تعليم المجموعة كغير مقروءة"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "Označi grupu kao nepročitanu"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "Marcar Grupo como Não Lido"
}
}
}
},
"workspaceGroup.contextMenu.clearLatestNotifications": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Clear Latest Notifications"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "最新の通知をクリア"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "최근 알림 모두 지우기"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "清除最新通知"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "清除最新通知"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Neueste Mitteilungen löschen"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Borrar últimas notificaciones"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Effacer les dernières notifications"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "Cancella ultime notifiche"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "Ryd seneste notifikationer"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "Tøm siste varsler"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "Wyczyść najnowsze powiadomienia"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Очистить последние уведомления"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "ล้างการแจ้งเตือนล่าสุด"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "Son bildirimleri temizle"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "Очистити останні сповіщення"
}
},
"ar": {
"stringUnit": {
"state": "translated",
"value": "مسح أحدث الإشعارات"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "Očisti najnovije obavijesti"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "Limpar Notificações Mais Recentes"
}
}
}
},
"workspaceGroup.contextMenu.ungroup": {
"extractionState": "manual",
"localizations": {

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.

P1 Missing km (Khmer) locale in all three new strings

Every other workspaceGroup.contextMenu.* string in the catalog (.rename, .pin, .unpin, .editConfig, .openDocs, .delete, .ungroup) includes a km translation — Khmer is present in 443 strings across the catalog. All three new keys (workspaceGroup.contextMenu.markRead, .markUnread, .clearLatestNotifications) are missing the km locale entry. Khmer-locale users will see the raw defaultValue English fallback for these menu items instead of a translated string.

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!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 7671684 — added km (Khmer) entries to all five new workspaceGroup.contextMenu.* strings.

— Claude Code

Comment on lines +161 to +171
onMarkUnread: { [weak notificationStore, memberWorkspaceIds] in
guard let notificationStore else { return }
for id in memberWorkspaceIds {
notificationStore.markUnread(forTabId: id)
}
},
onClearLatestNotifications: { [weak notificationStore, memberWorkspaceIds] in
guard let notificationStore else { return }
for id in memberWorkspaceIds {
notificationStore.clearLatestNotification(forTabId: id)
}

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.

P1 onMarkUnread sets workspaceManualUnread = true on members that are already unread via notifications

canMarkWorkspaceUnread(forTabIds:) returns true when any member is not unread, but the closure then calls markUnread(forTabId:) on all members unconditionally. For a member that is already unread due to a real notification, markUnread(forTabId:) sets workspaceManualUnread = true even though the user never intended to manipulate that workspace. The per-workspace "Mark as Unread" button is disabled for already-unread workspaces precisely to avoid this — markRead(id:) (fired when the OS notification center dismisses a notification) does not clear the manual flag, so those workspaces can end up stuck unread after the notification is externally dismissed. The fix is to guard with canMarkWorkspaceUnread(forTabIds: [id]) per-member before calling markUnread.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 7671684 — mark-all now guards each member with canMarkWorkspaceUnread(forTabIds: [id]) before calling markUnread, so already-unread members (e.g. unread via a notification) are skipped and never get the manual-unread flag set.

— Claude Code

azooz2003-bit and others added 2 commits June 21, 2026 14:09
Address dogfood feedback:
- Plain Mark Group as Read/Unread and Clear Latest Notifications now act on
  the group's own representation (the anchor workspace) only, matching a
  normal workspace row, instead of cascading to every member.
- Add explicit Mark All Workspaces in Group as Read / as Unread items for
  the bulk action over all members (anchor included).
- Add New Workspace in Group to the group header context menu (reuses the
  existing onTapPlus path and the plus-button's localized string).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mark All Workspaces in Group as Read/Unread now skips the anchor workspace,
so it changes only the contained workspaces and never the group's own row
read status. The anchor's status stays owned by Mark Group as Read/Unread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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/VerticalTabsSidebar`+WorkspaceGroups.swift:
- Around line 167-177: The onMarkAllRead and onMarkAllUnread closures currently
loop through memberWorkspaceIds and call notificationStore.markRead(forTabId:)
and notificationStore.markUnread(forTabId:) individually for each workspace,
causing repeated rescans of notification state. Add two new batched methods to
TerminalNotificationStore: markRead(forTabIds: [UUID]) and markUnread(forTabIds:
[UUID]) that process all workspace IDs in a single mutation pass, then replace
the for loops in both closures to call these new batched methods instead,
passing the entire memberWorkspaceIds array directly.
🪄 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: 9d63701c-5534-415b-9ce1-b70a3427e477

📥 Commits

Reviewing files that changed from the base of the PR and between b9da491 and fe57cc3.

📒 Files selected for processing (3)
  • Resources/Localizable.xcstrings
  • Sources/SidebarWorkspaceGroupHeaderView.swift
  • Sources/VerticalTabsSidebar+WorkspaceGroups.swift

Comment thread Sources/VerticalTabsSidebar+WorkspaceGroups.swift Outdated
azooz2003-bit and others added 2 commits June 21, 2026 14:21
… locale

Address review findings:
- Codex P1: mark-all closures captured the member ID list, which is not an
  Equatable input on the .equatable() header, so a same-count membership swap
  could leave the action operating on stale IDs. Resolve members live from
  tabManager.tabs by groupId at action time instead of capturing the list.
- Greptile P1: guard each member with canMarkWorkspaceRead/Unread before
  mutating, so mark-all-unread never sets the manual-unread flag on a member
  already unread via a notification (which a later dismissal can't clear).
- Greptile: add the km (Khmer) locale entry to the five new group strings to
  match the rest of the workspaceGroup.contextMenu.* catalog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The added menu items plus the upstream hover-tracker rework pushed
SidebarWorkspaceGroupHeaderView.swift to 560 lines, over the 500-line
file-length budget (workflow-guard-tests). Move the cohesive, separable
DropZone/DropAction/DropPolicy/DropDelegate types into a new
SidebarWorkspaceGroupHeaderDropDelegate.swift (wired into the pbxproj and
normalized), leaving the view file at 372 lines. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 83b9dda7 Deployed Jun 21, 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.

1 participant