Skip to content

Extract Ghostty trigger decoding + config-store reload from AppDelegate into CmuxGhosttyShortcutDecoding - #6183

Closed
azooz2003-bit wants to merge 1 commit into
mainfrom
feat-menu-and-configuration-reload-wiring
Closed

azooz2003-bit wants to merge 1 commit into
mainfrom
feat-menu-and-configuration-reload-wiring

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Pulls the byte-identical, cleanly-separable pieces of AppDelegate's configuration-reload domain into a new leaf package, CmuxGhosttyShortcutDecoding (no dependencies).

What moved

  • The ~85-line ghostty-trigger → shortcut decode logic from storedShortcutFromGhosttyTrigger becomes a tested value-object decoder: GhosttyTriggerShortcutDecoder over GhosttyTriggerInput / GhosttyTriggerPhysicalKey / GhosttyModifierMask, producing GhosttyTriggerShortcut. The glyph table (←/→/↑/↓, letters, digits, punctuation), catch-all rejection, unicode-scalar lowercasing, and the "bogus empty trigger" rule now live in the package.
  • reloadCmuxConfigStores becomes CmuxConfigStoreReloadCoordinator (@MainActor @Observable), which sequences per-window store loadAll() (deduped by object identity) and the window-title refresh through injected seams.

Seams (constructor-injected, no god-type reach in the package)

  • CmuxConfigStoreReloading: CmuxConfigStore conforms via an empty extension (its existing loadAll() satisfies it).
  • CmuxConfigStoreReloadEnvironment: AppDelegate conforms, exposing its mainWindowContexts stores and refreshWindowTitlesAcrossMainWindows() behind a read-only seam. The coordinator holds the environment weakly (AppDelegate owns the coordinator), so no retain cycle.

Byte-identical notes

  • storedShortcutFromGhosttyTrigger is now a thin adapter: lift the C ghostty_input_trigger_s into GhosttyTriggerInput, decode, map back onto the app-target StoredShortcut. The GhosttyKit-boundary key translation lives in a new app-target file (GhosttyTriggerPhysicalKey+GhosttyKit.swift) as a failable init, so AppDelegate.swift shrinks instead of growing.
  • reloadCmuxConfigStores forwards to the coordinator: same iteration/dedupe order, same title refresh, same #if DEBUG cmuxDebugLog("cmuxConfig.reload source=... stores=...") via the coordinator's onReload hook.

Left in the app target (intentional)

Menu-item wiring (menuNeedsUpdate, configureReloadConfigurationMenuItem) and the quit-warning / goto-split stored-property reads were not extracted: they are bound to NSMenu/NSApp.mainMenu, the app-target KeyboardShortcutSettings, and the keystroke-critical handleCustomShortcut path, so a byte-identical thin forward is not sound without dragging AppKit/GhosttyKit into a package. The largest clean subset (the decode logic + config-store reload orchestration) was extracted instead.

Tests

13 behavior tests with fakes for the seams: decoder glyphs/mods/rejections over every physical key; coordinator dedupe/refresh/report and weak-environment teardown. swift build + swift test green in the package.

Stats

  • Package wired into cmux.xcodeproj (6 pbxproj entries mirroring CmuxControlSocket).
  • AppDelegate.swift budget ratcheted 17778 → 17729.

NOTE: full app build + cmux test targets are validated by GitHub CI.


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


Summary by cubic

Extracts Ghostty trigger-to-shortcut decoding and config-store reload orchestration out of AppDelegate into a new leaf package, CmuxGhosttyShortcutDecoding, keeping behavior byte-identical while adding focused tests and simplifying app wiring.

  • Refactors
    • Introduced GhosttyTriggerShortcutDecoder over GhosttyTriggerInput/GhosttyTriggerPhysicalKey/GhosttyModifierMask, producing GhosttyTriggerShortcut with the same glyph table, unicode lowercasing, catch-all rejection, and empty-trigger rule.
    • Added CmuxConfigStoreReloadCoordinator (@MainActor @Observable) to dedupe loadAll() across windows and refresh titles via injected seams: CmuxConfigStoreReloading and CmuxConfigStoreReloadEnvironment (held weakly, onReload for debug).
    • App wiring: AppDelegate now lifts ghostty_input_trigger_s into GhosttyTriggerInput, decodes, and maps to StoredShortcut; reloads forward to the coordinator. Bridge added in GhosttyTriggerPhysicalKey+GhosttyKit.swift; CmuxConfigStore conforms to CmuxConfigStoreReloading.
    • Tests: 13 behavior tests cover decoder glyphs/mods/rejections and coordinator dedupe/refresh/teardown.

Written for commit 73aebaa. Summary will update on new commits.

Review in cubic

…te into CmuxGhosttyShortcutDecoding

Pull the byte-identical pieces of AppDelegate's configuration-reload domain into a
new leaf package, CmuxGhosttyShortcutDecoding (depends on nothing).

What moved:
- The ~85-line ghostty-trigger -> shortcut decode logic from
  storedShortcutFromGhosttyTrigger becomes a tested value-object decoder:
  GhosttyTriggerShortcutDecoder over GhosttyTriggerInput / GhosttyTriggerPhysicalKey
  / GhosttyModifierMask, producing GhosttyTriggerShortcut. The glyph table
  (←/→/↑/↓, letters, digits, punctuation), the catch-all rejection, the
  unicode-scalar lowercasing, and the "bogus empty trigger" rule all live in the
  package now.
- reloadCmuxConfigStores becomes CmuxConfigStoreReloadCoordinator (@mainactor
  @observable), which sequences per-window store loadAll() (deduped by object
  identity) and the window-title refresh through two injected protocol seams,
  CmuxConfigStoreReloading and CmuxConfigStoreReloadEnvironment.

Seams (constructor-injected, no god-type reach in the package):
- CmuxConfigStoreReloading: CmuxConfigStore conforms (empty extension; existing
  loadAll() satisfies it).
- CmuxConfigStoreReloadEnvironment: AppDelegate conforms, exposing its
  mainWindowContexts stores and refreshWindowTitlesAcrossMainWindows() behind a
  read-only seam. The coordinator holds the environment WEAKLY (AppDelegate owns
  the coordinator) so there is no retain cycle.

App side stays byte-identical:
- storedShortcutFromGhosttyTrigger is now a thin adapter: lift the C
  ghostty_input_trigger_s into GhosttyTriggerInput, decode, map back onto the
  app-target StoredShortcut. The GhosttyKit-boundary key translation lives in a new
  app-target file (GhosttyTriggerPhysicalKey+GhosttyKit.swift) as a failable init,
  so AppDelegate.swift shrinks rather than grows.
- reloadCmuxConfigStores forwards to the coordinator; same iteration/dedupe order,
  same refresh, same #if DEBUG cmuxDebugLog("cmuxConfig.reload source=... stores=...")
  via the coordinator's onReload hook.

Menu-item wiring (menuNeedsUpdate, configureReloadConfigurationMenuItem) and the
quit-warning / goto-split stored-property reads were left in the app target: they
are bound to NSMenu/NSApp.mainMenu, the app-target KeyboardShortcutSettings, and the
keystroke-critical handleCustomShortcut path, so a byte-identical thin forward is not
sound without dragging AppKit/GhosttyKit into a package.

Tests: 13 behavior tests (decoder glyphs/mods/rejections over all physical keys;
coordinator dedupe/refresh/report and weak-environment teardown) using fakes for the
seams.

Package wired into cmux.xcodeproj (6 pbxproj entries mirroring CmuxControlSocket);
AppDelegate.swift budget ratcheted 17778 -> 17729.

NOTE: full app build + tests validated by GitHub CI.

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

vercel Bot commented Jun 15, 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 15, 2026 8:20pm
cmux-staging Building Building Preview, Comment Jun 15, 2026 8:20pm

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@azooz2003-bit, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 4 minutes and 10 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: abfbb6bd-8572-490f-a756-184a859f9426

📥 Commits

Reviewing files that changed from the base of the PR and between 17b5826 and 73aebaa.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (14)
  • Packages/CmuxGhosttyShortcutDecoding/Package.swift
  • Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/CmuxConfigStoreReloadCoordinator.swift
  • Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/CmuxConfigStoreReloading.swift
  • Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/GhosttyModifierMask.swift
  • Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/GhosttyTriggerInput.swift
  • Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/GhosttyTriggerPhysicalKey.swift
  • Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/GhosttyTriggerShortcut.swift
  • Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/GhosttyTriggerShortcutDecoder.swift
  • Packages/CmuxGhosttyShortcutDecoding/Tests/CmuxGhosttyShortcutDecodingTests/CmuxConfigStoreReloadCoordinatorTests.swift
  • Packages/CmuxGhosttyShortcutDecoding/Tests/CmuxGhosttyShortcutDecodingTests/GhosttyTriggerShortcutDecoderTests.swift
  • Sources/AppDelegate.swift
  • Sources/CmuxConfigStore+ConfigStoreReloading.swift
  • Sources/GhosttyTriggerPhysicalKey+GhosttyKit.swift
  • cmux.xcodeproj/project.pbxproj
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-menu-and-configuration-reload-wiring

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 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extracts the Ghostty trigger-decoding and config-store reload orchestration from AppDelegate into a new zero-dependency SwiftPM package, CmuxGhosttyShortcutDecoding, wired into cmux.xcodeproj following the CmuxControlSocket pattern.

  • Trigger decoding (storedShortcutFromGhosttyTrigger) is replaced by a stateless GhosttyTriggerShortcutDecoder over value types (GhosttyTriggerInput, GhosttyTriggerPhysicalKey, GhosttyModifierMask), with the GhosttyKit C-boundary translation kept in the app target as GhosttyTriggerPhysicalKey+GhosttyKit.swift.
  • Config-store reload (reloadCmuxConfigStores) delegates to CmuxConfigStoreReloadCoordinator, which sequences per-window loadAll() (deduped by ObjectIdentifier), title refresh, and the debug log hook — all byte-identical to the original — through constructor-injected seams (CmuxConfigStoreReloading, CmuxConfigStoreReloadEnvironment) backed by a weak reference to avoid a retain cycle with AppDelegate.

Confidence Score: 4/5

This is a structural refactor that is byte-identical to the original behavior; the new package has no external dependencies and 13 passing tests covering all decode paths and coordinator edge cases.

The extraction is clean and well-tested. Two issues in CmuxConfigStoreReloadCoordinator.swift are worth addressing before merge: public import Observation inadvertently re-exports the Observation module to all consumers of the package, and @observable is applied to a class with no trackable stored state — neither causes a runtime regression today, but the first quietly widens the package's API surface.

Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/CmuxConfigStoreReloadCoordinator.swift — the public import and @observable annotations.

Important Files Changed

Filename Overview
Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/CmuxConfigStoreReloadCoordinator.swift New coordinator class correctly sequences per-window store reloads and title refresh on MainActor; has two minor issues: public import Observation inadvertently re-exports the Observation module, and @Observable is applied to a class with no trackable stored state.
Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/CmuxConfigStoreReloading.swift Clean protocol seam definitions with correct @mainactor isolation and AnyObject constraint for ObjectIdentifier deduplication.
Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/GhosttyTriggerShortcutDecoder.swift Stateless decoder that faithfully reproduces the original glyph-selection + modifier-rejection logic; Sendable, well-tested, no issues.
Packages/CmuxGhosttyShortcutDecoding/Sources/CmuxGhosttyShortcutDecoding/GhosttyTriggerPhysicalKey.swift Complete glyph table for all 47 mapped physical keys; byte-identical to the original switch branches; CaseIterable allows exhaustive test coverage.
Sources/AppDelegate.swift AppDelegate correctly adopts CmuxConfigStoreReloadEnvironment, delegates reload to the new coordinator via lazy property, and slims storedShortcutFromGhosttyTrigger to a thin GhosttyKit-boundary adapter.
Sources/GhosttyTriggerPhysicalKey+GhosttyKit.swift GhosttyKit boundary failable init that maps C enum values to the package enum; mirrors the original switch exhaustively with correct default: return nil.
Sources/CmuxConfigStore+ConfigStoreReloading.swift Empty retroactive conformance correctly wires CmuxConfigStore.loadAll() to the protocol seam without any behavioral change.
Packages/CmuxGhosttyShortcutDecoding/Package.swift Clean no-dependency package with Swift 6 language mode, ExistentialAny, and InternalImportsByDefault; mirrors the CmuxControlSocket pattern.
cmux.xcodeproj/project.pbxproj Six pbxproj entries added for CmuxGhosttyShortcutDecoding (package reference, product dependency, two source file references, two build file entries); mirrors the CmuxControlSocket pattern.

Sequence Diagram

sequenceDiagram
    participant AD as AppDelegate
    participant CSRC as CmuxConfigStoreReloadCoordinator
    participant Env as CmuxConfigStoreReloadEnvironment (AppDelegate)
    participant Store as CmuxConfigStore (×N, deduped)

    AD->>CSRC: reload(source:)
    CSRC->>Env: reloadableConfigStores
    Env-->>CSRC: [CmuxConfigStoreReloading]
    loop each distinct store (ObjectIdentifier dedup)
        CSRC->>Store: loadAll()
    end
    CSRC->>Env: refreshWindowTitlesAfterConfigReload()
    CSRC->>AD: "onReload(source, storeCount) [#if DEBUG]"

    Note over AD: storedShortcutFromGhosttyTrigger
    AD->>AD: GhosttyTriggerPhysicalKey(ghosttyPhysicalKey:)
    AD->>CSRC: GhosttyTriggerShortcutDecoder.decode(GhosttyTriggerInput)
    CSRC-->>AD: GhosttyTriggerShortcut?
    AD-->>AD: StoredShortcut?
Loading

Reviews (1): Last reviewed commit: "Extract Ghostty trigger decoding + confi..." | Re-trigger Greptile

@@ -0,0 +1,54 @@
public import Observation

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 Unintentional transitive re-export of Observation

With InternalImportsByDefault enabled in Package.swift, public import Observation re-exports the Observation module to every importer of CmuxGhosttyShortcutDecoding. This means app-target files that import CmuxGhosttyShortcutDecoding will silently gain access to withObservationTracking, Observable, and the rest of the Observation API without explicitly importing it — an unintended expansion of the package's public API surface. A plain import Observation (which defaults to internal under InternalImportsByDefault) is sufficient: @Observable only needs Observation at compile time for the coordinator itself, not at the use sites of the package.

Comment on lines +12 to +13
@Observable
public final class CmuxConfigStoreReloadCoordinator {

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 @Observable with no observable stored state

CmuxConfigStoreReloadCoordinator is decorated @Observable, but neither of its stored properties — environment (a weak existential set once in init) and onReload (a let closure) — represent state that mutates after initialization in a way that observation tracking would surface. The @Observable macro synthesizes an _$observationRegistrar and per-property access / mutation hooks that will never fire, adding dead boilerplate with no current SwiftUI observer. If this is a placeholder for future observable state (e.g. a reload-in-progress flag), a comment explaining the intent would help; otherwise the annotation is misleading.

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!

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Superseded by #6227 — consolidated into the existing CmuxTerminalCore package (no new micro-package) per over-engineering review. The extraction is preserved there.

This branch was successfully deployed

1 active deployment
Preview – cmux — 73aebaa6 Deployed Jun 15, 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