make # this show available commandsRequires mise for zig, swiftlint, swift-format, xcbeautify, and xcsift tooling. Run mise install once to fetch the pinned versions.
Supacode is a macOS terminal emulator that for running multiple coding agents in parallel in Git worktrees, using GhosttyKit as the underlying terminal.
- TCA (swift-composable-architecture): App state, reducers, side effects
- GhosttyKit: Terminal emulator (built from Zig source in ThirdParty/ghostty)
- Sparkle: Auto-update framework
- swift-dependencies: Dependency injection for TCA clients
- PostHog: Analytics
- Sentry: Error tracking
- Target macOS 26.0+, Swift 6.0
- Before doing a big feature or when planning, consult with pfw (pointfree) skills on TCA, Observable best practices first.
- Use
@ObservableStatefor TCA feature state; use@Observablefor non-TCA shared stores; neverObservableObject - Always mark
@Observableclasses with@MainActor - Modern SwiftUI only:
foregroundStyle(),NavigationStack,ButtonoveronTapGesture() - When a new logic changes in the Reducer, always add tests
- In unit tests, never use
Task.sleep; useTestClock(or an injected clock) and drive time withadvance. - Prefer Swift-native APIs over Foundation where they exist (e.g.,
replacing()notreplacingOccurrences()) - Avoid
GeometryReaderwhencontainerRelativeFrame()orvisualEffect()would work - Do not use NSNotification to communicate between reducers.
- Prefer
@Shareddirectly in reducers for app storage and shared settings; do not introduce new dependency clients solely to wrap@Shared. - Use
SupaLoggerfor all logging. Never useprint()oros.Loggerdirectly.SupaLoggerprints in DEBUG and usesos.Loggerin release. - Avoid top-level free functions. Default to
staticmethods, computed properties, or instance methods on a relevant type (enum/struct/extension). Free functions pollute the module namespace, are harder to discover, and easily drift from the inline implementation a consumer ends up writing instead. If the operation is pure and stateless, make it astaticon a caselessenumor the most relevant type, not a top-levelfunc. - Closure-typed focused values invalidate the AppKit menu on every body run (closures have no Equatable conformance, so SwiftUI re-publishes every time). Always wrap menu-bar action closures with
FocusedAction<Input>and publish via.focusedSceneAction(_:enabled:token:perform:)/.focusedAction(_:enabled:token:perform:). The wrapper dedupes on(isEnabled, token), so AppKit only rebuilds the menu when something the menu actually displays changes. Token rules inApp/Models/FocusedAction.swift: settokento a hashable projection of any captured state that affects behavior; leave itnilwhen the closure captures only the store /@Statebindings. Consumers should read the action with@FocusedValue(\.x)and gate withaction?.isEnabled != true, notaction == nil. - Sidebar rows must not fan out invalidation. Per-row state lives in
RepositoriesFeature.State.sidebarItemsso a per-leaf mutation (notification tick, agent activity, running-script update) invalidates only that leaf, not every sibling. The view renders the cachedstate.sidebarStructure(computed in the reducer's post-reduce hook), never readingsidebarItems[id:]from a view body; derive per-leaf data incomputeSidebarStructure(...), not in the view. - Never lift content-specific (e.g. terminal-only) mechanics or state into the layout layer.
LayoutFeatureowns topology and strip mechanics only (panes, tabs, selection, focus, zoom, rename identity); anything a specific content kind produces (agent badges, progress, script locks, busyness, reported title, pwd) lives on the content side, exposed to the strip through the content's observableTabChrome(Features/Terminal/Content/TabChrome.swift), never as layout reducer state or actions. High-frequency reported signals (title, pwd, bell) are the sharp edge of this rule: an agent TUI rewrites its title several times a second, so a reported title lands on the content's chrome and reaches the layout only as one discrete commit at teardown (titleCommitted), never one reducer action per report. The strip and window title read the live value offTabChrome(TabTitle.resolved), and persistence pulls it into the snapshot (TabTitle.stored).
- Buttons must have tooltips explaining the action and associated hotkey
- Use Dynamic Type, avoid hardcoded font sizes
- Components should be layout-agnostic (parents control layout, children control appearance)
- Never use custom colors, always use system provided ones.
- We use
.monospaced()modifier on fonts when appropriate
- After a task, ensure the app builds:
make build-app - Automatically commit your changes and your changes only. Do not use
git add . - Before you go on your task, check the current git branch name, if it's something generic like an animal name, name it accordingly. Do not do this for main branch
- Do not open a pull request unless the user explicitly asks for one. Commit to the working branch and let the user decide when to push and open a PR.
- When the user does ask you to open an issue or pull request, follow the templates in
.github: fill the bug or feature issue form, and use the pull request template (link the issue withCloses #<number>, complete the checklist, and disclose any AI tools you used). A human is the author of record: never set an AI agent as a commit author or co-author.