Skip to content

Polish welcome, feedback, and shortcuts flows - #1169

Merged
lawrencecchen merged 2 commits into
mainfrom
feat-welcome-command
Mar 11, 2026
Merged

lawrencecchen merged 2 commits into
mainfrom
feat-welcome-command

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • polish cmux welcome copy, shortcuts, and support links
  • add cmux feedback and cmux shortcuts CLI flows
  • tighten first-run welcome and settings/feedback focus behavior

Verification

  • ./scripts/reload.sh --tag settings-focus-fix
  • ./scripts/reload.sh --tag keyboard-shortcuts-copy
  • ./scripts/reload.sh --tag welcome-email

Summary by CodeRabbit

  • New Features

    • New welcome experience: command and "Welcome" sidebar option to open a Welcome workspace; auto-shown on first workspace creation.
    • New shortcuts and feedback commands/UI: open keyboard shortcuts, compose/submit feedback (supports attachments and JSON output).
    • In-app flows to open settings and ensure windows activate reliably.
  • Documentation / Localization

    • Added multi-language "Welcome" localization key and updated help/usage text to document new commands.

Adds `cmux welcome` CLI command that prints a blue-to-purple gradient
chevron logo, version info, and key shortcuts. Auto-runs once on first
workspace creation via UserDefaults. Adds "Welcome" to the ? help menu
in the sidebar footer, which opens a new workspace running the command.
@vercel

vercel Bot commented Mar 11, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 11, 2026 3:58am

@coderabbitai

coderabbitai Bot commented Mar 11, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f17b837a-cb0e-4a2e-b5d3-12ac940dbd94

📥 Commits

Reviewing files that changed from the base of the PR and between fc2c543 and 82a7086.

📒 Files selected for processing (5)
  • CLI/cmux.swift
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/TabManager.swift
  • Sources/TerminalController.swift

📝 Walkthrough

Walkthrough

Adds a multi-surface welcome flow plus keyboard-shortcuts and feedback CLI/UI commands. Introduces CLI handlers (welcome/shortcuts/feedback), app-level welcome workspace creation and dispatch, V2 feedback/settings RPCs, localization for a sidebar welcome label, and user-defaults tracking for shown state.

Changes

Cohort / File(s) Summary
CLI: new commands & connection logic
CLI/cmux.swift
Adds welcome, shortcuts, and feedback top-level commands; new helpers for running feedback/shortcuts, client connect/auth, JSON output, repeated-option parsing, and printWelcome() ASCII renderer. Also updates usage/help text and env socket resolution to fallback to CMUX_SOCKET.
App lifecycle & welcome dispatch
Sources/AppDelegate.swift, Sources/TabManager.swift, Sources/cmuxApp.swift
Adds openWelcomeWorkspace() and sendWelcomeCommandWhenReady(...); updates sendTextWhenReady with beforeSend hook; TabManager gains autoWelcomeIfNeeded and polling-based sendWelcomeWhenReady; introduces WelcomeSettings.shownKey and telemetry helper.
UI: Sidebar & feedback bridge
Sources/ContentView.swift
Adds .welcome action to sidebar help menu and invokes AppDelegate.openWelcomeWorkspace(); introduces FeedbackComposerBridge and FeedbackComposerBridgeError to centralize feedback compose/submit logic and validation.
Terminal RPC v2 handlers
Sources/TerminalController.swift
Adds v2 handlers: v2FeedbackOpen, v2SettingsOpen, v2FeedbackSubmit; expands v2 capabilities manifest and maps feedback errors to v2 error codes with async submission and timeout handling.
Localization
Resources/Localizable.xcstrings
Adds sidebar.help.welcome localization entries (multi-language) for the new sidebar welcome label.
Minor UI/window utilities
Sources/AppDelegate.swift
Adds mainWindowContainingWorkspace(_:), refines app activation and settings-window fronting logic.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as ContentView (Sidebar)
    participant App as AppDelegate
    participant TM as TabManager
    participant WS as Workspace
    participant Term as Terminal Surface
    participant CLI as cmux CLI

    User->>UI: Click "Welcome" in sidebar
    UI->>App: openWelcomeWorkspace()
    App->>WS: Create & activate workspace
    App->>TM: sendWelcomeCommandWhenReady(to: workspace)
    loop Poll (max 60)
        TM->>WS: Check focused terminal panel/surface
        alt surface ready
            TM->>Term: Schedule "cmux welcome" (0.5s)
            Term->>CLI: Execute "cmux welcome"
            CLI-->>Term: Print welcome screen
            TM->>App: Completed
        else not ready
            TM->>TM: Wait 0.05s, retry
        end
    end
Loading
sequenceDiagram
    participant User
    participant CLI as cmux CLI
    participant App as AppDelegate
    participant TermCtrl as TerminalController
    participant Feedback as FeedbackComposerBridge
    participant Settings as System/Settings

    User->>CLI: `cmux feedback` or `feedback.submit` via socket
    CLI->>App: connectClient / authenticateClientIfNeeded (may launch app)
    CLI->>TermCtrl: v2FeedbackOpen / v2FeedbackSubmit (via socket)
    TermCtrl->>Feedback: openComposer or submit(email, body, images)
    Feedback-->>TermCtrl: success / error
    TermCtrl-->>CLI: v2 response (opened/submitted or error code)
    CLI-->>User: prints JSON or human message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
A hop, a beep, a welcome light—
I bring you shortcuts, feedback bright.
From warren to terminal, greetings unfurl,
New workspaces bloom for every coder-bun and girl. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add cmux welcome command' accurately summarizes the main feature addition—introducing a new welcome command to the CLI.
Description check ✅ Passed The description covers the main changes (command functionality, auto-run behavior, UI integration), testing performed, and key implementation details, though it lacks a demo video.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-welcome-command

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a cmux welcome CLI command that renders a blue-to-purple gradient chevron logo with version info and keyboard shortcuts, auto-runs it once on first workspace creation (gated by a UserDefaults flag), and exposes it via a new "Welcome" entry in the sidebar ? help menu.

Key changes:

  • CLI/cmux.swift: New printWelcome() helper, welcome branch in the command dispatcher, and updated --help output.
  • Sources/cmuxApp.swift: New WelcomeSettings enum centralising the UserDefaults key for the app target.
  • Sources/TabManager.swift: Auto-shows welcome on the first addWorkspace(select: true) call; adds a sendWelcomeWhenReady polling helper.
  • Sources/AppDelegate.swift: openWelcomeWorkspace() for the manual help-menu trigger; adds an identical sendWelcomeCommandWhenReady polling helper — this duplicates the logic in TabManager and could be consolidated.
  • Sources/ContentView.swift: Adds the "Welcome" option to the sidebar help menu; the action handler is missing isPopoverPresented = false, so the popover will remain open after the new workspace is created.
  • Resources/Localizable.xcstrings: Adds English and Japanese localizations for the new help-menu label.

Confidence Score: 4/5

  • Safe to merge with minor UX and maintainability improvements recommended.
  • The feature is self-contained and the happy path works correctly. The only actionable bug is the missing popover dismissal in the .welcome case, which leaves the help popover visible after clicking "Welcome". The duplicated polling logic and hardcoded key comment are maintainability concerns but do not affect runtime correctness.
  • Sources/ContentView.swift (missing popover dismissal) and Sources/TabManager.swift / Sources/AppDelegate.swift (duplicated polling logic).

Important Files Changed

Filename Overview
Sources/ContentView.swift Adds "Welcome" entry to the sidebar help popover menu; missing isPopoverPresented = false before opening the welcome workspace, which leaves the popover visible after clicking the option.
Sources/TabManager.swift Hooks into addWorkspace to auto-show welcome on first workspace creation; introduces a sendWelcomeWhenReady polling helper that is identical to the one added in AppDelegate, creating code duplication.
Sources/AppDelegate.swift Adds openWelcomeWorkspace() for the manual help-menu trigger and sendWelcomeCommandWhenReady polling helper; logic is duplicated from TabManager's equivalent.
CLI/cmux.swift Adds cmux welcome command with gradient logo and keyboard shortcuts, registers it in the help text and command list; the UserDefaults write uses a hardcoded string that duplicates the app-side constant.
Resources/Localizable.xcstrings Adds English and Japanese localizations for the new sidebar.help.welcome string; straightforward and correct.
Sources/cmuxApp.swift Introduces the WelcomeSettings enum with the shownKey constant; clean addition with no issues.

Sequence Diagram

sequenceDiagram
    participant User
    participant HelpMenu as SidebarHelpMenu (ContentView)
    participant AppDelegate
    participant TabManager
    participant Workspace
    participant Terminal

    Note over TabManager: First workspace creation (auto-run)
    TabManager->>TabManager: addWorkspace(select: true)
    TabManager->>TabManager: check cmuxWelcomeShown (false)
    TabManager->>TabManager: set cmuxWelcomeShown = true
    TabManager->>TabManager: sendWelcomeWhenReady(workspace)
    loop Poll every 50ms (max 60 attempts)
        TabManager->>Workspace: focusedTerminalPanel?.surface != nil?
        alt surface ready
            TabManager->>Terminal: sendText("cmux welcome\n") after 0.5s
            Terminal->>User: prints gradient logo + shortcuts
        end
    end

    Note over HelpMenu: Manual trigger via ? menu
    User->>HelpMenu: click "Welcome"
    HelpMenu->>AppDelegate: openWelcomeWorkspace()
    AppDelegate->>TabManager: addWorkspace(select: true)
    Note right of TabManager: cmuxWelcomeShown already true,<br/>auto-run skipped
    TabManager-->>AppDelegate: new Workspace
    AppDelegate->>AppDelegate: sendWelcomeCommandWhenReady(workspace)
    loop Poll every 50ms (max 60 attempts)
        AppDelegate->>Workspace: focusedTerminalPanel?.surface != nil?
        alt surface ready
            AppDelegate->>Terminal: sendText("cmux welcome\n") after 0.5s
            Terminal->>User: prints gradient logo + shortcuts
        end
    end
Loading

Last reviewed commit: fc2c543

Comment thread Sources/ContentView.swift
Comment on lines +8653 to +8658
case .welcome:
Task { @MainActor in
if let appDelegate = AppDelegate.shared {
appDelegate.openWelcomeWorkspace()
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Popover not dismissed before opening welcome workspace

Every other case in this switch that triggers a non-URL action — specifically .sendFeedback — explicitly calls isPopoverPresented = false before executing its action. The .welcome case is missing this dismissal, so the help popover will remain visible over the newly-created workspace after the user clicks "Welcome".

Suggested change
case .welcome:
Task { @MainActor in
if let appDelegate = AppDelegate.shared {
appDelegate.openWelcomeWorkspace()
}
}
case .welcome:
isPopoverPresented = false
Task { @MainActor in
if let appDelegate = AppDelegate.shared {
appDelegate.openWelcomeWorkspace()
}
}

Comment thread Sources/TabManager.swift
Comment on lines +871 to +885
private func sendWelcomeWhenReady(to workspace: Workspace, attempt: Int = 0) {
let maxAttempts = 60
if let terminalPanel = workspace.focusedTerminalPanel,
terminalPanel.surface.surface != nil {
// Wait a bit more for the shell prompt to be ready
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
terminalPanel.sendText("cmux welcome\n")
}
return
}
guard attempt < maxAttempts else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
self?.sendWelcomeWhenReady(to: workspace, attempt: attempt + 1)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicate polling implementation

TabManager.sendWelcomeWhenReady and AppDelegate.sendWelcomeCommandWhenReady are byte-for-byte identical (same polling interval, same maxAttempts, same sendText call). Consider extracting a shared free function or a static helper (e.g., on Workspace or a WelcomeHelper type) so any future changes (retry count, delay, text) only need to be made in one place:

// e.g. in a shared file
func sendWelcomeWhenReady(to workspace: Workspace, attempt: Int = 0) {
    let maxAttempts = 60
    if let terminalPanel = workspace.focusedTerminalPanel,
       terminalPanel.surface.surface != nil {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            terminalPanel.sendText("cmux welcome\n")
        }
        return
    }
    guard attempt < maxAttempts else { return }
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
        sendWelcomeWhenReady(to: workspace, attempt: attempt + 1)
    }
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread CLI/cmux.swift Outdated
Comment on lines +6719 to +6720
UserDefaults.standard.set(true, forKey: "cmuxWelcomeShown")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hardcoded key duplicated across separate compile targets

The UserDefaults key used here ("cmuxWelcomeShown") is also defined as WelcomeSettings.shownKey in Sources/cmuxApp.swift. Because the CLI is a separate compile target it cannot import that constant, so the string ends up duplicated. Adding a brief inline comment (e.g. // Keep in sync with WelcomeSettings.shownKey in Sources/cmuxApp.swift) would help future maintainers avoid accidental key drift between the two targets.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc2c543e18

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread CLI/cmux.swift Outdated
Comment on lines +889 to +891
if command == "welcome" {
printWelcome()
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route welcome --help to usage path

Handle welcome after the generic subcommand-help check (or special-case --help) so cmux welcome --help prints usage instead of running the command. In the current order this branch always calls printWelcome(), which also writes cmuxWelcomeShown; asking for help can therefore accidentally mark the welcome flow as already shown and suppress the first-launch auto-welcome behavior.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
Sources/cmuxApp.swift (1)

2818-2820: LGTM! Clean, minimal addition.

The WelcomeSettings enum follows the established pattern of other settings enums in this file. The key name cmuxWelcomeShown is appropriately prefixed and descriptive.

For consistency with sibling settings enums (e.g., TelemetrySettings.isEnabled(defaults:)), you could optionally add a helper method:

♻️ Optional: Add helper methods for consistency
 enum WelcomeSettings {
     static let shownKey = "cmuxWelcomeShown"
+
+    static func hasShown(defaults: UserDefaults = .standard) -> Bool {
+        defaults.bool(forKey: shownKey)
+    }
+
+    static func setShown(_ shown: Bool = true, defaults: UserDefaults = .standard) {
+        defaults.set(shown, forKey: shownKey)
+    }
 }

,

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

In `@Sources/cmuxApp.swift` around lines 2818 - 2820, Add an optional helper
method to WelcomeSettings to mirror sibling enums (e.g.,
TelemetrySettings.isEnabled(defaults:)) by adding a static func like
isShown(defaults: UserDefaults = .standard) -> Bool that reads
UserDefaults.standard.bool(forKey: WelcomeSettings.shownKey); update callers to
use WelcomeSettings.isShown(defaults:) where appropriate for consistency and
testability while keeping the existing shownKey constant.
Sources/TabManager.swift (2)

864-867: Welcome flag set before command is delivered.

The shownKey flag is set to true immediately, before the welcome command is actually sent. If terminal readiness times out (60 attempts exhausted) or the workspace is closed, the user won't see the welcome and won't get another chance on future launches.

If this is intentional (one-shot attempt on first workspace creation), the current behavior is fine. If you want to guarantee the welcome is shown at least once, consider setting the flag only after successfully sending the command.

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

In `@Sources/TabManager.swift` around lines 864 - 867, The code sets
WelcomeSettings.shownKey immediately before calling sendWelcomeWhenReady(to:),
which marks the welcome as shown even if sending fails or times out; change the
flow so the UserDefaults flag is written only after sendWelcomeWhenReady
completes successfully (e.g. add a completion/result handler or make
sendWelcomeWhenReady return success/failure and, upon success, call
UserDefaults.standard.set(true, forKey: WelcomeSettings.shownKey)). Update the
call site that currently uses select and newWorkspace to set the flag only on
confirmed delivery, and adjust sendWelcomeWhenReady(to:)
signature/implementation to surface success so you can reliably persist
shownKey.

871-885: Workspace captured strongly in retry closure; add validation before sending.

The workspace parameter is captured strongly across retries, keeping it alive even if removed from tabs. Consider:

  1. Capturing workspace weakly or by ID
  2. Validating the workspace still exists before sending the command

This avoids sending commands to detached workspaces and allows cleanup if the user closes the workspace during the retry window.

♻️ Suggested refactor with weak capture and existence check
-    private func sendWelcomeWhenReady(to workspace: Workspace, attempt: Int = 0) {
+    private func sendWelcomeWhenReady(workspaceId: UUID, attempt: Int = 0) {
         let maxAttempts = 60
+        guard let workspace = tabs.first(where: { $0.id == workspaceId }) else { return }
         if let terminalPanel = workspace.focusedTerminalPanel,
            terminalPanel.surface.surface != nil {
             // Wait a bit more for the shell prompt to be ready
-            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
+                guard self?.tabs.contains(where: { $0.id == workspaceId }) == true else { return }
                 terminalPanel.sendText("cmux welcome\n")
             }
             return
         }
         guard attempt < maxAttempts else { return }
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
-            self?.sendWelcomeWhenReady(to: workspace, attempt: attempt + 1)
+            self?.sendWelcomeWhenReady(workspaceId: workspaceId, attempt: attempt + 1)
         }
     }

Update the call site accordingly:

         if select && !UserDefaults.standard.bool(forKey: WelcomeSettings.shownKey) {
             UserDefaults.standard.set(true, forKey: WelcomeSettings.shownKey)
-            sendWelcomeWhenReady(to: newWorkspace)
+            sendWelcomeWhenReady(workspaceId: newWorkspace.id)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TabManager.swift` around lines 871 - 885, The retry closure in
sendWelcomeWhenReady strongly captures the Workspace; change the logic to avoid
keeping a dead workspace alive by either accepting a workspace identifier (e.g.
workspace.id) instead of a Workspace object or by capturing the workspace weakly
in the DispatchQueue closures, and before calling terminalPanel.sendText("cmux
welcome\n") validate that the workspace still exists in TabManager's tabs
collection and that workspace.focusedTerminalPanel is non-nil and its surface is
non-nil; also capture self weakly in the retry closure (use [weak self, weak
workspace] or [weak self] plus lookup by id) and bail out if validation fails to
prevent sending to detached workspaces.
🤖 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 889-892: The early-return when command == "welcome" prevents the
shared subcommand-help flow from running; instead, change the logic around the
`if command == "welcome"` branch to first detect help flags (e.g., check for
"--help" or "-h" in the arguments) or delegate to the common subcommand-help
handler before returning. Locate the `command` check and `printWelcome()` call
in CLI/cmux.swift and modify it so that `printWelcome()` is only called for a
plain "welcome" invocation, while "welcome --help" routes to the existing
subcommand-help path (do not remove the shared help handler).
- Around line 4064-4070: Replace the hardcoded multiline help/banners in the
"welcome" switch arm with localized string lookups (use String(localized:
"welcome.help", defaultValue: "Usage: cmux welcome\n\nShow a welcome screen with
the cmux logo and useful shortcuts.\nAuto-runs once on first launch.")) and
similarly convert the ASCII-art/banner strings you added elsewhere to localized
keys (e.g., "welcome.banner.line1", "welcome.banner.line2", etc.) and add
corresponding entries to the Localizable.xcstrings resource so all user-facing
text is localized; update the code to reference those keys (String(localized:
... , defaultValue: ...)) instead of raw literals.

In `@Sources/AppDelegate.swift`:
- Around line 4916-4918: The delayed send captures a possibly stale
terminalPanel; re-resolve the current terminal when the timer fires by replacing
the captured use with a fresh lookup of workspace.focusedTerminalPanel inside
the DispatchQueue.main.asyncAfter block (i.e., call
workspace.focusedTerminalPanel at deadline execution and guard-let it before
calling sendText). Ensure you only send "cmux welcome\n" to the newly resolved
terminalPanel and safely bail out if focusedTerminalPanel is nil or changed.

---

Nitpick comments:
In `@Sources/cmuxApp.swift`:
- Around line 2818-2820: Add an optional helper method to WelcomeSettings to
mirror sibling enums (e.g., TelemetrySettings.isEnabled(defaults:)) by adding a
static func like isShown(defaults: UserDefaults = .standard) -> Bool that reads
UserDefaults.standard.bool(forKey: WelcomeSettings.shownKey); update callers to
use WelcomeSettings.isShown(defaults:) where appropriate for consistency and
testability while keeping the existing shownKey constant.

In `@Sources/TabManager.swift`:
- Around line 864-867: The code sets WelcomeSettings.shownKey immediately before
calling sendWelcomeWhenReady(to:), which marks the welcome as shown even if
sending fails or times out; change the flow so the UserDefaults flag is written
only after sendWelcomeWhenReady completes successfully (e.g. add a
completion/result handler or make sendWelcomeWhenReady return success/failure
and, upon success, call UserDefaults.standard.set(true, forKey:
WelcomeSettings.shownKey)). Update the call site that currently uses select and
newWorkspace to set the flag only on confirmed delivery, and adjust
sendWelcomeWhenReady(to:) signature/implementation to surface success so you can
reliably persist shownKey.
- Around line 871-885: The retry closure in sendWelcomeWhenReady strongly
captures the Workspace; change the logic to avoid keeping a dead workspace alive
by either accepting a workspace identifier (e.g. workspace.id) instead of a
Workspace object or by capturing the workspace weakly in the DispatchQueue
closures, and before calling terminalPanel.sendText("cmux welcome\n") validate
that the workspace still exists in TabManager's tabs collection and that
workspace.focusedTerminalPanel is non-nil and its surface is non-nil; also
capture self weakly in the retry closure (use [weak self, weak workspace] or
[weak self] plus lookup by id) and bail out if validation fails to prevent
sending to detached workspaces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d99a8700-d12e-4443-b826-b87a5800c3c0

📥 Commits

Reviewing files that changed from the base of the PR and between 22ae558 and fc2c543.

📒 Files selected for processing (6)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/TabManager.swift
  • Sources/cmuxApp.swift

Comment thread CLI/cmux.swift Outdated
Comment on lines +889 to +892
if command == "welcome" {
printWelcome()
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

cmux welcome --help is currently unreachable.

This branch returns before the shared subcommand-help path runs, so cmux welcome --help will print the banner instead of usage text.

Suggested fix
-        if command == "welcome" {
-            printWelcome()
-            return
-        }
+        if command == "welcome" {
+            if dispatchSubcommandHelp(command: command, commandArgs: commandArgs) {
+                return
+            }
+            printWelcome()
+            return
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if command == "welcome" {
printWelcome()
return
}
if command == "welcome" {
if dispatchSubcommandHelp(command: command, commandArgs: commandArgs) {
return
}
printWelcome()
return
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 889 - 892, The early-return when command ==
"welcome" prevents the shared subcommand-help flow from running; instead, change
the logic around the `if command == "welcome"` branch to first detect help flags
(e.g., check for "--help" or "-h" in the arguments) or delegate to the common
subcommand-help handler before returning. Locate the `command` check and
`printWelcome()` call in CLI/cmux.swift and modify it so that `printWelcome()`
is only called for a plain "welcome" invocation, while "welcome --help" routes
to the existing subcommand-help path (do not remove the shared help handler).

Comment thread CLI/cmux.swift
Comment on lines +4064 to +4070
case "welcome":
return """
Usage: cmux welcome

Show a welcome screen with the cmux logo and useful shortcuts.
Auto-runs once on first launch.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Localize the new welcome/help copy.

The added welcome help text and banner strings are all hardcoded. That makes the new feature non-localizable even though the repo requires localized user-facing Swift strings.

As per coding guidelines "All user-facing strings must be localized using String(localized: "key.name", defaultValue: "English text") and added to Resources/Localizable.xcstrings".

Also applies to: 6667-6715

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

In `@CLI/cmux.swift` around lines 4064 - 4070, Replace the hardcoded multiline
help/banners in the "welcome" switch arm with localized string lookups (use
String(localized: "welcome.help", defaultValue: "Usage: cmux welcome\n\nShow a
welcome screen with the cmux logo and useful shortcuts.\nAuto-runs once on first
launch.")) and similarly convert the ASCII-art/banner strings you added
elsewhere to localized keys (e.g., "welcome.banner.line1",
"welcome.banner.line2", etc.) and add corresponding entries to the
Localizable.xcstrings resource so all user-facing text is localized; update the
code to reference those keys (String(localized: ... , defaultValue: ...))
instead of raw literals.

Comment thread Sources/AppDelegate.swift Outdated
Comment on lines +4916 to +4918
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
terminalPanel.sendText("cmux welcome\n")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Re-resolve the target terminal inside the delayed send.

terminalPanel is captured before the extra 500ms wait. If the new workspace is immediately closed or focus shifts to another panel during that delay, cmux welcome gets sent to a stale surface instead of the workspace’s current terminal. Re-check workspace.focusedTerminalPanel when the timer fires.

💡 Suggested fix
-            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
-                terminalPanel.sendText("cmux welcome\n")
-            }
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                guard let terminalPanel = workspace.focusedTerminalPanel,
+                      terminalPanel.surface.surface != nil else {
+                    return
+                }
+                terminalPanel.sendText("cmux welcome\n")
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
terminalPanel.sendText("cmux welcome\n")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
guard let terminalPanel = workspace.focusedTerminalPanel,
terminalPanel.surface.surface != nil else {
return
}
terminalPanel.sendText("cmux welcome\n")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 4916 - 4918, The delayed send
captures a possibly stale terminalPanel; re-resolve the current terminal when
the timer fires by replacing the captured use with a fresh lookup of
workspace.focusedTerminalPanel inside the DispatchQueue.main.asyncAfter block
(i.e., call workspace.focusedTerminalPanel at deadline execution and guard-let
it before calling sendText). Ensure you only send "cmux welcome\n" to the newly
resolved terminalPanel and safely bail out if focusedTerminalPanel is nil or
changed.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 6 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="CLI/cmux.swift">

<violation number="1" location="CLI/cmux.swift:889">
P2: Handle `--help` for the welcome subcommand before returning. As written, `cmux welcome --help` prints the welcome screen instead of the usage text because the command returns before the subcommand help check.</violation>
</file>

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

<violation number="1" location="Sources/TabManager.swift:871">
P3: Duplicate welcome-send helper: this logic already exists in AppDelegate (sendWelcomeCommandWhenReady). Consider reusing a shared helper to avoid drift if the readiness logic changes.</violation>
</file>

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

Comment thread CLI/cmux.swift Outdated
Comment thread Sources/TabManager.swift
@lawrencecchen lawrencecchen changed the title Add cmux welcome command Polish welcome, feedback, and shortcuts flows Mar 11, 2026
@lawrencecchen
lawrencecchen merged commit 52783bd into main Mar 11, 2026
11 of 12 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82a7086665

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread Sources/TabManager.swift
Comment on lines +865 to +867
if autoWelcomeIfNeeded && select && !UserDefaults.standard.bool(forKey: WelcomeSettings.shownKey) {
if let appDelegate = AppDelegate.shared {
appDelegate.sendWelcomeCommandWhenReady(to: newWorkspace, markShownOnSend: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark welcome as shown before scheduling auto-send

This branch gates auto-welcome on cmuxWelcomeShown, but the flag is only set later inside the asynchronous send path, so two workspace creations in quick succession can both observe false and queue cmux welcome. In that timing window, the “run once” welcome can execute multiple times across workspaces; mark the key when scheduling (or use an atomic one-shot gate) to enforce single execution.

Useful? React with 👍 / 👎.

Comment on lines +5491 to +5492
if semaphore.wait(timeout: .now() + 35) == .timedOut {
return .err(code: "timeout", message: "Feedback submission timed out", data: nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cancel timed-out feedback submission task

When the 35s wait times out, the method returns a timeout error but the Task created above is left running, so the submission can still complete in the background after the caller retries. On slow or flaky networks this can lead to duplicate feedback submissions and mismatched client state (timeout even though one request eventually succeeded). Store the task handle and cancel it on timeout.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 issues found across 5 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:8470">
P2: Localize the new errorDescription strings with String(localized:..., defaultValue:...) so they can be translated.</violation>

<violation number="2" location="Sources/ContentView.swift:8543">
P2: Localize the new userFacingMessage strings (use String(localized:..., defaultValue:...)) to follow the project’s localization requirement.</violation>
</file>

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

<violation number="1" location="Sources/TabManager.swift:866">
P2: Set the `shownKey` synchronously to prevent multiple workspaces from triggering the welcome command during startup.</violation>
</file>

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

<violation number="1" location="Sources/TerminalController.swift:5491">
P2: Cancel the in-flight feedback submission task when the 35s wait times out. Returning `timeout` while the original task keeps running can produce duplicate submissions if callers retry.</violation>
</file>

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

Comment thread Sources/ContentView.swift

private static func userFacingMessage(for error: Error) -> String {
guard let submissionError = error as? FeedbackComposerSubmissionError else {
return "Couldn't send feedback. Please try again."

@cubic-dev-ai cubic-dev-ai Bot Mar 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Localize the new userFacingMessage strings (use String(localized:..., defaultValue:...)) to follow the project’s localization requirement.

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

<comment>Localize the new userFacingMessage strings (use String(localized:..., defaultValue:...)) to follow the project’s localization requirement.</comment>

<file context>
@@ -8456,6 +8456,122 @@ private struct SidebarFeedbackComposerSheet: View {
+
+    private static func userFacingMessage(for error: Error) -> String {
+        guard let submissionError = error as? FeedbackComposerSubmissionError else {
+            return "Couldn't send feedback. Please try again."
+        }
+
</file context>
Fix with Cubic

Comment thread Sources/ContentView.swift
var errorDescription: String? {
switch self {
case .invalidEmail:
return "Enter a valid email address."

@cubic-dev-ai cubic-dev-ai Bot Mar 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Localize the new errorDescription strings with String(localized:..., defaultValue:...) so they can be translated.

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

<comment>Localize the new errorDescription strings with String(localized:..., defaultValue:...) so they can be translated.</comment>

<file context>
@@ -8456,6 +8456,122 @@ private struct SidebarFeedbackComposerSheet: View {
+    var errorDescription: String? {
+        switch self {
+        case .invalidEmail:
+            return "Enter a valid email address."
+        case .emptyMessage:
+            return "Enter a message before sending."
</file context>
Fix with Cubic

Comment thread Sources/TabManager.swift
])
#endif
if autoWelcomeIfNeeded && select && !UserDefaults.standard.bool(forKey: WelcomeSettings.shownKey) {
if let appDelegate = AppDelegate.shared {

@cubic-dev-ai cubic-dev-ai Bot Mar 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Set the shownKey synchronously to prevent multiple workspaces from triggering the welcome command during startup.

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

<comment>Set the `shownKey` synchronously to prevent multiple workspaces from triggering the welcome command during startup.</comment>

<file context>
@@ -861,9 +862,12 @@ class TabManager: ObservableObject {
-            UserDefaults.standard.set(true, forKey: WelcomeSettings.shownKey)
-            sendWelcomeWhenReady(to: newWorkspace)
+        if autoWelcomeIfNeeded && select && !UserDefaults.standard.bool(forKey: WelcomeSettings.shownKey) {
+            if let appDelegate = AppDelegate.shared {
+                appDelegate.sendWelcomeCommandWhenReady(to: newWorkspace, markShownOnSend: true)
+            } else {
</file context>
Fix with Cubic

semaphore.signal()
}

if semaphore.wait(timeout: .now() + 35) == .timedOut {

@cubic-dev-ai cubic-dev-ai Bot Mar 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Cancel the in-flight feedback submission task when the 35s wait times out. Returning timeout while the original task keeps running can produce duplicate submissions if callers retry.

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

<comment>Cancel the in-flight feedback submission task when the 35s wait times out. Returning `timeout` while the original task keeps running can produce duplicate submissions if callers retry.</comment>

<file context>
@@ -5379,6 +5392,109 @@ class TerminalController {
+            semaphore.signal()
+        }
+
+        if semaphore.wait(timeout: .now() + 35) == .timedOut {
+            return .err(code: "timeout", message: "Feedback submission timed out", data: nil)
+        }
</file context>
Fix with Cubic

bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* Add cmux welcome command with ASCII logo and shortcuts

Adds `cmux welcome` CLI command that prints a blue-to-purple gradient
chevron logo, version info, and key shortcuts. Auto-runs once on first
workspace creation via UserDefaults. Adds "Welcome" to the ? help menu
in the sidebar footer, which opens a new workspace running the command.

* Polish welcome, feedback, and shortcuts flows

This branch was successfully deployed

1 active deployment
Preview — 82a70866 Deployed Mar 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant