Skip to content

Add editable workspace descriptions - #2475

Merged
lawrencecchen merged 29 commits into
mainfrom
task-sidebar-description-notes
Apr 3, 2026
Merged

lawrencecchen merged 29 commits into
mainfrom
task-sidebar-description-notes

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a separate editable workspace description field below the sidebar title with multiline markdown rendering
  • wire workspace description editing into the command palette, a new Cmd-Shift-E shortcut, the View menu, and the workspace context menu
  • expose description read/write support through the socket API and cmux CLI, with persistence and focused regression coverage

Verification

  • built with ./scripts/reload.sh --tag task-sidebar-description-notes
  • did not run local test suites

Summary by cubic

Adds editable workspace descriptions with Markdown in the sidebar. Also improves the editor by fixing Shift‑Enter newlines, preventing focus steals, preserving line breaks/whitespace, keeping navigation inside the multiline editor, and capping editor growth.

  • New Features

    • Sidebar: shows a multiline Markdown description below the workspace title.
    • Command Palette: “Edit Workspace Description” multiline editor with Enter to save, Shift‑Enter for newline, Escape to cancel.
    • Shortcut & Menus: Cmd‑Shift‑E, View menu item, and workspace context menu; “Clear Workspace Description” is available when set.
    • Persistence & Search: description is saved/restored in session snapshots; workspace switcher indexes description text for fuzzy search.
    • API/CLI:
      • Socket API: workspace.create accepts description; workspace.action adds set_description and clear_description; workspace payloads include description.
      • cmux: new-workspace --description <text>; workspace-action set-description supports --description <text> or trailing text; workspace-action clear-description; current-workspace now uses v2 and respects --id-format.
  • Bug Fixes

    • Shift‑Enter inserts a newline in the description editor; prevent terminal focus restore while the command palette is open.
    • Preserve line breaks and leading/trailing whitespace in sidebar Markdown; fix description refresh lag; expose multiline descriptions to accessibility.
    • Keep navigation keys inside the multiline editor (don’t route to palette list).
    • Cap description editor height to prevent runaway growth.

Written for commit 8e7fc4c. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Add workspace descriptions: create, edit, or clear custom descriptions via CLI, menu, context menu, command palette, or keyboard shortcut (⌘+Shift+E). Descriptions persist across sessions, show in the sidebar, and are indexed for search. Multiline editing supported with refined Return/Shift-Return behavior.
  • Localization

    • Added localized labels, placeholders, and menu/command text for editing/clearing workspace descriptions.
  • Tests

    • New tests for shortcut routing, command-palette submission, persistence, and search indexing.

@vercel

vercel Bot commented Apr 1, 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 Apr 3, 2026 7:19am

@coderabbitai

coderabbitai Bot commented Apr 1, 2026 •

Copy link
Copy Markdown

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

Adds end-to-end workspace description support: CLI flag parsing, V2 socket actions (set/clear), Workspace model/persistence, TabManager APIs/notifications, command‑palette multiline editor with shortcut/menu wiring, localization entries, and unit tests.

Changes

Cohort / File(s) Summary
CLI
CLI/cmux.swift
Added --description parsing for new-workspace and workspace-action; consume flag before inferring trailing positional description; updated usage and unknown-flag messaging.
Localization
Resources/Localizable.xcstrings
Added 10 localization keys (en/ja) for editing/clearing workspace descriptions across shortcuts, menus, palette, and context menus.
App & Shortcut Routing
Sources/AppDelegate.swift, Sources/cmuxApp.swift
New request helpers/notification for command-palette edit request; added menu item and AppStorage-backed shortcut decoding; added shortcut routing branch for editWorkspaceDescription and Return modifier normalization logic for multiline palette submission.
Command‑palette / UI
Sources/ContentView.swift
Introduced multiline workspace-description editor mode (state, focus, measured height), representable-backed text editor, palette handlers/commands (edit/clear), fingerprints/metadata indexing update, sidebar rendering of descriptions, and palette submission flow.
Keyboard Shortcuts
Sources/KeyboardShortcutSettings.swift
Added editWorkspaceDescription action with localized label/defaultsKey and default shortcut ⌘⇧E.
Model & Persistence
Sources/Workspace.swift, Sources/SessionPersistence.swift
Added @Published var customDescription: String?, normalization and hasCustomDescription, persisted in SessionWorkspaceSnapshot and restored on load.
TabManager & Notifications
Sources/TabManager.swift
Added setCustomDescription(tabId:description:), clearCustomDescription(tabId:), included customDescription in session autosave fingerprinting, and notification name for command-palette requests.
V2 API / TerminalController
Sources/TerminalController.swift
Threaded params["description"] into workspace creation; added v2 actions set_description and clear_description with validation; included description in v2 workspace payloads.
Tests
cmuxTests/...
Added tests for shortcut routing (Cmd+Shift+E), Return/Shift+Return behavior in palette, session snapshot round-trip for descriptions, search/indexer keyword inclusion from descriptions, and shortcut defaults/menu mapping.

Sequence Diagram

sequenceDiagram
    participant User
    participant AppDelegate
    participant ContentView
    participant TabManager
    participant Workspace
    participant TerminalController

    User->>AppDelegate: Press Cmd+Shift+E
    AppDelegate->>ContentView: Post commandPaletteEditWorkspaceDescriptionRequested
    ContentView->>ContentView: Present multiline description editor
    User->>ContentView: Type description and press Return
    ContentView->>TabManager: setCustomDescription(tabId, text)
    TabManager->>Workspace: setCustomDescription(normalizedText)
    Workspace-->>TabManager: Publish customDescription change
    TabManager->>TerminalController: Emit updated v2 workspace payload (includes description)
    TerminalController-->>User: v2 response / payload with description
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰
I hopped to a palette, bright and keen,
Cmd‑Shift‑E to edit what’s unseen,
Lines trimmed and folded, stored with care,
Each workspace whispers notes to share,
A rabbit’s scribble — light as air.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add editable workspace descriptions' directly and accurately summarizes the main feature introduced in this changeset.
Description check ✅ Passed The pull request description provides a comprehensive summary of changes, testing approach, and key features with verification details.

✏️ 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 task-sidebar-description-notes

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 09c256c3f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/AppDelegate.swift Outdated
Comment on lines 9585 to 9589
let paletteRequiresCommandReturn = commandPaletteRequiresCommandReturnToSubmit(in: paletteWindow)
let normalizedReturnFlags = event.modifierFlags
.intersection(.deviceIndependentFlagsMask)
.subtracting([.numericPad, .function, .capsLock])
if normalizedFlags.isEmpty, event.keyCode == 53 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow caret navigation in workspace description input mode

After introducing workspace_description_input, this block only special-cases Return handling, but the global commandPaletteSelectionDeltaForKeyboardNavigation interception above still consumes plain Up/Down and Ctrl+N/Ctrl+P for all palette modes. In description-edit mode those move-selection notifications are ignored, so the multiline editor never receives those keys and users cannot move the caret between lines from the keyboard.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. The command-palette navigation router now stands down when the multiline workspace description editor is handling inline text, so Up/Down and Ctrl-N/Ctrl-P stay in the editor.

— Claude Code

Comment thread Sources/ContentView.swift Outdated
Comment on lines +4301 to +4304
private final class CommandPaletteMultilineTextView: NSTextView {
var onCommandReturn: (() -> Void)?

override func keyDown(with event: NSEvent) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve IME marked text in multiline description editor

This new editor uses a plain NSTextView, but command-palette shortcut handling suppresses submit/dismiss only when the first responder is a field editor. As a result, when users are composing marked text with an IME in the description editor, Escape or Cmd+Return can dismiss/submit the palette instead of completing/canceling composition, which is a regression for non-Latin input workflows.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an editable, markdown-rendered workspace description field across the full stack: the Workspace model, session persistence, the sidebar UI, the command palette (new workspaceDescriptionInput mode with a multiline NSTextView), keyboard shortcut (Cmd+Shift+E), View menu, context menu, socket API, and the cmux CLI. The implementation closely mirrors the existing rename flow and is well-tested with targeted regression coverage.

Key highlights:

  • normalizedCustomDescription correctly trims whitespace and normalises line endings before storing; an all-whitespace save silently clears the description (intentional).
  • The commandPaletteRequiresCommandReturnToSubmit guard in AppDelegate correctly prevents plain Return from submitting in description mode (allows multiline input), with the same Cmd+Return keycodes handled redundantly but harmlessly in both the AppDelegate monitor and CommandPaletteMultilineTextView.keyDown.
  • Session snapshots add customDescription as an optional field, so loading legacy sessions without it remains backward-compatible.
  • Two minor findings: a cosmetic alignment gap in the new-workspace CLI help text, and the sidebar markdown renderer uses .full syntax which may render non-interactive link decorations.

Confidence Score: 5/5

Safe to merge — no correctness, data-integrity, or reliability issues found; only two minor P2 style observations.

All remaining findings are P2 style suggestions (CLI help alignment and markdown syntax choice). The feature is well-structured, follows existing patterns exactly, is backward-compatible, and has solid test coverage across unit, persistence, search, and shortcut-routing layers.

No files require special attention.

Important Files Changed

Filename Overview
Sources/ContentView.swift Largest change: adds a new workspaceDescriptionInput command palette mode with a custom CommandPaletteMultilineTextEditor NSViewRepresentable, SidebarWorkspaceDescriptionText markdown renderer, command contributions, context menu items, search index tokens for descriptions, and all associated flow/focus logic. Pattern closely mirrors the existing rename flow.
Sources/AppDelegate.swift Adds requestCommandPaletteEditWorkspaceDescription, commandPaletteRequiresCommandReturnToSubmit (mode-aware submit guard so plain Return inserts a newline instead of submitting), keyboard event routing for Cmd+Shift+E, and requestEditWorkspaceDescriptionViaCommandPalette.
Sources/Workspace.swift Adds customDescription: String? as a @Published property with normalizedCustomDescription helper (trims whitespace, normalizes line endings), setCustomDescription/hasCustomDescription, session snapshot save/restore, and sidebar observation signal. Logic is clean and follows existing patterns for customTitle.
Sources/TerminalController.swift Exposes description in workspace list/create/action API responses, and adds set_description/clear_description actions with proper validation (non-empty trimmed guard). All reference-type mutation via tabManager.setCustomDescription reads correctly from the same Workspace instance.
CLI/cmux.swift Adds --description flag to both new-workspace and workspace-action commands, set-description/clear-description actions, updated help text and usage strings. Minor cosmetic alignment gap in new-workspace help after adding the description flag.
Sources/SessionPersistence.swift Adds optional customDescription: String? to SessionWorkspaceSnapshot; backward-compatible (optional decodes as nil from legacy sessions).

Sequence Diagram

sequenceDiagram
    participant U as User
    participant AD as AppDelegate
    participant CV as ContentView
    participant TM as TabManager
    participant W as Workspace
    participant SP as SessionPersistence

    U->>AD: Cmd+Shift+E (or menu / context menu)
    AD->>CV: post .commandPaletteEditWorkspaceDescriptionRequested
    CV->>CV: openCommandPaletteWorkspaceDescriptionInput()
    CV->>CV: mode = .workspaceDescriptionInput(target)

    U->>CV: types description text
    CV->>CV: commandPaletteWorkspaceDescriptionDraft updated

    U->>CV: Cmd+Return
    CV->>TM: setCustomDescription(tabId:, description:)
    TM->>W: setCustomDescription(_:)
    W->>W: normalizedCustomDescription() → trim + normalize EOL
    W-->>TM: customDescription updated (@Published)

    CV->>CV: dismissCommandPalette()

    Note over W,SP: On session save
    W->>SP: sessionSnapshot() includes customDescription
    SP->>SP: JSON encode → disk

    Note over SP,W: On session restore
    SP->>W: restoreSessionSnapshot() calls setCustomDescription
Loading

Comments Outside Diff (2)

  1. CLI/cmux.swift, line 113 (link)

    P2 --command flag not re-aligned in help text

    After adding --description <text> (which uses wider spacing), --command <text> was not updated to match the new column alignment. All other flags now use ~16 chars of padding before the description but --command still uses the old narrower spacing.

    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!

  2. Sources/ContentView.swift, line 1210-1214 (link)

    P2 .full markdown syntax may render non-interactive links in the sidebar

    AttributedString(markdown:options:) with .interpretedSyntax: .full parses all block-level Markdown including hyperlinks. In a SwiftUI Text view inside the sidebar, link attributes are applied visually (underlined, blue/tinted) but are not tappable — users may find clickable-looking links that do nothing.

    Consider restricting to inline-only syntax to avoid rendering non-interactive links:

Reviews (1): Last reviewed commit: "Add editable workspace descriptions" | Re-trigger Greptile

@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 14 files

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/AppDelegate.swift">

<violation number="1" location="Sources/AppDelegate.swift:9585">
P1: The global key event monitor consumes Up/Down arrow and Ctrl+N/Ctrl+P keys for command palette selection navigation before they reach the `NSTextView`. Although the `ContentView` notification handler ignores move-selection notifications when the mode is not `.commands`, the key event is already eaten at the `AppDelegate` level. This prevents keyboard caret navigation between lines in the multiline description editor. Add a mode check (similar to `paletteRequiresCommandReturn`) to skip the Up/Down interception when the palette is in `workspace_description_input` mode.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/AppDelegate.swift 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.

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
Sources/Workspace.swift (1)

6439-6464: ⚠️ Potential issue | 🟡 Minor

Preserve meaningful Markdown indentation.

trimmingCharacters(in: .whitespacesAndNewlines) strips leading spaces/tabs from the first line and trailing spaces from the last, which changes valid Markdown like indented code blocks or nested lists. Trim only newlines at the boundaries, and use whitespace trimming only for the emptiness check.

🛠️ Proposed fix
     private static func normalizedCustomDescription(_ description: String?) -> String? {
         let normalizedLineEndings = description?
             .replacingOccurrences(of: "\r\n", with: "\n")
             .replacingOccurrences(of: "\r", with: "\n")
-        let trimmed = normalizedLineEndings?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
-        guard !trimmed.isEmpty else { return nil }
-        return trimmed
+        let trimmedNewlines = normalizedLineEndings?.trimmingCharacters(in: .newlines) ?? ""
+        guard !trimmedNewlines.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return nil }
+        return trimmedNewlines
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Workspace.swift` around lines 6439 - 6464, The
normalizedCustomDescription function is currently removing leading spaces which
breaks Markdown indentation; change it to only normalize line endings then trim
only newlines from the description boundaries while using whitespace/newline
trimming solely for the emptiness check. Concretely, in
normalizedCustomDescription(_:) keep the line-ending normalization, compute an
emptiness check by trimming .whitespacesAndNewlines and returning nil if empty,
but return the description with only .newlines trimmed (not .whitespaces) so
leading spaces/tabs on lines are preserved; update any callers like
hasCustomDescription to rely on the unchanged API.
Sources/AppDelegate.swift (1)

9567-9604: ⚠️ Potential issue | 🟠 Major

Multiline description editing still swallows vertical caret navigation.

The new branch only changes submit behavior. commandPaletteSelectionDeltaForKeyboardNavigation(...) still runs first for every interactive palette mode, so plain Up/Down and Ctrl+N/Ctrl+P are consumed before the workspace_description_input editor can move its caret between lines.

Possible fix
+        let commandPaletteConsumesListNavigation = commandPaletteShortcutWindow.map {
+            !commandPaletteRequiresCommandReturnToSubmit(in: $0)
+        } ?? false
+
         if let delta = commandPaletteSelectionDeltaForKeyboardNavigation(
             flags: event.modifierFlags,
             chars: chars,
             keyCode: event.keyCode
         ),
            commandPaletteInteractiveInTargetWindow,
+           commandPaletteConsumesListNavigation,
            let paletteWindow = commandPaletteShortcutWindow {
             NotificationCenter.default.post(
                 name: .commandPaletteMoveSelection,
                 object: paletteWindow,
                 userInfo: ["delta": delta]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 9567 - 9604, The command palette
selection handler (commandPaletteSelectionDeltaForKeyboardNavigation(...)) is
consuming Up/Down and Ctrl+N/Ctrl+P before a multiline editor like
workspace_description_input can move its caret; change the logic so that when
the interactive palette's field editor is a multiline description editor (detect
via commandPaletteFieldEditorHasMarkedText(in:) or by checking the field/editor
identifier like "workspace_description_input") you skip calling/posting the
.commandPaletteMoveSelection and return false so the editor receives the key
event; specifically, in the if that currently checks
commandPaletteSelectionDeltaForKeyboardNavigation(...) and
commandPaletteInteractiveInTargetWindow/commandPaletteShortcutWindow, add a
guard that the palette field editor is not the multiline description editor (or
that commandPaletteFieldEditorHasMarkedText(in: paletteWindow) is false) before
posting .commandPaletteMoveSelection so vertical caret navigation is not
swallowed.
🧹 Nitpick comments (3)
cmuxTests/AppDelegateShortcutRoutingTests.swift (1)

2202-2262: Optional: also assert notification source metadata for routing provenance.

You already assert the correct notification name and target window. Adding an assertion for userInfo["source"] == "shortcut.editWorkspaceDescription" would make this test more regression-resistant if another trigger path starts posting the same notification.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/AppDelegateShortcutRoutingTests.swift` around lines 2202 - 2262,
The test testCmdShiftERequestsWorkspaceDescriptionEditorInCommandPalette
currently verifies the notification name and target window but not the routing
provenance; inside the notification handler registered as descriptionToken
(forName: .commandPaletteEditWorkspaceDescriptionRequested) assert that
notification.userInfo?["source"] as? String ==
"shortcut.editWorkspaceDescription" (or add a separate XCTAssert after the wait
using the captured notification.userInfo) so the test fails if the source
metadata differs; keep the existing expectation/teardown logic
(descriptionExpectation, observedWorkspaceWindow) and add this single assertion
to strengthen the regression check.
Sources/TerminalController.swift (1)

3346-3347: Consider unifying description validation between create and update paths for consistency.

Line 3346 uses v2RawString() (no trimming) then passes to ws.setCustomDescription() without validation, while the update path (line 4103–4108) validates via v2String() first and rejects whitespace-only input with an error. Both ultimately normalize through Workspace.setCustomDescription(), but the create path silently discards whitespace-only input whereas update path surfaces an error. Using the same validation strategy in both would make UX consistent.

Also applies to: 3359–3360

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TerminalController.swift` around lines 3346 - 3347, The create-path
is using v2RawString(...) which does no trimming/validation then calls
ws.setCustomDescription(...), causing whitespace-only descriptions to be
silently dropped while the update path uses v2String(...) and returns an error;
change the create-path usages (the v2RawString(...) calls around the description
handling and the other similar occurrences at the noted spots) to use
v2String(...) so input is trimmed/validated consistently, and propagate the
validation error back to the client in the same way the update path does;
reference v2RawString, v2String, ws.setCustomDescription, and
Workspace.setCustomDescription when making the change.
Sources/AppDelegate.swift (1)

5349-5352: Share the description-input mode identifier.

This has to stay exactly aligned with Sources/ContentView.swift’s workspace_description_input literal. If one side changes, this silently falls back to plain-Return submit behavior. A shared enum/constant would make that coupling explicit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 5349 - 5352, The function
commandPaletteRequiresCommandReturnToSubmit currently compares a hard-coded
string "workspace_description_input" which duplicates Sources/ContentView.swift
and can get out of sync; create a shared enum or constant (e.g.,
CommandPaletteMode or CommandPaletteMode.workspaceDescriptionInput) in a common
file and use its rawValue or constant name in both
commandPaletteRequiresCommandReturnToSubmit and Sources/ContentView.swift so
both sides reference the same identifier (update
commandPaletteSnapshot(windowId:).mode comparison to use the shared symbol
instead of the literal).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLI/cmux.swift`:
- Around line 1710-1712: Trim and validate the incoming --description before
adding it to params to match workspace-action set_description: replace the
current conditional that does if let descriptionOpt { params["description"] =
descriptionOpt } with logic that trims descriptionOpt using
descriptionOpt.trimmingCharacters(in: .whitespacesAndNewlines), then only set
params["description"] = trimmed if trimmed is not empty; if trimmed is empty,
reject/handle it the same way set_description does (e.g., return an error or
omit the param) so whitespace-only descriptions are not accepted.

In `@Sources/ContentView.swift`:
- Around line 12035-12039: The current conditional mounts
SidebarWorkspaceDescriptionText whenever tab.customDescription is non-nil, which
still renders for empty or whitespace-only descriptions; update the check around
where tab.customDescription is read (the branch that instantiates
SidebarWorkspaceDescriptionText) to trim the string and only render when the
trimmed description is non-empty (e.g., guard let desc =
tab.customDescription?.trimmingCharacters(in: .whitespacesAndNewlines),
!desc.isEmpty), passing the trimmed value to SidebarWorkspaceDescriptionText so
blank/whitespace descriptions are skipped and no phantom spacer row appears.
- Around line 4239-4284: isPaletteTextInputFirstResponder(_:) currently only
treats field editors or NSTextField as palette inputs, so add support for plain
NSTextView editors (like CommandPaletteMultilineTextEditorView's NSTextView) by
accepting an NSTextView that is editable and contained in the palette container;
rename helper firstEditableTextField(in:) to firstEditableTextInput(in:) and
update its implementation to search for both the first editable NSTextField and
the first editable NSTextView (check isEditable and that the view
isDescendant(of: containerView) or otherwise contained in the palette), and
update all callers to use firstEditableTextInput(in:) so the 80ms focus-lock
logic recognizes and preserves focus for the new plain NSTextView editor.
- Around line 4251-4273: The placeholder isn't hidden when text is
programmatically assigned because makeNSView and updateNSView set
textView.string directly; add a helper method setText(_:) on
CommandPaletteMultilineTextEditorView that sets textView.string, calls
updatePlaceholderVisibility() and forces a layout/frame sync (e.g.,
setNeedsLayout/layoutIfNeeded or adjust frame of textView) and then replace
direct assignments to textView.string in makeNSView and updateNSView with calls
to nsView.setText(text); keep other property syncs (placeholder,
onCommandReturn, accessibility) as-is and ensure context.coordinator.parent =
self remains in updateNSView.

---

Outside diff comments:
In `@Sources/AppDelegate.swift`:
- Around line 9567-9604: The command palette selection handler
(commandPaletteSelectionDeltaForKeyboardNavigation(...)) is consuming Up/Down
and Ctrl+N/Ctrl+P before a multiline editor like workspace_description_input can
move its caret; change the logic so that when the interactive palette's field
editor is a multiline description editor (detect via
commandPaletteFieldEditorHasMarkedText(in:) or by checking the field/editor
identifier like "workspace_description_input") you skip calling/posting the
.commandPaletteMoveSelection and return false so the editor receives the key
event; specifically, in the if that currently checks
commandPaletteSelectionDeltaForKeyboardNavigation(...) and
commandPaletteInteractiveInTargetWindow/commandPaletteShortcutWindow, add a
guard that the palette field editor is not the multiline description editor (or
that commandPaletteFieldEditorHasMarkedText(in: paletteWindow) is false) before
posting .commandPaletteMoveSelection so vertical caret navigation is not
swallowed.

In `@Sources/Workspace.swift`:
- Around line 6439-6464: The normalizedCustomDescription function is currently
removing leading spaces which breaks Markdown indentation; change it to only
normalize line endings then trim only newlines from the description boundaries
while using whitespace/newline trimming solely for the emptiness check.
Concretely, in normalizedCustomDescription(_:) keep the line-ending
normalization, compute an emptiness check by trimming .whitespacesAndNewlines
and returning nil if empty, but return the description with only .newlines
trimmed (not .whitespaces) so leading spaces/tabs on lines are preserved; update
any callers like hasCustomDescription to rely on the unchanged API.

---

Nitpick comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 2202-2262: The test
testCmdShiftERequestsWorkspaceDescriptionEditorInCommandPalette currently
verifies the notification name and target window but not the routing provenance;
inside the notification handler registered as descriptionToken (forName:
.commandPaletteEditWorkspaceDescriptionRequested) assert that
notification.userInfo?["source"] as? String ==
"shortcut.editWorkspaceDescription" (or add a separate XCTAssert after the wait
using the captured notification.userInfo) so the test fails if the source
metadata differs; keep the existing expectation/teardown logic
(descriptionExpectation, observedWorkspaceWindow) and add this single assertion
to strengthen the regression check.

In `@Sources/AppDelegate.swift`:
- Around line 5349-5352: The function
commandPaletteRequiresCommandReturnToSubmit currently compares a hard-coded
string "workspace_description_input" which duplicates Sources/ContentView.swift
and can get out of sync; create a shared enum or constant (e.g.,
CommandPaletteMode or CommandPaletteMode.workspaceDescriptionInput) in a common
file and use its rawValue or constant name in both
commandPaletteRequiresCommandReturnToSubmit and Sources/ContentView.swift so
both sides reference the same identifier (update
commandPaletteSnapshot(windowId:).mode comparison to use the shared symbol
instead of the literal).

In `@Sources/TerminalController.swift`:
- Around line 3346-3347: The create-path is using v2RawString(...) which does no
trimming/validation then calls ws.setCustomDescription(...), causing
whitespace-only descriptions to be silently dropped while the update path uses
v2String(...) and returns an error; change the create-path usages (the
v2RawString(...) calls around the description handling and the other similar
occurrences at the noted spots) to use v2String(...) so input is
trimmed/validated consistently, and propagate the validation error back to the
client in the same way the update path does; reference v2RawString, v2String,
ws.setCustomDescription, and Workspace.setCustomDescription when making the
change.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4bf6b0a2-31c6-49a0-992d-52e427edc74c

📥 Commits

Reviewing files that changed from the base of the PR and between e05d425 and 09c256c.

📒 Files selected for processing (14)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/SessionPersistence.swift
  • Sources/TabManager.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/SessionPersistenceTests.swift
  • cmuxTests/WorkspaceManualUnreadTests.swift
  • cmuxTests/WorkspaceUnitTests.swift

Comment thread CLI/cmux.swift
Comment on lines +1710 to +1712
if let descriptionOpt {
params["description"] = descriptionOpt
}

@coderabbitai coderabbitai Bot Apr 1, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize/validate new-workspace --description input for parity

workspace-action set_description trims and rejects empty descriptions, but new-workspace --description currently forwards raw input (including whitespace-only values). This creates inconsistent behavior for the same field.

Suggested fix
-            if let descriptionOpt {
-                params["description"] = descriptionOpt
-            }
+            if let descriptionOpt {
+                let description = descriptionOpt.trimmingCharacters(in: .whitespacesAndNewlines)
+                guard !description.isEmpty else {
+                    throw CLIError(message: "new-workspace --description requires non-empty text")
+                }
+                params["description"] = description
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 1710 - 1712, Trim and validate the incoming
--description before adding it to params to match workspace-action
set_description: replace the current conditional that does if let descriptionOpt
{ params["description"] = descriptionOpt } with logic that trims descriptionOpt
using descriptionOpt.trimmingCharacters(in: .whitespacesAndNewlines), then only
set params["description"] = trimmed if trimmed is not empty; if trimmed is
empty, reject/handle it the same way set_description does (e.g., return an error
or omit the param) so whitespace-only descriptions are not accepted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I checked the create path. new-workspace forwards the raw value, but workspace.create immediately normalizes through Workspace.setCustomDescription, so whitespace-only descriptions do not persist as state.

— Claude Code

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@lawrencecchen Thanks for confirming. Agreed — since workspace.create calls Workspace.setCustomDescription(...) which normalizes and treats whitespace-only as nil, there’s no risk of persisting empty descriptions. I’ll withdraw the CLI-side validation request.

If you want a small tidy-up later: we could trim client-side and simply omit the param when empty (no error), but it’s not necessary for correctness. Just say the word if you’d like that.


✏️ Learnings added
Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2475
File: CLI/cmux.swift:1710-1712
Timestamp: 2026-04-03T07:19:48.846Z
Learning: Repo manaflow-ai/cmux — Server path: TerminalController workspace.create applies Workspace.setCustomDescription(_:) which trims/normalizes and treats whitespace-only descriptions as nil. Therefore, CLI new-workspace can safely forward raw --description; whitespace-only input will not persist. No client-side error is required for this case.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: CLI/cmux.swift:1948-1978
Timestamp: 2026-03-25T00:33:26.452Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift, the set-workspace-color command requires exactly one trailing <hex> argument and enforces a strict 6-digit hex format (`#RRGGBB`, optional leading '#'); clear-workspace-color rejects any unexpected positional args beyond --workspace. This matches server-side normalization and prevents malformed inputs.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2398
File: CLI/cmux.swift:0-0
Timestamp: 2026-04-01T09:50:14.886Z
Learning: Repo manaflow-ai/cmux — CLI/cmux.swift: The cmux rpc subcommand prints the raw v2 JSON response by default and only applies formatIDs when the caller passes an explicit --id-format override. This preserves *_id fields for scripting unless formatting is opt-in.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: CLI/cmux.swift:1958-1960
Timestamp: 2026-03-25T00:33:00.675Z
Learning: Repo: manaflow-ai/cmux — CLI/cmux.swift set-workspace-color intentionally accepts both "#RRGGBB" and "RRGGBB" for ergonomics, matching server-side WorkspaceTabColorSettings.normalizedHex/TabManager normalization. Do not suggest enforcing a mandatory leading '#'; at most, suggest clarifying help/usage text.

Learnt from: apollow
Repo: manaflow-ai/cmux PR: 1089
File: Sources/TerminalController.swift:3180-3193
Timestamp: 2026-03-09T02:08:14.574Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, v2WorkspaceClearTags(params:) must only clear all tags when the "source" key is absent. If "source" is present but blank or non-string (v2String(...) returns nil), the API should return invalid_params. Current implementation uses hasSourceKey = params.keys.contains("source") and guards with if hasSourceKey && source == nil { return .err(...)}.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2398
File: CLI/cmux.swift:4036-4038
Timestamp: 2026-04-01T09:50:41.471Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift (buildInteractiveRemoteShellScript), remote shells intentionally export CMUX_SOCKET_PATH and CMUX_SOCKET as "127.0.0.1:<relayPort>" and prepend PATH with "$HOME/.cmux/bin"; CMUX_BUNDLED_CLI_PATH points to that wrapper. WorkspaceRemoteSessionController.remoteCLIWrapperScript() detects host:port, reads "~/.cmux/relay/<port>.daemon_path", and delegates to cmuxd-remote. Do not flag the non-UNIX value of CMUX_SOCKET_PATH/CMUX_SOCKET as a bug; it is the relay contract for remote sessions.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2514
File: CLI/cmux.swift:9698-9706
Timestamp: 2026-04-01T23:08:15.505Z
Learning: When loading/using a user-provided “custom executable” path in the CLI (e.g., from environment/config/UserDefaults), treat it as an executable file path only if it is NOT a directory and is executable. Concretely, check `isDirectory == false` before checking `isExecutableFile`; this avoids accepting directory-valued paths and should allow safe fallback to PATH or bundled/default executables when the candidate is invalid.

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:4012-4023
Timestamp: 2026-03-16T08:05:21.899Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, v2SurfaceSplitSized(params:) must validate "ratio" as follows: if the "ratio" key is present, it must be numeric (NSNumber/Double-coercible) and strictly 0 < ratio < 1, otherwise return invalid_params; when "ratio" is absent, use default 0.6. This mirrors the general pattern that a present-but-invalid param should yield invalid_params rather than falling back.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2514
File: Sources/GhosttyTerminalView.swift:3759-3761
Timestamp: 2026-04-01T22:57:41.165Z
Learning: Repo: manaflow-ai/cmux — In Sources/cmuxApp.swift, ClaudeCodeIntegrationSettings.customClaudePath(defaults:) trims surrounding whitespace and returns nil for empty/whitespace-only values; callers (e.g., TerminalSurface.createSurface(for:)) can safely set CMUX_CUSTOM_CLAUDE_PATH without additional trimming.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3607-3645
Timestamp: 2026-03-25T00:32:48.115Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, v2WorkspaceSetColor(params:) must validate the provided color with WorkspaceTabColorSettings.normalizedHex. If the color parameter is present but invalid, return invalid_params (no fallback). On success, apply the color and return the normalized (uppercase) `#RRGGBB` in the response payload.

Learnt from: ariel-wiz
Repo: manaflow-ai/cmux PR: 543
File: CLI/cmux.swift:2006-2007
Timestamp: 2026-03-11T09:27:14.201Z
Learning: In manaflow-ai/cmux (CLI/cmux.swift), all CLI error messages and help text use plain Swift string literals — there is no `String(localized:...)` usage anywhere in the repository, and no `Localizable.xcstrings` file exists. Do not flag CLI error/help strings as missing localization for this project.

Learnt from: arieltobiana
Repo: manaflow-ai/cmux PR: 1873
File: Sources/TerminalController.swift:4071-4087
Timestamp: 2026-03-20T17:18:30.333Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, v2WorkspaceAction(params:) -> case "set_color": palette names are resolved via WorkspaceTabColorSettings.defaultPaletteWithOverrides(), whose entries are always valid hex (validated by the UI). Therefore, additional normalization of entry.hex is unnecessary.

Learnt from: homanp
Repo: manaflow-ai/cmux PR: 883
File: CLI/cmux.swift:1569-1586
Timestamp: 2026-03-04T22:05:07.913Z
Learning: In manaflow-ai/cmux CLI (CLI/cmux.swift), for parity with existing browser commands, markdown.open intentionally normalizes --surface before resolving --workspace/--window, relying on server-side resolution to disambiguate final routing. Avoid one-off reordering in markdown; consider any change only as a coordinated, cross-command refactor.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2514
File: CLI/cmux.swift:9691-9711
Timestamp: 2026-04-01T22:58:26.254Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift (runClaudeTeams), custom Claude path resolution now trims whitespace and rejects paths that point to the cmux wrapper using isCmuxClaudeWrapper(), before falling back to PATH/bundled. The Resources/bin/claude wrapper also resolves the real path and compares against itself, requiring -f/-x to avoid recursion/self-reference.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2398
File: CLI/cmux.swift:0-0
Timestamp: 2026-04-01T09:50:23.728Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift within CMUXCLI.buildInteractiveRemoteShellScript(...), never export CMUX_TAB_ID from the workspace UUID. CMUX_TAB_ID must be surface-scoped: only set it when a surface ID is available (map CMUX_TAB_ID to CMUX_SURFACE_ID). Rationale: tab-action/rename-tab resolve CMUX_TAB_ID before CMUX_SURFACE_ID; workspace-scoped values misroute or fail.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:3491-3493
Timestamp: 2026-03-04T14:06:16.241Z
Learning: For manaflow-ai/cmux PR `#819` (Japanese i18n), keep scope limited to localization changes; UX enhancements like preferring workspace.customTitle in workspaceDisplayName() or altering move-target labels should be handled in a separate follow-up issue.

Learnt from: gaelic-ghost
Repo: manaflow-ai/cmux PR: 1926
File: scripts/build-sign-upload.sh:8-10
Timestamp: 2026-03-22T00:14:23.473Z
Learning: Repo: manaflow-ai/cmux — `scripts/lib/cmux-paths.sh` `cmux_paths_init()` intentionally preserves any pre-set `CMUX_*` environment variable overrides verbatim (does not canonicalize relative paths to absolute). This is a deliberate Stage 1 design choice; do not flag relative-override canonicalization as a bug unless a concrete reproducer is provided or a later stage explicitly tightens the override-semantics contract.

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/Workspace.swift:0-0
Timestamp: 2026-04-02T21:40:59.098Z
Learning: Repo: manaflow-ai/cmux — In Sources/Workspace.swift session snapshot logic, detected foreground command lines are only recorded if they pass SessionRestoreCommandSettings.isCommandAllowed(...). Commit f887189 moved gating to the SessionForegroundProcessCache so unallowed (potentially sensitive) commands are never written to the session JSON.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/ContentView.swift:2156-2171
Timestamp: 2026-03-23T06:08:14.740Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, openFileInTextEditor(_:) must attempt workspace.newTextEditorSplit(from:orientation:filePath:focus:) and, if that returns nil, fall back to workspace.newTextEditorSurface(inPane:filePath:focus:) using bonsplitController.focusedPaneId. Rationale: avoid dropping file-open requests when the focused panel is not pane-backed.

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:12:42.553Z
Learning: Repo: manaflow-ai/cmux — In Sources/Panels/BrowserPanel.swift, BrowserPanel.updateWorkspaceId(_:) is a dedicated method (var workspaceId) that updates both BrowserPanel.workspaceId and pickerMessageHandler?.updateWorkspaceId(_:) atomically. It is called from BrowserPanel.reattachToWorkspace(_:) when a panel moves between workspaces. This ensures BrowserPickerMessageHandler always posts notifications with the current workspaceId, not a stale one from panel initialization.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:776-777
Timestamp: 2026-04-03T03:35:40.100Z
Learning: Repo: manaflow-ai/cmux — As of PR `#2528`, workspace digit mapping (Cmd+1–9 and sidebar digits) intentionally uses TabManager.tabs as the canonical ordering for both sidebar rendering and app shortcut handling. Do not suggest switching to orderedSidebarWorkspaceIds() unless the model changes to a sidebar-ordered, group/collapse-aware source of truth.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:01:33.134Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, sendPickedElementToTerminal(workspaceId:summary:) resolves the target workspace via AppDelegate.shared?.workspaceFor(tabId: workspaceId), which searches across all mainWindowContexts (not self.tabManager which is active-window only). It first injects text into the focused terminal panel if it's a terminal, then falls back to iterating all panels in the workspace.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: apollow
Repo: manaflow-ai/cmux PR: 1089
File: CLI/cmux.swift:462-499
Timestamp: 2026-03-09T02:08:51.778Z
Learning: For Claude Code session tag extraction in CLI/cmux.swift, in ClaudeHookTagExtractor.extractTags(subtitle:body:), pre-redact sensitive spans (UUIDs, emails, access tokens, filesystem paths, ENV_VAR=..., long numerics) across the combined body+subtitle using unanchored sensitiveSpanPatterns before tokenization. Then tokenize and still filter each token with anchored sensitivePatterns. Rationale: prevents PII/path fragments from slipping into searchable tags after delimiter splitting.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift
Comment on lines +4251 to +4273
func makeNSView(context: Context) -> CommandPaletteMultilineTextEditorView {
let view = CommandPaletteMultilineTextEditorView()
view.placeholder = placeholder
view.textView.string = text
view.textView.delegate = context.coordinator
view.textView.onCommandReturn = onCommandReturn
view.textView.setAccessibilityLabel(accessibilityLabel)
view.textView.setAccessibilityIdentifier(accessibilityIdentifier)
view.setAccessibilityIdentifier(accessibilityIdentifier)
return view
}

func updateNSView(_ nsView: CommandPaletteMultilineTextEditorView, context: Context) {
context.coordinator.parent = self
if nsView.textView.string != text {
nsView.textView.string = text
}
nsView.placeholder = placeholder
nsView.textView.onCommandReturn = onCommandReturn
nsView.textView.setAccessibilityLabel(accessibilityLabel)
nsView.textView.setAccessibilityIdentifier(accessibilityIdentifier)
nsView.setAccessibilityIdentifier(accessibilityIdentifier)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '4251,4273p' Sources/ContentView.swift
printf '\n---\n'
sed -n '4334,4444p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 5703


🏁 Script executed:

# Search for setText, setString, or similar methods in CommandPaletteMultilineTextEditorView
rg -A 3 'CommandPaletteMultilineTextEditorView' Sources/ContentView.swift | grep -E '(setText|setString|func)' | head -20

Repository: manaflow-ai/cmux

Length of output: 239


🏁 Script executed:

# Check what calls CommandPaletteMultilineTextEditorView and how it sets text
rg 'CommandPaletteMultilineTextEditorView' Sources/ContentView.swift -B 5 -A 10 | head -100

Repository: manaflow-ai/cmux

Length of output: 1910


🏁 Script executed:

# Look for NSText.didChangeNotification usage to understand the notification behavior
rg 'didChangeNotification|textDidChange' Sources/ContentView.swift -B 2 -A 5

Repository: manaflow-ai/cmux

Length of output: 2472


🏁 Script executed:

# Check if syncTextViewFrameToContentSize exists and what it does
rg 'syncTextViewFrameToContentSize' Sources/ContentView.swift -B 2 -A 8

Repository: manaflow-ai/cmux

Length of output: 1512


🏁 Script executed:

# Check if there are other direct textView.string assignments in CommandPaletteMultilineTextEditorView
# Also look at the full CommandPaletteMultilineTextEditorView class definition
rg -A 150 'private final class CommandPaletteMultilineTextEditorView' Sources/ContentView.swift | grep -E '(textView\.string|func)' | head -20

Repository: manaflow-ai/cmux

Length of output: 513


Programmatic text loads won't hide the placeholder.

Direct assignment to textView.string in makeNSView and updateNSView bypasses NSText.didChangeNotification, so updatePlaceholderVisibility() is never called. Since placeholder visibility is only refreshed via placeholder.didSet (which fires with empty text before assignment) and the user-edit notification, opening an existing description leaves the placeholder painted on top of the prefilled text until the user edits.

Fix: Create a setText() helper method that sets the string, updates placeholder visibility, and syncs frame layout:

Suggested fix
         func makeNSView(context: Context) -> CommandPaletteMultilineTextEditorView {
             let view = CommandPaletteMultilineTextEditorView()
             view.placeholder = placeholder
-            view.textView.string = text
+            view.setText(text)
             view.textView.delegate = context.coordinator
             view.textView.onCommandReturn = onCommandReturn
             view.textView.setAccessibilityLabel(accessibilityLabel)
             view.textView.setAccessibilityIdentifier(accessibilityIdentifier)
             view.setAccessibilityIdentifier(accessibilityIdentifier)
             return view
         }

         func updateNSView(_ nsView: CommandPaletteMultilineTextEditorView, context: Context) {
             context.coordinator.parent = self
-            if nsView.textView.string != text {
-                nsView.textView.string = text
-            }
+            nsView.setText(text)
             nsView.placeholder = placeholder
             nsView.textView.onCommandReturn = onCommandReturn
             nsView.textView.setAccessibilityLabel(accessibilityLabel)
             nsView.textView.setAccessibilityIdentifier(accessibilityIdentifier)
             nsView.setAccessibilityIdentifier(accessibilityIdentifier)
     private final class CommandPaletteMultilineTextEditorView: NSView {
+        func setText(_ text: String) {
+            guard textView.string != text else { return }
+            textView.string = text
+            updatePlaceholderVisibility()
+            syncTextViewFrameToContentSize()
+        }
+
         var placeholder: String = "" {
             didSet {
                 placeholderField.stringValue = placeholder
                 updatePlaceholderVisibility()
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 4251 - 4273, The placeholder isn't
hidden when text is programmatically assigned because makeNSView and
updateNSView set textView.string directly; add a helper method setText(_:) on
CommandPaletteMultilineTextEditorView that sets textView.string, calls
updatePlaceholderVisibility() and forces a layout/frame sync (e.g.,
setNeedsLayout/layoutIfNeeded or adjust frame of textView) and then replace
direct assignments to textView.string in makeNSView and updateNSView with calls
to nsView.setText(text); keep other property syncs (placeholder,
onCommandReturn, accessibility) as-is and ensure context.coordinator.parent =
self remains in updateNSView.

Comment thread Sources/ContentView.swift
Comment on lines +12035 to +12039
if let description = tab.customDescription {
SidebarWorkspaceDescriptionText(
markdown: description,
isActive: usesInvertedActiveForeground
)

@coderabbitai coderabbitai Bot Apr 1, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Skip blank descriptions before rendering.

This branch mounts SidebarWorkspaceDescriptionText for any non-nil value, including "" / whitespace-only payloads coming from save, restore, CLI, or socket paths. That leaves a phantom spacer row under the workspace title.

Suggested direction
-            if let description = tab.customDescription {
+            if let description = tab.customDescription,
+               !description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                 SidebarWorkspaceDescriptionText(
                     markdown: description,
                     isActive: usesInvertedActiveForeground
                 )
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 12035 - 12039, The current
conditional mounts SidebarWorkspaceDescriptionText whenever
tab.customDescription is non-nil, which still renders for empty or
whitespace-only descriptions; update the check around where
tab.customDescription is read (the branch that instantiates
SidebarWorkspaceDescriptionText) to trim the string and only render when the
trimmed description is non-empty (e.g., guard let desc =
tab.customDescription?.trimmingCharacters(in: .whitespacesAndNewlines),
!desc.isEmpty), passing the trimmed value to SidebarWorkspaceDescriptionText so
blank/whitespace descriptions are skipped and no phantom spacer row appears.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This one is a false positive on current code. Workspace descriptions are normalized through Workspace.setCustomDescription before rendering, and whitespace-only values are collapsed to nil, so the sidebar does not get a phantom blank description row.

— Claude Code

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@lawrencecchen You're right—thanks for the correction. Workspace.setCustomDescription(_:) normalizes the value (via normalizedCustomDescription) so whitespace-only descriptions become nil, and the CLI/socket paths also enforce non-empty input; the sidebar won’t render a phantom row. I’m withdrawing the suggestion.


✏️ Learnings added
Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2475
File: Sources/ContentView.swift:12775-12779
Timestamp: 2026-04-03T07:19:41.602Z
Learning: Repo: manaflow-ai/cmux — Sources/Workspace.swift — Workspace.setCustomDescription(_:) calls normalizedCustomDescription(...) which converts CRLF/CR to "\n", trims whitespace/newlines, and returns nil for empty/whitespace-only input. Therefore UI sites (e.g., TabItemView in Sources/ContentView.swift) can rely on workspace.customDescription being nil when blank and should not add extra trimmed-string checks before rendering.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/ContentView.swift : In `TabItemView` in `ContentView.swift`: uses `Equatable` conformance + `.equatable()` to skip body re-evaluation during typing. Do not add `EnvironmentObject`, `ObservedObject` (besides `tab`), or `Binding` properties without updating the `==` function. Do not remove `.equatable()` from the ForEach call site. Do not read `tabManager` or `notificationStore` in the body; use precomputed `let` parameters instead.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T00:03:34.904Z
Learning: Applies to **/ContentView.swift : In `TabItemView` in `ContentView.swift`: uses `Equatable` conformance + `.equatable()` to skip body re-evaluation during typing. Do not add `EnvironmentObject`, `ObservedObject` (besides `tab`), or `Binding` properties without updating the `==` function. Do not remove `.equatable()` from the ForEach call site. Do not read `tabManager` or `notificationStore` in the body; use precomputed `let` parameters instead.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/ContentView.swift:8917-8921
Timestamp: 2026-04-03T03:35:56.499Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — ShortcutHintModifierPolicy.shouldShowHints(for:) reveals sidebar/titlebar shortcut hints when the current modifier flags exactly equal KeyboardShortcutSettings.shortcut(for: .selectWorkspaceByNumber).modifierFlags, and returns false for chorded-number mappings. This follows the configured workspace-number modifier (not strictly Command-only). TabItemView’s close-button suppression via showsModifierShortcutHints therefore tracks the configured modifier. The debug toggle showHintsOnCommandHold predates customizable shortcuts and conceptually means “show hints on holding the configured workspace-number modifier(s).”

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2514
File: Sources/GhosttyTerminalView.swift:3759-3761
Timestamp: 2026-04-01T22:57:41.165Z
Learning: Repo: manaflow-ai/cmux — In Sources/cmuxApp.swift, ClaudeCodeIntegrationSettings.customClaudePath(defaults:) trims surrounding whitespace and returns nil for empty/whitespace-only values; callers (e.g., TerminalSurface.createSurface(for:)) can safely set CMUX_CUSTOM_CLAUDE_PATH without additional trimming.

Learnt from: arieltobiana
Repo: manaflow-ai/cmux PR: 1873
File: Sources/TerminalController.swift:4071-4087
Timestamp: 2026-03-20T17:18:30.333Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, v2WorkspaceAction(params:) -> case "set_color": palette names are resolved via WorkspaceTabColorSettings.defaultPaletteWithOverrides(), whose entries are always valid hex (validated by the UI). Therefore, additional normalization of entry.hex is unnecessary.

Learnt from: Znboston
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-26T19:00:12.434Z
Learning: Repo: manaflow-ai/cmux — Sources/TabManager.swift — In `moveWorkspaceOutOfGroup(_:toSidebarIndex:)`, do NOT call `syncUngroupedSidebarOrder()` after inserting into `sidebarOrder`. That helper derives order from the `tabs` array insertion index, which is unrelated to the user's drag-drop target position. `sidebarOrder` is the source of truth for sidebar rendering; the raw insertion at the resolved sidebar index is correct and sufficient. Calling `syncUngroupedSidebarOrder()` would overwrite the intended drop position with stale `tabs` order, causing a snap-back regression.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: apollow
Repo: manaflow-ai/cmux PR: 1089
File: Sources/ContentView.swift:6055-6057
Timestamp: 2026-03-09T02:09:08.742Z
Learning: In manaflow-ai/cmux (Sources/ContentView.swift), within CommandPaletteSwitcherSearchIndexer.metadataKeywordsForSearch(_:detail:), when tags are present, only add "tag" and "topic" to contextKeywords; do not append "claude" unconditionally to avoid false-positive switcher matches.

Learnt from: andrekat
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-29T11:05:01.662Z
Learning: In manaflow-ai/cmux PR `#1773`: `setActiveProfileName(_:)` on `TabManager` (Sources/TabManager.swift) must call `updateWindowTitleForSelectedTab()` explicitly — the save and delete paths do not go through `selectedTabId.didSet`, so without the explicit call the AppKit window title is left stale with the old profile prefix. This was confirmed fixed in commit be9eaa26.

Learnt from: apollow
Repo: manaflow-ai/cmux PR: 1089
File: Sources/TerminalController.swift:3180-3193
Timestamp: 2026-03-09T02:08:14.574Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, v2WorkspaceClearTags(params:) must only clear all tags when the "source" key is absent. If "source" is present but blank or non-string (v2String(...) returns nil), the API should return invalid_params. Current implementation uses hasSourceKey = params.keys.contains("source") and guards with if hasSourceKey && source == nil { return .err(...)}.

Learnt from: outoftime
Repo: manaflow-ai/cmux PR: 1528
File: Sources/GhosttyTerminalView.swift:3220-3228
Timestamp: 2026-03-17T18:25:33.286Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift within TerminalSurface.createSurface(for:), when constructing XDG_DATA_DIRS for Fish vendor_conf.d auto-sourcing, treat empty or whitespace-only values from initialEnvironmentOverrides, env, getenv, and ProcessInfo as unset before prefixing the integrationDir. This avoids producing a trailing colon. Keep XDG_DATA_DIRS in protectedStartupEnvironmentKeys so initialEnvironmentOverrides cannot overwrite the prefixed value.

Learnt from: Znboston
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-26T21:24:07.678Z
Learning: Repo: manaflow-ai/cmux — Sources/SidebarSelectionState.swift — In `SidebarInteractionController.registerClick(on:location:...)`, call sites that use `onTapGesture` cannot supply real coordinates (SwiftUI's `onTapGesture` does not expose the tap location), so they pass `location: nil`. The `CGPoint?`-nil-gated distance check in `registerClick` is intentionally designed to skip spatial filtering for these callers. Target identity discrimination (`.groupHeader(groupId:)`, `.workspace(workspaceId:parentGroupId:)`, `.emptyArea`) prevents false double-click triggers across distinct targets. Do not flag missing coordinates from `onTapGesture` callsites as a defect; fixing it would require switching to a different gesture recognizer system.

Learnt from: homanp
Repo: manaflow-ai/cmux PR: 883
File: CLI/cmux.swift:1569-1586
Timestamp: 2026-03-04T22:05:07.913Z
Learning: In manaflow-ai/cmux CLI (CLI/cmux.swift), for parity with existing browser commands, markdown.open intentionally normalizes --surface before resolving --workspace/--window, relying on server-side resolution to disambiguate final routing. Avoid one-off reordering in markdown; consider any change only as a coordinated, cross-command refactor.

Learnt from: debgotwired
Repo: manaflow-ai/cmux PR: 1149
File: Sources/ContentView.swift:3977-3978
Timestamp: 2026-03-10T09:33:29.814Z
Learning: In manaflow-ai/cmux, for Sources/ContentView.swift, do not modify the sync-seeding behavior of scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:). It is shared infrastructure across all submenus. Treat brief initial flashes as consistent with existing submenus. Any UX improvement (for example making seeding synchronous on a forced corpus refresh) should not be implemented in feature-scoped PRs; instead implement and test such UX changes globally in a dedicated follow-up PR.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/ContentView.swift:3194-3196
Timestamp: 2026-03-04T14:05:48.668Z
Learning: In manaflow-ai/cmux (PR `#819`), Sources/ContentView.swift: The command palette’s external window labels intentionally use the global window index from the full orderedSummaries (index + 1), matching the Window menu in AppDelegate. Do not reindex after filtering out the current window to avoid mismatches (“Window 2” for an external window is expected).

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:12:42.553Z
Learning: Repo: manaflow-ai/cmux — In Sources/Panels/BrowserPanel.swift, BrowserPanel.updateWorkspaceId(_:) is a dedicated method (var workspaceId) that updates both BrowserPanel.workspaceId and pickerMessageHandler?.updateWorkspaceId(_:) atomically. It is called from BrowserPanel.reattachToWorkspace(_:) when a panel moves between workspaces. This ensures BrowserPickerMessageHandler always posts notifications with the current workspaceId, not a stale one from panel initialization.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/ContentView.swift:2156-2171
Timestamp: 2026-03-23T06:08:14.740Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, openFileInTextEditor(_:) must attempt workspace.newTextEditorSplit(from:orientation:filePath:focus:) and, if that returns nil, fall back to workspace.newTextEditorSurface(inPane:filePath:focus:) using bonsplitController.focusedPaneId. Rationale: avoid dropping file-open requests when the focused panel is not pane-backed.

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:01:33.134Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, sendPickedElementToTerminal(workspaceId:summary:) resolves the target workspace via AppDelegate.shared?.workspaceFor(tabId: workspaceId), which searches across all mainWindowContexts (not self.tabManager which is active-window only). It first injects text into the focused terminal panel if it's a terminal, then falls back to iterating all panels in the workspace.

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/Workspace.swift:0-0
Timestamp: 2026-04-02T21:40:59.098Z
Learning: Repo: manaflow-ai/cmux — In Sources/Workspace.swift session snapshot logic, detected foreground command lines are only recorded if they pass SessionRestoreCommandSettings.isCommandAllowed(...). Commit f887189 moved gating to the SessionForegroundProcessCache so unallowed (potentially sensitive) commands are never written to the session JSON.

Learnt from: Znboston
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-26T19:00:12.434Z
Learning: Repo: manaflow-ai/cmux — Sources/GroupHeaderView.swift and Sources/ContentView.swift — Palette color entry labels (`Text(entry.name)`) use dynamic palette data loaded at runtime and cannot be statically keyed with `String(localized:)`. This matches the existing workspace color picker pattern in ContentView.swift. Palette name localization would require restructuring the color palette system and is intentionally deferred; do not flag `entry.name` as a missing localization in this codebase.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:3491-3493
Timestamp: 2026-03-04T14:06:16.241Z
Learning: For manaflow-ai/cmux PR `#819` (Japanese i18n), keep scope limited to localization changes; UX enhancements like preferring workspace.customTitle in workspaceDisplayName() or altering move-target labels should be handled in a separate follow-up issue.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:776-777
Timestamp: 2026-04-03T03:35:40.100Z
Learning: Repo: manaflow-ai/cmux — As of PR `#2528`, workspace digit mapping (Cmd+1–9 and sidebar digits) intentionally uses TabManager.tabs as the canonical ordering for both sidebar rendering and app shortcut handling. Do not suggest switching to orderedSidebarWorkspaceIds() unless the model changes to a sidebar-ordered, group/collapse-aware source of truth.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: CLI/cmux.swift:1958-1960
Timestamp: 2026-03-25T00:33:00.675Z
Learning: Repo: manaflow-ai/cmux — CLI/cmux.swift set-workspace-color intentionally accepts both "#RRGGBB" and "RRGGBB" for ergonomics, matching server-side WorkspaceTabColorSettings.normalizedHex/TabManager normalization. Do not suggest enforcing a mandatory leading '#'; at most, suggest clarifying help/usage text.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: CLI/cmux.swift:1948-1978
Timestamp: 2026-03-25T00:33:26.452Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift, the set-workspace-color command requires exactly one trailing <hex> argument and enforces a strict 6-digit hex format (`#RRGGBB`, optional leading '#'); clear-workspace-color rejects any unexpected positional args beyond --workspace. This matches server-side normalization and prevents malformed inputs.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: moyashin63
Repo: manaflow-ai/cmux PR: 1074
File: Sources/AppDelegate.swift:7523-7545
Timestamp: 2026-03-09T01:38:24.337Z
Learning: When the command palette is visible (as in manaflow-ai/cmux Sources/AppDelegate.swift), ensure the shortcut handling consumes most Command shortcuts to protect the palette's text input. Specifically, do not allow UI zoom shortcuts (Cmd+Shift+= / Cmd+Shift+− / Cmd+Shift+0) to trigger while the palette is open. Do not reorder shortcut handlers (e.g., uiZoomShortcutAction(...)) to bypass this guard; users must close the palette before performing zoom actions. This guideline should apply to Swift source files handling global shortcuts within the app.

Learnt from: zlatkoc
Repo: manaflow-ai/cmux PR: 1368
File: Sources/Panels/BrowserPanel.swift:69-69
Timestamp: 2026-03-13T13:46:01.733Z
Learning: Do not wrap engine/brand name literals (e.g., displayName values such as Google, DuckDuckGo, Bing, Kagi, Startpage) in String(localized: ...). These are brand/product names that are not translatable UI text. Localization should apply to generic UI strings (labels, buttons, error messages, etc.). Apply this guideline across Swift source files under Sources/ (notably in BrowserPanel.swift and similar UI/engine-related strings) and flag only brand-name strings that are part of user-facing UI text appropriately for translation scope.

Learnt from: kjb0787
Repo: manaflow-ai/cmux PR: 1461
File: Sources/GhosttyTerminalView.swift:5904-5905
Timestamp: 2026-03-15T19:22:32.330Z
Learning: In Swift files under the Sources directory that manage terminal/scroll behavior, ensure the following: when preserving scroll across workspace switches, save savedScrollRow only if the scrollbar offset is greater than 0 (indicating the user has scrolled up). On restore, call scroll_to_row only if savedScrollRow is non-nil; if it is nil, rely on synchronizeScrollView() to keep bottom-pinned sessions following new output. This pattern should be applied wherever GhosttyTerminalView-like views implement setVisibleInUI(_:) to maintain consistent user scroll state across workspace switches.

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-16T08:02:06.824Z
Learning: In Swift sources, for any panel_id-only route handling in v2PanelMarkBackground(params:) and v2PanelMarkForeground(params:), first attempt v2ResolveTabManager(params:). Use the manager only if it actually owns the panelId; otherwise fall back to AppDelegate.shared?.locateSurface(surfaceId:) to locate the correct TabManager across windows. Apply this pattern to all panel_id-only routes to avoid active-window bias.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/GhosttyTerminalView.swift:3891-3903
Timestamp: 2026-04-02T21:37:21.463Z
Learning: In Swift source files like Sources/GhosttyTerminalView.swift, avoid logging raw startup commands or initialInput even in DEBUG (to prevent leaking sensitive paths/tokens and multiline content). If you need to diagnose startup/input, log only non-sensitive metadata such as (1) presence flags (e.g., hasStartupCommand/hasInitialInput), (2) byte counts, and (3) the relevant surface id (so issues can be correlated without exposing the underlying strings).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:6439-6444
Timestamp: 2026-04-03T03:35:54.082Z
Learning: In this repo’s keyboard shortcut implementation, ensure `KeyboardShortcutSettings.setShortcut(...)` does nothing (no-op) when `KeyboardShortcutSettings.isManagedBySettingsFile(action)` returns `true` (i.e., the shortcut is managed via `settings.json`). This prevents writing back overrides into `UserDefaults` and keeps `settings.json` as the source of truth.

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

2 issues found across 4 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/ContentView.swift">

<violation number="1" location="Sources/ContentView.swift:4295">
P1: Return now triggers submit in the workspace description editor, so users can’t insert a normal newline as documented. Submit should only happen on Command+Return.</violation>
</file>

<file name="Resources/Localizable.xcstrings">

<violation number="1" location="Resources/Localizable.xcstrings:86789">
P3: This localization value no longer matches the key’s `defaultValue` in code. If the catalog entry is missing (or in fallback scenarios), users will see outdated key instructions.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/ContentView.swift Outdated
"en": {
"stringUnit": {
"state": "translated",
"value": "Press Enter to save. Press Shift-Enter for a new line, or Escape to cancel."

@cubic-dev-ai cubic-dev-ai Bot Apr 1, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This localization value no longer matches the key’s defaultValue in code. If the catalog entry is missing (or in fallback scenarios), users will see outdated key instructions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/Localizable.xcstrings, line 86789:

<comment>This localization value no longer matches the key’s `defaultValue` in code. If the catalog entry is missing (or in fallback scenarios), users will see outdated key instructions.</comment>

<file context>
@@ -86786,13 +86786,13 @@
           "stringUnit": {
             "state": "translated",
-            "value": "Press Command-Return to save. Press Return for a new line, or Escape to cancel."
+            "value": "Press Enter to save. Press Shift-Enter for a new line, or Escape to cancel."
           }
         },
</file context>
Fix with Cubic

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 765a2d58ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/ContentView.swift Outdated
Comment on lines +4492 to +4494
private func textDidChange(_ notification: Notification) {
updatePlaceholderVisibility()
publishHeight()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Propagate multiline description edits into bound state

The new command-palette multiline editor never writes user edits back to the @Binding text, so submitting the workspace description uses stale state (commandPaletteWorkspaceDescriptionDraft) instead of what was typed. In this callback you only update placeholder/height, and when SwiftUI re-renders, updateNSView can copy the stale binding back into textView.string, discarding edits. This makes description editing unreliable or ineffective in normal use.

Useful? React with 👍 / 👎.

@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

Caution

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

⚠️ Outside diff range comments (1)
Sources/ContentView.swift (1)

4084-4228: ⚠️ Potential issue | 🔴 Critical

The multiline editor never writes user edits back into commandPaletteWorkspaceDescriptionDraft.

The Coordinator class is set as the delegate for the multiline NSTextView but does not implement textDidChange(_:). User edits trigger NSText.didChangeNotification but have no path back to parent.text. Additionally, updateEditorNSView writes nsView.textView.string = text without guarding against isProgrammaticMutation, creating a stale-binding hazard when SwiftUI refreshes. Add the missing delegate method and guard programmatic writes to match the pattern used in FeedbackComposerMessageEditor.Coordinator.

Suggested fix
         final class Coordinator: NSObject, NSTextFieldDelegate, NSTextViewDelegate {
             var parent: CommandPaletteSearchFieldRepresentable
             var isProgrammaticMutation = false
             weak var parentField: CommandPaletteNativeTextField?
             var pendingFocusRequest: Bool?
             var editorTextDidChangeObserver: NSObjectProtocol?
             weak var observedEditor: NSTextView?

             init(parent: CommandPaletteSearchFieldRepresentable) {
                 self.parent = parent
             }

             deinit {
                 detachEditorTextDidChangeObserver()
             }

+            func textDidChange(_ notification: Notification) {
+                guard !isProgrammaticMutation else { return }
+                guard let textView = notification.object as? NSTextView,
+                      !textView.isFieldEditor else { return }
+                parent.text = textView.string
+            }

             func controlTextDidChange(_ obj: Notification) {
                 guard !isProgrammaticMutation else { return }
                 guard let field = obj.object as? NSTextField else { return }
                 parent.text = field.stringValue
             }
             if configuration.allowsMultiline {
                 guard let nsView = nsView as? CommandPaletteMultilineTextEditorView else { return }
                 if nsView.textView.string != text {
+                    isProgrammaticMutation = true
                     nsView.textView.string = text
+                    isProgrammaticMutation = false
                 }
                 nsView.placeholder = placeholder

Also applies to: 4357-4370, 4403-4425

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 4084 - 4228, The multiline editor
never propagates user edits back because Coordinator doesn't implement
textDidChange(_:), and programmatic writes aren't guarded; implement
NSTextViewDelegate func textDidChange(_:) in Coordinator to set parent.text when
!isProgrammaticMutation (same pattern as the existing Notification observer),
and update the view-updating path (the code that sets nsView.textView.string =
text—look for updateEditorNSView / where nsView.textView.string is assigned) to
wrap assignments with isProgrammaticMutation true/false so programmatic updates
don't trigger the change handler; also ensure
attachEditorTextDidChangeObserverIfNeeded and detachEditorTextDidChangeObserver
remain consistent with the delegate-based update.
♻️ Duplicate comments (1)
Sources/ContentView.swift (1)

3957-3977: ⚠️ Potential issue | 🔴 Critical

The palette focus lock will steal focus from this new NSTextView.

This editor is a plain NSTextView, but WindowCommandPaletteOverlayController still only treats field editors / NSTextField as palette text inputs. The 80 ms focus-lock timer will therefore decide the editor is unfocused and move first responder back to containerView, dropping the caret shortly after open or when the window becomes key again. Please verify by opening the editor and waiting briefly or by re-keying the window.

Also applies to: 4476-4706

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 3957 - 3977, The new
CommandPaletteSearchFieldRepresentable uses an NSTextView-like editor but
WindowCommandPaletteOverlayController's focus-lock logic only recognizes NSTex
tField/field editors, so the 80ms timer reclaims firstResponder; update the
controller to treat this editor as a palette text input by extending its
detection (e.g., include NSTextView, objects conforming to NSTextInputClient, or
check for presence of CommandPaletteSearchFieldRepresentable via responder
chain) so that commandPaletteShouldFocusWorkspaceDescriptionEditor and similar
bindings are respected and the caret isn't stolen; ensure the same change is
applied to the other affected range (around the code handling the palette focus
lock referenced earlier).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/ContentView.swift`:
- Around line 1725-1729: The localized inputHint
("commandPalette.description.workspaceInputHint") is inconsistent with the
actual key handling: the UI currently saves on Return and only allows
Shift-Return for a newline; update the behavior so hint and handler match.
Either change the inputHint string to say "Press Return to save. Press
Shift-Return for a new line, or Escape to cancel." (update the localized
defaultValue and any other occurrence at the other location), or change the key
handling logic (the return/enter key handling in ContentView's text/key event
handlers such as the method that handles Return/Shift-Return/Command-Return) so
that Command+Return triggers save and plain Return inserts a newline; ensure
Escape still cancels and update both the inputHint property and the
corresponding key handling implementation consistently.

---

Outside diff comments:
In `@Sources/ContentView.swift`:
- Around line 4084-4228: The multiline editor never propagates user edits back
because Coordinator doesn't implement textDidChange(_:), and programmatic writes
aren't guarded; implement NSTextViewDelegate func textDidChange(_:) in
Coordinator to set parent.text when !isProgrammaticMutation (same pattern as the
existing Notification observer), and update the view-updating path (the code
that sets nsView.textView.string = text—look for updateEditorNSView / where
nsView.textView.string is assigned) to wrap assignments with
isProgrammaticMutation true/false so programmatic updates don't trigger the
change handler; also ensure attachEditorTextDidChangeObserverIfNeeded and
detachEditorTextDidChangeObserver remain consistent with the delegate-based
update.

---

Duplicate comments:
In `@Sources/ContentView.swift`:
- Around line 3957-3977: The new CommandPaletteSearchFieldRepresentable uses an
NSTextView-like editor but WindowCommandPaletteOverlayController's focus-lock
logic only recognizes NSTex tField/field editors, so the 80ms timer reclaims
firstResponder; update the controller to treat this editor as a palette text
input by extending its detection (e.g., include NSTextView, objects conforming
to NSTextInputClient, or check for presence of
CommandPaletteSearchFieldRepresentable via responder chain) so that
commandPaletteShouldFocusWorkspaceDescriptionEditor and similar bindings are
respected and the caret isn't stolen; ensure the same change is applied to the
other affected range (around the code handling the palette focus lock referenced
earlier).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e32a2aa7-ac7b-4ad7-8626-ffc338165724

📥 Commits

Reviewing files that changed from the base of the PR and between 765a2d5 and a907336.

📒 Files selected for processing (1)
  • Sources/ContentView.swift

Comment thread Sources/ContentView.swift

@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 1 file (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/ContentView.swift">

<violation number="1" location="Sources/ContentView.swift:4084">
P2: Multiline workspace description edits don’t update the bound draft because the new coordinator doesn’t handle NSTextView text changes. This leaves commandPaletteWorkspaceDescriptionDraft stuck at the old value when the user presses Enter.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/ContentView.swift 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.

Actionable comments posted: 2

♻️ Duplicate comments (3)
Sources/ContentView.swift (3)

1725-1729: ⚠️ Potential issue | 🟡 Minor

The description hint is still backwards.

The handler saves on plain Return and only inserts a newline on Shift-Return, but this copy still says Command-Return saves and Return inserts a newline. Update the localized text or the key handling so they match.

Also applies to: 4171-4178

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 1725 - 1729, The UI hint text in the
ContentView's inputHint property is incorrect (it says Command-Return saves and
Return inserts a newline) — update the localized default string for inputHint to
match the actual key handling (e.g., "Press Return to save. Press Shift-Return
for a new line, or Escape to cancel.") or alternatively change the key-handling
logic to match the current copy; ensure you update both occurrences of the
inputHint localized text (the var inputHint in ContentView and the second
occurrence around the other inputHint/description location) so the displayed
help text matches the implemented behavior.

3866-3885: ⚠️ Potential issue | 🔴 Critical

The shared editor path is still incompatible with the palette focus lock.

These flows now mount a plain NSTextView via CommandPaletteEditorView, but the palette focus-lock helpers above still only recognize field editors / NSTextField inputs. After the 80 ms lock fires, first responder can get pulled off the editor and back to the container, which breaks typing in both rename and description mode. Please verify both flows still keep keyboard focus after opening.

Also applies to: 3957-3976, 4236-4305

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 3866 - 3885, The palette focus-lock
helpers are only detecting NSTextField/field editors and not the plain
NSTextView mounted by CommandPaletteEditorView, causing firstResponder to be
stolen after the 80ms lock; update the focus-lock logic (the helpers used with
CommandPaletteSearchFieldRepresentable and the isCommandPaletteRenameFocused
binding) to also recognize NSTextView instances (or views conforming to
NSTextInputClient / the CommandPaletteEditorView type) as valid focus targets so
the lock does not move focus back to the container, and verify both rename and
description flows keep keyboard focus after the lock fires.

4379-4383: ⚠️ Potential issue | 🟡 Minor

Prefilled text still leaves the editor placeholder stale.

configureEditorView writes textView.string directly, but placeholder visibility only refreshes from placeholder.didSet or NSText.didChangeNotification. Opening rename/description with existing text can therefore paint the placeholder over the initial value until the first edit. Please verify both prefilled flows.

Also applies to: 4580-4588

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 4379 - 4383, configureEditorView
currently assigns view.textView.string directly which doesn't trigger
placeholder visibility updates; after setting view.textView.string = text call
the same refresh path used when the user edits text — either invoke the view
method that updates the placeholder (e.g., view.updatePlaceholderVisibility() or
view.refreshPlaceholder() if present) or post NSText.didChangeNotification for
that textView (NotificationCenter.default.post(name:
NSText.didChangeNotification, object: view.textView)) so the
placeholder.didSet/notification handlers run; apply the same fix to the other
prefilled flow referenced (the block around the second occurrence) so both
prefilled paths refresh placeholder state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/ContentView.swift`:
- Around line 12234-12239: TabItemView currently overrides the row's
accessibility announcement with only the workspace title and position, so the
visible SidebarWorkspaceDescriptionText (from tab.customDescription) isn't
exposed to VoiceOver; update TabItemView's accessibility configuration to
include tab.customDescription (or a trimmed version) by appending it to the
accessibilityLabel or placing it in accessibilityValue/Hint so screen readers
announce the description along with the title/position, ensuring you reference
TabItemView, tab.customDescription, and SidebarWorkspaceDescriptionText when
making the change.
- Around line 3866-3885: The rename field currently accepts embedded newlines
because the NSTextView textDidChange handler forwards textView.string directly
to commandPaletteRenameDraft and applyRenameFlow only trims leading/trailing
whitespace; update the text normalization to strip or replace internal newlines
before assigning or persisting: in the CommandPaletteSearchFieldRepresentable
textDidChange handler (where textView.string is forwarded to
commandPaletteRenameDraft) and in applyRenameFlow (the function that
finalizes/commits the rename) normalize the input by removing or converting all
newline characters (e.g., replace occurrences of "\n" and "\r" with a single
space or empty string) so multiline paste cannot persist newlines into titles or
workspace names.

---

Duplicate comments:
In `@Sources/ContentView.swift`:
- Around line 1725-1729: The UI hint text in the ContentView's inputHint
property is incorrect (it says Command-Return saves and Return inserts a
newline) — update the localized default string for inputHint to match the actual
key handling (e.g., "Press Return to save. Press Shift-Return for a new line, or
Escape to cancel.") or alternatively change the key-handling logic to match the
current copy; ensure you update both occurrences of the inputHint localized text
(the var inputHint in ContentView and the second occurrence around the other
inputHint/description location) so the displayed help text matches the
implemented behavior.
- Around line 3866-3885: The palette focus-lock helpers are only detecting
NSTextField/field editors and not the plain NSTextView mounted by
CommandPaletteEditorView, causing firstResponder to be stolen after the 80ms
lock; update the focus-lock logic (the helpers used with
CommandPaletteSearchFieldRepresentable and the isCommandPaletteRenameFocused
binding) to also recognize NSTextView instances (or views conforming to
NSTextInputClient / the CommandPaletteEditorView type) as valid focus targets so
the lock does not move focus back to the container, and verify both rename and
description flows keep keyboard focus after the lock fires.
- Around line 4379-4383: configureEditorView currently assigns
view.textView.string directly which doesn't trigger placeholder visibility
updates; after setting view.textView.string = text call the same refresh path
used when the user edits text — either invoke the view method that updates the
placeholder (e.g., view.updatePlaceholderVisibility() or
view.refreshPlaceholder() if present) or post NSText.didChangeNotification for
that textView (NotificationCenter.default.post(name:
NSText.didChangeNotification, object: view.textView)) so the
placeholder.didSet/notification handlers run; apply the same fix to the other
prefilled flow referenced (the block around the second occurrence) so both
prefilled paths refresh placeholder state.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 486a599d-3dec-45c7-ac67-b4487b4ce10d

📥 Commits

Reviewing files that changed from the base of the PR and between a907336 and fb171a5.

📒 Files selected for processing (1)
  • Sources/ContentView.swift

Comment thread Sources/ContentView.swift Outdated
Comment on lines +3866 to +3885
CommandPaletteSearchFieldRepresentable(
style: .editor(
.singleLine(
accessibilityLabel: target.placeholder,
onDeleteBackward: handleCommandPaletteRenameDeleteBackward(modifiers:)
)
),
accessibilityIdentifier: "CommandPaletteRenameField",
placeholder: target.placeholder,
text: $commandPaletteRenameDraft,
isFocused: Binding(
get: { isCommandPaletteRenameFocused },
set: { isCommandPaletteRenameFocused = $0 }
),
onSubmit: {
continueRenameFlow(target: target)
}
.onTapGesture {
handleCommandPaletteRenameInputInteraction()
}
},
onEscape: { dismissCommandPalette() },
onMoveSelection: { _ in }
)

@coderabbitai coderabbitai Bot Apr 1, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the ContentView.swift file around the specified lines
fd -t f ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 158


🏁 Script executed:

# Get the total line count to understand file size
wc -l Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 91


🏁 Script executed:

# Examine the code around lines 3866-3885
sed -n '3860,3890p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 1488


🏁 Script executed:

# Also check the other locations mentioned: 4202-4205 and 4644-4649
sed -n '4195,4210p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 597


🏁 Script executed:

# Check the third location
sed -n '4637,4655p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 1057


🏁 Script executed:

# Find the full textDidChange implementation
sed -n '4202,4220p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 833


🏁 Script executed:

# Find the continueRenameFlow function
rg -n "func continueRenameFlow" Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 138


🏁 Script executed:

# Search for normalization or trim operations on commandPaletteRenameDraft
rg -n "commandPaletteRenameDraft" Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 647


🏁 Script executed:

# Look for any newline removal or normalization in the codebase
rg -n "trimmingCharacters|replacingOccurrences.*newline|removing.*newline" Sources/ContentView.swift -A 2

Repository: manaflow-ai/cmux

Length of output: 7585


🏁 Script executed:

# Get the full continueRenameFlow and applyRenameFlow functions
sed -n '8067,8080p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 806


🏁 Script executed:

# Get the complete applyRenameFlow function
sed -n '8067,8100p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 1528


🏁 Script executed:

# Check what happens with the normalized name - is it used for actual rename?
rg -n "func applyRenameFlow" Sources/ContentView.swift -A 20

Repository: manaflow-ai/cmux

Length of output: 1063


🏁 Script executed:

# Look at the specific lines mentioned: 4202-4205 and 4644-4649
# First location context
sed -n '4195,4225p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 1307


🏁 Script executed:

# Check if there are other rename flows and what they do
# Look for any other places that might use the same pattern with NSTextView and single-line restriction
rg -n "\.singleLine" Sources/ContentView.swift -B 2 -A 5

Repository: manaflow-ai/cmux

Length of output: 885


🏁 Script executed:

# Verify the exact behavior of trimmingCharacters with newlines
python3 << 'EOF'
# Test what trimmingCharacters does with embedded newlines
test_strings = [
    "line1\nline2",
    "\nline1\nline2\n",
    "  line1\nline2  ",
]

for s in test_strings:
    # Swift's trimmingCharacters(in: .whitespacesAndNewlines) is equivalent to
    # stripping leading/trailing whitespace and newlines
    trimmed = s.strip()
    print(f"Original: {repr(s)}")
    print(f"After trim: {repr(trimmed)}")
    print(f"Still has embedded newline: {chr(10) in trimmed}")
    print()
EOF

Repository: manaflow-ai/cmux

Length of output: 325


NSTextView rename editors allow embedded newlines to be persisted despite .singleLine configuration.

The .singleLine style only restricts layout rendering, not content. The textDidChange handler forwards textView.string verbatim to commandPaletteRenameDraft, and applyRenameFlow trims only leading/trailing whitespace with trimmingCharacters(in: .whitespacesAndNewlines). Embedded newlines survive this trimming and are persisted to workspace or tab titles. Pasting multi-line values into either rename flow will commit hidden \n characters into persistent data.

Also applies to: 4202-4205, 4644-4649 (though 4644-4649 is layout-only)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 3866 - 3885, The rename field
currently accepts embedded newlines because the NSTextView textDidChange handler
forwards textView.string directly to commandPaletteRenameDraft and
applyRenameFlow only trims leading/trailing whitespace; update the text
normalization to strip or replace internal newlines before assigning or
persisting: in the CommandPaletteSearchFieldRepresentable textDidChange handler
(where textView.string is forwarded to commandPaletteRenameDraft) and in
applyRenameFlow (the function that finalizes/commits the rename) normalize the
input by removing or converting all newline characters (e.g., replace
occurrences of "\n" and "\r" with a single space or empty string) so multiline
paste cannot persist newlines into titles or workspace names.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not applicable on current head. Rename is back on the native single-line TextField path, so this outdated NSTextView-specific newline persistence finding no longer matches the code.

— Claude Code

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@lawrencecchen Thanks for the correction — you’re right. On current head the rename flow is back on the native single-line TextField path, and the NSTextView/multiline concern no longer applies. I’m withdrawing that finding.


✏️ Learnings added
Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2475
File: Sources/ContentView.swift:0-0
Timestamp: 2026-04-03T07:19:33.539Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, the command‑palette rename flow uses a single-line SwiftUI TextField via commandPaletteEditorField(style: .singleLine(...)); the multiline NSTextView editor is only used for the workspace description input. Do not flag newline persistence for rename.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: debgotwired
Repo: manaflow-ai/cmux PR: 1149
File: Sources/ContentView.swift:3977-3978
Timestamp: 2026-03-10T09:33:29.814Z
Learning: In manaflow-ai/cmux, for Sources/ContentView.swift, do not modify the sync-seeding behavior of scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:). It is shared infrastructure across all submenus. Treat brief initial flashes as consistent with existing submenus. Any UX improvement (for example making seeding synchronous on a forced corpus refresh) should not be implemented in feature-scoped PRs; instead implement and test such UX changes globally in a dedicated follow-up PR.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-05T03:20:48.079Z
Learning: Applies to **/*TerminalView.swift : Terminal find layering contract: SurfaceSearchOverlay must be mounted from GhosttySurfaceScrollView in Sources/GhosttyTerminalView.swift (AppKit portal layer), not from SwiftUI panel containers

Learnt from: Znboston
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-26T19:00:12.434Z
Learning: Repo: manaflow-ai/cmux — Sources/GroupHeaderView.swift and Sources/ContentView.swift — Palette color entry labels (`Text(entry.name)`) use dynamic palette data loaded at runtime and cannot be statically keyed with `String(localized:)`. This matches the existing workspace color picker pattern in ContentView.swift. Palette name localization would require restructuring the color palette system and is intentionally deferred; do not flag `entry.name` as a missing localization in this codebase.

Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1805
File: Sources/ContentView.swift:662-672
Timestamp: 2026-03-19T10:48:34.765Z
Learning: Repo manaflow-ai/cmux — Sources/ContentView.swift: FileDropOverlayView is anchored to the window’s contentView (theme frame). Within its bounds, the only non-terminal/non-browser area is the Sidebar, so performDragOperation’s folder-drop fallback (calling onDrop when no terminal/web view is under the cursor) is effectively sidebar-only and does not require additional hit-testing for the titlebar or other regions.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/ContentView.swift:3194-3196
Timestamp: 2026-03-04T14:05:48.668Z
Learning: In manaflow-ai/cmux (PR `#819`), Sources/ContentView.swift: The command palette’s external window labels intentionally use the global window index from the full orderedSummaries (index + 1), matching the Window menu in AppDelegate. Do not reindex after filtering out the current window to avoid mismatches (“Window 2” for an external window is expected).

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/GhosttyTerminalView.swift : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers. Portal-hosted terminal views can sit above SwiftUI during split/workspace churn.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-05T00:05:51.436Z
Learning: Applies to **/GhosttyTerminalView.swift : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`

Learnt from: apollow
Repo: manaflow-ai/cmux PR: 1089
File: Sources/ContentView.swift:6055-6057
Timestamp: 2026-03-09T02:09:08.742Z
Learning: In manaflow-ai/cmux (Sources/ContentView.swift), within CommandPaletteSwitcherSearchIndexer.metadataKeywordsForSearch(_:detail:), when tags are present, only add "tag" and "topic" to contextKeywords; do not append "claude" unconditionally to avoid false-positive switcher matches.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T00:03:34.904Z
Learning: Applies to {**/GhosttyTerminalView.swift,**/TerminalPanelView.swift} : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:12:42.553Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift and Sources/Panels/BrowserPanel.swift, BiDi override (U+202A–202E, U+2066–2069) and zero-width char (U+200B–200F, U+FEFF) filtering is implemented via a shared `dangerousScalars: Set<UInt32>` in each class. v2SanitizeWebText() truncates to 200 chars; v2SanitizeXPath() caps at 2000 chars for selector fidelity. Both use a shared v2SanitizeScalar() predicate. BrowserPickerMessageHandler.sanitize() uses the same dangerousScalars pattern with a 200-char cap.

Learnt from: solssak
Repo: manaflow-ai/cmux PR: 2397
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-31T02:33:40.981Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, prefer ghostty_surface_text (via writePTYText) for large socket text payloads; do not manually wrap with \e[200~/\e[201~. Ghostty inserts bracketed-paste markers itself when mode 2004 is enabled. Threshold is pasteTextThreshold = 256; control scalars still go through handleControlScalar and short text through sendTextEvent.

Learnt from: andrekat
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-29T11:05:01.662Z
Learning: In manaflow-ai/cmux PR `#1773`: `setActiveProfileName(_:)` on `TabManager` (Sources/TabManager.swift) must call `updateWindowTitleForSelectedTab()` explicitly — the save and delete paths do not go through `selectedTabId.didSet`, so without the explicit call the AppKit window title is left stale with the old profile prefix. This was confirmed fixed in commit be9eaa26.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2514
File: Sources/GhosttyTerminalView.swift:3759-3761
Timestamp: 2026-04-01T22:57:41.165Z
Learning: Repo: manaflow-ai/cmux — In Sources/cmuxApp.swift, ClaudeCodeIntegrationSettings.customClaudePath(defaults:) trims surrounding whitespace and returns nil for empty/whitespace-only values; callers (e.g., TerminalSurface.createSurface(for:)) can safely set CMUX_CUSTOM_CLAUDE_PATH without additional trimming.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/ContentView.swift:2156-2171
Timestamp: 2026-03-23T06:08:14.740Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, openFileInTextEditor(_:) must attempt workspace.newTextEditorSplit(from:orientation:filePath:focus:) and, if that returns nil, fall back to workspace.newTextEditorSurface(inPane:filePath:focus:) using bonsplitController.focusedPaneId. Rationale: avoid dropping file-open requests when the focused panel is not pane-backed.

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:28:45.700Z
Learning: Repo: manaflow-ai/cmux — In Sources/Panels/BrowserPanel.swift, BrowserPickerMessageHandler uses three sanitizer methods that mirror TerminalController exactly: filtered(_:) strips control chars and dangerousScalars (BiDi/zero-width); sanitizeWebText(_:) applies filtered + prefix(200) + trim for display text; sanitizeXPath(_:) applies filtered + prefix(2000) + trim for selector fidelity. sanitizeXPath is called on body["xpath"] and sanitizeWebText on body["text"] in userContentController(_:didReceive:).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/KeyboardShortcutSettings.swift:509-528
Timestamp: 2026-04-03T03:35:27.463Z
Learning: Repo: manaflow-ai/cmux — In Sources/KeyboardShortcutSettings.swift, ShortcutStroke.isEscapeCancelEvent(_:) already gates the U+001B character-based escape checks behind a modifier guard: it computes `normalizedFlags` (stripping capsLock/function/numericPad) and only treats the escape character as cancel when `normalizedFlags.isEmpty` (or keyCode is 36/76). Modified bindings like Control+[ and Command+[ produce U+001B but have non-empty normalizedFlags, so they are NOT treated as cancel and can be recorded normally. Only the physical Escape key (keyCode 53) is unconditionally treated as cancel.

Learnt from: BillionClaw
Repo: manaflow-ai/cmux PR: 1755
File: Sources/GhosttyTerminalView.swift:4889-4903
Timestamp: 2026-03-18T16:50:14.396Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, GhosttyNSView.performKeyEquivalent(with:) must only route Command key equivalents to NSApp.mainMenu when shouldRouteCommandEquivalentDirectlyToMainMenu(event) returns true; otherwise, handle the key directly via keyDown to forward unbound Cmd+Shift combinations to the terminal and preserve the Cmd+` exclusion.

Learnt from: arieltobiana
Repo: manaflow-ai/cmux PR: 1873
File: Sources/TerminalController.swift:4071-4087
Timestamp: 2026-03-20T17:18:30.333Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, v2WorkspaceAction(params:) -> case "set_color": palette names are resolved via WorkspaceTabColorSettings.defaultPaletteWithOverrides(), whose entries are always valid hex (validated by the UI). Therefore, additional normalization of entry.hex is unnecessary.

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:12:42.553Z
Learning: Repo: manaflow-ai/cmux — In Sources/Panels/BrowserPanel.swift, BrowserPanel.updateWorkspaceId(_:) is a dedicated method (var workspaceId) that updates both BrowserPanel.workspaceId and pickerMessageHandler?.updateWorkspaceId(_:) atomically. It is called from BrowserPanel.reattachToWorkspace(_:) when a panel moves between workspaces. This ensures BrowserPickerMessageHandler always posts notifications with the current workspaceId, not a stale one from panel initialization.

Learnt from: qkrwpdlr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-23T07:01:33.134Z
Learning: Repo: manaflow-ai/cmux — In Sources/TerminalController.swift, sendPickedElementToTerminal(workspaceId:summary:) resolves the target workspace via AppDelegate.shared?.workspaceFor(tabId: workspaceId), which searches across all mainWindowContexts (not self.tabManager which is active-window only). It first injects text into the focused terminal panel if it's a terminal, then falls back to iterating all panels in the workspace.

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/Workspace.swift:0-0
Timestamp: 2026-04-02T21:40:59.098Z
Learning: Repo: manaflow-ai/cmux — In Sources/Workspace.swift session snapshot logic, detected foreground command lines are only recorded if they pass SessionRestoreCommandSettings.isCommandAllowed(...). Commit f887189 moved gating to the SessionForegroundProcessCache so unallowed (potentially sensitive) commands are never written to the session JSON.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/ContentView.swift:8917-8921
Timestamp: 2026-04-03T03:35:56.499Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — ShortcutHintModifierPolicy.shouldShowHints(for:) reveals sidebar/titlebar shortcut hints when the current modifier flags exactly equal KeyboardShortcutSettings.shortcut(for: .selectWorkspaceByNumber).modifierFlags, and returns false for chorded-number mappings. This follows the configured workspace-number modifier (not strictly Command-only). TabItemView’s close-button suppression via showsModifierShortcutHints therefore tracks the configured modifier. The debug toggle showHintsOnCommandHold predates customizable shortcuts and conceptually means “show hints on holding the configured workspace-number modifier(s).”

Learnt from: homanp
Repo: manaflow-ai/cmux PR: 883
File: CLI/cmux.swift:1569-1586
Timestamp: 2026-03-04T22:05:07.913Z
Learning: In manaflow-ai/cmux CLI (CLI/cmux.swift), for parity with existing browser commands, markdown.open intentionally normalizes --surface before resolving --workspace/--window, relying on server-side resolution to disambiguate final routing. Avoid one-off reordering in markdown; consider any change only as a coordinated, cross-command refactor.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:3491-3493
Timestamp: 2026-03-04T14:06:16.241Z
Learning: For manaflow-ai/cmux PR `#819` (Japanese i18n), keep scope limited to localization changes; UX enhancements like preferring workspace.customTitle in workspaceDisplayName() or altering move-target labels should be handled in a separate follow-up issue.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:776-777
Timestamp: 2026-04-03T03:35:40.100Z
Learning: Repo: manaflow-ai/cmux — As of PR `#2528`, workspace digit mapping (Cmd+1–9 and sidebar digits) intentionally uses TabManager.tabs as the canonical ordering for both sidebar rendering and app shortcut handling. Do not suggest switching to orderedSidebarWorkspaceIds() unless the model changes to a sidebar-ordered, group/collapse-aware source of truth.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: CLI/cmux.swift:1958-1960
Timestamp: 2026-03-25T00:33:00.675Z
Learning: Repo: manaflow-ai/cmux — CLI/cmux.swift set-workspace-color intentionally accepts both "#RRGGBB" and "RRGGBB" for ergonomics, matching server-side WorkspaceTabColorSettings.normalizedHex/TabManager normalization. Do not suggest enforcing a mandatory leading '#'; at most, suggest clarifying help/usage text.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: CLI/cmux.swift:1948-1978
Timestamp: 2026-03-25T00:33:26.452Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift, the set-workspace-color command requires exactly one trailing <hex> argument and enforces a strict 6-digit hex format (`#RRGGBB`, optional leading '#'); clear-workspace-color rejects any unexpected positional args beyond --workspace. This matches server-side normalization and prevents malformed inputs.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: moyashin63
Repo: manaflow-ai/cmux PR: 1074
File: Sources/AppDelegate.swift:7523-7545
Timestamp: 2026-03-09T01:38:24.337Z
Learning: When the command palette is visible (as in manaflow-ai/cmux Sources/AppDelegate.swift), ensure the shortcut handling consumes most Command shortcuts to protect the palette's text input. Specifically, do not allow UI zoom shortcuts (Cmd+Shift+= / Cmd+Shift+− / Cmd+Shift+0) to trigger while the palette is open. Do not reorder shortcut handlers (e.g., uiZoomShortcutAction(...)) to bypass this guard; users must close the palette before performing zoom actions. This guideline should apply to Swift source files handling global shortcuts within the app.

Learnt from: zlatkoc
Repo: manaflow-ai/cmux PR: 1368
File: Sources/Panels/BrowserPanel.swift:69-69
Timestamp: 2026-03-13T13:46:01.733Z
Learning: Do not wrap engine/brand name literals (e.g., displayName values such as Google, DuckDuckGo, Bing, Kagi, Startpage) in String(localized: ...). These are brand/product names that are not translatable UI text. Localization should apply to generic UI strings (labels, buttons, error messages, etc.). Apply this guideline across Swift source files under Sources/ (notably in BrowserPanel.swift and similar UI/engine-related strings) and flag only brand-name strings that are part of user-facing UI text appropriately for translation scope.

Learnt from: kjb0787
Repo: manaflow-ai/cmux PR: 1461
File: Sources/GhosttyTerminalView.swift:5904-5905
Timestamp: 2026-03-15T19:22:32.330Z
Learning: In Swift files under the Sources directory that manage terminal/scroll behavior, ensure the following: when preserving scroll across workspace switches, save savedScrollRow only if the scrollbar offset is greater than 0 (indicating the user has scrolled up). On restore, call scroll_to_row only if savedScrollRow is non-nil; if it is nil, rely on synchronizeScrollView() to keep bottom-pinned sessions following new output. This pattern should be applied wherever GhosttyTerminalView-like views implement setVisibleInUI(_:) to maintain consistent user scroll state across workspace switches.

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-16T08:02:06.824Z
Learning: In Swift sources, for any panel_id-only route handling in v2PanelMarkBackground(params:) and v2PanelMarkForeground(params:), first attempt v2ResolveTabManager(params:). Use the manager only if it actually owns the panelId; otherwise fall back to AppDelegate.shared?.locateSurface(surfaceId:) to locate the correct TabManager across windows. Apply this pattern to all panel_id-only routes to avoid active-window bias.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/GhosttyTerminalView.swift:3891-3903
Timestamp: 2026-04-02T21:37:21.463Z
Learning: In Swift source files like Sources/GhosttyTerminalView.swift, avoid logging raw startup commands or initialInput even in DEBUG (to prevent leaking sensitive paths/tokens and multiline content). If you need to diagnose startup/input, log only non-sensitive metadata such as (1) presence flags (e.g., hasStartupCommand/hasInitialInput), (2) byte counts, and (3) the relevant surface id (so issues can be correlated without exposing the underlying strings).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:6439-6444
Timestamp: 2026-04-03T03:35:54.082Z
Learning: In this repo’s keyboard shortcut implementation, ensure `KeyboardShortcutSettings.setShortcut(...)` does nothing (no-op) when `KeyboardShortcutSettings.isManagedBySettingsFile(action)` returns `true` (i.e., the shortcut is managed via `settings.json`). This prevents writing back overrides into `UserDefaults` and keeps `settings.json` as the source of truth.

Comment thread Sources/ContentView.swift

@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 1 file (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/ContentView.swift">

<violation number="1" location="Sources/ContentView.swift:4211">
P2: Strip embedded newlines when the editor is in single-line mode; otherwise pasting multi-line text can persist `\n` into workspace/tab names.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/ContentView.swift Outdated

func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else { return }
parent.text = textView.string

@cubic-dev-ai cubic-dev-ai Bot Apr 1, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Strip embedded newlines when the editor is in single-line mode; otherwise pasting multi-line text can persist \n into workspace/tab names.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/ContentView.swift, line 4211:

<comment>Strip embedded newlines when the editor is in single-line mode; otherwise pasting multi-line text can persist `\n` into workspace/tab names.</comment>

<file context>
@@ -4203,6 +4206,11 @@ struct ContentView: View {
 
+            func textDidChange(_ notification: Notification) {
+                guard let textView = notification.object as? NSTextView else { return }
+                parent.text = textView.string
+            }
+
</file context>
Suggested change
parent.text = textView.string
let rawText = textView.string
if textView.textContainer?.maximumNumberOfLines == 1 {
parent.text = rawText
.replacingOccurrences(of: "\r", with: " ")
.replacingOccurrences(of: "\n", with: " ")
} else {
parent.text = rawText
}
Fix with Cubic

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cae0e5b5ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/ContentView.swift Outdated
Comment on lines +4494 to +4498
parent.onSubmit()
return true
}
if normalizedFlags == [.shift] {
return false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow Shift+Enter to insert newlines in description editor

This new multiline handler explicitly returns false for Shift+Enter so AppKit can insert a newline, but command-palette shortcut routing still treats Shift+Return as submit (shouldSubmitCommandPaletteWithReturn) and consumes the key before this editor path runs. In workspaceDescriptionInput mode, pressing Shift+Enter therefore saves/dismisses instead of creating a new line, which makes multiline descriptions effectively unusable from the keyboard.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e7fc4c07a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +4114 to +4115
guard let descriptionRaw = v2String(params, "description"),
!descriptionRaw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve raw description text in workspace set_description

Using v2String(params, "description") trims leading/trailing whitespace before setCustomDescription, so workspace.action set_description cannot round-trip intentional indentation or trailing newlines (e.g., Markdown lists/code blocks). This is a regression in the new action path because descriptions are otherwise stored with edge whitespace preserved; validate emptiness with a trimmed copy, but pass the raw string to avoid lossy updates.

Useful? React with 👍 / 👎.

@lawrencecchen
lawrencecchen merged commit 4fb5c23 into main Apr 3, 2026
36 checks passed
@lawrencecchen
lawrencecchen deleted the task-sidebar-description-notes branch April 3, 2026 07:27
@Sean529 Sean529 mentioned this pull request Apr 4, 2026
Jesssullivan added a commit to Jesssullivan/cmux that referenced this pull request Apr 5, 2026
* fix: fall back to focused surface when new-split target is stale

When `new-split` receives a surface UUID that no longer exists (e.g. a
closed teammate pane), fall back to the focused surface instead of
returning a "Surface not found" error. This matches `new-pane` behavior
and fixes agent spawning after team shutdown in Claude Code.

* Add editable workspace descriptions (manaflow-ai#2475)

* Add editable workspace descriptions

* Add workspace description focus UI tests

* Include workspace description UI tests in project

* Fix workspace description palette focus

* Stabilize workspace description UI tests

* Force socket mode in description UI tests

* Use tagged socket path in description UI tests

* Fix workspace description UI test socket setup

* Start control socket for UI test launches

* Use socket env overrides in description UI tests

* Rewrite description UI tests without socket access

* Verify saved description by reopening editor

* Add workspace description focus debug logs

* Prevent terminal focus restore during command palette

* Trace workspace description shift-enter handling

* Add failing Shift-Enter description UI test

* Fix Shift-Enter in workspace description editor

* Use live multiline editor state for description submit

* Log submitted and normalized workspace descriptions

* Trace lower-level Shift-Enter editor routing

* Add failing sidebar markdown line-break regression test

* Preserve workspace description line breaks in sidebar

* Add sidebar multiline description UI smoke test

* Expose multiline sidebar descriptions to accessibility

* Fix sidebar description refresh lag

* Add failing workspace description whitespace test

* Preserve workspace description whitespace

* fix: keep multiline palette navigation in editor

* fix: cap workspace description editor growth

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Detect listening ports for remote SSH workspaces (manaflow-ai#2398)

* Add failing SSH remote port detection regression

* Detect listening ports for remote SSH workspaces

* Retry remote SSH TTY reporting until the target surface exists

* Address relay RPC review comments

* Avoid host-wide remote port leaks for cmux ssh

* Make remote port scans prompt-aware

* Fall back when remote ss omits pid data

* Tighten remote port scan handoff

* Keep raw RPC output intact

* Clean up relay TTY handoff review follow-up

* Fix remote ssh port surfacing

* Fix cmux ssh bootstrap and orphan cleanup

* Address SSH remote review feedback

* Stabilize SSH remote metadata regression

* Fix shell timing and suppressed focus recovery

* Harden suppressed focus recovery in tests

* Stabilize focus recovery regression timing

* Fix SSH relay auth and bootstrap handoff

* Fix SSH metadata cleanup assertion

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Add SSH foreground-auth regression tests

* Defer remote bootstrap until SSH auth succeeds

* Fix session restore terminal cursor focus race (manaflow-ai#2471)

* Fix session restore terminal cursor focus race

* Fix terminal ready focus observer key

* wip

* Revert accidental submodule pointer changes from wip commit

The wip commit bumped ghostty and bonsplit pointers alongside the
actual code fix. Revert them to match main so the PR only contains
the session cursor race fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Support chorded keyboard shortcuts (manaflow-ai#2528)

* Support chorded keyboard shortcuts

* Fix escape handling in shortcut recorder

* Add settings.json shortcut overrides

* Add regression test for chord reset on deactivate

* Fix shortcut chord cleanup edge cases

* Add regression tests for chord prefix edge cases

* Fix configurable chord prefix routing

* Add CLI reload-config command

* Simplify reload-config CLI command

* Add regression tests for shortcut reload edges

* Fix shortcut reload and chord edge cases

* Add managed settings.json defaults and schema docs

* fix: harden shortcut routing edge cases

* fix: preserve palette and browser shortcut routing

* chore: retrigger missing PR workflows

* feat: add settings.json entry points

* feat: make workspace color palette a named dictionary

* feat: add textedit button for settings file

* refactor: reorder ghostty menu items

* refactor: restore settings menu ordering

* refactor: restyle settings file entry

* refactor: move settings file actions into header

* refactor: simplify settings file header action

* docs: explain shortcut chords from settings

* refactor: reorder shortcut chord actions

* fix: notify after swapping settings file store

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Find Homebrew go for dev remote bootstrap

* Fix missing sidebar git branch metadata for workspaces (manaflow-ai#2563)

* Fix sidebar workspace branch backfill

* Add stale branch sidebar regression

* Patch GhosttyKit header compatibility

* Match Ghostty clipboard callback signature

* Tighten sidebar git metadata polling

* Fix Ghostty clipboard callback bridge

---------

Co-authored-by: austinpower1258 <austinwang115@gmail.com>

* Reuse SSH control master for remote relay

* fix: harden deferred ssh reconnect handling

* Fix missing sidebar ports for agent-run dev servers (manaflow-ai#2562)

* test: cover agent-owned sidebar ports

* fix: track agent dev-server ports in sidebar

* fix: harden ssh localcommand escaping

* Fix sidebar layout loop and CLI socket deadlocks (manaflow-ai#2601)

* Fix sidebar layout loop and CLI socket deadlocks

* Fix sidebar socket target validation

* Move sidebar tab resolution onto main queue

* Fix sidebar mutation closure parameters

* fix: add missing CryptoKit import from upstream merge

---------

Co-authored-by: Anusheel Bhushan <anusheel@gmail.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Austin Wang <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@austinywang austinywang mentioned this pull request Apr 6, 2026
3 tasks
adamghaleb added a commit to adamghaleb/cmux that referenced this pull request Aug 4, 2026
Transcribed from upstream manaflow-ai/cmux HEAD b46dcb7 per
ADR-0004 amendment (narrow slices with upstream PR breadcrumbs,
never rebase). Implements fadi-orchestrator#26 and #27.

fadi-orchestrator#26:
- workspace.action set_color/clear_color with upstream's shape:
  16-color named palette or #RRGGBB, named_colors in the error
  payload (upstream: PR manaflow-ai#1873). Drives the existing customColor
  rail end to end (sidebar left rail, bonsplit tab accent,
  ghostty_surface_set_accent_color).
- workspace.action set_description/clear_description wired into new
  per-workspace state Workspace.customDescription, persisted through
  SessionWorkspaceSnapshot (upstream: PR manaflow-ai#2475).
- CLI: workspace-action --color/--description flags + help.
- waitAfterCommand child-exit handling: TerminalChildExitPolicy
  (upstream: PR manaflow-ai#8681) — SHOW_CHILD_EXITED now reads runtime ms;
  normal/established exits (Ctrl+D, tmux detach) close the pane so a
  spawn-command surface is never stranded, abnormal fast exits keep
  the dead surface visible for inspection (handler returns false so
  ghostty renders its error text).
- Spawn-command seam (ADR-0004): surface.create accepts an optional
  "command"; plumbed through newTerminalSurface -> TerminalPanel ->
  TerminalSurface into ghostty_surface_config_s.command with
  wait_after_command=true.

fadi-orchestrator#27:
- Label precedence: upstream's CustomTitleSource (.user/.auto)
  provenance for workspace and panel custom titles (upstream: PR
  manaflow-ai#5547). .auto (daemon semantic label) beats auto-derived titles but
  never overwrites or clears a user rename; ordering documented on
  Workspace.resolvedPanelTitle; provenance persists through session
  snapshots.
- workspace.set_auto_title verb transcribed (probe/panel_id/
  panel_only_if_multiple), minus upstream's opt-in auto-naming
  setting — fadid is the labeler here, so always enabled.
- Hover description: .help() on the sidebar workspace row, applied
  only when a description exists. (The pane tab bar lives in the
  vendor/bonsplit submodule — follow-up via bonsplit remote.)

Also contained build fixes in shared files, required to compile the
fadi-layer snapshot: PanelType.observatory switch arms in
Workspace.swift, dedup of Color(hex:) in ContentView (Fadicode's
internal extension collides with the old private one), and pbxproj
registration for Sources/Fadicode/Helpers/NetworkRetry.swift which
existed on disk but was never in the target.

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

This branch was successfully deployed

1 active deployment
Preview — 8e7fc4c0 Deployed Apr 3, 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