cmux-architecture
cmux Architecture
Package architecture
We are migrating cmux from a single app target into Swift Packages under Packages/. Every new package must satisfy three rules:
- Ergonomic. Public API surface matches what callers naturally want to write. Default to internal access; expose
publiconly for types and functions that downstream consumers actually use. Avoid friction such as forcing every call site through a builder or wrapper when a direct API is fine. - No dependency cycles. Packages form a strict DAG. A package may only depend on packages strictly lower in the graph. When two packages need to share a type, lift it to a common lower-level package or define a protocol seam in the consumer. Every new dependency edge requires re-checking that the graph stays acyclic.
- Clear but not overly narrow responsibilities. A package owns one full domain (e.g. settings, appearance, workspace, terminal, browser, command palette), not a slice of one. A package called "appearance math" or "workspace model" is too narrow — it forces every consumer that touches the surrounding domain to also depend on the sibling slices. Prefer a single
CmuxAppearancethat owns settings, theming, colors, glass, and snapshots together, overCmuxAppearanceMath+CmuxAppearanceTheme+CmuxAppearanceSettings. Don't fragment a domain intoCmuxFooFormatting+CmuxFooLogic+CmuxFooState— that's folder structure inside a single package, not module structure. A package boundary exists because more than one consumer needs the contents, or a build/test seam needs to exist.
When in doubt, extract leaf-first: pull out the package that has no internal dependencies. Consumers in the app target stay put and only update imports. Each leaf shrinks the app target without requiring downstream packages to exist yet.
The existing packages under Packages/ predate this policy and should not be used as design references.
Wiring a new local package into the project. cmux.xcodeproj lists package dependencies explicitly (it is not a synchronized-folder project). Adding Packages/CmuxFoo means mirroring an existing package's project.pbxproj entries — one XCLocalSwiftPackageReference (in the project's packageReferences), one XCSwiftPackageProductDependency, and a PBXBuildFile linked in the Frameworks phase of every target that imports it. The app-target packages link into both cmux and cmux-unit (so tests can import and inject them); copy a recent leaf like CmuxSocketControl for the exact shape, then run scripts/normalize-pbxproj.py and scripts/check-pbxproj.sh. A package the app builds against but cmux-unit does not link will compile the app yet fail the test target.
Refactor architecture: layers, Coordinator/Service/Repository, dependency inversion
These higher-level patterns are binding on every new or moved/meaningfully-rewritten file. (The full blueprint, with worked examples and the per-god decomposition, lives in the cmuxterm-hq control repo under docs/cmux-refactor-audit/blueprint/; the enforceable core is below.)