Skip to content

iOS: support arbitrary terminal themes - #6664

Merged
lawrencecchen merged 10 commits into
mainfrom
feat-ios-arbitrary-themes
Jul 4, 2026
Merged

lawrencecchen merged 10 commits into
mainfrom
feat-ios-arbitrary-themes

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The iOS terminal only rendered the Monokai theme, hardcoded in four places: the two ghostty config builders in GhosttyRuntime (the per-process config and the on-disk default config), the SwiftUI letterbox/toolbar chrome (TerminalPalette), and the input accessory bar fill. This makes iOS support arbitrary themes instead.

A new TerminalTheme value type lives in the shared CMUXMobileCore package: background, foreground, cursor, cursor-text, selection background/foreground, and the 16 ANSI palette colors, all as #rrggbb hex strings. It is Codable, Equatable, and Sendable (no UIKit/AppKit), so a theme can be produced anywhere, decoded from JSON, and consumed by the embedded libghostty runtime on iOS. It carries a built-in .monokai, an isValid check, a validatedOrDefault() fallback, and a ghosttyColorDirectives generator that emits the matching ghostty config lines.

TerminalThemeStore holds the active theme process-wide behind a lock. GhosttyRuntime builds its config from TerminalThemeStore.current and exposes setTheme(_:) / currentTheme; call setTheme before the runtime is first built. The terminal chrome (TerminalPalette, the input bar) reads the same store so it blends with whatever theme is active rather than a fixed color. Any nil, invalid, or incomplete (not exactly 16 palette entries) theme falls back to Monokai, so the terminal always renders with a complete palette.

This reuses the existing color shape already on the wire (the render-grid Style colors and OSC color fields are the same #rrggbb hex strings) instead of inventing a parallel format, and keeps Monokai as the default so behavior is unchanged until a theme is supplied.

Testing

swift test on Packages/Shared/CMUXMobileCore passes (119 tests, 10 suites). New TerminalThemeTests cover: Monokai validity, JSON encode/decode round-trip, decoding an arbitrary (Solarized-style) theme from JSON, invalid/short-palette fallback to Monokai, hex parsing edge cases, ghostty directive generation (including conditional cursor-text), and the store set/fallback behavior.

The iOS-only packages (CmuxMobileTerminal, CmuxMobileShellUI) depend on UIKit and the GhosttyKit.xcframework binary, which is not built in this worktree, so they were not compiled locally. The changed iOS files use only the public TerminalTheme / TerminalThemeStore APIs that compile cleanly in the CMUXMobileCore package build, and parse clean against the iOS SDK. Full Xcode build verification is left to CI.

Issues

None. Plain-text feature task, no associated GitHub issue.


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


Note

Medium Risk
Touches mobile host RPC, ghostty runtime config updates, and several iOS UI paths; malformed themes are isolated, but live config refresh affects all mounted surfaces.

Overview
Adds a shared TerminalTheme model and TerminalThemeStore so iOS is no longer hardcoded to Monokai. The Mac mobile.host.status payload now includes the effective theme from GhosttyConfig.load(); iOS decodes it leniently (bad theme does not break status) and applies it on connect and on the slower identity recovery path.

MobileShellComposite.applyTerminalTheme updates the store and bumps terminalThemeGeneration only when colors actually change. GhosttySurfaceRepresentable reacts to that counter and calls GhosttyRuntime.applyLiveThemeIfRunning() to rebuild ghostty config and refresh surfaces in place, preserving scrollback. SwiftUI/UIKit chrome (TerminalPalette, input accessory bar, surface backgrounds) follows the same store. Mac→phone mapping omits cursorText unless the Mac config explicitly set it.

Reviewed by Cursor Bugbot for commit 68707c9. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Adds end-to-end support for arbitrary terminal themes on iOS and syncs the Mac’s effective theme to the phone. Theme changes now live‑recolor the terminal and UI without remounting, so scrollback is preserved.

  • New Features

    • Mac includes its resolved theme in mobile.host.status; built from GhosttyConfig.load() and serialized via MobileHostTerminalTheme to match TerminalTheme.
    • iOS decodes theme in MobileHostStatusResponse and applies it via MobileShellComposite.applyTerminalTheme(_:), which updates @MainActor TerminalThemeStore and bumps terminalThemeGeneration only on real changes; also reapplies on the full-timeout status recovery path.
    • Live recolor: GhosttySurfaceRepresentable watches themeGeneration and calls GhosttyRuntime.applyLiveThemeIfRunning(), which rebuilds the runtime config (ghostty_app_update_config / ghostty_surface_update_config) and updates all surfaces once per theme value; GhosttySurfaceView, TerminalInputTextView, and TerminalPalette read from TerminalThemeStore to update backgrounds and chrome in place.
  • Bug Fixes

    • Host-status decode tolerates malformed theme objects; falls back to Monokai without breaking transport/identity.
    • Deduped live theme applies by tracking the last applied theme to avoid redundant config rebuilds.
    • Fixed stale local backgrounds by sourcing from the theme store; the input accessory bar recolors live, and cursor labels avoid miscolor by only sending cursorText when explicitly set on the Mac.
    • CI/lint: refreshed Swift file-length budget and marked TerminalThemeStore / TerminalPalette @MainActor to satisfy namespace-type lint.

Written for commit 68707c9. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features

    • Added validated, customizable terminal themes (16-color palette, optional cursor text) with generated Ghostty color directives.
    • Introduced a process-wide theme store and runtime APIs to update the active theme and recolor live surfaces in place.
    • iOS UI now derives terminal background/foreground and input accessory bar colors from the active theme, with generation-based live updates.
  • Bug Fixes

    • Host status decoding now safely handles missing or malformed theme data.
  • Tests

    • Added unit/integration coverage for theme validation, JSON behavior, directive generation/normalization, theme-store fallback, and live apply/update flows.

The iOS terminal hardcoded the Monokai palette in two places in
GhosttyRuntime (the per-process config and the on-disk default config),
plus the SwiftUI letterbox chrome and the input accessory bar. Introduce
a shared TerminalTheme value type in CMUXMobileCore (bg, fg, cursor,
cursor-text, selection, and the 16 ANSI palette colors as hex strings,
Codable/Equatable/Sendable) with a built-in Monokai default and a
ghostty config directive generator. TerminalThemeStore holds the active
theme process-wide; GhosttyRuntime builds its config from it and exposes
setTheme(_:), and the terminal chrome reads the same store so it blends
with any theme. Invalid or incomplete themes fall back to Monokai.

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

vercel Bot commented Jun 23, 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 4, 2026 8:17am
cmux-staging Building Building Preview, Comment Jul 4, 2026 8:17am

@coderabbitai

coderabbitai Bot commented Jun 23, 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

Adds a shared terminal theme model and store, includes theme data in host status payloads, decodes it on iOS, and propagates theme-driven colors through runtime, shell, and SwiftUI surface updates.

Changes

Terminal Theme System

Layer / File(s) Summary
Theme model, store, and tests
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalTheme.swift, Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalThemeStore.swift, Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/TerminalThemeTests.swift
TerminalTheme defines background, foreground, cursor, optional cursorText, selection colors, and a 16-entry palette. isValid checks palette size and hex parsing; rgbComponents(_:) parses #rrggbb and rrggbb; ghosttyColorDirectives emits parsed directives; validatedOrDefault() falls back to TerminalTheme.monokai. TerminalThemeStore provides global current and set(_:). Tests cover model and store behavior.
Host status theme payload and decode
Sources/Mobile/MobileHostTerminalTheme.swift, Sources/Mobile/MobileHostService.swift, Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileHostStatusResponse.swift, Packages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileTerminalDTODecodeTests.swift, cmux.xcodeproj/project.pbxproj
GhosttyConfig is converted into a resolved TerminalTheme and serialized into the mobile host status payload. MobileHostStatusResponse adds an optional theme field and decodes it. Tests cover nested theme decoding, and the Xcode project includes the new source file.
Runtime theme consumers
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttyRuntime.swift, Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift, Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPalette.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift
GhosttyRuntime adds theme APIs and uses TerminalThemeStore.current.ghosttyColorDirectives for iOS default config generation. GhosttySurfaceView refreshes mounted surfaces from the active theme, GhosttySurfaceRepresentable triggers live recoloring by generation, TerminalPalette computes colors from the active theme, and TerminalInputTextView derives the accessory bar color from the active theme background.
Shell theme application and remounting
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift, Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/TerminalThemeApplyTests.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
MobileShellComposite tracks terminalThemeGeneration, applies the host theme during transport resolution, and updates the generation only when the effective theme changes. Tests cover generation updates and .monokai fallback. WorkspaceDetailView passes terminalThemeGeneration into the surface representable and keeps the SwiftUI identity terminal-scoped.

Estimated code review effort: 4 (Complex) | ~45 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 No Ambient Global State ❌ Error Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalThemeStore.swift:18-30 adds a process-wide singleton (private init, static storage/current/set), which is ambient global state. Move theme ownership into an injectable app-scoped object (e.g. MobileShellComposite or GhosttyRuntime) and pass it to consumers instead of static access.
Docstring Coverage ⚠️ Warning Docstring coverage is 29.03% 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 Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: The new theme store is explicitly @MainActor, and all new reads/writes occur from MainActor UI/service code; no background access or implicit MainActor model was introduced.
Cmux Swift Blocking Runtime ✅ Passed The diff uses @MainActor state updates and theme-generation counters only; no semaphores, locks, sync waits, sleeps, or delayed dispatch were added.
Cmux Browser Automation Off-Main ✅ Passed Only .github/swift-file-length-budget.tsv changed; no browser automation routing or WebKit wait/off-main code was touched.
Cmux Expensive Synchronous Load ✅ Passed No agent-history loader was added or moved; the only sync load in changed files is GhosttyConfig.load() for theme sync, not RestorableAgentSessionIndex/SharedLiveAgentIndex.
Cmux Cache Substitution Correctness ✅ Passed PASS: The only cache-like store is a live, event-driven theme singleton; persistence/status still reads fresh GhosttyConfig, and the rest are transient UI recolors, not snapshot history.
Cmux No Hacky Sleeps ✅ Passed No changed TS/JS/shell/build/runtime scripts; the only non-Swift file is a TSV budget update, and no sleep/delay/poll patterns were found in changed files.
Cmux Algorithmic Complexity ✅ Passed Changed loops are fixed-size (16-color palette) or single linear passes over mounted surfaces; no nested scans, rescans, or hot-path sort/filter churn introduced.
Cmux Swift Concurrency ✅ Passed Touched Swift code adds main-actor theme state and synchronous propagation only; no new DispatchQueue, Combine, completion-handler, or fire-and-forget Task pattern appears in the diff.
Cmux Swift @Concurrent ✅ Passed No new @concurrent/nonisolated-async misuse found; the theme flow is synchronous on @MainActor, and async UI callbacks already hop explicitly to @MainActor.
Cmux Swift File And Package Boundaries ✅ Passed New production Swift files are small (<400 lines), and all large existing files only gained small, glue-like edits; core theme logic was extracted into CMUXMobileCore.
Cmux Swiftpm Lockfiles ✅ Passed No Package.resolved or .gitignore changes appear in the PR, and the Xcode project edit only adds a source file; packageReferences are unchanged.
Cmux Swift Logging ✅ Passed No new disallowed logging was added; the only NSLog is unchanged and #if DEBUG, and file-scoped Logger declarations match HEAD~1.
Cmux User-Facing Error Privacy ✅ Passed Touched production code only adds theme plumbing and a generic fallback label; I found no new user-facing error/alert/API error text exposing vendor names or secrets.
Cmux Full Internationalization ✅ Passed The feature commits add theme/config logic and tests only; no new user-facing Swift text or locale assets were introduced.
Cmux Swiftui State Layout ✅ Passed No new ObservableObject/@published, GeometryReader, row-store, or render-time state writes were introduced; theme syncing is via UIKit bridge callbacks and existing geometry state is legacy.
Cmux Architecture Rethink ✅ Passed Pass: theme state is centralized in a documented @MainActor store with clear invariants; no sleeps, observers, locks, or split ownership beyond required bridge plumbing.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No touched Swift file adds or changes standalone window/controller code, and nothing in the diff touches the cmuxAuxiliaryWindowIdentifiers path.
Cmux Source Artifacts ✅ Passed Only changed path is .github/swift-file-length-budget.tsv, a tracked config/budget file; no logs, caches, temp dirs, or generated artifacts were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The theme commits add only theme/runtime changes; diff scans found no new debug/test-only members or #if DEBUG seams in production Sources.
Title check ✅ Passed The title is concise and accurately summarizes the main change: iOS support for arbitrary terminal themes.
Description check ✅ Passed The description covers the summary and testing well, but it is missing the Demo Video, Review Trigger, and Checklist sections from the template.
✨ 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 feat-ios-arbitrary-themes

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 23, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR removes four Monokai hardcodes in the iOS terminal and replaces them with a TerminalTheme value type in CMUXMobileCore. The Mac sends its resolved effective palette in mobile.host.status; iOS decodes it leniently, writes it to a @MainActor-isolated TerminalThemeStore, and drives a live recolor of the ghostty runtime, surface backgrounds, toolbar chrome, and input accessory bar via a terminalThemeGeneration counter — all without remounting the surface or discarding scrollback.

  • New shared model: TerminalTheme (Codable/Equatable/Sendable, no UIKit) carries the full 16-color palette plus cursor, selection, and optional cursor-text colors; TerminalThemeStore is a @MainActor singleton; GhosttyRuntime gains setTheme/applyLiveThemeIfRunning/rebuildConfigFromStore and is already @MainActor-isolated.
  • Mac wire encoding: MobileHostTerminalTheme builds the JSON payload from GhosttyConfig.load() (cached, NSLock-guarded); cursorText is only forwarded when hasParsedCursorTextColor is true, avoiding spurious cursor-label mis-color on the phone.
  • Live recolor path: MobileShellComposite.applyTerminalTheme bumps terminalThemeGeneration only on real value changes; GhosttySurfaceRepresentable observes the generation and calls applyLiveThemeIfRunning, which deduplicates via lastAppliedTheme to avoid redundant config rebuilds when multiple surfaces are mounted.

Confidence Score: 5/5

Safe to merge. The concurrency model is correct throughout: GhosttyRuntime is @MainActor-isolated, TerminalThemeStore is @mainactor, and the deduplication guards in applyLiveThemeIfRunning prevent redundant config rebuilds.

The implementation correctly threads the theme from Mac config → wire → iOS store → live runtime recolor without any actor isolation violations. The fallback to Monokai is applied at every boundary (decode error, nil theme, invalid palette) so a bad payload cannot leave the terminal in a broken state. The two observations flagged are minor style issues (a double-optional flatten and a redundant in-place write during theme refresh) with no behavioral impact.

GhosttySurfaceView.swift — the refreshThemeColors method calls applyBackgroundColorFromConfig after setting the same values, which is a harmless double-write but worth cleaning up.

Important Files Changed

Filename Overview
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalTheme.swift New value type for terminal themes: Codable, Equatable, Sendable with Monokai default, isValid check, hex parsing, and ghosttyColorDirectives generation. Clean design, well-separated from UIKit.
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalThemeStore.swift @MainActor-isolated process-wide singleton; stores and validates the active TerminalTheme. Correctly replaces the NSLock pattern flagged in a prior review.
Sources/Mobile/MobileHostTerminalTheme.swift TerminalTheme extension on the Mac side: builds the wire theme from GhosttyConfig (handles cursor-text sentinel correctly) and emits the JSON object with camelCase keys matching Codable's synthesized CodingKeys.
Sources/Mobile/MobileHostService.swift publicStatusPayload now calls GhosttyConfig.load() (cached, NSLock-guarded) and builds the theme payload; added to every unauthenticated status response. Cache hit path is fast; nonisolated context is consistent with existing design.
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileHostStatusResponse.swift Adds optional theme field decoded leniently with try?/decodeIfPresent to prevent a malformed theme from breaking transport negotiation; the try? + ?? nil pattern is slightly unusual but semantically correct.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Adds terminalThemeGeneration counter (wrapping) and applyTerminalTheme, which updates the @mainactor store and bumps the generation only on real changes. Correctly called on both the fast probe and slow-timeout recovery paths.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift Wires themeGeneration into makeUIView and updateUIView to drive applyLiveThemeIfRunning; coordinator tracks lastAppliedThemeGeneration to deduplicate within a single SwiftUI render cycle. Theme excluded from .id() key to preserve scrollback.
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttyRuntime.swift @MainActor-isolated class gains setTheme, applyLiveThemeIfRunning, rebuildConfigFromStore; deduplicates rebuilds via lastAppliedTheme. Hardcoded Monokai directives replaced with ghosttyColorDirectives from the live store. Config lifecycle (create/update/free) looks correct.
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift applyBackgroundColorFromConfig now sources background from theme store instead of ghostty_config_get in release builds; adds refreshThemeColors and the static refreshAllSurfacesForThemeChange. The applyBackgroundColorFromConfig call inside refreshThemeColors re-sets the same theme-store color, which is harmless but redundant.
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift Replaces hardcoded monokaiBarColor with a computed themeBarColor from TerminalThemeStore; adds accessoryBarBackgroundView weak ref and refreshThemeColors for live recolor. Clean integration.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPalette.swift Fixed Monokai constants replaced with computed properties reading TerminalThemeStore.current; @mainactor isolated to match the store. dimForeground now derived from foreground opacity instead of a separate hardcoded constant.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Mac as Mac (MobileHostService)
    participant Wire as mobile.host.status JSON
    participant RPC as MobileHostStatusResponse (iOS)
    participant Shell as MobileShellComposite
    participant Store as TerminalThemeStore (@MainActor)
    participant SwiftUI as WorkspaceDetailView (SwiftUI)
    participant Rep as GhosttySurfaceRepresentable
    participant Runtime as GhosttyRuntime (@MainActor)
    participant Surface as GhosttySurfaceView

    Mac->>Wire: publicStatusPayload() with theme (GhosttyConfig.load())
    Wire->>RPC: decode leniently (try? — bad theme → nil, not a decode failure)
    RPC->>Shell: applyTerminalTheme(payload.theme)
    Shell->>Store: TerminalThemeStore.set(theme) → validatedOrDefault()
    Shell->>Shell: "if current != previous → terminalThemeGeneration &+= 1"
    Shell->>SwiftUI: "@Observable change triggers body re-render"
    SwiftUI->>Rep: updateUIView(themeGeneration: N)
    Rep->>Runtime: applyLiveThemeIfRunning()
    Runtime->>Store: "read current (if current == lastAppliedTheme → no-op)"
    Runtime->>Runtime: rebuildConfigFromStore()
    Runtime->>Surface: ghostty_surface_update_config (all registered surfaces)
    Runtime->>Surface: refreshAllSurfacesForThemeChange()
    Surface->>Surface: refreshThemeColors() → backgroundColor, inputProxy, cursor overlay
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"}}}%%
sequenceDiagram
    participant Mac as Mac (MobileHostService)
    participant Wire as mobile.host.status JSON
    participant RPC as MobileHostStatusResponse (iOS)
    participant Shell as MobileShellComposite
    participant Store as TerminalThemeStore (@MainActor)
    participant SwiftUI as WorkspaceDetailView (SwiftUI)
    participant Rep as GhosttySurfaceRepresentable
    participant Runtime as GhosttyRuntime (@MainActor)
    participant Surface as GhosttySurfaceView

    Mac->>Wire: publicStatusPayload() with theme (GhosttyConfig.load())
    Wire->>RPC: decode leniently (try? — bad theme → nil, not a decode failure)
    RPC->>Shell: applyTerminalTheme(payload.theme)
    Shell->>Store: TerminalThemeStore.set(theme) → validatedOrDefault()
    Shell->>Shell: "if current != previous → terminalThemeGeneration &+= 1"
    Shell->>SwiftUI: "@Observable change triggers body re-render"
    SwiftUI->>Rep: updateUIView(themeGeneration: N)
    Rep->>Runtime: applyLiveThemeIfRunning()
    Runtime->>Store: "read current (if current == lastAppliedTheme → no-op)"
    Runtime->>Runtime: rebuildConfigFromStore()
    Runtime->>Surface: ghostty_surface_update_config (all registered surfaces)
    Runtime->>Surface: refreshAllSurfacesForThemeChange()
    Surface->>Surface: refreshThemeColors() → backgroundColor, inputProxy, cursor overlay
Loading

Reviews (7): Last reviewed commit: "ci: refresh Swift file-length budget aft..." | Re-trigger Greptile

Comment on lines +11 to +28
nonisolated(unsafe) private static var storage: TerminalTheme = .monokai
private static let lock = NSLock()

/// The active theme, defaulting to ``TerminalTheme/monokai``.
public static var current: TerminalTheme {
lock.lock()
defer { lock.unlock() }
return storage
}

/// Sets the active theme. An invalid or `nil` theme falls back to Monokai so
/// the terminal always renders with a complete palette.
public static func set(_ theme: TerminalTheme?) {
let resolved = theme?.validatedOrDefault() ?? .monokai
lock.lock()
storage = resolved
lock.unlock()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Manual NSLock where actor isolation would be the correct shape

TerminalThemeStore uses nonisolated(unsafe) + NSLock — a pattern the project's concurrency rules flag as a blocking primitive that should instead be expressed with actor isolation. An actor would also give Swift's type system full insight into the isolation boundary, instead of relying on the manual nonisolated(unsafe) escape hatch that bypasses strict-concurrency checking. The only practical obstacle is that current needs to be synchronous to work in string interpolation inside GhosttyRuntime; a nonisolated stored-property approach on an actor (reading an @unchecked Sendable snapshot) or a global actor with a synchronous getter keeps callsites identical while staying within Swift 6's isolation model.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

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

Comment on lines +23 to +28
public static func set(_ theme: TerminalTheme?) {
let resolved = theme?.validatedOrDefault() ?? .monokai
lock.lock()
storage = resolved
lock.unlock()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 current guards the unlock with defer to be exception-safe, but set(_:) does not. While a plain struct assignment can't throw today, the inconsistency is fragile — a future refactor that adds any work between lock.lock() and lock.unlock() would silently deadlock if it throws. Align both paths with defer.

Suggested change
public static func set(_ theme: TerminalTheme?) {
let resolved = theme?.validatedOrDefault() ?? .monokai
lock.lock()
storage = resolved
lock.unlock()
}
public static func set(_ theme: TerminalTheme?) {
let resolved = theme?.validatedOrDefault() ?? .monokai
lock.lock()
defer { lock.unlock() }
storage = resolved
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalTheme.swift`:
- Around line 54-60: The isValid property currently permits colors in
non-canonical formats (like bare "rrggbb" without the # prefix) because
rgbComponents() accepts both formats, but the documented contract specifies
colors should be stored as `#RRGGBB` hex strings. To enforce the canonical format,
modify the isValid property to validate that all colors (background, foreground,
cursor, selectionBackground, selectionForeground, cursorText, and palette
entries) match the pattern `#RRGGBB` (with the hash prefix) before passing them to
rgbComponents(). Additionally, normalize any color input in the initializer by
ensuring all color values are prefixed with # if missing, so that
ghosttyColorDirectives emits the correct canonical format and the contract is
consistently enforced throughout the codebase.

In
`@Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalThemeStore.swift`:
- Around line 11-28: The TerminalThemeStore currently uses manual NSLock
synchronization on nonisolated(unsafe) static storage for thread safety, but all
call sites are main-thread-bound, so this is unnecessary complexity. Replace the
manual locking approach by applying `@MainActor` isolation to the
TerminalThemeStore class itself. Remove the nonisolated(unsafe) annotation and
NSLock declaration, eliminate the lock.lock() and lock.unlock() calls from both
the current getter and set method, and rely on `@MainActor` to provide Swift 6
concurrency safety at the class level instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ef89dcdf-50d4-4538-83da-6a4e1e5eb156

📥 Commits

Reviewing files that changed from the base of the PR and between a8dcb6d and 5b93a03.

📒 Files selected for processing (6)
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalTheme.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalThemeStore.swift
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/TerminalThemeTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPalette.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttyRuntime.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift

Comment on lines +54 to +60
public var isValid: Bool {
guard palette.count == Self.paletteCount else { return false }
var colors = [background, foreground, cursor, selectionBackground, selectionForeground]
colors.append(contentsOf: palette)
if let cursorText { colors.append(cursorText) }
return colors.allSatisfy { Self.rgbComponents($0) != nil }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Ghostty configuration require a leading "#" for hex colors in directives like background = ...andpalette = N=..., or are bare RRGGBB values valid?

💡 Result:

In Ghostty configuration, a leading "#" is optional for hex color values. Both #RRGGBB and bare RRGGBB formats are valid for directives such as background, foreground, and palette [1][2][3]. Specifically, documentation for directives like background = ... and palette = N=... explicitly states that the color component can be specified as either a hex code (with or without the hash, e.g., #RRGGBB or RRGGBB) or a named X11 color [1][2][3]. For the palette directive, while the hex color value allows the optional "#", the palette index N supports decimal, binary (0b), octal (0o), and hexadecimal (0x) formats [2][3][4].

Citations:


🏁 Script executed:

find . -name "TerminalTheme.swift" -path "*CMUXMobileCore*" | head -1

Repository: manaflow-ai/cmux

Length of output: 230


🏁 Script executed:

cat -n "Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalTheme.swift" | head -100

Repository: manaflow-ai/cmux

Length of output: 5458


Enforce canonical #RRGGBB format in isValid and during emission.

Lines 9–10 and 14–25 document that colors are stored as #rrggbb hex strings. However, rgbComponents() accepts bare rrggbb (line 62), so isValid (line 59) permits it, and ghosttyColorDirectives (lines 80–94) emits the original format unchanged. A theme with background: "ff8000" passes validation and produces background = ff8000, breaking the stated #rrggbb contract and risking drift at integration boundaries. Normalize to canonical #rrggbb by validating that colors match the #RRGGBB pattern and normalizing input in the initializer or during storage.

Proposed fix
+    private static func isCanonicalHex(_ value: String?) -> Bool {
+        guard let value, value.count == 7, value.first == "#" else { return false }
+        return Int(value.dropFirst(), radix: 16) != nil
+    }

     /// Whether every color string parses and the palette has exactly 16 entries.
     public var isValid: Bool {
         guard palette.count == Self.paletteCount else { return false }
         var colors = [background, foreground, cursor, selectionBackground, selectionForeground]
         colors.append(contentsOf: palette)
         if let cursorText { colors.append(cursorText) }
-        return colors.allSatisfy { Self.rgbComponents($0) != nil }
+        return colors.allSatisfy { Self.isCanonicalHex($0) }
     }
🤖 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/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalTheme.swift`
around lines 54 - 60, The isValid property currently permits colors in
non-canonical formats (like bare "rrggbb" without the # prefix) because
rgbComponents() accepts both formats, but the documented contract specifies
colors should be stored as `#RRGGBB` hex strings. To enforce the canonical format,
modify the isValid property to validate that all colors (background, foreground,
cursor, selectionBackground, selectionForeground, cursorText, and palette
entries) match the pattern `#RRGGBB` (with the hash prefix) before passing them to
rgbComponents(). Additionally, normalize any color input in the initializer by
ensuring all color values are prefixed with # if missing, so that
ghosttyColorDirectives emits the correct canonical format and the contract is
consistently enforced throughout the codebase.

Source: Learnings

Comment thread Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/TerminalThemeStore.swift Outdated
…apply)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The local surface background (area behind/around cells + letterbox + input
accessory bar) was sourced from the once-built singleton ghostty config, so a
theme change left it on the old color while cells repainted. Source the local
background from TerminalThemeStore and rebuild the live runtime config on a
theme-generation change (ghostty_app_update_config + ghostty_surface_update_config)
so the renderer's defaults and palette follow the new theme without a remount.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread Sources/Mobile/MobileHostTerminalTheme.swift
Comment thread Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileHostStatusResponse.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift`:
- Around line 91-94: The theme refresh is being triggered from each
GhosttySurfaceRepresentable instance, causing repeated global rebuilds for a
single themeGeneration change. Move the call to
GhosttyRuntime.applyLiveThemeIfRunning into a single process-wide apply path, or
add an applied-generation guard inside GhosttyRuntime/shared theme handling so
only one refresh happens per generation. Update the GhosttySurfaceRepresentable
coordinator flow and lastAppliedThemeGeneration bookkeeping so mounted views do
not each reapply the same global theme refresh.

In
`@Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift`:
- Around line 323-325: The accessory bar setup in TerminalInputTextView should
not keep using hard-coded light/dark control tints when the background changes
with the active theme. Update the accessory control styling near
accessoryBarBackgroundView and the dismiss button configuration to derive
icon/text colors from the current theme foreground or choose a contrasting
variant based on background luminance, so the controls remain readable across
light and dark themes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a199259e-c76f-497e-b5d0-f1a523f0e3b5

📥 Commits

Reviewing files that changed from the base of the PR and between 0e83b49 and d252e4a.

📒 Files selected for processing (5)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttyRuntime.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift

Comment on lines +91 to +94
if themeGeneration > 0 {
GhosttyRuntime.applyLiveThemeIfRunning()
}
context.coordinator.lastAppliedThemeGeneration = themeGeneration

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Apply the global theme refresh once per generation, not once per view.

GhosttyRuntime.applyLiveThemeIfRunning() already rebuilds the shared config and refreshes all registered surfaces. Calling it from each representable instance means one themeGeneration bump triggers one full global refresh per mounted view, and the first mount after a theme change can rebuild twice because GhosttyRuntime.shared() has already read the current store. Move this to a single process-wide apply point, or add a runtime-level applied-generation guard, so one theme change produces one batch update.

As per path instructions, apply .github/review-bot-rules/algorithmic-complexity.md: “flag nested full-collection scans, per-target rescans for batch actions”.

Also applies to: 113-115

🤖 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/GhosttySurfaceRepresentable.swift`
around lines 91 - 94, The theme refresh is being triggered from each
GhosttySurfaceRepresentable instance, causing repeated global rebuilds for a
single themeGeneration change. Move the call to
GhosttyRuntime.applyLiveThemeIfRunning into a single process-wide apply path, or
add an applied-generation guard inside GhosttyRuntime/shared theme handling so
only one refresh happens per generation. Update the GhosttySurfaceRepresentable
coordinator flow and lastAppliedThemeGeneration bookkeeping so mounted views do
not each reapply the same global theme refresh.

Source: Path instructions

Comment on lines +323 to +325
backgroundView.backgroundColor = Self.themeBarColor
backgroundView.translatesAutoresizingMaskIntoConstraints = false
self.accessoryBarBackgroundView = backgroundView

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the accessory controls choose a contrasting foreground.

This bar now inherits any terminal background, but the controls in the same block still assume a dark surface (for example, the dismiss button’s fixed light-gray tint a few lines below). Light themes will leave at least that affordance below usable contrast. Please derive the toolbar icon/text colors from the active theme foreground, or switch between light/dark variants based on the background luminance.

🤖 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/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift`
around lines 323 - 325, The accessory bar setup in TerminalInputTextView should
not keep using hard-coded light/dark control tints when the background changes
with the active theme. Update the accessory control styling near
accessoryBarBackgroundView and the dismiss button configuration to derive
icon/text colors from the current theme foreground or choose a contrasting
variant based on background luminance, so the controls remain readable across
light and dark themes.

lawrencecchen and others added 2 commits July 2, 2026 20:16
…emes

# Conflicts:
#	Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
#	Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift
#	Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
…dedup, cursor-text, MainActor)

Fixes from Cursor Bugbot, CodeRabbit, and Greptile on the arbitrary-themes PR:

- MobileHostStatusResponse: decode `theme` leniently (`try?`). A present-but-
  malformed theme no longer fails the whole host-status decode, which had
  forced raw-bytes transport and skipped capability/identity adoption over a
  cosmetic field.
- GhosttyRuntime.applyLiveThemeIfRunning: dedupe by theme value. One
  themeGeneration bump fires updateUIView on every mounted representable; the
  rebuild + refresh-all now runs once per theme value instead of once per view.
  Seeded from init so the first mount after a change doesn't rebuild a config
  that already matches.
- MobileHostTerminalTheme (Mac producer): only serialize cursor-text when the
  config actually parsed a `cursor-text` directive (hasParsedCursorTextColor).
  Otherwise the phone emitted an explicit cursor-text the Mac never had and
  mis-colored the cursor label; nil lets the phone derive contrast like the Mac.
- TerminalThemeStore: @mainactor instead of nonisolated(unsafe) + NSLock. All
  producers/consumers are main-actor; the compiler now proves it. TerminalPalette
  marked @mainactor to match.
- scheduleHostIdentityAdoptionIfNeeded: also applyTerminalTheme on the full-
  timeout recovery path, so a theme skipped by the 750ms probe timeout still
  adopts. applyTerminalTheme is idempotent.
- TerminalTheme.ghosttyColorDirectives: emit canonical `#rrggbb` (normalize bare
  `rrggbb` input) so the stored contract holds at the ghostty boundary.
- Refreshed the stale terminalThemeGeneration doc comment (live-recolor, not
  remount).

Tests: new canonical-hex emission test and malformed-theme-tolerance decode
test; existing CMUXMobileCore (146) and CmuxMobileRPC (68) suites green.

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

This comment has been minimized.

The arbitrary-themes work grows several already-large iOS files (GhosttyRuntime,
GhosttySurfaceView, MobileShellComposite, TerminalInputTextView, MobileHostService)
and newly tracks GhosttySurfaceRepresentable past 500 lines. This is real feature
debt (live recolor + theme sync), not gratuitous bloat; splitting these god-files
is a separate refactor. Refresh the budget to current counts.

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.

2 issues found and verified against the latest diff

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="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift:2998">
P2: Theme can be applied from a stale status response after reconnect, which can recolor the active terminal/chrome to the wrong host theme. This happens because `applyTerminalTheme(payload.theme)` runs after an `await` without revalidating `remoteClient === client`; adding that guard before applying theme keeps stale responses inert.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPalette.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPalette.swift:23">
P3: This fallback branch is dead for current usage, because `TerminalThemeStore.current` is always validated/Monokai and `background`/`foreground` always parse. Keeping an unreachable `.black` path also conflicts with the Monokai-fallback policy used elsewhere, which can mislead future maintenance.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

// only bumps the generation on a real change), so re-applying here is
// free in the common case and keeps the phone's colors in sync with
// the Mac even when the probe could not.
self.applyTerminalTheme(payload.theme)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Theme can be applied from a stale status response after reconnect, which can recolor the active terminal/chrome to the wrong host theme. This happens because applyTerminalTheme(payload.theme) runs after an await without revalidating remoteClient === client; adding that guard before applying theme keeps stale responses inert.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift, line 2998:

<comment>Theme can be applied from a stale status response after reconnect, which can recolor the active terminal/chrome to the wrong host theme. This happens because `applyTerminalTheme(payload.theme)` runs after an `await` without revalidating `remoteClient === client`; adding that guard before applying theme keeps stale responses inert.</comment>

<file context>
@@ -2965,6 +2988,14 @@ public final class MobileShellComposite: MobileTerminalOutputSinking {
+            // only bumps the generation on a real change), so re-applying here is
+            // free in the common case and keeps the phone's colors in sync with
+            // the Mac even when the probe could not.
+            self.applyTerminalTheme(payload.theme)
             await self.applyHostReportedIdentity(
                 client: client,
</file context>

/// Dimmed terminal foreground (`#c8c8c0`).
static let dimForeground = Color(red: 0xc8 / 255.0, green: 0xc8 / 255.0, blue: 0xc0 / 255.0)
private static func color(_ hex: String) -> Color {
guard let rgb = TerminalTheme.rgbComponents(hex) else { return .black }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This fallback branch is dead for current usage, because TerminalThemeStore.current is always validated/Monokai and background/foreground always parse. Keeping an unreachable .black path also conflicts with the Monokai-fallback policy used elsewhere, which can mislead future maintenance.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPalette.swift, line 23:

<comment>This fallback branch is dead for current usage, because `TerminalThemeStore.current` is always validated/Monokai and `background`/`foreground` always parse. Keeping an unreachable `.black` path also conflicts with the Monokai-fallback policy used elsewhere, which can mislead future maintenance.</comment>

<file context>
@@ -1,17 +1,30 @@
-    /// Dimmed terminal foreground (`#c8c8c0`).
-    static let dimForeground = Color(red: 0xc8 / 255.0, green: 0xc8 / 255.0, blue: 0xc0 / 255.0)
+    private static func color(_ hex: String) -> Color {
+        guard let rgb = TerminalTheme.rgbComponents(hex) else { return .black }
+        return Color(
+            red: Double(rgb.red) / 255.0,
</file context>

Comment thread Sources/Mobile/MobileHostTerminalTheme.swift
TerminalThemeStore and TerminalPalette are new caseless static-only types the
package-conventions lint (namespace-enum/namespace-type) flags after the main
rebase. TerminalPalette becomes an internal namespace struct (not an enum, not
public, so neither rule applies). TerminalThemeStore stays a process-wide theme
singleton (public), converted enum->struct with a reviewed inline
lint:allow namespace-type justification: it holds one global rendering resource,
not dependency-bearing logic that belongs on an instantiated value. No call-site
changes.

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

# Conflicts:
#	.github/swift-file-length-budget.tsv

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 68707c9. Configure here.

// color overrides into a complete effective palette; the phone applies
// it so its embedded terminal renders with the Mac's colors instead of
// the built-in Monokai default.
let theme = TerminalTheme(ghosttyConfig: GhosttyConfig.load())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Status theme uses stale config cache

Medium Severity

publicStatusPayload builds the wire theme from GhosttyConfig.load() with the default cached path. If the Mac terminal palette changed but the load cache was not invalidated yet, the next mobile.host.status response can advertise colors that no longer match the Mac’s effective terminal theme.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 68707c9. Configure here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`:
- Around line 64-66: Mark the new SwiftUI state owned by WorkspaceDetailView as
private by updating the declarations of ignoredChatSessionRefreshKey,
ignoredChatSessionRefreshID, and ignoredChatSessionRefreshTask to use private
`@State`. Keep the change limited to these three properties in WorkspaceDetailView
so their state remains internally managed and not externally writable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f1c3a6f9-d9b0-4d15-8256-e19c910e5a9d

📥 Commits

Reviewing files that changed from the base of the PR and between 7980012 and 68707c9.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (2)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • cmux.xcodeproj/project.pbxproj

@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

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

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

Inline comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`:
- Around line 64-66: Mark the new SwiftUI state owned by WorkspaceDetailView as
private by updating the declarations of ignoredChatSessionRefreshKey,
ignoredChatSessionRefreshID, and ignoredChatSessionRefreshTask to use private
`@State`. Keep the change limited to these three properties in WorkspaceDetailView
so their state remains internally managed and not externally writable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f1c3a6f9-d9b0-4d15-8256-e19c910e5a9d

📥 Commits

Reviewing files that changed from the base of the PR and between 7980012 and 68707c9.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (2)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • cmux.xcodeproj/project.pbxproj
🛑 Comments failed to post (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift (1)

64-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mark new @State properties private.

ignoredChatSessionRefreshKey, ignoredChatSessionRefreshID, and ignoredChatSessionRefreshTask should be private per SwiftUI convention (state owned by the view shouldn't be externally writable).

🔧 Proposed fix
-    `@State` var ignoredChatSessionRefreshKey: String?
-    `@State` var ignoredChatSessionRefreshID: UUID?
-    `@State` var ignoredChatSessionRefreshTask: Task<[ChatSessionDescriptor]?, Never>?
+    `@State` private var ignoredChatSessionRefreshKey: String?
+    `@State` private var ignoredChatSessionRefreshID: UUID?
+    `@State` private var ignoredChatSessionRefreshTask: Task<[ChatSessionDescriptor]?, Never>?
📝 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.

    `@State` private var ignoredChatSessionRefreshKey: String?
    `@State` private var ignoredChatSessionRefreshID: UUID?
    `@State` private var ignoredChatSessionRefreshTask: Task<[ChatSessionDescriptor]?, Never>?
🧰 Tools
🪛 SwiftLint (0.65.0)

[Warning] 66-66: Prefer empty collection over optional collection

(discouraged_optional_collection)


[Warning] 64-64: SwiftUI state properties should be private

(private_swiftui_state)


[Warning] 65-65: SwiftUI state properties should be private

(private_swiftui_state)


[Warning] 66-66: SwiftUI state properties should be private

(private_swiftui_state)

🤖 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/WorkspaceDetailView.swift`
around lines 64 - 66, Mark the new SwiftUI state owned by WorkspaceDetailView as
private by updating the declarations of ignoredChatSessionRefreshKey,
ignoredChatSessionRefreshID, and ignoredChatSessionRefreshTask to use private
`@State`. Keep the change limited to these three properties in WorkspaceDetailView
so their state remains internally managed and not externally writable.

Source: Linters/SAST tools

@lawrencecchen
lawrencecchen merged commit a955024 into main Jul 4, 2026
24 of 36 checks passed
@lawrencecchen
lawrencecchen deleted the feat-ios-arbitrary-themes branch July 4, 2026 09:14
azooz2003-bit added a commit that referenced this pull request Jul 4, 2026
…ng, themes)

Delta: #7318/#7319 mouse cursor-shape + right/middle drag forwarding, #7320 ghostty
upstream, #6664 iOS themes, #7196 iOS scroll, #7257/#7307 web, #7329 CLI suggestions,
#7222 codex resume update-suppression. Conflicts resolved keeping HEAD structure:
- GhosttyTerminalView.swift: took main's viewDidEndLiveResize + resetCursorRects
  (cursor-shape #7318; ghosttyMouseShape/ghosttyMouseCursor already in HEAD).
- Mobile/MobileHostService.swift: union (kept HEAD sharedRequestActivity +
  main publicStatusPayload theme payload #6664).
- project.pbxproj: union-dedup, normalized; test-wiring green.
- swift-file-length-budget.tsv regenerated.

#7222 (codex update-check suppression) partially landed: the CMUXAgentLaunch package
part (AgentResumeArgv.codexUpdateCheckSuppressionOverride) auto-merged and is present;
the app-target files (+CodexUpdateCheck.swift + test) extend SurfaceResumeCommandCanonicalizer,
a type HEAD dissolved into the CmuxWorkspaces package, so they can't compile and the
suppression was never wired (call site was in the HEAD-deleted +PortableAgentExecutable).
Removed those two files; re-wire recorded in merge-deferred-gaps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
azooz2003-bit added a commit that referenced this pull request Jul 4, 2026
The union kept main's publicStatusPayload (mobile.host.status theme, #6664) which
references mobileHostCapabilities, but MobileHostService+Capabilities.swift (which
defines it) was not brought into the merge (new-in-main file, anomalously not
auto-added). Restored it from origin/main and wired it into pbxproj (Mobile group,
bare path). Its deps (TerminalTheme, mobileHostJSONObject) are present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lawrencecchen
lawrencecchen restored the feat-ios-arbitrary-themes branch July 18, 2026 10:24

This branch was successfully deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant