Skip to content

Consolidate ghostty extractions into CmuxTerminalCore (no new packages) - #6227

Merged
azooz2003-bit merged 6 commits into
mainfrom
feat-consolidate-ghostty
Jun 16, 2026
Merged

azooz2003-bit merged 6 commits into
mainfrom
feat-consolidate-ghostty

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jun 16, 2026 •

Copy link
Copy Markdown
Collaborator

Folds two rejected per-sliver Ghostty refactor branches into the existing CmuxTerminalCore domain package. No new top-level packages are created; the extracted types live where the rest of the terminal/Ghostty domain already lives, and the app target loses two package dependencies.

Slivers folded (logic byte-identical):

  • feat-ghostty-engine-config-loader -> Packages/CmuxTerminalCore/.../ConfigDiscovery/. GhosttyConfigDiscovery plus its GhosttyConfigFileReading / GhosttyFontProbing seams and UserFontConfigSummary. The discovery type already targeted CmuxTerminalCore's GhosttyConfig and CmuxFoundation's CmuxGhosttyConfigPathResolver, so the only change is dropping the now-internal public import CmuxTerminalCore. GhosttyTerminalView forwards through its existing import CmuxTerminalCore.

  • feat-menu-and-configuration-reload-wiring -> Packages/CmuxTerminalCore/.../ShortcutDecoding/. GhosttyTriggerShortcutDecoder and its value types (GhosttyTriggerInput, GhosttyModifierMask, GhosttyTriggerPhysicalKey, GhosttyTriggerShortcut), plus CmuxConfigStoreReloadCoordinator and its CmuxConfigStoreReloading / CmuxConfigStoreReloadEnvironment seams. Because CmuxTerminalCore already re-vends the GhosttyKit binary target, the GhosttyTriggerPhysicalKey+GhosttyKit boundary adapter moves into the package alongside its type (import flips from the old package to GhosttyKit, switch body unchanged) instead of staying app-side. The empty CmuxConfigStore: CmuxConfigStoreReloading conformer stays in the app target with its import flipped to CmuxTerminalCore. The coordinator already holds its environment weakly, so the app delegate owning it creates no retain cycle.

Both Packages/CmuxGhosttyConfigLoader/ and Packages/CmuxGhosttyShortcutDecoding/ directories are deleted, along with their pbxproj wiring and the now-dangling app-target source entries for the moved GhosttyKit adapter. AppDelegate and GhosttyTerminalView drop the redundant per-sliver imports.

Verification: scripts/lint-ios-package-conventions.sh shows no new ERROR lines from these files and zero new lint:allow (the one pre-existing ERROR, ComposerDictationTextMerge, is byte-identical on main and untouched here). swift build and swift test in Packages/CmuxTerminalCore pass (122 tests, 26 suites), including the moved GhosttyConfigDiscovery*, GhosttyTriggerShortcutDecoder, and CmuxConfigStoreReloadCoordinator suites. .github/swift-file-length-budget.tsv reconciled (budget respected; GhosttyConfigDiscovery.swift retracked at the new path). pbxproj passes plutil -lint, normalize-pbxproj.py (no-op), check-pbxproj.sh, and lint-pbxproj-test-wiring.sh. Full-app build left to CI.

Supersedes the PRs for feat-ghostty-engine-config-loader and feat-menu-and-configuration-reload-wiring.

🤖 Generated with Claude Code


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


Summary by cubic

Consolidates Ghostty config discovery, shortcut decoding, and config reload orchestration into CmuxTerminalCore, removing two packages and simplifying app code with no behavior changes. Fixes a cross‑module access error by making the GhosttyKit key‑mapping initializer public.

  • Refactors

    • Moved GhosttyConfigDiscovery (+ GhosttyConfigFileReading, GhosttyFontProbing, UserFontConfigSummary) to Packages/CmuxTerminalCore/.../ConfigDiscovery/; GhosttyTerminalView now calls this and drops large inline logic.
    • Moved GhosttyTriggerShortcutDecoder (+ value types) and CmuxConfigStoreReloadCoordinator (+ seams) to Packages/CmuxTerminalCore/.../ShortcutDecoding/; integrated GhosttyTriggerPhysicalKey+GhosttyKit inside the package.
    • AppDelegate conforms to CmuxConfigStoreReloadEnvironment and owns the coordinator; added Sources/CmuxConfigStore+ConfigStoreReloading.swift so CmuxConfigStore conforms.
    • Deleted Packages/CmuxGhosttyConfigLoader/ and Packages/CmuxGhosttyShortcutDecoding/; tests re-homed under Packages/CmuxTerminalCore/Tests/; file-length budget and pbxproj updated.
  • Bug Fixes

    • Made GhosttyTriggerPhysicalKey.init?(ghosttyPhysicalKey:) public and used public import GhosttyKit to resolve cross-module access.
    • Removed unused @Observable from CmuxConfigStoreReloadCoordinator.

Written for commit 80ff2f7. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added locale-driven automatic CJK font fallback mapping, with safeguards when the user already configures codepoint maps or multi-entry font-family fallbacks.
    • Added managed default appearance support, including conditional theme override selection for light/dark.
    • Introduced improved Ghostty trigger/shortcut decoding for consistent shortcut keys and glyphs.
    • Added coordinated cmux configuration store reloads across windows, including automatic window title refresh.
  • Tests
    • Added/expanded tests for CJK mapping & config parsing (includes, legacy rules), theme override behavior, reload deduping, and shortcut decoding.

azooz2003-bit and others added 2 commits June 15, 2026 20:59
Moves GhosttyConfigDiscovery and its GhosttyConfigFileReading /
GhosttyFontProbing / UserFontConfigSummary seams (byte-identical logic) from
the rejected per-sliver CmuxGhosttyConfigLoader package into the existing
CmuxTerminalCore domain package under ConfigDiscovery/. The discovery type
already targeted CmuxTerminalCore's GhosttyConfig and CmuxFoundation's
CmuxGhosttyConfigPathResolver, so the only change is dropping the now-internal
'import CmuxTerminalCore' and re-homing the test as @testable import
CmuxTerminalCore. GhosttyTerminalView forwards to it via the existing
CmuxTerminalCore import (no new app-side import). The CmuxGhosttyConfigLoader
package and its 6 pbxproj entries are removed; zero new top-level packages.

Supersedes PR for feat-ghostty-engine-config-loader.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Core

Moves GhosttyTriggerShortcutDecoder and its value types (GhosttyTriggerInput,
GhosttyModifierMask, GhosttyTriggerPhysicalKey, GhosttyTriggerShortcut) plus the
CmuxConfigStoreReloadCoordinator and its CmuxConfigStoreReloading /
CmuxConfigStoreReloadEnvironment protocol seams (byte-identical logic) from the
rejected per-sliver CmuxGhosttyShortcutDecoding package into the existing
CmuxTerminalCore domain package under ShortcutDecoding/.

Because CmuxTerminalCore already re-vends the GhosttyKit binary target, the
GhosttyTriggerPhysicalKey+GhosttyKit boundary adapter (mapping
ghostty_input_key_e onto the key token) moves into the package alongside its
type instead of staying app-side; its import flips from
CmuxGhosttyShortcutDecoding to GhosttyKit, the switch body is unchanged. The
empty CmuxConfigStore: CmuxConfigStoreReloading conformer stays in the app
target with its import flipped to CmuxTerminalCore. AppDelegate forwards through
its existing CmuxTerminalCore import (redundant CmuxGhosttyShortcutDecoding
import dropped). The coordinator already holds its environment weakly, so the
app delegate owning it creates no retain cycle.

The CmuxGhosttyShortcutDecoding package and its pbxproj wiring are removed, along
with the now-dangling app-target source entries for the moved GhosttyKit adapter;
zero new top-level packages. Supersedes PR for
feat-menu-and-configuration-reload-wiring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 16, 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 Jun 16, 2026 10:59pm
cmux-staging Building Building Preview, Comment Jun 16, 2026 10:59pm

@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c99ccbdf-77d2-452a-b64b-ee144e4856fe

📥 Commits

Reviewing files that changed from the base of the PR and between 7c6d4e7 and 80ff2f7.

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

📝 Walkthrough

Walkthrough

Extracts Ghostty config discovery logic (CJK font fallback, theme override, scan-path resolution, config parsing) from GhosttyTerminalView.swift into a new injected GhosttyConfigDiscovery value type backed by GhosttyConfigFileReading and GhosttyFontProbing protocol seams. Adds a typed Ghostty trigger-to-shortcut decoding pipeline replacing hardcoded AppDelegate switch logic with composable types. Introduces CmuxConfigStoreReloadCoordinator to deduplicate per-window config store reloads behind CmuxConfigStoreReloading/CmuxConfigStoreReloadEnvironment protocol seams.

Changes

GhosttyConfigDiscovery extraction and delegation

Layer / File(s) Summary
File-reading and font-probing protocol seams
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigFileReading.swift, GhosttyFontProbing.swift
GhosttyConfigFileReading protocol and FileManagerGhosttyConfigFileReader implementation for filesystem abstraction; GhosttyFontProbing protocol and CoreTextGhosttyFontProbe implementation for CoreText font lookup with weight trait support and name normalization.
UserFontConfigSummary: font directive aggregation
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/UserFontConfigSummary.swift
UserFontConfigSummary struct tracks containsCodepointMap flag and ordered, de-duplicated effectiveFontFamilies list; methods applyFontCodepointMap and recordFontFamily mutate state with empty-value clearing and hasExplicitFontFallbackChain detection.
GhosttyConfigDiscovery: init, CJK mappings, theme override, font resolution
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift (lines 1–303)
Locale-driven CJK mapping computation with guards for existing codepoint-map/font-family directives; conditional theme-override resolution extracting light/dark conditionals; font normalization, glyph coverage probing, and regular-weight face verification helpers.
Config scanning, line parsing, and scan-path resolution
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift (lines 304–560)
userFontConfigSummary recursively scans config files following config-file includes with seen-path deduplication; robust line parsing handling BOM, comments, blank lines, quoted key=value pairs, and include path resolution with tilde expansion; loadedGhosttyConfigScanPaths builds paths from native Ghostty, cmux Application Support, and release-bundle directories with conditional legacy inclusion based on file sizes.
GhosttyConfigDiscovery tests
Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift
Test suites covering CJK range mapping (Japanese/Korean/Chinese with shared-range-once behavior), font-codepoint-map and font-family parsing with empty-value clearing, config-file include resolution, legacy config rules, scan-path locations, and conditional theme override logic.
GhosttyTerminalView delegation to GhosttyConfigDiscovery
Sources/GhosttyTerminalView.swift
GhosttyApp gains shared configDiscovery instance; all CJK, theme-override, font-resolution, scan-path, and legacy-config helpers replaced with one-line delegations into configDiscovery; configPaths parameters change from computed defaults to optional nil; prior in-file Unicode range constants and CoreText probing removed.

Ghostty trigger-to-shortcut decoding pipeline

Layer / File(s) Summary
Physical key enum, modifier mask, trigger input, and shortcut data types
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerPhysicalKey.swift, GhosttyModifierMask.swift, GhosttyTriggerInput.swift, GhosttyTriggerShortcut.swift
GhosttyTriggerPhysicalKey enum with glyph property; GhosttyModifierMask wrapping raw UInt32 with Boolean modifier flags; GhosttyTriggerInput with nested Tag enum (physical/unicode/catchAll); GhosttyTriggerShortcut with key string and modifier Booleans.
GhosttyKit initializer, decoder, and AppDelegate integration
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerPhysicalKey+GhosttyKit.swift, GhosttyTriggerShortcutDecoder.swift, Sources/AppDelegate.swift
Failable init mapping ghostty_input_key_e to physical key cases; stateless GhosttyTriggerShortcutDecoder.decode(_:) selecting key glyph by tag type, rejecting unmapped/empty inputs; AppDelegate.storedShortcutFromGhosttyTrigger derives GhosttyTriggerInput, decodes via static decoder, constructs StoredShortcut.
GhosttyTriggerShortcutDecoder tests
Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyTriggerShortcutDecoderTests.swift
Tests validate physical arrow and letter/punctuation key glyph mapping, modifier-bit decoding, rejection of unmapped/empty inputs, unicode scalar lowercasing, empty modifier mask rejection, and coverage of all GhosttyTriggerPhysicalKey cases with round-trip glyph verification.

Config store reload coordination and AppDelegate integration

Layer / File(s) Summary
Reload coordination protocols
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/CmuxConfigStoreReloading.swift
CmuxConfigStoreReloading protocol exposes loadAll() for per-window stores; CmuxConfigStoreReloadEnvironment provides reloadableConfigStores list and refreshWindowTitlesAfterConfigReload() hook; both marked @MainActor.
CmuxConfigStoreReloadCoordinator and store conformance
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/CmuxConfigStoreReloadCoordinator.swift, Sources/CmuxConfigStore+ConfigStoreReloading.swift
CmuxConfigStoreReloadCoordinator weakly references environment, deduplicates stores by ObjectIdentifier, calls loadAll() on each distinct store, triggers title refresh, reports via onReload callback with store count; CmuxConfigStore empty conformance relying on existing loadAll().
AppDelegate reload coordination wiring
Sources/AppDelegate.swift
AppDelegate conforms to CmuxConfigStoreReloadEnvironment; gains lazy configStoreReloadCoordinator, computed reloadableConfigStores, simplified refreshWindowTitlesAfterConfigReload() that only refreshes main window titles; reloadCmuxConfigStores(source:) delegates to coordinator.
CmuxConfigStoreReloadCoordinator tests
Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/CmuxConfigStoreReloadCoordinatorTests.swift
Tests verify reload invoked once per distinct store, title refresh called once per reload, correct distinct store count reported, title refresh with zero store count, and zero report after environment deallocation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • lawrencecchen

Poem

🐇 Hoppin' through config files, one by one,
CJK mappings are auto-done!
Shortcut triggers, decoded with grace,
No more hardcoded keys to chase.
Reload coords keep stores in line —
This bunny thinks the diff looks fine! ✨

🚥 Pre-merge checks | ✅ 20 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: consolidating Ghostty-related code from two packages into CmuxTerminalCore while dropping package dependencies.
Description check ✅ Passed The description is comprehensive and follows the template with Summary and Testing sections, detailing what changed, why, verification steps, and testing approach.
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 All new production code properly handles Swift actor isolation. Value types crossing actor boundaries are marked Sendable; MainActor-isolated types and protocols are explicitly annotated; non-Senda...
Cmux Swift Blocking Runtime ✅ Passed All 13 new production Swift files are free of blocking synchronization. CmuxConfigStoreReloadCoordinator uses @MainActor isolation; GhosttyConfigDiscovery uses injected I/O protocols; no semaphores...
Cmux Expensive Synchronous Load ✅ Passed PR consolidates existing Ghostty config discovery logic from two packages into CmuxTerminalCore; no new synchronous file I/O added, only refactoring. Logic was byte-identical and already running sy...
Cmux Cache Substitution Correctness ✅ Passed GhosttyConfigDiscovery reads fresh from disk on every method call via injected seams; static instance is composition-root convenience, not result cache. Used for config initialization, not persiste...
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift source files and Xcode project configuration; rule scopes TypeScript, JavaScript, shell, and non-Swift build/runtime scripts. Swift timing is excluded from this check.
Cmux Algorithmic Complexity ✅ Passed All code paths use tiny fixed-size collections (CJK ranges ~10, languages ~5, config paths ~7, windows ~20) or proper Set-based deduplication. No nested full-collection scans, per-target rescans, o...
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns. GhosttyConfigDiscovery and GhosttyTriggerShortcutDecoder are moved byte-identical from rejected packages; all new protocols use synchronous methods appropria...
Cmux Swift @Concurrent ✅ Passed No violations of the @concurrent annotation rule found. GhosttyConfigDiscovery performs synchronous file I/O but is called from non-MainActor-isolated GhosttyApp class methods. CmuxConfigStoreReloa...
Cmux Swift File And Package Boundaries ✅ Passed All files respect size/responsibility boundaries: GhosttyConfigDiscovery (560 lines, coherent config-discovery domain), all others ≤174 lines; no mixed UI/state/persistence/network responsibilities...
Cmux Swift Logging ✅ Passed All 13 new production code files contain zero logging violations; pre-existing NSLog/Logger in modified files were not introduced by this PR and remain compliant.
Cmux User-Facing Error Privacy ✅ Passed No user-facing error messages, vendor names, provider flags, credentials, tokens, or prohibited content found. PR adds internal domain types, seam protocols, tests, and refactored app code with no...
Cmux Full Internationalization ✅ Passed PR moves existing code (logic byte-identical) containing only literal tokens: system font names, Unicode range IDs, config keys, and keyboard glyphs already in use. No new user-facing UI text, loca...
Cmux Swiftui State Layout ✅ Passed PR introduces only value types (structs/enums), protocols, and one @MainActor class with no @Observable/@Published/@StateObject/@EnvironmentObject. AppDelegate and GhosttyTerminalView are AppKit-ba...
Cmux Architecture Rethink ✅ Passed PR moves config discovery and shortcut decoding into CmuxTerminalCore with clean architecture: CmuxConfigStoreReloadCoordinator delegates pure coordination logic (dedup, reload, refresh, report) wi...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR refactors backend domain logic (config discovery, shortcut decoding, reload coordination) from deleted packages into CmuxTerminalCore; no new NSWindow/NSPanel/NSWindowController/SwiftUI Window c...
Cmux Source Artifacts ✅ Passed All PR changes are intentional source files (Swift code, tests, build config) with deliberate product/test system reasons; no local artifacts, logs, caches, or temp folders added to source control.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-consolidate-ghostty

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Consolidates two previously-rejected per-package Ghostty feature branches (feat-ghostty-engine-config-loader and feat-menu-and-configuration-reload-wiring) into the existing CmuxTerminalCore package, deleting CmuxGhosttyConfigLoader and CmuxGhosttyShortcutDecoding entirely. All moved logic is byte-identical to what lived in the app target.

  • ConfigDiscovery (GhosttyConfigDiscovery + seams): lifted out of GhosttyTerminalView.swift; the app now holds a single private static let configDiscovery and forwards all CJK/legacy/theme calls through it. The file-reader and font-probe collaborators are injected, making the 182-test suite fully controllable without touching the real filesystem or CoreText.
  • ShortcutDecoding (GhosttyTriggerShortcutDecoder, GhosttyModifierMask, GhosttyTriggerInput, GhosttyTriggerPhysicalKey, GhosttyTriggerShortcut): the large per-key switch and modifier bit-checks move into typed value models; the GhosttyKit boundary adapter (GhosttyTriggerPhysicalKey+GhosttyKit) moves inside the package and is made public to resolve the cross-module access error.
  • ConfigReload (CmuxConfigStoreReloadCoordinator + CmuxConfigStoreReloading/CmuxConfigStoreReloadEnvironment seams): AppDelegate gains a lazy coordinator owned through a weak environment reference; the original dedup-and-reload loop is now covered by 70 coordinator tests.

Confidence Score: 5/5

Safe to merge — all moved logic is byte-identical to the app-target originals, 122 passing tests cover the relocated suites, and the two deleted packages are fully replaced by equivalent code inside CmuxTerminalCore.

The change is a structural consolidation with no new behavior. All CJK, legacy-config, theme-override, shortcut-decoder, and reload-coordinator logic is provably byte-identical to what it replaces. The injected seams (GhosttyConfigFileReading, GhosttyFontProbing, CmuxConfigStoreReloadEnvironment) are correctly narrow, the weak-reference retain-cycle avoidance in the coordinator is sound, and the GhosttyKit modifier bit positions are documented against the fixed Ghostty ABI. No actor isolation or concurrency regressions introduced.

No files require special attention.

Important Files Changed

Filename Overview
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift 560-line discovery value; all CJK/legacy/theme config logic extracted from GhosttyTerminalView; injected seams make tests hermetic. File added to swift-file-length-budget.tsv.
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/CmuxConfigStoreReloadCoordinator.swift @mainactor coordinator holds environment weakly; deduplicates stores by ObjectIdentifier matching original Set loop; onReload hook wired to DEBUG log in AppDelegate.
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerShortcutDecoder.swift Byte-identical shortcut decode logic; lifts physical-key glyph selection and modifier check into typed values; all edge cases (catchAll, unmapped key, nil scalar, empty key, no modifiers) preserved.
Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerPhysicalKey+GhosttyKit.swift GhosttyKit boundary adapter; public import GhosttyKit; all 48 key cases mapped; switch body byte-identical to original per PR description.
Sources/AppDelegate.swift AppDelegate gains CmuxConfigStoreReloadEnvironment conformance and lazy configStoreReloadCoordinator; storedShortcutFromGhosttyTrigger now lifts to GhosttyTriggerInput and delegates to decoder; no behavioral changes.
Sources/GhosttyTerminalView.swift ~350 lines removed; all CJK/legacy/scan-path logic now forwarded through configDiscovery; method signatures changed configPaths from [String] default to [String]? = nil (resolved internally), preserving equivalent behavior.
Sources/CmuxConfigStore+ConfigStoreReloading.swift Trivial empty conformance extension; CmuxConfigStore.loadAll() already satisfies the CmuxConfigStoreReloading requirement.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph AppTarget["App Target"]
        AD["AppDelegate\n(CmuxConfigStoreReloadEnvironment)"]
        GTV["GhosttyApp\n(GhosttyTerminalView.swift)"]
        CCS["CmuxConfigStore\n(CmuxConfigStoreReloading)"]
        ADConf["configStoreReloadCoordinator\n(lazy)"]
        ADStatic["private static let\nconfigDiscovery"]
    end

    subgraph CmuxTerminalCore["Packages/CmuxTerminalCore"]
        GCD["GhosttyConfigDiscovery\n(ConfigDiscovery/)"]
        GCFR["GhosttyConfigFileReading\n(protocol seam)"]
        GFP["GhosttyFontProbing\n(protocol seam)"]
        CCSRC["CmuxConfigStoreReloadCoordinator\n(ShortcutDecoding/)"]
        CCSR["CmuxConfigStoreReloading\n(protocol seam)"]
        CCSRE["CmuxConfigStoreReloadEnvironment\n(protocol seam)"]
        GTSD["GhosttyTriggerShortcutDecoder"]
        GTPK["GhosttyTriggerPhysicalKey\n+GhosttyKit boundary"]
        GMM["GhosttyModifierMask"]
    end

    AD --> |"conforms to"| CCSRE
    AD --> |"owns"| ADConf
    ADConf --> |"wraps"| CCSRC
    CCSRC --> |"weak ref via"| CCSRE
    CCS --> |"conforms to"| CCSR
    CCSRC --> |"drives reloads via"| CCSR

    GTV --> |"owns static"| ADStatic
    ADStatic --> |"instance of"| GCD
    GCD --> |"uses"| GCFR
    GCD --> |"uses"| GFP

    GTV --> |"delegates CJK/theme/path calls to"| GCD
    GTV --> |"static decoder"| GTSD
    GTSD --> |"uses"| GTPK
    GTSD --> |"uses"| GMM
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    subgraph AppTarget["App Target"]
        AD["AppDelegate\n(CmuxConfigStoreReloadEnvironment)"]
        GTV["GhosttyApp\n(GhosttyTerminalView.swift)"]
        CCS["CmuxConfigStore\n(CmuxConfigStoreReloading)"]
        ADConf["configStoreReloadCoordinator\n(lazy)"]
        ADStatic["private static let\nconfigDiscovery"]
    end

    subgraph CmuxTerminalCore["Packages/CmuxTerminalCore"]
        GCD["GhosttyConfigDiscovery\n(ConfigDiscovery/)"]
        GCFR["GhosttyConfigFileReading\n(protocol seam)"]
        GFP["GhosttyFontProbing\n(protocol seam)"]
        CCSRC["CmuxConfigStoreReloadCoordinator\n(ShortcutDecoding/)"]
        CCSR["CmuxConfigStoreReloading\n(protocol seam)"]
        CCSRE["CmuxConfigStoreReloadEnvironment\n(protocol seam)"]
        GTSD["GhosttyTriggerShortcutDecoder"]
        GTPK["GhosttyTriggerPhysicalKey\n+GhosttyKit boundary"]
        GMM["GhosttyModifierMask"]
    end

    AD --> |"conforms to"| CCSRE
    AD --> |"owns"| ADConf
    ADConf --> |"wraps"| CCSRC
    CCSRC --> |"weak ref via"| CCSRE
    CCS --> |"conforms to"| CCSR
    CCSRC --> |"drives reloads via"| CCSR

    GTV --> |"owns static"| ADStatic
    ADStatic --> |"instance of"| GCD
    GCD --> |"uses"| GCFR
    GCD --> |"uses"| GFP

    GTV --> |"delegates CJK/theme/path calls to"| GCD
    GTV --> |"static decoder"| GTSD
    GTSD --> |"uses"| GTPK
    GTSD --> |"uses"| GMM
Loading

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

Comment on lines +1 to +54
public import Observation

/// Orchestrates reloading every per-window cmux configuration store and refreshing
/// window titles, as the app delegate's `reloadCmuxConfigStores(source:)` did.
///
/// Owned by the app delegate and given a weak reference to it through
/// `CmuxConfigStoreReloadEnvironment`, so the delegate retaining the coordinator
/// does not create a retain cycle. The coordinator does no I/O itself; it sequences
/// the stores' own `loadAll()` and the environment's title refresh, deduping shared
/// stores by object identity exactly as before.
@MainActor
@Observable
public final class CmuxConfigStoreReloadCoordinator {
private weak var environment: (any CmuxConfigStoreReloadEnvironment)?
private let onReload: (@MainActor (_ source: String, _ storeCount: Int) -> Void)?

/// Creates a coordinator.
/// - Parameters:
/// - environment: The source of per-window stores and the title refresher.
/// Held weakly because the environment (the app delegate) owns this
/// coordinator.
/// - onReload: An optional hook invoked after each reload with the reload
/// source and the number of distinct stores reloaded. The app target wires
/// this to its debug log; tests use it to observe behavior.
public init(
environment: any CmuxConfigStoreReloadEnvironment,
onReload: (@MainActor (_ source: String, _ storeCount: Int) -> Void)? = nil
) {
self.environment = environment
self.onReload = onReload
}

/// Reloads every distinct per-window configuration store, then refreshes window
/// titles, then reports the reload through `onReload`.
///
/// Distinct stores are determined by object identity so a store shared across
/// windows reloads once, preserving the original iteration-and-dedupe order.
/// - Parameter source: A short tag describing what triggered the reload.
public func reload(source: String) {
guard let environment else {
onReload?(source, 0)
return
}

var seenStores = Set<ObjectIdentifier>()
for store in environment.reloadableConfigStores {
let identifier = ObjectIdentifier(store)
guard seenStores.insert(identifier).inserted else { continue }
store.loadAll()
}
environment.refreshWindowTitlesAfterConfigReload()
onReload?(source, seenStores.count)
}
}

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 Config-reload types placed in ShortcutDecoding/ directory

CmuxConfigStoreReloadCoordinator, CmuxConfigStoreReloading, and CmuxConfigStoreReloadEnvironment are conceptually about config-store lifecycle and reload coordination, not shortcut decoding. Grouping them under ShortcutDecoding/ means a future reader tracing the reload flow won't find them there. A ConfigReload/ subdirectory alongside ConfigDiscovery/ and ShortcutDecoding/ would make the directory layout self-documenting.

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!

…observable

The GhosttyKit-to-core mapping init init?(ghosttyPhysicalKey:) was internal to
CmuxTerminalCore but called from the app target (AppDelegate), an inaccessible-
initializer compile error. Make it public and promote the GhosttyKit import to
public import so the C parameter type is valid in the public signature.

CmuxConfigStoreReloadCoordinator was @observable but nothing observes it (the
app delegate holds it as a lazy var and only calls reload(source:)); the weak
environment seam should not be tracked. Drop @observable and the Observation
import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	.github/swift-file-length-budget.tsv

@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/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift`:
- Around line 461-482: The early return statement in the guard at
currentBundleIdentifier prevents the release-bundle config path evaluation block
from executing when the current bundle identifier is nil or empty. Move the
release-bundle config discovery logic (the releaseDir, releaseLegacyConfig,
releaseConfig, and the shouldIncludeLegacyGhosttyConfigInScanPaths check)
outside of and after the guard statement so that release-bundle paths are
evaluated regardless of whether currentBundleIdentifier exists. Keep the
appSupportConfigURLs evaluation inside the guard since it depends on the current
bundle identifier.
- Around line 315-335: The code has two performance issues: the initial loop
scanning configPaths (lines 315-321) lacks deduplication and will rescan paths,
and the while loop (line 325) uses removeFirst() on an Array which is O(n) per
operation. Initialize loadedRecursivePaths before the first loop (not just
before the while loop), then in the initial loop standardize each path and check
if it's already in loadedRecursivePaths before calling scanFontConfigFile,
adding it afterward. For the while loop, replace the inefficient
Array.removeFirst() pattern by changing recursiveConfigPaths to use a Deque
collection type (or similar efficient queue) to achieve O(1) popleft operations,
or alternatively convert to using a Set and iterating while removing elements.
🪄 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: 1bace6f7-85d0-4bd2-abcb-251654fba9fc

📥 Commits

Reviewing files that changed from the base of the PR and between a0eeca0 and 48bbdef.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (19)
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigFileReading.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyFontProbing.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/UserFontConfigSummary.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/CmuxConfigStoreReloadCoordinator.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/CmuxConfigStoreReloading.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyModifierMask.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerInput.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerPhysicalKey+GhosttyKit.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerPhysicalKey.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerShortcut.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerShortcutDecoder.swift
  • Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/CmuxConfigStoreReloadCoordinatorTests.swift
  • Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift
  • Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyTriggerShortcutDecoderTests.swift
  • Sources/AppDelegate.swift
  • Sources/CmuxConfigStore+ConfigStoreReloading.swift
  • Sources/GhosttyTerminalView.swift
  • cmux.xcodeproj/project.pbxproj

Comment on lines +315 to +335
for path in configPaths.map({ NSString(string: $0).expandingTildeInPath }) {
scanFontConfigFile(
atPath: path,
summary: &summary,
recursiveConfigPaths: &recursiveConfigPaths
)
}

var loadedRecursivePaths = Set<String>()
while !recursiveConfigPaths.isEmpty {
let path = recursiveConfigPaths.removeFirst()
let resolved = (path as NSString).standardizingPath
guard !loadedRecursivePaths.contains(resolved) else { continue }
loadedRecursivePaths.insert(resolved)

scanFontConfigFile(
atPath: path,
summary: &summary,
recursiveConfigPaths: &recursiveConfigPaths
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid O(n²) queue churn and duplicate top-level rescans in include traversal.

Line 325 uses removeFirst() on an Array, which is O(n) per pop, and Lines 315-321 scan top-level paths without a dedupe set. In a large include tree, this creates avoidable quadratic work and repeated file parses on launch-path config discovery.

Suggested refactor
-        var recursiveConfigPaths: [String] = []
-
-        for path in configPaths.map({ NSString(string: $0).expandingTildeInPath }) {
-            scanFontConfigFile(
-                atPath: path,
-                summary: &summary,
-                recursiveConfigPaths: &recursiveConfigPaths
-            )
-        }
-
-        var loadedRecursivePaths = Set<String>()
-        while !recursiveConfigPaths.isEmpty {
-            let path = recursiveConfigPaths.removeFirst()
-            let resolved = (path as NSString).standardizingPath
-            guard !loadedRecursivePaths.contains(resolved) else { continue }
-            loadedRecursivePaths.insert(resolved)
+        var recursiveConfigPaths: [String] = []
+        var enqueued = Set<String>()
+        func enqueue(_ rawPath: String) {
+            let expanded = NSString(string: rawPath).expandingTildeInPath
+            let resolved = (expanded as NSString).standardizingPath
+            if enqueued.insert(resolved).inserted {
+                recursiveConfigPaths.append(resolved)
+            }
+        }
+
+        for path in configPaths {
+            enqueue(path)
+        }
+
+        var index = 0
+        while index < recursiveConfigPaths.count {
+            let path = recursiveConfigPaths[index]
+            index += 1
 
             scanFontConfigFile(
                 atPath: path,
                 summary: &summary,
                 recursiveConfigPaths: &recursiveConfigPaths
             )
         }

As per coding guidelines, config discovery over user-provided path/include collections should dedupe absolute paths and avoid repeated full rescans.

🤖 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/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift`
around lines 315 - 335, The code has two performance issues: the initial loop
scanning configPaths (lines 315-321) lacks deduplication and will rescan paths,
and the while loop (line 325) uses removeFirst() on an Array which is O(n) per
operation. Initialize loadedRecursivePaths before the first loop (not just
before the while loop), then in the initial loop standardize each path and check
if it's already in loadedRecursivePaths before calling scanFontConfigFile,
adding it afterward. For the while loop, replace the inefficient
Array.removeFirst() pattern by changing recursiveConfigPaths to use a Deque
collection type (or similar efficient queue) to achieve O(1) popleft operations,
or alternatively convert to using a Set and iterating while removing elements.

Source: Coding guidelines

Comment on lines +461 to +482
guard let bundleId = currentBundleIdentifier,
!bundleId.isEmpty else { return paths }

let appSupportConfigURLs = cmuxAppSupportConfigURLs(
currentBundleIdentifier: bundleId,
appSupportDirectory: appSupportDirectory
)
paths.append(contentsOf: appSupportConfigURLs.map(\.path))

let releaseDir = appSupportDirectory.appendingPathComponent(Self.releaseBundleIdentifier, isDirectory: true)
let releaseLegacyConfig = releaseDir.appendingPathComponent("config", isDirectory: false)
let releaseConfig = releaseDir.appendingPathComponent("config.ghostty", isDirectory: false)

let releaseConfigSize = fileReader.fileSize(atPath: releaseConfig.path)
let releaseLegacyConfigSize = fileReader.fileSize(atPath: releaseLegacyConfig.path)

if Self.shouldIncludeLegacyGhosttyConfigInScanPaths(
newConfigFileSize: releaseConfigSize,
legacyConfigFileSize: releaseLegacyConfigSize
), !paths.contains(releaseLegacyConfig.path) {
paths.append(releaseLegacyConfig.path)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not return before evaluating release-bundle config paths.

Line 461 returns early when currentBundleIdentifier is nil/empty, so the release-bundle block at Lines 470-482 is skipped entirely. That drops release Application Support config discovery in a valid call path.

Suggested fix
-        guard let bundleId = currentBundleIdentifier,
-              !bundleId.isEmpty else { return paths }
-
-        let appSupportConfigURLs = cmuxAppSupportConfigURLs(
-            currentBundleIdentifier: bundleId,
-            appSupportDirectory: appSupportDirectory
-        )
-        paths.append(contentsOf: appSupportConfigURLs.map(\.path))
+        if let bundleId = currentBundleIdentifier, !bundleId.isEmpty {
+            let appSupportConfigURLs = cmuxAppSupportConfigURLs(
+                currentBundleIdentifier: bundleId,
+                appSupportDirectory: appSupportDirectory
+            )
+            paths.append(contentsOf: appSupportConfigURLs.map(\.path))
+        }

Based on PR objectives, release-bundle support directories should be part of scan-path resolution regardless of current build identity.

🤖 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/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift`
around lines 461 - 482, The early return statement in the guard at
currentBundleIdentifier prevents the release-bundle config path evaluation block
from executing when the current bundle identifier is nil or empty. Move the
release-bundle config discovery logic (the releaseDir, releaseLegacyConfig,
releaseConfig, and the shouldIncludeLegacyGhosttyConfigInScanPaths check)
outside of and after the guard statement so that release-bundle paths are
evaluated regardless of whether currentBundleIdentifier exists. Keep the
appSupportConfigURLs evaluation inside the guard since it depends on the current
bundle identifier.

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

@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/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/UserFontConfigSummary.swift`:
- Around line 41-51: The recordFontFamily method performs an O(n) array contains
check on every insertion, creating quadratic time complexity during large
directive scans. Refactor this by maintaining a separate Set property alongside
the effectiveFontFamilies array: use the Set for O(1) membership lookups in the
guard statement at line 47, and update both the Set and array when appending a
new font family. This preserves the order of effectiveFontFamilies while
achieving constant-time deduplication.
🪄 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: d503d93a-4e53-4dab-92e0-932088b29128

📥 Commits

Reviewing files that changed from the base of the PR and between 0b55d6a and 7c6d4e7.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (19)
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigFileReading.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyFontProbing.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/UserFontConfigSummary.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/CmuxConfigStoreReloadCoordinator.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/CmuxConfigStoreReloading.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyModifierMask.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerInput.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerPhysicalKey+GhosttyKit.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerPhysicalKey.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerShortcut.swift
  • Packages/CmuxTerminalCore/Sources/CmuxTerminalCore/ShortcutDecoding/GhosttyTriggerShortcutDecoder.swift
  • Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/CmuxConfigStoreReloadCoordinatorTests.swift
  • Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift
  • Packages/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyTriggerShortcutDecoderTests.swift
  • Sources/AppDelegate.swift
  • Sources/CmuxConfigStore+ConfigStoreReloading.swift
  • Sources/GhosttyTerminalView.swift
  • cmux.xcodeproj/project.pbxproj

Comment on lines +41 to +51
public mutating func recordFontFamily(_ value: String) {
if value.isEmpty {
effectiveFontFamilies.removeAll()
return
}

guard !effectiveFontFamilies.contains(value) else {
return
}

effectiveFontFamilies.append(value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use constant-time dedupe tracking for font-family entries.

Line 47 does an array contains check for every insert, which makes large directive scans quadratic. Keep order in effectiveFontFamilies, but track membership in a Set for O(1) dedupe.

♻️ Suggested change
 public struct UserFontConfigSummary: Equatable, Sendable {
@@
     public var effectiveFontFamilies: [String] = []
+    private var seenFontFamilies: Set<String> = []
@@
     public mutating func recordFontFamily(_ value: String) {
         if value.isEmpty {
             effectiveFontFamilies.removeAll()
+            seenFontFamilies.removeAll()
             return
         }
 
-        guard !effectiveFontFamilies.contains(value) else {
+        guard seenFontFamilies.insert(value).inserted else {
             return
         }
 
         effectiveFontFamilies.append(value)
     }
 }

As per coding guidelines, config discovery over user-controlled collections should avoid repeated full scans and prefer Set/Dictionary deduping.

🤖 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/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/UserFontConfigSummary.swift`
around lines 41 - 51, The recordFontFamily method performs an O(n) array
contains check on every insertion, creating quadratic time complexity during
large directive scans. Refactor this by maintaining a separate Set property
alongside the effectiveFontFamilies array: use the Set for O(1) membership
lookups in the guard statement at line 47, and update both the Set and array
when appending a new font family. This preserves the order of
effectiveFontFamilies while achieving constant-time deduplication.

Source: Coding guidelines

# Conflicts:
#	.github/swift-file-length-budget.tsv
@azooz2003-bit
azooz2003-bit merged commit 441fa2c into main Jun 16, 2026
24 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-consolidate-ghostty branch June 16, 2026 23:35
azooz2003-bit added a commit that referenced this pull request Jun 20, 2026
…hful lift remains

Scope ad-config-reload-17 (drain ghostty/cmux config-reload + menu-item glue
into a package) maps entirely onto residue that is already extracted, frozen,
or app-coupled. No byte-identical source change is available here.

Findings (all verified against base 2078bcb):
- Config-store reload orchestration (reloadCmuxConfigStores / reloadableConfigStores
  / refreshWindowTitlesAfterConfigReload / configStore(for:|forTabManager:) /
  firstContextWithConfigStore) was ALREADY drained into
  CmuxConfigStoreReloadCoordinator + CmuxConfigStoreReloadEnvironment in
  CmuxTerminalCore by #6227 (441fa2c, an ancestor of this base). AppDelegate's
  methods are thin forwarders / irreducible live-state seam conformance reading
  app-target windowConfigStores + mainWindowContexts. Coordinator is wired
  (lazy var configStoreReloadCoordinator, environment: self) and tested
  (CmuxConfigStoreReloadCoordinatorTests, 150 pkg tests green).
- Ghostty trigger decode (storedShortcutFromGhosttyTrigger) already routes through
  the package decoder GhosttyTriggerShortcut(decoding:) + GhosttyTriggerInput /
  GhosttyTriggerPhysicalKey / GhosttyModifierMask in CmuxTerminalCore (#6227).
  The app residue lifts the C ghostty_input_trigger_s and maps to app-target
  StoredShortcut: irreducible C-type + app-type coupling.
- CmuxThemeNotifications.reloadConfig ("com.cmuxterm.themes.reload-config") is a
  DistributedNotificationCenter CROSS-PROCESS wire contract with the CLI
  (CLI/CMUXCLI+Themes.swift cmuxThemesReloadNotificationName). Frozen; cannot
  become an AsyncStream without breaking `cmux themes`.
- .ghosttyConfigDidReload is an in-process notification with 8+ external SwiftUI
  .onReceive observers; its Notification.Name is owned by the HIGHER package
  CmuxTerminal (depends on CmuxTerminalCore), so an AsyncStream bridge cannot live
  in CmuxTerminalCore or CmuxSettings without a DAG cycle, and the
  NotificationCenter->AsyncStream conversion is behavior-affecting (main-hop /
  lifecycle change), flagged as deferred modernization in AppDelegate.plan.md L96.
  Not a byte-identical lift; out of scope here.
- Menu-item glue (installReloadConfigurationMenuItemAction /
  scheduleReloadConfigurationMenuItemRefresh / menuNeedsUpdate /
  configureReloadConfigurationMenuItem / reloadConfigurationMenuItem),
  reloadConfiguration, and refreshTerminalSurfacesAfterGhosttyConfigReload are
  NSMenu + GhosttyApp.shared + TabManager/TerminalPanel iteration. Charter keeps
  NSMenu hosting app-side; localized title resolution must stay app-bundle-bound.

No source change. Build SUCCEEDED at base; CmuxTerminalCore 150 tests green.
Residue documented for the integrator.

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

This branch was successfully deployed

1 active deployment
Preview – cmux — 80ff2f76 Deployed Jun 16, 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