Skip to content

Show and customize workspace group icons - #5031

Closed
lawrencecchen wants to merge 27 commits into
mainfrom
task-custom-group-icon
Closed

lawrencecchen wants to merge 27 commits into
mainfrom
task-custom-group-icon

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented May 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Render workspace group headers through a validated SF Symbol fallback so empty or unavailable icons show the default folder icon instead of a blank slot.
  • Add a group header context-menu action to set or clear a group-specific SF Symbol icon.
  • Document the header menu action and existing group icon/color CLI commands.

Testing

  • node -e "JSON.parse(require('fs').readFileSync('Resources/Localizable.xcstrings','utf8')); console.log('Localizable.xcstrings JSON OK')"
  • ./scripts/lint-pbxproj-test-wiring.sh
  • ./scripts/reload.sh --tag grpico --swift-frontend-workaround

Issues

  • Task: user request to show workspace group icons and customize group icons.

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


Note

Medium Risk
Changes first-responder yielding for overlay text fields (regression-sensitive around #5269); icon persistence is otherwise low-impact UI state.

Overview
Adds custom workspace group icons (single emoji or validated SF Symbol) with a shared WorkspaceGroupIconPickerView: keyword search over a baked emoji catalog, typed symbol resolution, paging in the grid, and clear-to-default. Headers render via WorkspaceGroupIconPreview / RenderableWorkspaceGroupIcon, with folder.fill when nothing valid is stored; TabManager.setWorkspaceGroupIcon now normalizes through normalizedWorkspaceGroupIcon.

Entry points: sidebar group header “Set Group Icon…” (popover) and Command Palette palette.setGroupIcon when workspace.inGroup is true (inline picker, palette stays open). New command.setGroupIcon.* and workspaceGroup.icon.* strings are localized.

ForeignFirstResponderPolicy is updated so keystrokes reach NSText field editors in the main window or child overlay windows (e.g. SwiftUI popover/command-palette search), without weakening the #5269 rule for stranded responders in unrelated windows. SF Symbol renderability lookup is capped (512 entries, 128-char names) for picker-driven searches.

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


Summary by cubic

Show workspace group icons and add a fast, searchable picker to set a custom emoji or SF Symbol from the header menu or the Command Palette. Icons fall back to folder.fill; input is validated and localized. Header icons now scale with the sidebar font size.

  • New Features

    • Headers render emoji or SF Symbols via WorkspaceGroupIconPreview/RenderableWorkspaceGroupIcon with a folder.fill fallback; icons scale with the sidebar font size.
    • “Set Group Icon…” opens WorkspaceGroupIconPickerView: keyword emoji search (baked dataset with exact/prefix/substring), typed SF Symbol support, virtualized grid (common set seeded; full catalog loads off the main thread), and “Clear Icon”. Single emoji are stored; invalid or multi-emoji inputs are rejected. TabManager.setWorkspaceGroupIcon uses normalizedWorkspaceGroupIcon.
    • Command Palette adds “Set Group Icon…” when workspace.inGroup is true; opens inline and keeps the palette open. Strings (command.setGroupIcon.*, workspaceGroup.icon.*) localized.
  • Bug Fixes

Written for commit 140bbfd. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added ability to customize workspace group icons with emoji or SF Symbols via "Set Group Icon" context menu option
    • Introduced interactive icon picker with search, emoji suggestions, and symbol catalog
  • Documentation

    • Updated workspace groups documentation with new icon customization CLI commands and UI menu options
  • Tests

    • Added validation tests for emoji and symbol icon handling

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@vercel

vercel Bot commented May 31, 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 6, 2026 2:48pm
cmux-staging Building Building Preview, Comment Jun 6, 2026 2:48pm

@coderabbitai

coderabbitai Bot commented May 31, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

PR adds a complete workspace group icon picker feature. Users can set custom SF Symbol or single-emoji icons via context menu. Implementation includes emoji-aware icon normalization, a searchable picker UI with system symbol and emoji catalogs, header integration with popover, sidebar wiring, multilingual localization, and test coverage.

Changes

Set Workspace Group Icon

Layer / File(s) Summary
Icon Resolution Contract and Normalization
Sources/RenderableSystemSymbol.swift, Sources/TabManager.swift
RenderableWorkspaceGroupIcon enum distinguishes system symbols from emoji. New helpers normalize and validate single-emoji inputs, prefer explicit emoji, and fall back to system symbol resolution. TabManager.setWorkspaceGroupIcon routes symbol normalization through the new path.
Icon Picker UI and Catalogs
Sources/WorkspaceGroupIconPickerView.swift
WorkspaceGroupIconPickerView provides search input, conditional "Current"/"Emoji"/"Symbols" sections, and lazy pagination. System icon catalog loads symbols from SFSymbols plist with fallback list and renderability filtering. Emoji catalog provides fixed list with trimmed matching. Supporting views render previews, section titles, and selectable rows. Clear Icon button returns nil.
Header View Icon Display and Picker Trigger
Sources/SidebarWorkspaceGroupHeaderView.swift
Header stores current icon symbol and computes displayed icon type. New onSetIcon closure callback wired to TabManager. Context menu includes "Set Group Icon…" action that presents the picker in a popover. Selection dismisses popover and invokes callback.
Sidebar Initialization and Handler Wiring
Sources/VerticalTabsSidebar+WorkspaceGroups.swift
Sidebar passes group's stored icon to header view. Header's onSetIcon handler weakly captures TabManager and calls setWorkspaceGroupIcon(groupId:symbol:) on icon selection.
Localization Resources
Resources/Localizable.xcstrings, Prototypes/SettingsShellLab/Sources/SettingsShellLab/Resources/Localizable.xcstrings
Adds context menu label, dialog title, search placeholder, section labels (Current/Emoji/Symbols), empty state, instructions, confirm/clear action labels, placeholder symbol (folder.fill), and invalid-icon error title and message across all locales.
Project Build Configuration
cmux.xcodeproj/project.pbxproj
Registers new WorkspaceGroupIconPickerView.swift as build file, file reference, sources group member, and target compile source.
Tests and Documentation
cmuxTests/WorkspaceGroupTests.swift, docs/workspace-groups.md
Emoji resolution tests confirm trimmed single emoji and configured fallback on invalid symbol. Extended setWorkspaceGroupIcon tests verify emoji trimming, multi-emoji rejection, and whitespace-only clearing. Docs clarify workspace group actions and document new CLI subcommands for group appearance.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • manaflow-ai/cmux#4815: Introduces the workspace-group sidebar/header model and config infrastructure that this PR extends with icon selection UI and emoji-aware normalization.
  • manaflow-ai/cmux#5037: Modifies the same icon normalization and storage code paths in TabManager.setWorkspaceGroupIcon and SidebarWorkspaceGroupHeaderView rendering, complementing this PR's icon picker UI work.

Suggested Reviewers

  • Ari4ka

Poem

🐰 A picker springs forth from the sidebar's green,
Where emoji and symbols dance in between,
Each group now wears its own custom face,
With catalogs gleaming in their embrace. ✨


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift @Concurrent ❌ Error WorkspaceGroupSystemIconCatalog performs file I/O in init() accessed from @MainActor View context without explicit actor hop, blocking main thread during UI rendering. Initialize catalog on background queue at startup or add @concurrent with lazy background file loading.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The PR title "Show and customize workspace group icons" accurately summarizes the main changes: displaying workspace group icons with a fallback and enabling customization through a UI picker.
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 TabManager @MainActor, View types on MainActor, WorkspaceGroup Sendable, pure helpers unmarked. No isolation mistakes or mutable Sendable without protection.
Cmux Swift Blocking Runtime ✅ Passed PR only adds icon picker UI and validation logic; single TabManager.swift change swaps normalization functions. No blocking synchronization primitives introduced.
Cmux No Hacky Sleeps ✅ Passed No TypeScript, JavaScript, or shell runtime code changes in this PR; all code changes are Swift (exempt) or non-runtime files (localization/docs/project).
Cmux Algorithmic Complexity ✅ Passed Fixed-size collections (emoji ~36, symbols paginated by 180) with early-exit matching. No nested unbounded loops or hot-path rescans in UI. Single-pass candidate iteration with limit-based pagination.
Cmux Swift Concurrency ✅ Passed @escaping closure callbacks in WorkspaceGroupIconPickerView are standard SwiftUI View boundaries. No legacy async patterns (DispatchQueue.global, Combine state, completion-handlers) introduced.
Cmux Swift File And Package Boundaries ✅ Passed WorkspaceGroupIconPickerView.swift is 306 lines (under 400-line threshold), contains app-specific UI properly coupled to picker feature, and no boundary violations detected in modified files.
Cmux Swift Logging ✅ Passed No logging violations found. The diff adds no print, debugPrint, dump, NSLog, or ad hoc file logging in production code. Silent fallbacks in WorkspaceGroupIconPickerView are permitted.
Cmux User-Facing Error Privacy ✅ Passed All user-facing strings comply with privacy rules: generic labels, public Apple terminology, and safe error messages without vendor names or credentials.
Cmux Full Internationalization ✅ Passed All user-facing Swift text uses String(localized:defaultValue:). New workspaceGroup.icon.* strings have complete translations for all 20 supported locales.
Cmux Swiftui State Layout ✅ Passed New SwiftUI views use simple @State for UI state; no ObservableObject/@published; list rows receive snapshots; GeometryReader only measures; no render-time mutations.
Cmux Architecture Rethink ✅ Passed Icon state owned by TabManager, single update path, no timing repairs or multiple owners. @State limited to local UI search. Parent-child value passing avoids split lifecycle.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds icon picker as SwiftUI View via .popover() modifier (allowed per rules). No NSWindow, NSPanel, WindowGroup, or cmux.* window identifiers created or assigned. No custom Cmd+W handling.
Description check ✅ Passed The PR description covers all required sections: a clear summary of changes, detailed testing approach with specific commands, and linked task context.
✨ 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 task-custom-group-icon

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.

coderabbitai[bot]
coderabbitai Bot previously requested changes May 31, 2026

@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: 3

🤖 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 `@docs/workspace-groups.md`:
- Around line 69-70: Document that the optional flags for the CLI commands "cmux
workspace-group set-color" (--hex) and "cmux workspace-group set-icon"
(--symbol) will clear the custom value when omitted; update the docs after the
listed commands to add a short sentence such as: omitting --hex or --symbol
clears the custom color or icon and reverts to the default, so users know
omission does not produce an error but resets the value.

In `@Sources/SidebarWorkspaceGroupDialogs.swift`:
- Around line 71-78: The accessory text field isn't getting focused and its
contents selected when the icon prompt is re-shown; instead of only setting
alert.window.initialFirstResponder, call alert.window.makeFirstResponder(input)
and then invoke input.selectText(nil) (the same pattern used in the rename flow)
after attaching the accessory view so the field is focused and its text selected
for easy overwrite; locate the code that configures the NSTextField (`input`)
and the NSAlert (`alert`) and replace or augment the initialFirstResponder
assignment with the makeFirstResponder + selectText sequence.
- Around line 44-120: The prototype resource catalog is missing the new
localization keys used by presentSidebarWorkspaceGroupIconPrompt and
presentSidebarWorkspaceGroupInvalidIconAlert; add entries for
workspaceGroup.icon.title, workspaceGroup.icon.message,
workspaceGroup.icon.confirm, workspaceGroup.icon.clear,
workspaceGroup.icon.placeholder, workspaceGroup.icon.invalid.title,
workspaceGroup.icon.invalid.message, and workspaceGroup.contextMenu.setIcon to
Prototypes/SettingsShellLab/Sources/SettingsShellLab/Resources/Localizable.xcstrings
for the supported locales (at least en and ja) so the prototype target can
resolve the strings referenced by those functions, or remove/guard usages in
those functions for that target if you prefer not to add the keys.
🪄 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: ace757e3-ef03-41db-a04f-9996a154bbd3

📥 Commits

Reviewing files that changed from the base of the PR and between ca73d65 and 508e7b2.

📒 Files selected for processing (7)
  • Resources/Localizable.xcstrings
  • Sources/SidebarWorkspaceGroupDialogs.swift
  • Sources/SidebarWorkspaceGroupHeaderView.swift
  • Sources/TabManager.swift
  • Sources/VerticalTabsSidebar+WorkspaceGroups.swift
  • cmuxTests/WorkspaceGroupTests.swift
  • docs/workspace-groups.md

Comment thread docs/workspace-groups.md Outdated
Comment on lines +69 to +70
cmux workspace-group set-color <group-id> [--hex #RRGGBB]
cmux workspace-group set-icon <group-id> [--symbol <sf-symbol>]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider documenting the behavior when optional flags are omitted.

The square brackets indicate --hex and --symbol are optional, but it's not explicit whether omitting them clears the respective value or produces an error. Adding a brief note (e.g., "Omit the flag to clear the custom value") would improve clarity for CLI users.

📝 Example clarification

After line 70, you could add:

Omitting `--hex` or `--symbol` clears the custom color or icon, respectively, reverting to the default.
🤖 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 `@docs/workspace-groups.md` around lines 69 - 70, Document that the optional
flags for the CLI commands "cmux workspace-group set-color" (--hex) and "cmux
workspace-group set-icon" (--symbol) will clear the custom value when omitted;
update the docs after the listed commands to add a short sentence such as:
omitting --hex or --symbol clears the custom color or icon and reverts to the
default, so users know omission does not produce an error but resets the value.

Comment thread Sources/SidebarWorkspaceGroupDialogs.swift Outdated
Comment thread Sources/SidebarWorkspaceGroupDialogs.swift Outdated
@greptile-apps

greptile-apps Bot commented May 31, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds workspace group icon customization: headers now render emoji or SF Symbols (with folder.fill fallback) via WorkspaceGroupIconPreview and RenderableWorkspaceGroupIcon, and users can pick or clear an icon from the group header context menu or the Command Palette. It also fixes a focus-policy regression where NSText field editors in SwiftUI popovers/overlays were being reclaimed by the terminal instead of yielding keystrokes.

  • Icon picker: WorkspaceGroupIconPickerView provides a baked emoji catalog (generated by scripts/generate-workspace-group-emoji.swift into WorkspaceGroupEmojiData.swift), keyword search, typed SF Symbol validation, pagination, and a "Clear Icon" action — all with a 512-entry renderability cache and 128-char symbol name guard in RenderableSystemSymbol.
  • Focus policy fix: shouldRespectForeignFirstResponder now walks the parent-window chain (up to 8 hops) for NSText responders so SwiftUI popover field editors are respected, while preserving the Clicking a pane (terminal or browser) sometimes fails to activate focus — only clicking the tab works #5269 recovery path for detached or unrelated-window text editors.
  • Localization: All 15 new string keys are fully translated across all 20 supported locales in Resources/Localizable.xcstrings and the Prototypes catalog.

Confidence Score: 5/5

The change is safe to merge: the emoji/symbol validation path is well-tested, all new user-facing strings carry complete translations, and the focus-policy fix is narrowly scoped to NSText in overlay windows with clear regression tests.

The baked emoji catalog eliminates the disk I/O concern flagged in the previous review. The renderability cache is properly capped. Localization is complete across all 20 supported locales. The focus-policy change is backed by four targeted tests that cover the #5269 regression path. Two minor style items were flagged but neither affects correctness.

No files require special attention.

Important Files Changed

Filename Overview
Sources/WorkspaceGroupIconPickerView.swift New 168-line picker view; baked emoji search, paginated LazyVGrid, typed SF Symbol validation. matchedEmoji search runs on every body evaluation including pagination-triggered re-renders.
Sources/App/ForeignFirstResponderPolicy.swift Adds parent-window chain walk (≤8 hops) for NSText responders so SwiftUI popover field editors yield keystrokes; falls through to existing guard for non-text responders. Tests updated and cover all four new cases.
Sources/RenderableSystemSymbol.swift Adds capped (512-entry) renderability cache with 128-char length guard, emoji normalization via Unicode scalar properties, and resolvedWorkspaceGroupIconValue returning typed RenderableWorkspaceGroupIcon.
Sources/WorkspaceGroupEmojiCatalog.swift Search helper over baked WorkspaceGroupEmojiData; AND-token filter via substring match, ranked exact/word-prefix/substring. In-memory, fixed-size dataset; no runtime Unicode scanning.
Sources/SidebarWorkspaceGroupHeaderView.swift Adds storedIconSymbol prop and onSetIcon callback; replaces Image(systemName:) with WorkspaceGroupIconPreview; wires context-menu "Set Group Icon…" to popover presentation.
Sources/ContentView.swift Adds iconPicker(groupId:) command-palette mode, workspaceInGroup context key, beginSetGroupIconFlow, and commandPaletteSetGroupIconView; all existing mode switches updated.
Resources/Localizable.xcstrings All 15 new string keys (workspaceGroup.icon.*, workspaceGroup.contextMenu.setIcon, command.setGroupIcon.*) are fully translated across all 20 supported locales.
cmuxTests/ForeignFirstResponderPolicyTests.swift Tests updated with four distinct cases: child-window text editor respected, unrelated-window text editor reclaimed, detached non-text reclaimed, detached text editor reclaimed.
cmuxTests/WorkspaceGroupTests.swift New tests: emoji storage/rejection, cache cap enforcement, overlong symbol rejection, and emoji catalog keyword search (exact/prefix/empty/no-match cases).
scripts/generate-workspace-group-emoji.swift Dev-only generator script that produces WorkspaceGroupEmojiData.swift; uses try! (acceptable for a dev script). Uses identical emoji detection logic as the production normalizedEmoji helper.

Sequence Diagram

sequenceDiagram
    participant User
    participant SidebarHeader as SidebarWorkspaceGroupHeaderView
    participant Picker as WorkspaceGroupIconPickerView
    participant Catalog as WorkspaceGroupEmojiCatalog
    participant Symbol as RenderableSystemSymbol
    participant TabMgr as TabManager

    User->>SidebarHeader: Right-click → "Set Group Icon…"
    SidebarHeader->>Picker: Present popover(storedIconSymbol)
    Picker->>Symbol: normalizedWorkspaceGroupIcon(currentSymbol)
    Symbol-->>Picker: RenderableWorkspaceGroupIcon (.emoji / .systemSymbol)

    User->>Picker: Type search query
    Picker->>Catalog: search(searchText)
    Catalog->>Symbol: normalizedEmoji(rawQuery)
    Symbol-->>Catalog: emoji? / nil
    Catalog-->>Picker: [emoji…]
    Picker->>Symbol: normalizedWorkspaceGroupIcon(searchText)
    Symbol->>Symbol: isRenderable(symbol) → NSImage(systemSymbolName:)
    Symbol-->>Picker: symbol? / nil

    User->>Picker: Select emoji or symbol
    Picker->>SidebarHeader: onSelect(symbol?)
    SidebarHeader->>TabMgr: setWorkspaceGroupIcon(groupId, symbol)
    TabMgr->>Symbol: normalizedWorkspaceGroupIcon(symbol)
    Symbol-->>TabMgr: normalized?
    TabMgr-->>SidebarHeader: stored icon updated
    SidebarHeader->>SidebarHeader: "isIconPickerPresented = false"
Loading

Reviews (21): Last reviewed commit: "Scale workspace group header icons with ..." | Re-trigger Greptile

Comment on lines +71 to +78
let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 240, height: 24))
input.stringValue = nextValue
input.placeholderString = String(
localized: "workspaceGroup.icon.placeholder",
defaultValue: "folder.fill"
)
alert.accessoryView = input
alert.window.initialFirstResponder = input

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Missing focus and text-selection setup for icon input field

The rename dialog (line 34-37 of the same file) uses DispatchQueue.main.async { makeFirstResponder(input); input.selectText(nil) } queued before runModal() so AppKit honours keyboard focus reliably and the existing text is pre-selected. The icon dialog only sets alert.window.initialFirstResponder = input, which AppKit does not always honour when a modal window is first shown. As a result, the user's cursor lands on the "Set Icon" button rather than the text field, and the current symbol value is not selected — requiring an extra click before they can type.

Comment on lines +101 to +103
private var displayedIconSymbol: String {
WorkspaceGroupIconSymbol.resolved(explicit: iconSymbol, configured: nil)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant NSImage allocation in SwiftUI render path

iconSymbol passed to this view is already the fully-resolved, renderable symbol produced by WorkspaceGroupIconSymbol.resolved(explicit:configured:) in VerticalTabsSidebar+WorkspaceGroups.swift. Calling resolved(explicit: iconSymbol, configured: nil) here triggers an additional NSImage(systemSymbolName:) allocation on every render pass that evaluates displayedIconSymbol, for no benefit. The symbol is guaranteed valid at this point and can be used directly.

Suggested change
private var displayedIconSymbol: String {
WorkspaceGroupIconSymbol.resolved(explicit: iconSymbol, configured: nil)
}
private var displayedIconSymbol: String {
iconSymbol
}

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!

Comment on lines +4 to +25
enum WorkspaceGroupIconSymbol {
static let defaultSymbol = "folder.fill"

static func normalized(_ raw: String?) -> String? {
guard let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines),
!trimmed.isEmpty else { return nil }
return trimmed
}

static func resolved(explicit: String?, configured: String?) -> String {
for candidate in [explicit, configured] {
guard let normalized = normalized(candidate),
isRenderable(normalized) else { continue }
return normalized
}
return defaultSymbol
}

static func isRenderable(_ symbol: String) -> Bool {
NSImage(systemSymbolName: symbol, accessibilityDescription: nil) != nil
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Utility type defined in a view file, used across multiple non-view files

WorkspaceGroupIconSymbol is a pure validation/normalization utility with no SwiftUI or AppKit view responsibility, but it is defined inside SidebarWorkspaceGroupHeaderView.swift. It is used by TabManager.swift, SidebarWorkspaceGroupDialogs.swift, and VerticalTabsSidebar+WorkspaceGroups.swift — all non-view call sites. Moving this enum to a dedicated WorkspaceGroupIconSymbol.swift would keep the view file focused on layout and make the utility easier to unit-test in isolation.

Rule Used: Flag Swift changes that add too much unrelated res... (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!

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread docs/workspace-groups.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/workspace-groups.md (1)

77-95: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider adding examples for the new appearance commands.

The Examples section demonstrates create, new-workspace, and list, but doesn't show set-color or set-icon usage. Adding quick examples would help users understand both setting and clearing behavior.

📚 Suggested examples

After line 95, you could add:

Set a custom icon and color for a group:

```bash
cmux workspace-group set-icon workspace_group:1 --symbol "ladybug.fill"
cmux workspace-group set-color workspace_group:1 --hex "`#7A4FD8`"

Clear custom appearance to revert to defaults:

cmux workspace-group set-icon workspace_group:1
cmux workspace-group set-color workspace_group:1

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/workspace-groups.md around lines 77 - 95, Add examples for the new
appearance commands to the Examples section: show usage of the CLI commands
workspace-group set-icon and workspace-group set-color with arguments (e.g.,
workspace_group:1 --symbol "ladybug.fill" and workspace_group:1 --hex "#7A4FD8")
and also show how to clear them by calling the same commands without their
optional flags; place these examples near the existing create/new-workspace/list
examples so users can see both setting and clearing behavior.


</details>

</blockquote></details>

</blockquote></details>
🤖 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 `@docs/workspace-groups.md`:
- Around line 77-95: Add examples for the new appearance commands to the
Examples section: show usage of the CLI commands workspace-group set-icon and
workspace-group set-color with arguments (e.g., workspace_group:1 --symbol
"ladybug.fill" and workspace_group:1 --hex "`#7A4FD8`") and also show how to clear
them by calling the same commands without their optional flags; place these
examples near the existing create/new-workspace/list examples so users can see
both setting and clearing behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ac9251e4-f1d5-473b-9a40-93887f9936ff

📥 Commits

Reviewing files that changed from the base of the PR and between ab1f071 and a99b6dd.

📒 Files selected for processing (1)
  • docs/workspace-groups.md

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 2, 2026

@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/WorkspaceGroupIconPickerView.swift`:
- Around line 256-273: The plist-loading helpers loadSymbolNames() and
loadSearchTerms() silently return fallbacks when the private SFSymbols files
can't be read; add a one-time diagnostic (e.g., process logger or telemetry
event) inside both failure branches to record that the system plist failed to
load and include the attempted path and error info, and then strengthen the
fallback: when loadSearchTerms() would return [:], build a fallback map from
fallbackSymbolNames to at least the symbol name tokens (so matching() still
finds items via search), and ensure fallbackSymbolNames is used consistently by
loadSymbolNames() and loadSearchTerms() to preserve search UX.
🪄 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: 33965558-6e12-40bb-9994-f03bb391b664

📥 Commits

Reviewing files that changed from the base of the PR and between a99b6dd and c113cfa.

📒 Files selected for processing (9)
  • Prototypes/SettingsShellLab/Sources/SettingsShellLab/Resources/Localizable.xcstrings
  • Resources/Localizable.xcstrings
  • Sources/RenderableSystemSymbol.swift
  • Sources/SidebarWorkspaceGroupHeaderView.swift
  • Sources/TabManager.swift
  • Sources/VerticalTabsSidebar+WorkspaceGroups.swift
  • Sources/WorkspaceGroupIconPickerView.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/WorkspaceGroupTests.swift

Comment thread Sources/WorkspaceGroupIconPickerView.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 10 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/WorkspaceGroupIconPickerView.swift Outdated
Comment thread Sources/WorkspaceGroupIconPickerView.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

3 issues found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/WorkspaceGroupEmojiCatalog.swift">

<violation number="1" location="Sources/WorkspaceGroupEmojiCatalog.swift:14">
P2: Text-based emoji search is non-functional: `localizedCaseInsensitiveContains` on emoji characters only matches if the query is a substring of the emoji's Unicode scalar representation, not the emoji's description name. Typing "rocket" to find 🚀, "folder" to find 📁, or "brain" to find 🧠 returns zero results, making the search field ineffective for its primary purpose.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/WorkspaceGroupSystemIconCatalogStore.swift Outdated
Comment thread Sources/WorkspaceGroupEmojiCatalog.swift Outdated
Comment thread Sources/WorkspaceGroupIconPickerView.swift Outdated
@lawrencecchen
lawrencecchen dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] June 2, 2026 23:30

Stale bot review addressed by later commits; latest CodeRabbit status is passing on 6f6d737.

Comment thread Sources/WorkspaceGroupIconPickerView.swift
Comment thread Sources/WorkspaceGroupEmojiCatalog.swift Outdated
# Conflicts:
#	Sources/SidebarWorkspaceGroupHeaderView.swift
#	cmux.xcodeproj/project.pbxproj
Comment thread Sources/WorkspaceGroupIconPreview.swift
Comment thread Sources/WorkspaceGroupIconPickerView.swift
The picker rebuilt the full emoji set by scanning ~15k Unicode scalars on
every body evaluation (and again per cell via the pagination onAppear),
pegging the main thread while browsing. Build the de-duplicated catalog once
into a cached static, load it off the main thread on first open (seeded with a
small common set so the grid is instant), and keep the bounded pagination so
view materialization stays capped as the catalog grows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The icon picker resolves typed search text through isRenderable, which cached
every distinct query in the global renderabilityCache with no eviction, so a
long typing session grew process memory without bound. Cap the cache (dropping
it wholesale when full, since real symbol names are few and cheap to relookup)
and reject pathologically long names before they reach AppKit. Adds a bounded-
cache regression test: without the cap, 712 distinct queries exceed the 512
limit; with it, the cache stays capped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cmd+Shift+P (the command palette) now offers "Set Group Icon…" whenever the
focused workspace belongs to a group, resolving from either the group's anchor
or a child workspace. Running it opens the existing group icon picker inline in
the palette (a new .iconPicker mode, modeled on the rename flow), sharing the
same setWorkspaceGroupIcon action as the sidebar context menu. Localized en/ja.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the full locale set used by neighboring command-palette strings and the
other workspace-group icon strings in this PR. Title reuses the existing
workspaceGroup.contextMenu.setIcon translations verbatim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The picker had no searchable metadata: the catalog was bare emoji characters
discovered by a ~15k-scalar Unicode scan at runtime, so typing a name could
never find an emoji and the first open paid the scan as latency.

Replace it with a baked dataset (Sources/WorkspaceGroupEmojiData.swift,
generated by scripts/generate-workspace-group-emoji.swift from Unicode scalar
names). Runtime is now a static array with keyword search (exact, then
word-prefix, then substring), so "rocket" finds the rocket emoji, loading is
instant, and there is no runtime scan. Adds a behavioral search test.

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

Typing in the workspace group icon picker did nothing: keystrokes went to the
terminal instead of the search field. Root cause is in the terminal focus
policy, not the picker. cmux's keyRepair (and the symmetric applyFirstResponder
paths) reclaim first responder for the terminal unless the active first
responder passes shouldRespectForeignFirstResponder. That policy applied a
window-membership guard (.window === window, added for issue #5269) before
checking whether the responder is a text editor. SwiftUI hosts a popover/palette
overlay's field editor (_SystemTextFieldFieldEditor, an NSText subclass) in a
backing window that differs from the main window even though it is the main
window's active first responder, so the guard rejected it and the terminal stole
every keystroke.

Check `firstResponder is NSText` before the window-membership guard. The
#5269 stranding the guard defends against only affects terminal surfaces (plain
NSViews, not NSText), so respecting in-flight text editors regardless of their
backing window cannot resurrect it; the guard still applies to non-text
(right-sidebar/dock) focus owners. Also focus the picker's search field on
appear so typing lands immediately.

Verified end-to-end via Computer Use: opening the picker and typing "rocket"
filters to the rocket emoji with no keyRepair steal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread Sources/App/ForeignFirstResponderPolicy.swift
lawrencecchen and others added 2 commits June 6, 2026 03:20
The overlay-text-field exception accepted any NSText before the window guard,
including a detached one (window == nil) such as a removed field left as first
responder, which would permanently block terminal focus recovery. Require a
non-nil backing window: an overlay editor's window is non-nil (just different
from the main window), while a stale detached editor is reclaimed (issue #5269).

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

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 60d5496. Configure here.

Comment thread Sources/SidebarWorkspaceGroupHeaderView.swift Outdated
lawrencecchen and others added 2 commits June 6, 2026 03:47
The previous "non-nil backing window" exception still respected a text editor
stranded in an unrelated window, which could reopen the #5269 focus-recovery
hole. Walk the editor window's parent chain instead: respect a text editor only
when its window is the repair window or a child window it presents. Verified at
runtime that a SwiftUI popover's field editor lives in an `_NSPopoverWindow`
whose parent is the main window (chain `[popover, main]`), so this respects the
overlay case while reclaiming from genuinely unrelated or detached editors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WorkspaceGroupIconPreview hard-coded the glyph point size, so when it replaced
the header's Image(systemName:).font(.system(size: metrics.iconFontSize)) the
group icon stopped scaling with the configured sidebar font size and changed its
default size. Thread the metric-derived iconFontSize through the preview; picker
rows keep the default.

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

This branch was successfully deployed

1 active deployment
Preview – cmux — 140bbfdd Deployed Jun 6, 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.

2 participants