Skip to content

Commit 0af56a5

Browse files
committed
add building instructions
1 parent 0039f30 commit 0af56a5

20 files changed

Lines changed: 5166 additions & 0 deletions

phases/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Bòcan — Phase Specs
2+
3+
One file per phase. Point a fresh Claude Code / Copilot session at a single file and let it work. Do not skip ahead; phases are dependency-ordered.
4+
5+
## How to use
6+
7+
1. Open a **fresh session** per phase (keeps context lean).
8+
2. Tell the assistant: *"Read `phases/phase-NN-<name>.md` and `phases/_standards.md`. Implement exactly what is specified. Use Context7 for every listed lookup. Write tests as you go. Commit with Conventional Commits. Stop when every acceptance-criteria box is checked."*
9+
3. Run `make lint && make test-coverage` before declaring the phase done.
10+
4. Open a PR. Only move to the next phase once CI is green and the checklist is fully ticked.
11+
12+
## Files
13+
14+
| Phase | File | Output |
15+
|---|---|---|
16+
|| [_standards.md](_standards.md) | Cross-cutting rules referenced by every phase |
17+
| 0 | [phase-00-foundations.md](phase-00-foundations.md) | Repo, CI, Makefile, logger, empty app |
18+
| 1 | [phase-01-audio-engine.md](phase-01-audio-engine.md) | Single-file playback, AVFoundation + FFmpeg decoders |
19+
| 2 | [phase-02-persistence.md](phase-02-persistence.md) | GRDB + schema + repositories |
20+
| 3 | [phase-03-library-scanning.md](phase-03-library-scanning.md) | Folder scan, TagLib, FSEvents |
21+
| 4 | [phase-04-library-ui.md](phase-04-library-ui.md) | 3-pane browser, Table, search |
22+
| 5 | [phase-05-queue-gapless.md](phase-05-queue-gapless.md) | Queue, gapless, MPNowPlaying |
23+
| 6 | [phase-06-manual-playlists.md](phase-06-manual-playlists.md) | CRUD playlists |
24+
| 7 | [phase-07-smart-playlists.md](phase-07-smart-playlists.md) | Rule builder, SQL compiler |
25+
| 8 | [phase-08-metadata-editor.md](phase-08-metadata-editor.md) | Tag editor + cover art fetch |
26+
| 9 | [phase-09-eq-effects.md](phase-09-eq-effects.md) | 10-band EQ, ReplayGain, crossfeed |
27+
| 10 | [phase-10-mini-player-polish.md](phase-10-mini-player-polish.md) | Mini player, themes, dock tile |
28+
| 11 | [phase-11-lyrics.md](phase-11-lyrics.md) | LRC + embedded lyrics |
29+
| 12 | [phase-12-visualizers.md](phase-12-visualizers.md) | FFT + Metal/Canvas visualizers |
30+
| 13 | [phase-13-scrobbling.md](phase-13-scrobbling.md) | Last.fm + ListenBrainz |
31+
| 14 | [phase-14-playlist-import-export.md](phase-14-playlist-import-export.md) | M3U/M3U8/PLS/XSPF |
32+
| 15 | [phase-15-casting.md](phase-15-casting.md) | AirPlay 2 + Google Cast |
33+
| 16 | [phase-16-distribution.md](phase-16-distribution.md) | Sign, notarize, DMG, Sparkle |
34+
35+
## Conventions used in every phase file
36+
37+
- **Prerequisites** — what must already exist
38+
- **Goal / Non-goals** — keep scope honest
39+
- **Implementation plan** — ordered, small, committable steps
40+
- **Definitions & contracts** — types/protocols/SQL the assistant should produce verbatim
41+
- **Context7 lookups** — drop these into prompts
42+
- **Dependencies** — exact SPM / Homebrew additions
43+
- **Test plan** — specific cases, not vibes
44+
- **Acceptance criteria** — checklist to tick before merging
45+
- **Gotchas** — the things that will bite you, named in advance
46+
- **Handoff** — what the next phase expects

phases/_standards.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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

Comments
 (0)