cmux NEXT: pick each machine's color (appearance.machineColors)
Every machine mark draws a color hashed from the machine's name, and two machines can land on the same or a disliked hue. Now Settings > Accounts > Machines shows a color well on each machine's row: pick a color and that machine's sidebar rows, tab badges and the machine icon on workspace hover cards take it; Automatic removes the pick. Nothing changes until someone picks a color.
Storage. One string_map setting, appearance.machineColors, maps a machine's color key (its name trimmed and lowercased, the key MachineColor already hashes) to #RRGGBB or to one of the eight accent names MachineColor picks from (red … blue), which follow the theme. The card writes hex; the accent names are for cmux.json. It reuses the existing string_map kind, so the daemon's validator and the MDM export need no new kind. A bad entry is dropped with a diagnostic at its key and the rest still apply. Agents may set it (looks only, like appearance.accent).
One resolver. MachineColor.rgb(tokens:overrides:) returns the pick from MachineColorOverrides, else the hash. MachineColor.color reads the picks from ThemeStore.shared.machineColors. ThemeCoordinator feeds the store from the setting, and the store repaints when the picks change. Sidebar rows (SidebarMachineMark) and tab badges already go through MachineColor.color, so neither call site changed its fallback.
Tab badges keep today's look. A remote tab's machine badge stays textTertiary unless its machine has a pick; with a pick it draws the pick (MachineColor.pickedColor, the same resolver). Nothing changes by default.
Hover cards keep today's look. The workspace hover card's machine row tints its icon textSecondary unless the machine has a pick; with a pick the icon draws the pick (HoverCardRowContent.iconMachine, resolved through MachineColor.pickedColor in the row's theme scope). The connection-status dot keeps its own tone.
Settings page. The host lists' machine rows now carry color (the color its marks draw in now) and color_key. The Machines card draws the row; SectionView drops the plain map row for it, as it does for the Chief pet's rows. The well commits on the native change event and keeps other machines' picks. If cmux.json spells a machine another way ("Big-Red "), that entry is replaced, not duplicated.
Not in this PR (follow-ups):
- Coloring tab badges by default with the automatic machine color is a separate call for the machine-identity work; this PR leaves the default badge grey.
- The agent pane's
machineColor.ts(composer chip, environment picker, inspector) still uses only the hash. The pane gets no settings value today except theagentPane.display.*keys over its owndisplayevent. Applying the picks there needs that event, or a new one, to carryappearance.machineColors. I didn't build a new bridge for it. - Settings lists SSH and Cloud machines only, as before. A paired server's color can be set in cmux.json under its name.
Changelog
- Added: Pick a color for each machine in Settings > Accounts > Machines; its sidebar rows, tab badges and hover card machine icon use it.
Testing
MachineColorTests(CmuxNextDesign): a picked hex wins, a picked accent follows the theme, an unset machine keeps its automatic color, keys normalize both ways, values that aren't colors are ignored, and the store repaints only on a change.MachineColorsSettingTests(CmuxNextSettings): parse, per-key diagnostics, a non-object value, and the row's kind, section, agent policy and page home.TabMachineBadgeColorTests(CmuxNextAppTests, read from the strip as drawn): with no pick, or a pick for another machine, the badge istextTertiary; with a pick it draws the pick.HoverCardMachineColorTests(CmuxNextAppTests, read from the card as drawn): with no pick, or a pick for another machine, the machine row's icon istextSecondary; with a pick it draws the pick; the connection dot stays.dot(.success)either way.hostSections.test.tsx: the well shows the host's color, a pick writes{…, "build-mac": "#12ab34"}and keepsbig-red's pick, Automatic removes only that key, and no plain map row is drawn.
Observed / Not run
- Fleet app compile at faf389b6b7c: passed (job 98648427c8c25a2f5f804817). At 6c6f7b9cfb6: passed (job d7018e18fac3d3481cc096f6). At 681a1b3862c: passed (job a9a6bf76fdacb6ec3b51cdfc). At 796d390e0fd (feat-cmux-next merged in): passed (job eba7670ad6ea1eb2bbcb0219). At the head 0ae41e3ad24: passed (job 92c3ca34fad937b065cca26c).
- Settings exports regenerated on the fleet (step ec44103a1e072352675629b3 at faf389b6b7c).
ManagedPreferencesManifestTestsandSettingsSchemaExportTestspassed there. The defaults snapshot addssetting.appearance.machineColors: {}(a new key; no existing default changes). - Exports at 796d390e0fd: the fleet export step (cd79b055) waited over 2 hours for a worker, so
appearance.machineColorswas carried onto feat-cmux-next's exports with three-way text merges (merge base 5363032df88); the JSON and plist parse. feat-cmux-next's own exports are already stale against its sources, sodefaults stamp/The settings exports match their sourcesare red at base f58123484705 too (defaults_guard.py check-stampexits 1 there); the PR autofix regenerates them. - Live repaint: the Codex review at 796d390e0fd found an open hover card's icon kept its old color after a pick (the store's repaint runs view appearance hooks, not the panel's scope responders). d3d6e119ca2 adds
aPickWhileTheCardIsOpenRecolorsTheIcon; 0ae41e3ad24 recolors the row inviewDidChangeEffectiveAppearance. - Base update at c410c8fb91d: feat-cmux-next 51b295b866e merged in. The Codex review at 891b56609ae (an earlier base merge, conflict in
WorkspaceHoverCardContent.swiftresolved to base's row plus the pick) found the hand-merged settings exports had dropped rows base added since (Sidebar Style, Needs You, Other Apps, widget ordering). The exports andsettings-schema.sources.sha256are now base's files plus only theappearance.machineColorsentries (diff against base adds lines, deletes none); the PR autofix regenerates them. The CmuxNextSettings string catalog is base's text plus the three new keys. editors.test.tsx: react-apps-check at the earlier head failed the accounts editors test (every row on a page needs an editor); 891b56609ae teaches it thatappearance.machineColorsdraws as the Machines card's color wells.- Fleet app compile at 891b56609ae: passed (job 710b6059fa88930ed7b42faa). At the head c410c8fb91d: passed (job 80f7b07459b9696c499a5eb4).
- Static checks:
prepush-gate.py(no new violations at the head),check-l10n.sh(0 errors, 21 locales),localization_catalog.py check, and the What's New digest of the new highlight. - The first red runs at 7363594ecbf (steps e627224dac794676b6171e10, a0deec311c9d762738fc516b) never ran the tests: their base f1d4be73d5d failed to build the web bundles (the ContextSection.css red fixed by #23935). The red is rerun on
cmux/machine-color-override-red(23ffbf61b5a = the test commit merged with feat-cmux-next 5363032df88, no fix). - Queued on the fleet, results to follow here:
- Swift suites red at 23ffbf61b5a: step 54eabd50a2fedeafa0b6be08
TabMachineBadgeColorTestsred at a683228a6f9 (test only): step 799b6cddd48830576b4a3776HoverCardMachineColorTestsred at e2fd57f91ae (test only): step e625c516db3fb39c5989119a- Swift suites green at 681a1b3862c (
MachineColorTests,MachineColorsSettingTests,SettingsPageLayoutHomeTests,TabMachineBadgeColorTests,HoverCardMachineColorTests,HoverCardRowTests,HoverCardSharedRowsTests): step c3810c3973cefee4a505dd3a (replaces f6686022f9498869b95de0f8 at 6c6f7b9cfb6, cancelled before it ran) - none of these fleet steps had run when this merged (the fleet queue was starved);
aPickWhileTheCardIsOpenRecolorsTheIconhas no fleet run hostSections.test.tsxred at 23ffbf61b5a: step 60a34e737102df093abf2883; green at 6c6f7b9cfb6: step ca5b95887175249af1455e58 (webviews are unchanged since)
No capture: the color well is on a webview Settings page (the gallery's machines states now carry a color), and the sidebar rows, tab badges and hover cards look as before until someone picks a color. Not run: a live app check of the repaint after a pick.
Agent session: Air-Blue
🤖 Generated with Claude Code