|
| 1 | +# Cross-Cutting Standards |
| 2 | + |
| 3 | +Every phase assumes these. Re-read once, then obey without being asked. |
| 4 | + |
| 5 | +## Language & Platform |
| 6 | + |
| 7 | +- **Swift 6.0+** with `-strict-concurrency=complete`. No `@preconcurrency` escape hatches except at clearly-marked third-party boundaries, with a TODO and a justification. |
| 8 | +- **macOS 14+ deployment target** (Sonoma). Nothing older. |
| 9 | +- **Xcode 16+**. |
| 10 | +- **SwiftUI** primary; reach for `NSViewRepresentable`/`NSHostingController` only when SwiftUI genuinely cannot deliver. Document every drop-down to AppKit with a one-line comment explaining why. |
| 11 | +- **SPM only**. No CocoaPods, no Carthage, no manually-vendored xcframeworks unless they are the only option (e.g. FFmpeg binary artifacts). |
| 12 | + |
| 13 | +## Module layout |
| 14 | + |
| 15 | +Every feature is a Swift Package under `Modules/<Name>/`. A module has: |
| 16 | + |
| 17 | +``` |
| 18 | +Modules/<Name>/ |
| 19 | +├── Package.swift |
| 20 | +├── Sources/<Name>/ |
| 21 | +│ └── *.swift |
| 22 | +└── Tests/<Name>Tests/ |
| 23 | + └── *.swift |
| 24 | +``` |
| 25 | + |
| 26 | +Modules depend **only** on lower-level modules (no cycles). Current stack from bottom up: |
| 27 | + |
| 28 | +``` |
| 29 | +Observability → Persistence → AudioEngine → Metadata → Library → Playback → UI → App |
| 30 | +``` |
| 31 | + |
| 32 | +A module never imports `AppKit` unless it has no other choice (UI module is the only one expected to). |
| 33 | + |
| 34 | +## Naming |
| 35 | + |
| 36 | +- App display name: **Bòcan** |
| 37 | +- Binary / bundle / package / repo / module prefix: `bocan` (lowercase, ASCII) |
| 38 | +- Bundle ID: `io.cloudcauldron.bocan` |
| 39 | +- Type prefix: none. Swift modules namespace types. |
| 40 | +- Log subsystem: `io.cloudcauldron.bocan` |
| 41 | + |
| 42 | +## Concurrency |
| 43 | + |
| 44 | +- Public APIs that do async work are `async throws` and annotated `Sendable` where relevant. |
| 45 | +- Long-lived state is owned by `actor`s, not classes with locks. |
| 46 | +- `@MainActor` everything touching SwiftUI view state. |
| 47 | +- No `DispatchQueue.global().async` in new code. Use `Task` or `TaskGroup`. |
| 48 | +- Cancellation is respected: every loop over an `AsyncSequence` or long operation checks `Task.checkCancellation()`. |
| 49 | + |
| 50 | +## Error handling |
| 51 | + |
| 52 | +- Each module defines a single `*Error: Error, Sendable` enum (e.g. `AudioEngineError`). |
| 53 | +- Errors carry context (URL, underlying error, human-readable reason) — not bare cases. |
| 54 | +- No `try?` in production code paths unless an `else { log.warning }` branch also exists. |
| 55 | +- `fatalError` is banned outside `#if DEBUG` or truly unreachable `default:` branches. |
| 56 | + |
| 57 | +## Logging |
| 58 | + |
| 59 | +- Use the `AppLogger` facade from `Observability`, never `print`, never raw `os_log`. |
| 60 | +- Categories (create the module's category on first use): `app`, `audio`, `library`, `metadata`, `persistence`, `ui`, `network`, `playback`, `cast`, `scrobble`. |
| 61 | +- Every async op: `log.debug("op.start", [...])` / `log.debug("op.end", ["ms": duration])`. |
| 62 | +- Every caught error: `log.error("op.failed", ["reason": ..., "error": String(reflecting: err)])`. |
| 63 | +- **Redact** anything matching keys in `Observability.sensitiveKeys` (`apiKey`, `token`, `sessionKey`, `password`, `authorization`). Add to that list as you add integrations. |
| 64 | + |
| 65 | +## Testing |
| 66 | + |
| 67 | +- **Swift Testing** (`import Testing`, `@Test`, `#expect`, `#require`) for unit + integration tests. `XCTest` only where a framework forces it (e.g. XCUITest). |
| 68 | +- **80% line coverage minimum** per module, enforced in CI. |
| 69 | +- Every public function has at least one `@Test`. |
| 70 | +- Every bug fix begins with a failing regression test. |
| 71 | +- UI: **swift-snapshot-testing** for every view, in light and dark mode, at representative sizes. |
| 72 | +- Property-based tests (swift-testing's `arguments:` or hand-rolled) for anything with interesting algebra (queue ops, criteria compiler, LRC parser, etc.). |
| 73 | +- Fixtures live in `Tests/Fixtures/` at repo root. Never generate fixtures at test time unless deterministic. |
| 74 | +- Tests must not hit the network. Use a `URLProtocol` stub or a protocol-based HTTP client mock. |
| 75 | + |
| 76 | +## Linting & formatting |
| 77 | + |
| 78 | +- `swiftlint` and `swiftformat` configs at repo root. |
| 79 | +- CI fails on any lint or format diff. |
| 80 | +- Pre-commit hook installs with `make bootstrap` and runs both on changed files. |
| 81 | + |
| 82 | +## Commits & PRs |
| 83 | + |
| 84 | +- **Conventional Commits** (`feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`, `build:`, `ci:`, `perf:`). A commit scope matches the module: `feat(audio): schedule gapless handoff`. |
| 85 | +- One logical change per commit. PR titles mirror the leading commit. |
| 86 | +- Every PR links to its phase file. |
| 87 | + |
| 88 | +## Security & privacy |
| 89 | + |
| 90 | +- **Sandbox on**, hardened runtime on, library validation on. |
| 91 | +- Entitlements added per phase, never upfront "just in case". |
| 92 | +- No analytics without explicit opt-in. MetricKit (which stays on-device) is fine. |
| 93 | +- Secrets never in the repo. `.env` is gitignored; CI uses GitHub Actions secrets. |
| 94 | +- Sensitive file access goes through `SecurityScope` helper (Phase 3) — never raw `URL.startAccessingSecurityScopedResource()` scattered around. |
| 95 | + |
| 96 | +## Performance baselines |
| 97 | + |
| 98 | +- App cold launch < 1.5s on an M-series Mac. |
| 99 | +- Library view renders 10k tracks at 60fps scroll. |
| 100 | +- Scrub / seek latency < 50ms. |
| 101 | +- Idle CPU < 1% while paused. |
| 102 | +- Idle CPU < 5% while playing a local file (no visualizer). |
| 103 | + |
| 104 | +## Accessibility |
| 105 | + |
| 106 | +- Every interactive element has an `accessibilityLabel`. |
| 107 | +- Full keyboard navigation. No mouse-only actions. |
| 108 | +- VoiceOver rotor reaches every meaningful view. |
| 109 | +- Respects `reduceMotion`, `increaseContrast`, `differentiateWithoutColor`, `reduceTransparency`. |
| 110 | +- Passes Accessibility Inspector audits on key screens. |
| 111 | + |
| 112 | +## Localization |
| 113 | + |
| 114 | +- Use **String Catalogs** (`.xcstrings`) from day one, even if only `en` ships. |
| 115 | +- No string literals in views; all via catalogue. |
| 116 | +- Dates, numbers, durations via `Formatter` / `Duration.formatted`. |
| 117 | + |
| 118 | +## Context7 |
| 119 | + |
| 120 | +Add `use context7` to every prompt that touches evolving APIs. Explicit lookups are listed per phase. |
| 121 | + |
| 122 | +## What "done" means |
| 123 | + |
| 124 | +A phase is done when: |
| 125 | + |
| 126 | +1. Every acceptance-criteria box in the phase file is ticked. |
| 127 | +2. `make lint && make test-coverage` is green. |
| 128 | +3. CI is green on the PR. |
| 129 | +4. The phase's "Handoff" contract is honoured (the next phase's prerequisites hold). |
| 130 | +5. Nothing marked `TODO(phase-NN)` remains. |
0 commit comments