Repository navigation
Contributing
-
mainis always green:swift testpasses,xcodebuildsucceeds. - Feature work lives on
feat/<short-name>branches. - Bug fixes on
fix/<issue-number>-<short-name>. - Docs on
docs/<short-name>. - Open a PR against
main. Squash-and-merge is fine for small changes; merge-commit is fine for multi-commit features.
Every feature and bug fix follows TDD:
- Write the failing test first.
- Run
swift test(or the relevant filter) and confirm it fails for the right reason. - Write the minimum code to make it pass.
- Refactor if needed.
- Commit.
For code that can't be unit-tested (AX API, Carbon hotkeys, NSWindow lifecycle, EventKit trigger watchers, NSWorkspace app launching, Shortcuts URL scheme), manually verify with docs/TESTING.md (V0.1 / V0.2 / V0.3 / V0.4 smoke sections) and write a short note in the PR describing what you observed. V0.4's test suite is 157 pure-logic tests — add more when you add new SceneCore types. The V0.4 cycle followed TDD heavily for Workspace / WorkspaceStore / WorkspaceTrigger: write the Codable round-trip test first, make it pass, then move on.
Use Conventional Commits:
-
feat(core): <what>— new behavior inSceneCore -
feat(app): <what>— new behavior inSceneApp -
fix(core|app): <what>— bug fix -
chore: <what>— tooling, scaffolding, build changes -
docs: <what>— README, wiki, install docs -
test: <what>— new tests only (no production code change)
Commit messages should explain the why, not the what. The diff already shows the what.
Co-Authored-By trailer: the V0.4 cycle used Claude Code for large swaths of the implementation. When AI assisted materially with a commit, add the trailer at the bottom of the commit message:
Co-Authored-By: Claude <noreply@anthropic.com>
Keep the trailer honest — only add it when the AI actually contributed, not as a blanket policy. Pass the message via a HEREDOC so the trailer isn't mangled:
git commit -m "$(cat <<'EOF'
feat(core): LayoutEngine.apply retries Electron windows
Cursor / VS Code / Slack settle ±3–5 px off the target rect on the
first setFrame. Retry once with a compensating offset so the window
lands within tolerance. One retry is enough in practice; more tend to
oscillate without converging.
Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)"Before opening a PR:
-
swift testpasses locally. -
xcodebuildsucceeds for theSceneAppscheme. - You ran relevant items from Testing if the change touches runtime behavior.
- No new external dependencies added to
Package.swiftwithout discussion. - No files under
docs/superpowers/,.claude/,CLAUDE.md, or other AI-session artifacts sneaked in (they should stay gitignored). -
README.md+ wiki pages updated if user-visible behavior changed.
When adding new code, follow the layering:
-
Pure logic, no system calls? →
Sources/SceneCore/Layout/orSources/SceneCore/Interaction/orSources/SceneCore/Workspace/. Write unit tests. -
Wraps an AX / CoreGraphics / Carbon API? →
Sources/SceneCore/AX/orSources/SceneCore/Interaction/. Compile-check only (AX API can't be mocked cleanly). -
Touches
NSWindow,NSEvent,MenuBarExtra,AppDelegate,NSWorkspace, EventKit, Shortcuts URL scheme? →SceneApp/SceneApp/. Verified via the smoke test. -
Touches user-visible copy / onboarding? → add the key to
SceneApp/SceneApp/Resources/Localizable.xcstrings(V0.4 switched fully to the String Catalog). UseLocalizedStringKeyin SwiftUI (Text("settings.tab.workspaces")). At minimum, add en + zh-HK values; zh-TW + ja fall back to en if missing.
If a change crosses the SceneCore / SceneApp boundary, split into two commits — the first touches only SceneCore, the second wires it up in SceneApp.
Swift 6 strict concurrency is on. A few patterns that come up often:
- UI-touching types (
Coordinator,DragSwapController,OnboardingWindowController) are@MainActor. - Files declaring
@PublishedorObservableObjectwithout importing SwiftUI mustimport Combineexplicitly. - Delayed work started from a non-main actor closure (e.g.,
UNUserNotificationCenter.getNotificationSettings) must hop toDispatchQueue.main.asyncbefore updatingNSStatusItem. - Use
Task { @MainActor in … }insideNSEventglobal monitors, Carbon event handlers, and timers.
Keep Package.swift dependencies empty. The current tech stack (Foundation, AppKit, SwiftUI, Carbon, ApplicationServices, UserNotifications, EventKit, os.Logger) is enough through V0.4. Adding a dep should require a concrete rationale and reviewer sign-off.
- Swift 5.9, 4-space indent, 120-col soft limit.
-
publiconly where the API truly needs to be re-exported; preferinternal. - No comments explaining what the code does — rely on clear names. Comments are only for why (a non-obvious invariant, a workaround for a specific macOS behavior).
- Error types live in the module that throws them. Keep the cases small and specific.
English
繁體中文(香港)