Skip to content

Add iOS custom toolbar macros - #6924

Closed
austinywang wants to merge 13 commits into
mainfrom
issue-6087-ios-custom-key-combos-macros-in-the-toolbar-e
Closed

austinywang wants to merge 13 commits into
mainfrom
issue-6087-ios-custom-key-combos-macros-in-the-toolbar-e

Conversation

@austinywang

@austinywang austinywang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6087

Summary

  • Add multi-step custom toolbar macro payloads alongside text and key-combo actions
  • Expose Text / Key Combo / Macro modes in the iOS custom toolbar action editor
  • Persist and validate custom macro actions with localized editor strings

Tests

  • swift test --package-path Packages/iOS/CmuxMobileTerminalKit
  • jq empty ios/cmux/Resources/Localizable.xcstrings
  • swift build --package-path Packages/iOS/CmuxMobileShellUI (blocked before Swift compilation: missing local GhosttyKit.xcframework artifact)

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


Summary by cubic

Adds iOS custom toolbar macros so a button can send text, a key combo, or a multi-step macro. Validates and encodes actions, rejects unsupported/partial macros, localizes the editor (incl. “Step N”), and fixes a macro-step deletion crash for #6087.

  • New Features

    • CmuxMobileTerminalKit: added ToolbarActionMacroStep and .macro in ToolbarActionPayload; moved output encoding to payload/steps; normalized newlines to \r; concatenated step outputs; CustomToolbarAction.output now delegates to payload.
    • CmuxMobileShellUI: editor adds an Action Type picker (Text, Key Combo, Macro), key-combo fields with Shift/Control/Option toggles and a localized key picker with an “unsupported combo” hint, and a macro step editor with add/remove, per-step kind, stable step IDs, and localized titles including “Step N”.
    • Persistence/tests: Codable round-trips for text/key/macro; TerminalAccessoryConfiguration persists and keeps macro actions enabled; tests cover macro concatenation and rejection of unsupported/partial/empty macros; new localized strings and special-key display names.
  • Bug Fixes

    • Prevent index-binding crashes when deleting macro steps by iterating over element bindings and deleting/numbering by stable step id.

Written for commit 4737f62. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Custom toolbar actions now support three modes: literal text, a single special-key combo, and multi-step macros.
    • Added an iOS macro editor to add/edit/delete steps, including localized step/mode labels and a key-combo selector.
  • Bug Fixes
    • Payload-driven validation and saving now prevent saving unsupported or empty macro/key-combo configurations, and terminal output correctly reflects macro step encoding.
  • Documentation
    • Updated iOS localization strings for the toolbar action editor workflow.
  • Tests
    • Added macro output, encoding/decoding, and persistence coverage to ensure correct round-trips.

@vercel

vercel Bot commented Jun 26, 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 Jul 5, 2026 12:01am
cmux-staging Building Building Preview, Comment Jul 5, 2026 12:01am

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds iOS toolbar actions for text, key combos, and macros. It introduces new payload and macro-step types, updates action output encoding, expands the toolbar editor UI to switch modes and edit macro steps, and adds localization and tests.

Changes

iOS toolbar macros

Layer / File(s) Summary
Payload encoding
Packages/iOS/CmuxMobileTerminalKit/Sources/CmuxMobileTerminalKit/ToolbarAction*.swift, Packages/iOS/CmuxMobileTerminalKit/Sources/CmuxMobileTerminalKit/CustomToolbarAction.swift, Packages/iOS/CmuxMobileTerminalKit/Tests/CmuxMobileTerminalKitTests/ToolbarCustomizationTests.swift, ios/cmuxPackage/Tests/cmuxFeatureTests/TerminalAccessoryConfigurationTests.swift
ToolbarActionMacroStep and ToolbarActionPayload encode text, key-combo, and macro output; CustomToolbarAction.output now delegates to the payload, and tests cover macro encoding, Codable round-tripping, and configuration persistence.
Editor primitives
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Toolbar*.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalSpecialKey+ToolbarEditor.swift, ios/cmux/Resources/Localizable.xcstrings
Adds the iOS editor mode and step-kind enums, macro step drafts, key-combo fields, macro step editing UI, special-key display helpers, and step-editing localization strings.
Editor orchestration
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CustomToolbarActionEditorView.swift, ios/cmux/Resources/Localizable.xcstrings
The main toolbar action editor now switches between text, key-combo, and macro modes, seeds the new state, derives validation from the computed payload, and saves actions with the selected payload.

Estimated code review effort: 4 (Complex) | ~60 minutes


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error CustomToolbarActionEditorView line 162 does firstIndex inside ForEach over macroSteps, making body rendering O(n²) for user-defined macro step lists. Precompute step indices in one pass or use an id/index map so each row doesn’t rescan macroSteps during SwiftUI rendering.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (23 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements iOS custom key-combo and multi-step macro toolbar actions, matching #6087's acceptance criteria.
Out of Scope Changes check ✅ Passed The changes are aligned with the toolbar macro feature and related editor, payload, tests, and localization updates.
Cmux Swift Actor Isolation ✅ Passed No new actor-isolation regressions: the added model enums/structs are plain value types in terminal kit, and the new SwiftUI views are intentionally main-actor bound.
Cmux Swift Blocking Runtime ✅ Passed The changed Swift files are state-driven UI/payload helpers and tests; no semaphores, sync waits, sleeps, asyncAfter, or manual locks were added.
Cmux Browser Automation Off-Main ✅ Passed PASS: The diff is only a ghostty submodule bump; none of the browser-automation routing files or wait/callback paths in the rule were changed.
Cmux Expensive Synchronous Load ✅ Passed Diff stays in toolbar editor/payload code; no agent-history/session/transcript load or heavy JSON parsing was added to any main-actor or interactive path.
Cmux Cache Substitution Correctness ✅ Passed No fresh authoritative read was swapped for a cache in any persistence/snapshot path; save/persist use the current editor state and write directly.
Cmux No Hacky Sleeps ✅ Passed No TS/JS/shell/build-runtime files were changed; the only non-Swift path is a submodule pointer, with no evidence of new fixed waits.
Cmux Swift Concurrency ✅ Passed Edited files only add SwiftUI/model/payload code; no DispatchQueue, Combine, completion-handler APIs, or fire-and-forget Tasks were introduced.
Cmux Swift @Concurrent ✅ Passed PASS: The edited Swift files are synchronous UI/value code; no new async, nonisolated, or @concurrent work was added.
Cmux Swift File And Package Boundaries ✅ Passed PASS: All production Swift additions are small, modular package files; the largest editor is 329 lines and core macro/payload logic is isolated in CmuxMobileTerminalKit.
Cmux Swiftpm Lockfiles ✅ Passed No cmux-owned Package.swift/.gitignore/Package.resolved or Xcode project dependency changes appear in the diff; vendor/bonsplit is only a vendored submodule pointer update.
Cmux Swift Logging ✅ Passed Patch scan found no added/changed print/debugPrint/dump/NSLog/Logger usage or sensitive-data logs in the Swift diff.
Cmux User-Facing Error Privacy ✅ Passed New user-facing copy is generic; the added error-like message says only that a key combo isn’t supported, with no vendor/internal secrets or raw upstream text.
Cmux Full Internationalization ✅ Passed All new user-facing Swift strings use L10n.string/String.localizedStringWithFormat, and ios/cmux/Resources/Localizable.xcstrings has en/ja translations for every added key.
Cmux Swiftui State Layout ✅ Passed The touched SwiftUI views use only local @State/@binding; no new ObservableObject/@published, GeometryReader, lazy-row store refs, or render-time state writes appear.
Cmux Architecture Rethink ✅ Passed PASS: The PR adds localized macro editor/payload support with no sleeps, observers, locks, side channels, or split lifecycle ownership in the changed Swift files.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only adds iOS toolbar editor views and payload/model types; no NSWindow/NSPanel/NSWindowController/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Only a submodule pointer changed; its diff touches source files (include/ghostty.h, src/Surface.zig, src/apprt/embedded.zig), not artifacts.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: The added production Swift files only introduce toolbar mode/macro types and UI; none contain #if DEBUG or test-only/debug seam members/names.
Cmux No Ambient Global State ✅ Passed New Swift additions are instance-based views/models or enum cases; no new top-level funcs/vars or singleton/static-only namespace was introduced.
Title check ✅ Passed The title clearly summarizes the main change: adding iOS custom toolbar macros.
Description check ✅ Passed The description covers the required summary and testing details and is mostly complete, though the demo video and checklist sections are omitted.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-6087-ios-custom-key-combos-macros-in-the-toolbar-e

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds iOS custom toolbar macros, extending the existing text-only toolbar action to support three modes: literal text, a single modified key combo, and a multi-step macro that concatenates step outputs. It also fixes an index-out-of-range crash when deleting macro steps (#6087) by switching to element bindings keyed on stable step UUIDs instead of index-backed bindings.

  • New model layer (ToolbarActionMacroStep, ToolbarActionPayload.macro): The .macro case concatenates step byte sequences and refuses to emit a partial result if any step is unencodable. CustomToolbarAction.output is simplified to delegate entirely to payload.output.
  • New editor UI (CustomToolbarActionEditorView + five new helper types): Adds an action-type picker (Text / Key Combo / Macro), key-combo modifier toggles, a dynamic macro step list with add/remove buttons, and "unsupported combo" hints. All new user-facing strings are added to Localizable.xcstrings with en and ja translations.
  • Test coverage: New tests verify macro output concatenation, rejection of partial/empty macros, Codable round-trips for all three payload kinds, and persistence through TerminalAccessoryConfiguration.

Confidence Score: 5/5

Safe to merge — well-validated model layer, correct ForEach deletion fix, and complete localization coverage.

The macro model correctly guards against partial output (any unencodable step causes the whole macro to return nil), the ForEach element-binding approach is the right fix for the deletion crash, all 24 new string keys carry both en and ja translations, and the new tests cover concatenation, rejection, Codable round-trips, and persistence. No actor isolation, blocking-primitive, or ambient-global-state issues were found in the new production sources.

No files require special attention.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileTerminalKit/Sources/CmuxMobileTerminalKit/ToolbarActionPayload.swift Adds .macro([ToolbarActionMacroStep]) case and moves encoding logic into output. Partial-macro guard (returns nil if any step is unencodable) is correct. public import Foundation re-export for Data is proper Swift 6 usage.
Packages/iOS/CmuxMobileTerminalKit/Sources/CmuxMobileTerminalKit/ToolbarActionMacroStep.swift New value type for individual macro steps. Encoding logic matches the old CustomToolbarAction.output and correctly normalizes \n→\r. public import Foundation re-export is appropriate.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CustomToolbarActionEditorView.swift Adds mode picker and three section variants (text, keyCombo, macro). ForEach element-binding pattern with id-keyed deletion correctly fixes the deletion crash. seed(from:) correctly reconstructs all three payload kinds. Localized strings are present for all user-facing text.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/ToolbarMacroStepDraft.swift Editor-side draft for a single macro step. Stable UUID identity for ForEach and deletion. macroStep validation (empty text + no runAfterTyping → nil) prevents invalid steps from entering the payload.
ios/cmux/Resources/Localizable.xcstrings Adds 24 new string keys for the toolbar action editor. Every new key has both en and ja translations, matching the catalog's two existing locales. Extraction state is manual throughout, consistent with the rest of the file.
Packages/iOS/CmuxMobileTerminalKit/Tests/CmuxMobileTerminalKitTests/ToolbarCustomizationTests.swift Adds three new tests: macro output concatenation, rejection of unencodable/empty macros, and Codable round-trip for the new .macro case. Coverage is thorough for the model layer.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User taps Save] --> B{isValid?}
    B -- No --> C[Save disabled]
    B -- Yes --> D{mode}
    D -- .text --> E[ToolbarActionPayload.text]
    D -- .keyCombo --> F[ToolbarActionPayload.keyCombo]
    D -- .macro --> G[Collect macroSteps]
    G --> H{All steps valid?}
    H -- No --> I[macroPayload = nil, disabled]
    H -- Yes --> J[ToolbarActionPayload.macro steps]
    E --> K[payload.output]
    F --> K
    J --> K
    K --> L{switch payload}
    L -- .text --> M[normalize newline to CR]
    L -- .keyCombo --> N[TerminalKeyEncoder.encode]
    L -- .macro --> O{for each step output}
    O -- any nil --> P[whole macro = nil]
    O -- all non-nil --> Q[Data sequence concat]
    M --> R[CustomToolbarAction.output send to terminal]
    N --> R
    Q --> R
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[User taps Save] --> B{isValid?}
    B -- No --> C[Save disabled]
    B -- Yes --> D{mode}
    D -- .text --> E[ToolbarActionPayload.text]
    D -- .keyCombo --> F[ToolbarActionPayload.keyCombo]
    D -- .macro --> G[Collect macroSteps]
    G --> H{All steps valid?}
    H -- No --> I[macroPayload = nil, disabled]
    H -- Yes --> J[ToolbarActionPayload.macro steps]
    E --> K[payload.output]
    F --> K
    J --> K
    K --> L{switch payload}
    L -- .text --> M[normalize newline to CR]
    L -- .keyCombo --> N[TerminalKeyEncoder.encode]
    L -- .macro --> O{for each step output}
    O -- any nil --> P[whole macro = nil]
    O -- all non-nil --> Q[Data sequence concat]
    M --> R[CustomToolbarAction.output send to terminal]
    N --> R
    Q --> R
Loading

Reviews (13): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

@blacksmith-sh

This comment has been minimized.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CustomToolbarActionEditorView.swift (1)

246-249: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use String.localizedStringWithFormat instead of String(format:) for the localized step title.

L10n.string returns a plain String, and the format argument (index) is applied separately via String(format:). Based on prior repo guidance, this overload pattern should use String.localizedStringWithFormat so numeral/positional formatting respects the user's locale, rather than plain String(format:).

💬 Proposed fix
     private func macroStepTitle(index: Int) -> String {
         let format = L10n.string("mobile.toolbar.editor.stepHeaderFormat", defaultValue: "Step %d")
-        return String(format: format, index)
+        return String.localizedStringWithFormat(format, index)
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CustomToolbarActionEditorView.swift`
around lines 246 - 249, The localized step title in macroStepTitle(index:) is
using String(format:), which can ignore locale-specific number formatting.
Update that helper to use String.localizedStringWithFormat with the format
returned by L10n.string so the step label respects the user’s locale while
keeping the same stepHeaderFormat behavior.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CustomToolbarActionEditorView.swift`:
- Around line 246-249: The localized step title in macroStepTitle(index:) is
using String(format:), which can ignore locale-specific number formatting.
Update that helper to use String.localizedStringWithFormat with the format
returned by L10n.string so the step label respects the user’s locale while
keeping the same stepHeaderFormat behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 36769dd5-2e55-4213-bdbf-3da860f2a51a

📥 Commits

Reviewing files that changed from the base of the PR and between a6bfce1 and be0dd0a.

📒 Files selected for processing (2)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CustomToolbarActionEditorView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalSpecialKey+ToolbarEditor.swift

Iterate the macro editor over element bindings (ForEach($macroSteps))
instead of index-backed $macroSteps[index]. A macro row can delete
itself, and an index captured in a binding goes stale the instant the
array shrinks; SwiftUI can re-read that binding during the Form diff and
crash with an index-out-of-range (most reproducibly when deleting a
middle or last step). Element bindings stay valid across removals, and
the row now deletes and numbers itself by step id.

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

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

No issues found across 13 files

Re-trigger cubic

macroStepTitle builds a user-facing 'Step N' header. Use
String.localizedStringWithFormat so the index respects the user's
locale, matching the app's dominant localized-format idiom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
austinywang and others added 2 commits July 1, 2026 20:48
The Vercel preview deployment for the prior merge commit was created but
never picked up by a builder (commit status stuck 'pending', never
updated), while recent main commits deploy fine. Empty commit to
re-trigger a fresh deploy. No source changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 4737f626 Deployed Jul 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS: custom key combos / macros in the toolbar (e.g. rotate Claude permission-mode / effort)

3 participants