Repository navigation
Polish welcome, feedback, and shortcuts flows - #1169
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 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 unit tests (beta)
Comment |
Greptile SummaryThis PR adds a Key changes:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
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
Last reviewed commit: fc2c543 |
| case .welcome: | ||
| Task { @MainActor in | ||
| if let appDelegate = AppDelegate.shared { | ||
| appDelegate.openWelcomeWorkspace() | ||
| } | ||
| } |
There was a problem hiding this comment.
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".
| 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() | |
| } | |
| } |
| 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) | ||
| } | ||
| } |
There was a problem hiding this comment.
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!
| UserDefaults.standard.set(true, forKey: "cmuxWelcomeShown") | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
💡 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".
| if command == "welcome" { | ||
| printWelcome() | ||
| return |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
Sources/cmuxApp.swift (1)
2818-2820: LGTM! Clean, minimal addition.The
WelcomeSettingsenum follows the established pattern of other settings enums in this file. The key namecmuxWelcomeShownis 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
shownKeyflag is set totrueimmediately, 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
workspaceparameter is captured strongly across retries, keeping it alive even if removed fromtabs. Consider:
- Capturing workspace weakly or by ID
- 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
📒 Files selected for processing (6)
CLI/cmux.swiftResources/Localizable.xcstringsSources/AppDelegate.swiftSources/ContentView.swiftSources/TabManager.swiftSources/cmuxApp.swift
| if command == "welcome" { | ||
| printWelcome() | ||
| return | ||
| } |
There was a problem hiding this comment.
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.
| 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).
| case "welcome": | ||
| return """ | ||
| Usage: cmux welcome | ||
|
|
||
| Show a welcome screen with the cmux logo and useful shortcuts. | ||
| Auto-runs once on first launch. | ||
| """ |
There was a problem hiding this comment.
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.
| DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { | ||
| terminalPanel.sendText("cmux welcome\n") | ||
| } |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
💡 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".
| if autoWelcomeIfNeeded && select && !UserDefaults.standard.bool(forKey: WelcomeSettings.shownKey) { | ||
| if let appDelegate = AppDelegate.shared { | ||
| appDelegate.sendWelcomeCommandWhenReady(to: newWorkspace, markShownOnSend: true) |
There was a problem hiding this comment.
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 👍 / 👎.
| if semaphore.wait(timeout: .now() + 35) == .timedOut { | ||
| return .err(code: "timeout", message: "Feedback submission timed out", data: nil) |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
|
|
||
| private static func userFacingMessage(for error: Error) -> String { | ||
| guard let submissionError = error as? FeedbackComposerSubmissionError else { | ||
| return "Couldn't send feedback. Please try again." |
There was a problem hiding this comment.
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>
| var errorDescription: String? { | ||
| switch self { | ||
| case .invalidEmail: | ||
| return "Enter a valid email address." |
There was a problem hiding this comment.
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>
| ]) | ||
| #endif | ||
| if autoWelcomeIfNeeded && select && !UserDefaults.standard.bool(forKey: WelcomeSettings.shownKey) { | ||
| if let appDelegate = AppDelegate.shared { |
There was a problem hiding this comment.
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>
| semaphore.signal() | ||
| } | ||
|
|
||
| if semaphore.wait(timeout: .now() + 35) == .timedOut { |
There was a problem hiding this comment.
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>
* 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
Summary
cmux welcomecopy, shortcuts, and support linkscmux feedbackandcmux shortcutsCLI flowsVerification
./scripts/reload.sh --tag settings-focus-fix./scripts/reload.sh --tag keyboard-shortcuts-copy./scripts/reload.sh --tag welcome-emailSummary by CodeRabbit
New Features
Documentation / Localization