Add editable workspace descriptions - #2475
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 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".
| let paletteRequiresCommandReturn = commandPaletteRequiresCommandReturnToSubmit(in: paletteWindow) | ||
| let normalizedReturnFlags = event.modifierFlags | ||
| .intersection(.deviceIndependentFlagsMask) | ||
| .subtracting([.numericPad, .function, .capsLock]) | ||
| if normalizedFlags.isEmpty, event.keyCode == 53 { |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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
| private final class CommandPaletteMultilineTextView: NSTextView { | ||
| var onCommandReturn: (() -> Void)? | ||
|
|
||
| override func keyDown(with event: NSEvent) { |
There was a problem hiding this comment.
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 SummaryThis PR adds an editable, markdown-rendered workspace description field across the full stack: the Key highlights:
Confidence Score: 5/5Safe 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
Sequence DiagramsequenceDiagram
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
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | 🟡 MinorPreserve 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 | 🟠 MajorMultiline 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 theworkspace_description_inputeditor 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 notificationsourcemetadata 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 tows.setCustomDescription()without validation, while the update path (line 4103–4108) validates viav2String()first and rejects whitespace-only input with an error. Both ultimately normalize throughWorkspace.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’sworkspace_description_inputliteral. 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
📒 Files selected for processing (14)
CLI/cmux.swiftResources/Localizable.xcstringsSources/AppDelegate.swiftSources/ContentView.swiftSources/KeyboardShortcutSettings.swiftSources/SessionPersistence.swiftSources/TabManager.swiftSources/TerminalController.swiftSources/Workspace.swiftSources/cmuxApp.swiftcmuxTests/AppDelegateShortcutRoutingTests.swiftcmuxTests/SessionPersistenceTests.swiftcmuxTests/WorkspaceManualUnreadTests.swiftcmuxTests/WorkspaceUnitTests.swift
| if let descriptionOpt { | ||
| params["description"] = descriptionOpt | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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`.
| 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) | ||
|
|
There was a problem hiding this comment.
🧩 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.swiftRepository: 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 -20Repository: 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 -100Repository: 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 5Repository: 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 8Repository: 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 -20Repository: 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.
| if let description = tab.customDescription { | ||
| SidebarWorkspaceDescriptionText( | ||
| markdown: description, | ||
| isActive: usesInvertedActiveForeground | ||
| ) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
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.
| "en": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "Press Enter to save. Press Shift-Enter for a new line, or Escape to cancel." |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
💡 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".
| private func textDidChange(_ notification: Notification) { | ||
| updatePlaceholderVisibility() | ||
| publishHeight() |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/ContentView.swift (1)
4084-4228:⚠️ Potential issue | 🔴 CriticalThe multiline editor never writes user edits back into
commandPaletteWorkspaceDescriptionDraft.The
Coordinatorclass is set as the delegate for the multilineNSTextViewbut does not implementtextDidChange(_:). User edits triggerNSText.didChangeNotificationbut have no path back toparent.text. Additionally,updateEditorNSViewwritesnsView.textView.string = textwithout guarding againstisProgrammaticMutation, creating a stale-binding hazard when SwiftUI refreshes. Add the missing delegate method and guard programmatic writes to match the pattern used inFeedbackComposerMessageEditor.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 = placeholderAlso 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 | 🔴 CriticalThe palette focus lock will steal focus from this new
NSTextView.This editor is a plain
NSTextView, butWindowCommandPaletteOverlayControllerstill only treats field editors /NSTextFieldas palette text inputs. The 80 ms focus-lock timer will therefore decide the editor is unfocused and move first responder back tocontainerView, 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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
Sources/ContentView.swift (3)
1725-1729:⚠️ Potential issue | 🟡 MinorThe 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 | 🔴 CriticalThe shared editor path is still incompatible with the palette focus lock.
These flows now mount a plain
NSTextViewviaCommandPaletteEditorView, but the palette focus-lock helpers above still only recognize field editors /NSTextFieldinputs. 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 | 🟡 MinorPrefilled text still leaves the editor placeholder stale.
configureEditorViewwritestextView.stringdirectly, but placeholder visibility only refreshes fromplaceholder.didSetorNSText.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
| 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 } | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the ContentView.swift file around the specified lines
fd -t f ContentView.swiftRepository: manaflow-ai/cmux
Length of output: 158
🏁 Script executed:
# Get the total line count to understand file size
wc -l Sources/ContentView.swiftRepository: manaflow-ai/cmux
Length of output: 91
🏁 Script executed:
# Examine the code around lines 3866-3885
sed -n '3860,3890p' Sources/ContentView.swiftRepository: 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.swiftRepository: manaflow-ai/cmux
Length of output: 597
🏁 Script executed:
# Check the third location
sed -n '4637,4655p' Sources/ContentView.swiftRepository: manaflow-ai/cmux
Length of output: 1057
🏁 Script executed:
# Find the full textDidChange implementation
sed -n '4202,4220p' Sources/ContentView.swiftRepository: manaflow-ai/cmux
Length of output: 833
🏁 Script executed:
# Find the continueRenameFlow function
rg -n "func continueRenameFlow" Sources/ContentView.swiftRepository: manaflow-ai/cmux
Length of output: 138
🏁 Script executed:
# Search for normalization or trim operations on commandPaletteRenameDraft
rg -n "commandPaletteRenameDraft" Sources/ContentView.swiftRepository: 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 2Repository: manaflow-ai/cmux
Length of output: 7585
🏁 Script executed:
# Get the full continueRenameFlow and applyRenameFlow functions
sed -n '8067,8080p' Sources/ContentView.swiftRepository: manaflow-ai/cmux
Length of output: 806
🏁 Script executed:
# Get the complete applyRenameFlow function
sed -n '8067,8100p' Sources/ContentView.swiftRepository: 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 20Repository: 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.swiftRepository: 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 5Repository: 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()
EOFRepository: 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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
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.
|
|
||
| func textDidChange(_ notification: Notification) { | ||
| guard let textView = notification.object as? NSTextView else { return } | ||
| parent.text = textView.string |
There was a problem hiding this comment.
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>
| 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 | |
| } |
0803262 to
cae0e5b
Compare
There was a problem hiding this comment.
💡 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".
| parent.onSubmit() | ||
| return true | ||
| } | ||
| if normalizedFlags == [.shift] { | ||
| return false |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
💡 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".
| guard let descriptionRaw = v2String(params, "description"), | ||
| !descriptionRaw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { |
There was a problem hiding this comment.
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 👍 / 👎.
* 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>
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>
Summary
cmuxCLI, with persistence and focused regression coverageVerification
./scripts/reload.sh --tag task-sidebar-description-notesSummary 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
workspace.createacceptsdescription;workspace.actionaddsset_descriptionandclear_description; workspace payloads includedescription.cmux:new-workspace --description <text>;workspace-action set-descriptionsupports--description <text>or trailing text;workspace-action clear-description;current-workspacenow uses v2 and respects--id-format.Bug Fixes
Written for commit 8e7fc4c. Summary will update on new commits.
Summary by CodeRabbit
New Features
Localization
Tests