High-level system design for BrewUI. Update when structure changes. Owns: layers, data flow, integrations (Homebrew CLI / JSON API), and product constraints. Defers naming and day-to-day coding patterns to
CONVENTIONS.md. Record durable rationale in.ai/memory.md.
| Concern | Choice |
|---|---|
| Language | Swift 6.0 (strict concurrency mode) |
| UI Framework | SwiftUI β pure; use AppKit only when SwiftUI cannot meet a requirement |
| State management | @Observable (Swift 5.9+) |
| Concurrency | async/await throughout; actor isolation for shared mutable state |
| Package manager | Swift Package Manager |
| macOS targets | Tahoe 26, Sequoia 15, Sonoma 14 β minimum: Tahoe 26 |
| Data sources | Homebrew JSON API (formulae.brew.sh) + brew CLI subprocess |
| Deployment | Unsandboxed macOS app (default Homebrew prefix) |
Flow: View β ViewModel β Repository or Interactor β Services β brew CLI or JSON API.
Guiding patterns:
- MVVM-C (lightweight): Views stay declarative; ViewModels own presentation state; coordination/navigation policy is centralized in small coordinator-style shell types when needed.
- Clean Architecture principles: Depend inward on abstractions, keep use cases in Interactors/Repositories, isolate infrastructure in Services, and keep UI/framework concerns out of domain decisions.
- Emergent architecture: Prefer the smallest pattern that solves todayβs problem; evolve structure incrementally as features and complexity grow.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BrewUI (macOS App) β
β Views (SwiftUI) βββΆ ViewModels β
β β β β
β β Repositories Interactors β
β β ββββββ¬βββββ β
β β Services (brew + JSON API) β
ββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββ
βΌ
brew CLI (subprocess) Β· formulae.brew.sh JSON API
- Views: SwiftUI; thin β bind state, forward actions.
- Feature root views (
*Root): Composition boundaries that bridge app-level dependencies into feature content. Root wrappers read environment-level dependencies and construct/inject content-view dependencies while managing view-model lifecycle boundaries. - ViewModels: Presentation state and mapping; delegate work downward. Keep domain rules in Interactors or Models, not here.
- Presentation boundary: Domain models remain UI-agnostic. Map domain values to UI-ready properties through feature ViewModels for top-level surfaces, or dedicated feature Item types for subview/action-level presentation needs.
- Repositories: CRUD-shaped access to a data source (CLI output, API, storage, in-memory). Swap the implementation, keep the contract.
- Interactors: One use case each β not generic CRUD (e.g. a doctor run). Prefer protocols + real/mock impls for tests. (One-shot reads such as the
brew configsnapshot are modelled as Repositories here, not Interactors.) - Services: Infrastructure grouped by integration boundaries.
- Command center:
BrewCommandCenter(actor protocol; app defaultSerialBrewCommandCenter) β serializes mutatingbrewwork, tracks in-flight / failed operation state (BrewOperationID+BrewOperationPhase) for UI across surfaces, and runs smallBrewMutatingCommandtypes that callBrewCommandRunning+ the brew locator. It does not own read/parsing ofbrew list/brew infooutput β that stays in repositories. Feature-scoped executors (e.g. upgrade helpers) should stay thin and be invoked from commands the center schedules, not as a second parallel pipeline. - Models: Domain-only value types and relationships shared across layers. Keep UI/presentation helpers, transport decoding models, and infrastructure-state containers out of domain models.
Run Homebrew commands asynchronously via subprocess; support cancellation; stream or preserve stdout/stderr for transparency and logs. Always make the exact command visible to the user; treat CLI text output as unstable (tolerant parsing, fallbacks).
Use the Homebrew JSON API where it helps. Prefer optional / resilient decoding β schema can change; never crash on unknown fields. Combine with CLI only as needed when the app grows.
- macOS-only. No iOS / iPadOS / cross-platform for now.
- Default Homebrew prefix only:
/opt/homebrew(Apple Silicon) or/usr/local(Intel). No custom prefix initially. - No custom taps initially β core tap scope.
brewis the source of truth β BrewUI does not poke Homebrew internals.- Transparency β users see what runs; no hidden commands.
- Homebrew is separate β detect and degrade if missing; do not bundle Homebrew.
- Detection: try
/opt/homebrew/bin/brewthen/usr/local/bin/brew. - Open source β patterns should stay contributor-friendly.
- CLI drift:
brewtext is not a stable API β parsers must be tolerant. - JSON drift: decoding must stay resilient as the API evolves.
- UI tests: async output and sheets need deliberate sync; avoid flakiness.
- Accessibility: desktop workflows need keyboard and VoiceOver semantics, not only labels.
Change when layers or major assumptions shift. Put why in .ai/memory.md when it is non-obvious or contentious.