Add setting actions, setting presets, and cmux config set - #14868
Conversation
cmux.json actions can now change settings: "type": "setting" with a path and one of set, toggle, cycle, or unset, and "type": "settingPreset" to apply a named partial settings object from the new top-level settingPresets. `cmux config get|set|unset|toggle|cycle|preset` drive the same JSONConfigStore.apply path, so every entrypoint validates against the schema, keeps comments and unrelated keys, and publishes one atomic write under the cooperative writer lock. Setting actions only run when the global cmux.json declares them; project configs and packs can't ship a button that rewrites global settings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A preset's empty nested object merges nothing instead of replacing the whole section; a preset that sets nothing is refused. - A setting action with no source path fails closed. - Docs, schema, and comments say packs referenced by the global config may declare setting actions (they inherit its source path), and that confirm doesn't apply. - Move the CLI help note below the subcommand list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 7 minutes. View limit detailsLimit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Repository: manaflow-ai/cmux/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (53)
📝 WalkthroughWalkthroughThe change adds setting and preset operations to the configuration store and CLI. It also adds setting actions for global configuration and referenced packs, with optional confirmation. Schema lookup, value conversion, path validation, configuration loading, tests, and documentation support these operations. ChangesSetting changes and presets
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~50 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant TabBarButton
participant Workspace
participant CmuxSettingActionRunner
participant JSONConfigStore
participant GlobalConfig as Global cmux.json
TabBarButton->>Workspace: Select setting action
Workspace->>CmuxSettingActionRunner: Pass action and configuration metadata
CmuxSettingActionRunner->>CmuxSettingActionRunner: Check source and request confirmation if needed
CmuxSettingActionRunner->>JSONConfigStore: Apply setting change
JSONConfigStore->>GlobalConfig: Validate and write edits
Suggested reviewers: Merge Risk: 🔵 Low · up to The new setting actions and config commands appear sound. The dogfood tour, however, overwrites the global cmux.json of whoever runs it and makes no backup. Add a backup or use an isolated config location in the scenario; this does not affect shipped app behavior. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Project-controlled menu labels can disguise an existing global setting action. Exploitation requires the user to have configured that action and select the misleading menu entry. Global-source checks, retained confirmation settings, schema validation, and guarded writes limit the impact. Retained concerns
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (8 errors, 1 inconclusive)
✅ Passed checks (16 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 27.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 111 functions across 22 files. (30 skipped: 27 unsupported, 3 too large.) Full details: Cmux Swift Actor IsolationExplanation The diff adds a direct call to the Resolution Add an explicit MainActor boundary for the complete surface-button handling path. Prefer marking Full details: Cmux Swift Blocking RuntimeExplanation The PR adds production blocking synchronization in Resolution Remove the semaphore bridge. Make the config command path async, or use an async completion/continuation-based CLI entry point that keeps the process alive until Full details: Cmux Algorithmic ComplexityExplanation The new batch mutation path rescans the full JSONC document for every preset leaf edit. Resolution Add a batch JSONC source-edit operation. Parse and index the source once, plan all edit ranges against that snapshot, and apply the non-overlapping replacements in one pass, or group edits by parsed object and rewrite each object once. Keep the existing single-edit path for single edits if needed. Add a test or benchmark with about 1000 preset leaf edits to verify linear behavior in document size plus edit count. Full details: Cmux Swift ConcurrencyExplanation The new Resolution Make setting-action execution asynchronous and propagate Full details: Cmux Swift LoggingExplanation The PR adds three unguarded Resolution Replace the three added Full details: Cmux User-Facing Error PrivacyExplanation The new product CLI and app alert paths can expose a credential. The schema declares Resolution Classify credential-bearing settings, including Full details: Cmux Full InternationalizationExplanation The PR introduces untranslated production text. Resolution Add translated catalog entries for all 20 locales supported by Full details: Cmux Architecture RethinkExplanation The new Resolution Make the app-owned ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
All contributors have signed the CLA ✍️ ✅ |
Catch-up merge by scripts/ci/catch_up_pr.py (RFC manaflow-ai#14631). Merged by scripts/merge-main.sh: mf/main at b1daa44. Resolved generated files: - Resources/Localizable.xcstrings: xcstrings key-level union - cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py - Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift: generate-cmux-config-schema.py, regenerated from the merged schema Catch-up-previous-head: ba012db Catch-up-base: b1daa44
A setting or settingPreset action with "confirm": true now asks before saving and shows the equivalent `cmux config` command. The project-action trust prompt never covers the global config, so this is the only prompt these actions get. A path that only resolves when one of its keys contains "." (for example a workspaceGroups.byCwd entry for ~/src/app.web) is refused with a keyContainsDot error that says so, instead of "isn't a cmux setting". Such keys stay unsupported; there is no escaping syntax. Docs, the CLI contract, and the cmux-settings skill say so. Adds a test that packs the global config references may declare setting actions while a project config's packs may not, and that confirm reaches both the palette action and the tab bar button. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JSONSerialization prints a Double with 17 significant digits, so `cmux config set terminal.scrollSpeed 1.4`, a cycle entry, or a preset leaf wrote 1.3999999999999999 into cmux.json and the CLI echoed it. CmuxSettingValue now hands JSONSerialization an NSDecimalNumber built from Swift's shortest round-trip text, and preset leaves are re-encoded through CmuxSettingValue so every path writes 1.4. CI run 36310670056 on e478696 caught this through the new commandLineDescriptions and confirmationDialogShowsTheEquivalentCommand tests; this adds a file-text assertion for set and preset. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review fixes: - Most settings live in UserDefaults, and cmux.json only overrides them. toggle, cycle, and get now read a key the file doesn't set from the app's UserDefaults (CmuxSettingLiveValues, backed by the setting catalog) before the schema default, so the first press after changing a setting in the Settings window flips what the user sees. The app reads its own defaults; the CLI reads the enclosing app's domain. `cmux config get` says where the value came from, and `unset` is described as removing the key from cmux.json. - A project config could override a global setting action's title, shortcut, or confirm through an actions entry or a tab bar button. Both overrides are now ignored for setting actions unless they come from the global config. - The failure alert falls back to a localized "couldn't read or save cmux.json" message for store errors that have no description. - keyContainsDot only fires when the rejoined key exists in the file or looks like a path, so a typo under a map keyed by names stays an unknown path. - `cmux config get --json` uses `path` and `file` like the other subcommands, plus `source`. - The schema no longer says confirm doesn't apply to setting actions. - The setting-action docs keys are translated into the 18 other web locales, and CHANGELOG has a line. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Second review pass: - Some UserDefaults values aren't stored the way cmux.json spells them: app.minimalMode is a presentation-mode string, and app.keepWorkspaceOpenWhenClosingLastSurface is stored as the opposite flag. Lists and maps are often stored as text. The live resolver now maps those two explicitly and otherwise accepts only a scalar whose type the schema allows at that path (CmuxConfigSchemaPathLookup gains declaredTypes(at:)). A catalog-wide test fails if a key the resolver accepts stores a default that disagrees with the schema default, so a new transformed key has to get a mapping. - Re-setting a fraction that's already in the file is no longer reported as a change: receipts and the no-op check compare numbers in the same short form they are written in. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Catch-up merge by scripts/ci/catch_up_pr.py (RFC manaflow-ai#14631). Merged by scripts/merge-main.sh: origin/main at 5c68499. Resolved generated files: - Resources/Localizable.xcstrings: xcstrings key-level union Catch-up-previous-head: 7fb4665 Catch-up-base: 5c68499
CI failure attributionCI failed on
Matched log linesNot re-run automatically: Written by |
The catalog test caught terminal.sessionContentMaxWidth: UserDefaults stores -1 for "no cap", which cmux.json spells false. The live resolver now maps widths under the minimum to false. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…actions # Conflicts: # CHANGELOG.md
Catch-up merge by scripts/ci/catch_up_pr.py (RFC manaflow-ai#14631). Merged by scripts/merge-main.sh: origin/main at f5c179f. Catch-up-previous-head: 81bda19 Catch-up-base: f5c179f
|
Deployment failed for project cmux with the following error: |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift # cmux.xcodeproj/project.pbxproj
Catch-up merge by scripts/ci/catch_up_pr.py (RFC manaflow-ai#14631). Merged by scripts/merge-main.sh: origin/main at 478e323. Resolved conflicts: - Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/Resources/Localizable.xcstrings: xcstrings key-level union - Resources/Localizable.xcstrings: xcstrings key-level union - Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift: generate-cmux-config-schema.py, regenerated from the merged schema (both sides changed the schema) Catch-up-previous-head: 13aaffb Catch-up-base: 478e323 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Review: Global configuration packs could declare setting presets, but setting actions only passed the action registry to the mutation path, so pack presets failed as unknown presets. Fixed: Resolved trusted global-pack presets with direct global configuration precedence, passed them through command palette and surface tab actions without persisting pack metadata, and added regression coverage. Left: No findings. |
Catch-up merge by scripts/ci/catch_up_pr.py (RFC manaflow-ai#14631). Merged by scripts/merge-main.sh: origin/main at d87c3be, the newest commit with green CI fast guards (2 newer skipped). Resolved conflicts: - Resources/Localizable.xcstrings: xcstrings key-level union - cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py Catch-up-previous-head: 97d8ac9 Catch-up-base: d87c3be Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Prefer current global presets, guard symlink retargets during publication, and keep tour cleanup isolated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-main commit by scripts/merge-main.sh. Merged by scripts/merge-main.sh: origin/main at a6c098a. Resolved conflicts: - Resources/Localizable.xcstrings: xcstrings key-level union - cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py Merge-main-previous-head: a70f1fb Merge-main-base: a6c098a
|
Merge receipt for
Labeled |
main no longer compiles after this merge@teamleaderleo: after Evidence: https://github.com/manaflow-ai/cmux/actions/runs/36763044007/job/110050533736 Nothing blocks merging meanwhile. A fix-forward (or, failing that, a revert) is attempted automatically unless an open pull request already fixes this. main_compile_attribution.py: post-merge, nothing here gates a merge. |
#14868 merged 74c3a5f after its compile admission failed, so CmuxSettings, and with it the app, no longer builds on main: JSONConfigAtomicPublisher.swift:74: call can throw but is not marked with 'try' JSONConfigStore.swift:601: reference to property 'fileURL' in closure requires explicit use of 'self' to make capture semantics explicit The post-exchange rollback now uses `if try`, like the publisher's two other rollback call sites, so a failed rollback still reports sourceChangedRollbackFailed. The isTargetCurrent closure captures the store's nonisolated fileURL by value instead of the actor. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@teamleaderleo main doesn't compile since this merge. 74c3a5f's compile admission failed at 19:03:11Z, and the PR merged at 19:03:57Z: #16094 fixes both, together with the bonsplit pin that #15942 moved back. It adds Separately, |
#14868 left an untried call to the throwing rollbackIfStillOwned and an implicit self capture in the isTargetCurrent closure. Xcode 26.6 accepts both, Xcode 26.3 (the macOS 15 CI lane) rejects both, so every blacksmith-6vcpu-macos-15 run fails at TEST BUILD. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#14868 left an untried call to the throwing rollbackIfStillOwned and an implicit self capture in the isTargetCurrent closure. Xcode 26.6 accepts both, Xcode 26.3 (the macOS 15 CI lane) rejects both, so every blacksmith-6vcpu-macos-15 run fails at TEST BUILD. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
main doesn't compile since #13232 (ef75ca7) and #14868 (10e78b5) merged 13 minutes apart: Sources/CmuxConfig.swift:2816:51: error: missing argument for parameter 'actionReferenceID' in call #13232 added the required actionReferenceID field to ResolvedSurfaceTabBarButtonEntry. #14868 added a new return of that struct for a project button that shows a global setting action, without the field. That button still shows and runs the referenced action, like the ordinary resolved path below it, so it reports the same resolvedIdentifier. Actions & Launchers discovery then lists the action as placed on the tab bar. The argument shares a line to keep the file within its length budget. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#13232 made `actionReferenceID` a required field of ResolvedSurfaceTabBarButtonEntry; #14868, merged 13 minutes earlier, added a return of that entry for a project button showing an untrusted global setting action. Each compiled against its own base, together they don't: Sources/CmuxConfig.swift:2816:51: error: missing argument for parameter 'actionReferenceID' in call That branch renders the referenced action itself (its own title and confirm), so it carries the referenced action's identity, as the normal resolved branch below it does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 2546c91)
#13232 added an exhaustive switch over CmuxSurfaceTabBarButtonAction for the Actions discovery dialog; #14868 added the .setting case. Each passed CI alone, and main has failed to compile since both landed (switch must be exhaustive at AppDelegate+WorkspaceActionSave.swift:126). Setting and setting-preset actions are now listed with the type "setting". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#13232 added an exhaustive switch over CmuxSurfaceTabBarButtonAction for the Actions discovery dialog; #14868 added the .setting case. Each passed CI alone, and main has failed to compile since both landed (switch must be exhaustive at AppDelegate+WorkspaceActionSave.swift:126). Setting and setting-preset actions are now listed with the type "setting". Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#13232 added an exhaustive switch over CmuxSurfaceTabBarButtonAction for the Actions discovery dialog; #14868 added the .setting case. Each passed CI alone, and main has failed to compile since both landed (switch must be exhaustive at AppDelegate+WorkspaceActionSave.swift:126). Setting and setting-preset actions are now listed with the type "setting". Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> (cherry picked from commit d01e8ce)
The second compile error from #13232 and #14868 merging 13 minutes apart, hidden behind the first: Sources/AppDelegate+WorkspaceActionSave.swift:126:9: error: switch must be exhaustive #14868 added CmuxSurfaceTabBarButtonAction.setting, and #13232's Actions & Launchers summary switched over the enum without it. The summary's type token follows each action's cmux.json "type", so a setting preset shows "settingPreset" and any other setting change "setting". The switch is now one case per line, which keeps the file within its length budget. Every other exhaustive switch over the enum already handles .setting. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…16223) #14868 added a branch that resolves a project tab-bar button to a global setting action without relabeling it. #13232 then made ResolvedSurfaceTabBarButtonEntry require actionReferenceID. The two merged without a combined build, so main fails with "missing argument for parameter 'actionReferenceID'". The branch still places a button that runs the registered action, so it reports that action's id for Actions discovery like the trusted branch does. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#14868 added a branch that resolves a project tab-bar button to a global setting action without relabeling it. #13232 then made ResolvedSurfaceTabBarButtonEntry require actionReferenceID. The two merged without a combined build, so main fails with "missing argument for parameter 'actionReferenceID'". The branch still places a button that runs the registered action, so it reports that action's id for Actions discovery like the trusted branch does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… guard fetch history (#16094) * fix: pin bonsplit main with the deallocating-window hint fix main's app-host shards still abort with "objc: Cannot form weak reference to instance ... of class NSKVONotifying_NSWindow" (shard 3 of #15488 validation run 36732010954 on cmux14). manaflow-ai/bonsplit#261 (bb03f7d) fixes it, but main pins bd340ad, the hint-pill branch from #15821, which predates it. Pin bonsplit main's head, 7e5598e: it merges the hint-pill branch over bf5f051 (#268) and bb03f7d (#261), so main keeps #15821's bonsplit changes and gains the fix. The two app edits are #15942's adaptation to the performance changes that come with bf5f051: read pane tab ids through tabIds(inPane:), and correct the title-refresh comment now that bonsplit observes each tab item. Refs #15488 Co-authored-by: Leo Li <cheerleaderleo@outlook.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: say a title frame wakes only its tab's views With bonsplit observing each tab item, a title-only refresh no longer invalidates the whole tab bar subtree; the comment at the call site still said it did, contradicting the doc comment on refreshTabLabel. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(settings): add the missing try and capture that break main's compile #14868 merged 74c3a5f after its compile admission failed, so CmuxSettings, and with it the app, no longer builds on main: JSONConfigAtomicPublisher.swift:74: call can throw but is not marked with 'try' JSONConfigStore.swift:601: reference to property 'fileURL' in closure requires explicit use of 'self' to make capture semantics explicit The post-exchange rollback now uses `if try`, like the publisher's two other rollback call sites, so a failed rollback still reports sourceChangedRollbackFailed. The isTargetCurrent closure captures the store's nonisolated fileURL by value instead of the actor. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: expect the cancelled-run message from the tests gate The same change as #16168 (108bd10), carried here so this PR's Linux guards pass and its macOS jobs are not declined while main is red. #16150 made the ci.yml tests gate report a cancelled linux-preflight as "cancelled: linux-preflight"; the test kept the old text. Refs #15488 Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: let the submodule guard fetch history when GitHub can't answer The forward-only guard checks submodules out two commits deep. When an old pin sits deeper than that, it asks the GitHub compare API, which fails whenever the repository's shared Actions token is out of quota. The guard then reports "could not determine ancestry". It did so on every run of this PR (bf5f051 -> 7544622, three commits deep) and of #15942, although GitHub's compare says behind_by=10, ahead_by=0. As a last resort after the compare, the guard now fetches the missing history (commits and trees, no blobs) and decides locally. It never runs when the local check or GitHub already answered, so passing and rejected moves keep their current path. A shallow bonsplit clone at 7544622, as CI makes it, now resolves bf5f051 as forward. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): apply migrations the way production does everywhere #15423 added a CREATE INDEX CONCURRENTLY migration and taught the production migrator (migrate-planetscale.mjs) to run it outside a transaction. CI, web-validation and local databases still ran `drizzle-kit migrate`, which wraps every migration in one transaction, so main's web-db-migrations job fails with "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", and `bun run db:migrate` fails for anyone with a fresh local database. The production migrator's loop moves unchanged into scripts/cloud-vm/apply-migrations.mjs, and a new scripts/db-migrate.mjs runs it against DIRECT_DATABASE_URL or DATABASE_URL. Every caller of `drizzle-kit migrate` now uses it: ci-web, web-validation, cloud-vm-guest-install, ios-streamed-validate, db-local.sh, and dev-local.sh through db-local.sh. CI now exercises the code path production runs. Checked on a scratch Postgres 14: all 93 migrations apply, a second run applies none, and cloud_vms_observed_destroy_cleanup_idx is valid. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): use the transaction's json helper in the outbox test main's web typecheck fails since #15423: tests/vm-workflows.test.ts(6219,37): error TS18047: 'sql' is possibly 'null'. The test narrows the file's `let sql` at its start, but TypeScript drops that narrowing inside the `sql.begin` callback. The insert there now uses the transaction's own `tx.json`, which is also the connection that runs the insert. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document the French Actions discovery titles as invariant The same change as #16175 (ee38771), carried so this PR's static checks pass while main is red. #13232 added actions.discovery.menuTitle and actions.discovery.dialogTitle, whose French text is identical to the English, and the localization parity check fails on main. Refs #15488 Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): insert a real JSON null in the malformed cleanup-row test "Cloud VM database schema > rejects malformed transferred cleanup rows" (#15423) never ran on main, because main's migrations failed before the database behavior tests. With migrations fixed it fails: expect((insertError)?.code).toBe("23514") Expected: "23514" Received: "23502" Its first malformed value is `null`, and postgres.js binds `sql.json(null)` as SQL NULL. The NOT NULL column rejects that (23502) before the check constraint the test is about. The row under test is a JSON null document, so that case now inserts `'null'::jsonb`, and the check rejects it with 23514 like the other nine. Checked on a scratch Postgres 14 with postgres.js: sql.json(null) gives 23502, the JSON null gives 23514, all ten malformed values give 23514, and {modelPlane: true} and {homeVolume: "v"} are accepted. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(config): pass actionReferenceID on the setting-action trust path main doesn't compile since #13232 (ef75ca7) and #14868 (10e78b5) merged 13 minutes apart: Sources/CmuxConfig.swift:2816:51: error: missing argument for parameter 'actionReferenceID' in call #13232 added the required actionReferenceID field to ResolvedSurfaceTabBarButtonEntry. #14868 added a new return of that struct for a project button that shows a global setting action, without the field. That button still shows and runs the referenced action, like the ordinary resolved path below it, so it reports the same resolvedIdentifier. Actions & Launchers discovery then lists the action as placed on the tab bar. The argument shares a line to keep the file within its length budget. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(actions): name setting actions in the discovery summary The second compile error from #13232 and #14868 merging 13 minutes apart, hidden behind the first: Sources/AppDelegate+WorkspaceActionSave.swift:126:9: error: switch must be exhaustive #14868 added CmuxSurfaceTabBarButtonAction.setting, and #13232's Actions & Launchers summary switched over the enum without it. The summary's type token follows each action's cmux.json "type", so a setting preset shows "settingPreset" and any other setting change "setting". The switch is now one case per line, which keeps the file within its length budget. Every other exhaustive switch over the enum already handles .setting. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep Workspace+TitleOwnership.swift as main has it The title-frame comment tweak is cosmetic and was the only Swift change left in this PR. Without it the PR is web and CI only, so its checks don't wait on main's cmuxTests build. Refs #15488 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(web): pin the seats-follow-membership billing copy The billing panel's over-seat line is asserted here, and this test has been red on main since the dashboard SPA port: it already checks that no add-seats link is offered, and the port brought one back. Widen it to the copy the rule actually calls for, so both halves of the regression are covered. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Leo Li <cheerleaderleo@outlook.com> * fix(web): restore the seats-follow-membership copy the dashboard port dropped The Team subscription quantity follows the member count, so an over-seat line has nothing for an admin to act on: the reconciler updates Stripe on the next membership fact. That was settled in 06f4a7c, which reworded the line in all 20 locales, removed the add-seats link beside it, and dropped the members-page seat nudge. The dashboard SPA port rebuilt the billing panel from the pre-06f4a7c version at a new path, so git saw no conflict and the link came back, and the locale files went back to the soft-seat wording. `web/tests/ dashboard-billing-screen.test.tsx` has been red on main ever since, which fails the required `ci-status` on every web pull request. Restores the wording and drops the link. `seatNudge` and `seatNudgeAction` go too: the nudge they belonged to is gone from the members page and nothing reads them. `docs/team-settings-and-invites.md` already records the rule, and the stale "seats are soft" comment left hanging over an unrelated type in `team-members.tsx` is removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Leo Li <cheerleaderleo@outlook.com> * test(web): pin the new-team seat copy too The same merge-resolution path that reverted the billing panel's copy also reverted this line, and nothing asserted on it. Pin the sentence and the old wording's absence so a stale merge side fails the shard instead of shipping. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Leo Li <cheerleaderleo@outlook.com> * test(coderouter): close pinned proxy test connections Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> * fix(coderouter): handle pinned proxy body failures without hanging Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> * fix(ci): address follow-up review findings * merge: keep main's current bonsplit pin * fix(ci): harden locale and migration review follow-ups Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> * fix(ci): finish migration and locale follow-ups Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> * fix(web): preserve locale cookies during RSC navigation Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> * test(web): remove duplicate locale race case --------- Co-authored-by: Leo Li <cheerleaderleo@outlook.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
70e997f Merge pull request manaflow-ai#16199 from manaflow-ai/16189-cloud-sidebar-icons 5e4a6f5 Fix terminal scrollback follow after accepted input (manaflow-ai#16529) ae5c960 switch account, cmux sign-in page, and saved sessions like gmail (manaflow-ai#16364) 5610998 test: pin Flash While Typing off in the typing-dismiss no-flash test (manaflow-ai#16625) db21906 Fix sidebar template catalog and Cloud machine-row tests; drop stale preview generator (manaflow-ai#16595) 2ec0306 ci: pin Xcode 26.6 for macOS 27 runners (manaflow-ai#16547) dc56459 fix: make Cloud command palette actions follow workspace capabilities (manaflow-ai#16273) 638b468 Fix mobile Feed notification duplicates and update warning (manaflow-ai#16352) 49d798e test: use deterministic Cloud header sizing e2fc8fc Merge remote-tracking branch 'upstream/main' into 16189-cloud-sidebar-icons 4b2db7c test: allow Cloud header controls to settle 80ca30f Merge remote-tracking branch 'upstream/main' into 16189-cloud-sidebar-icons e2f2b7d fix: constrain Cloud header action layout 75990f6 fix: remove duplicate pane test binding 8738ba2 fix: restore custom sidebar preview resources 2c6819a Merge remote-tracking branch 'upstream/main' into 16189-cloud-sidebar-icons 017b63b fix: use local SSH command quoting ac5eba5 fix: compile sidebar usage owner selection 90b0655 fix: make custom upload endpoint policy explicit 82ddf7c fix: pass remote paste policy to custom uploads 77ec507 Merge remote-tracking branch 'upstream/main' into 16189-cloud-sidebar-icons eaceb98 Merge remote-tracking branch 'upstream/main' into 16189-cloud-sidebar-icons 8436277 Merge main (4e9d779) into 16189-cloud-sidebar-icons c17fa5d Merge main (488eaf7) into 16189-cloud-sidebar-icons 9672805 Cloud sidebar tests: import CmuxFoundation for GlobalFontMagnification eae5972 fix(tests): restore PaneResizeShortcutTests' controller binding dd91af2 fix(tests): allow bounded main queue drain timeout aff65ce test: check the vm ready poll interval in cmuxCLITests so cmuxTests compiles 91d743a Merge commit '5e83d8029eedca144c10096fa8b3664a940092b3' into 16189-cloud-sidebar-icons 022502a Merge main (7ba9740) into 16189-cloud-sidebar-icons 7f1297d fix: list setting actions in Actions discovery so main compiles (manaflow-ai#16222) aa5e7e8 Merge main (b3ca418) into 16189-cloud-sidebar-icons b53c137 Merge main (1831681) into 16189-cloud-sidebar-icons 4400412 Cloud sidebar: withhold New Workspace while the fleet read is failing 71ac098 fix: restore main's build after manaflow-ai#14868 and manaflow-ai#13232 crossed in CmuxConfig d9dfb3e Cloud workspace targeting: never resolve New Workspace to a locked machine 73f59e7 Merge main (c12e934) into 16189-cloud-sidebar-icons 5a43fcb Cloud sidebar: move section icons to headers and guard create rows 9d32421 Cloud sidebar: test section identity icons and guarded create rows
Tools that tweak cmux settings today rewrite
~/.config/cmux/cmux.jsonwithjqorplutiland then callcmux reload-config. That drops comments, can race the Settings window, and skips schema validation. This adds native ways to make those edits.Setting actions and presets
Two new action types in the
actionsregistry:{ "actions": { "scroll.cycle": { "type": "setting", "title": "Cycle Scroll Speed", "path": "terminal.scrollSpeed", "cycle": [1.0, 1.4, 1.8] }, "editor.wrap": { "type": "setting", "title": "Toggle Editor Wrap", "path": "fileEditor.wordWrap", "toggle": true }, "sidebar.quiet": { "type": "settingPreset", "title": "Quiet Sidebar", "preset": "sidebar.quiet", "confirm": true } }, "settingPresets": { "sidebar.quiet": { "sidebar": { "showPorts": false, "showPullRequests": false, "showLog": false } } } }settingaction takes apathplus exactly one ofset,toggle: true,cycle, orunset: true.toggleandcyclestart from the value cmux is using. That's the cmux.json value if the file sets the key; otherwise the value the Settings window stored in UserDefaults; otherwise the schema default. A value that isn't in thecyclelist moves to the first entry.unsetremoves the key from cmux.json, so the Settings window's value (or the default) applies again.settingPresetaction appliessettingPresets.<name>, a new global-only top-level key holding a partial cmux.json. Nested objects merge key by key, so a preset only changes the keys it names.actionreference), shortcuts, and the Command Palette. With"confirm": true, cmux asks before saving and shows the equivalentcmux configcommand.cmux config get|set|unset|toggle|cycle|presetcmux config set terminal.scrollSpeed 1.4,cmux config toggle fileEditor.wordWrap,cmux config preset sidebar.quiet, and so on.<value>is parsed as JSON; plain text is stored as a string.getsays where the value came from: cmux.json,(set in Settings, not cmux.json), or(default).--jsonprintspath,file,value,configured,source, anddefault; the write commands printok,file, andpaths.sidebar-font-sizeandsurface-tab-bar-font-sizekeys keep their Ghostty-config behavior.One mutation path
The actions and the CLI both call
JSONConfigStore.apply(_:liveValues:)in CmuxSettings.JSONConfigStore's internalmutateRoottakes a list of edits computed under the cooperative writer lock, sotoggle,cycle, and a preset read and rewrite the same document in one atomic publication. The existing single-pathset,reset, andundogo through it unchanged.Each change is validated before anything is written:
CmuxConfigSchemaPathLookupin CmuxFoundation walks the embedded schema and also supplies defaults and allowed types.actions,commands,ui,settingPresets, ...).Comments and unrelated keys survive, as with Settings-window writes. Fractions are written as typed (
1.4, not the1.3999999999999999JSONSerializationprints for aDouble), and re-setting the value already stored is a no-op. The validator learned"$ref": "#", so eachsettingPresetsentry validates as a partial cmux.json incmux config validateand in editors.Live values. Most settings are stored in UserDefaults and only overridden by cmux.json, so an absent key's "current value" comes from there (
CmuxSettingLiveValues). A stored value is only used when it's a scalar of a type the schema allows at that path; lists and maps are often stored as text, so they fall back to the default. Three keys store a different form and have explicit mappings:app.minimalMode(a mode string),app.keepWorkspaceOpenWhenClosingLastSurface(the inverse flag), andterminal.sessionContentMaxWidth(-1forfalse). A catalog-wide test fails if another key the resolver accepts stores a default that disagrees with the schema default, so a new transformed key has to get a mapping.Design choices
confirmof a global setting action, through anactionsoverride or a tab bar button. Without this, a repository's.cmux/cmux.jsoncould ship a harmless-looking button that setsautomation.socketControlMode.confirmis honored on setting and settingPreset actions like other action types. The project-action trust prompt never covers the global config, so this dialog is the only prompt these actions get..(for example aworkspaceGroups.byCwdentry for~/src/app.web) can't be addressed, because paths split on every.; there's deliberately no escaping syntax. Such a path is refused with an error that says so (keyContainsDot) instead of "isn't a cmux setting", when the rejoined key exists in the file or looks like a path. The docs, CLI contract, and cmux-settings skill say this.If a write is refused or fails, the palette, shortcut, or button shows an alert with the reason, for example an unknown path, a non-boolean toggle, a value outside the schema, or cmux.json that can't be read.
Tests
CmuxSettingChangeTests/CmuxSettingReadingTests, CmuxFoundationCmuxConfigSchemaPathLookupTests): comment and unrelated-key preservation; toggle and cycle from configured, live, and default values; refusals that leave the file byte-identical; preset merge and refusals; dotted keys versus typos; short-form fractions and the no-op re-set; the stored-value mappings; and the catalog-wide identity check.cmuxTests/CmuxConfigSettingActionTests): decoding and round-trip, the global-only registry rule, global packs allowed and project packs dropped,confirmreaching both the palette action and the button, a project config unable to relabel or unconfirm a global setting action, and the confirmation dialog's text.Verification
CmuxConfigSettingActionTests. Inswift-package-testsevery suite this PR adds or changes passed ("Setting changes applied through JSONConfigStore", "Setting readings", "cmux.json schema path lookup"). The job failed once onSimulatorWorkerClientReplayTests("Restart releases held input before replaying camera state…"), a package that only runs here because it depends on CmuxFoundation. That test passed in main's last three CI runs and passed when the job was rerun on the same head, so it's a flake. After the rerun, every check in that run is green. Guards, localization, schema parity, and web checks passed.1.3999999999999999serialization through the new command-description tests, and run https://github.com/manaflow-ai/cmux/actions/runs/36318285721 caught thesessionContentMaxWidthsentinel through the catalog test; both are fixed above.0828a893ed7bde939cc6757d, tagpr-14868-setting-actions-v4, bundlecmux DEV pr-14868-setting-actions-v4.app, artifactsha256:6a11b6d38c8af36d2cb24b2bc8fad7a8f0e73f9a0e00bbac82aef11d3571da46. Built with--backend-mode local, because the shared dev backend isn't reachable from this machine; nothing here touches the backend.publish-hqrefuses local-backend builds, so there's no HQ link; the zip and receipts are kept locally.keyContainsDotfiring on typos;get --jsonfield names; the schema text that saidconfirmdoesn't apply; the transformed UserDefaults keys; and the no-op re-set of fractions.Resources/Localizable.xcstringsthat it can't resolve because it doesn't run the repo's.xcstringsmerge driver;scripts/merge-main.shmerges it cleanly (it did for this head). Run it right before merge; the catch-up bot skips fork branches.localization_catalog.py check: 0 parity errors). The six custom-commands docs keys are in all 20 web locales.Dogfood (not run yet)
GUI dogfood on a fleet or cloud Mac was blocked: the cloud-mac lease joins the tailnet as
tag:cmux-loader, which this account's ACL can't reach, and no other GUI host is designated. Checks to run oncmux DEV pr-14868-setting-actions-v4.app(or a newer tag for a later head), with the example config above plus a comment in the file:{ "action": "editor.wrap" }and{ "action": "scroll.cycle" }toui.surfaceTabBar.buttons. Click each: word wrap flips in an open file editor, and scroll speed steps 1.0 → 1.4 → 1.8 → 1.0 (check withcmux config get terminal.scrollSpeed). The file keeps its comment and other keys, and writes1.4, not1.3999999999999999.cmux config preset sidebar.quiet; Cancel changes nothing, Change hides ports, PRs, and the log in the sidebar.cmux config set terminal.scrollSpeed 1.4,get,toggle fileEditor.wordWrap,cycle terminal.scrollSpeed 1 1.4 1.8,preset sidebar.quiet,unset terminal.scrollSpeed. Each updates the running app without a reload.cmux config set terminal.scrollSpeeed 1andcmux config set actions.x 1are refused and the file is byte-identical..cmux/cmux.jsonwith a setting action and an override ofeditor.wrap("title": "Run Tests","confirm": false): the project action isn't offered, and the global one keeps its title and confirm.Screenshots
CI dogfood tour on merged head
e82d0862894: run 36742715771.🤖 Generated with Claude Code
Summary by CodeRabbit
cmux configcommands to read and change settings. Changes are schema-validated, and existing comments and unrelated configuration are preserved.