Capstone · Milestone 2 — build and test with the full workflow
Unit 31 · Capstone · Milestone 2. You build the app against the plan, with the full modern workflow: Claude Code writing whole components, Swift Testing covering the logic, a code-review subagent on every PR, and hooks that run format and tests automatically. This is how the platform itself is built.
This is the longest milestone because it is the app. What makes it a capstone milestone rather than just "write the code" is the workflow around the code: you delegate boldly, you test what matters, and you automate the guardrails so quality is enforced by machinery, not willpower.
Step 1 — build a vertical slice at a time
Work story by story, not layer by layer. For each user story, build the whole slice — model, view model, view — get it running, then move on. This keeps the app runnable throughout and makes each Claude Code delegation well-scoped:
"Implement the 'mark item done' story: add a
toggleDone(_:)toItemListModelthat flips the flag through the repository and reloads, and a swipe action in the list that calls it. Here are the existing types."
A tightly scoped prompt with the surrounding types produces a reviewable diff. A vague "build the list screen" produces a wall of code you cannot review, which is how bugs land.
Step 2 — test the logic that matters
Swift Testing covers the parts where correctness is not obvious. Do not chase coverage percentages; cover the logic that would embarrass you if it broke:
import Testing @testable import MyApp @Test("toggling done moves an item out of the active filter") func toggleFiltersOut() async { let repo = InMemoryItemRepository(items: [Item(title: "a")]) let model = ItemListModel(repo: repo) await model.load() await model.toggleDone(model.items[0].id) #expect(model.activeItems.isEmpty) }
Because your view models take a repository protocol, you inject an in-memory fake and test the logic with no database and no network — fast, deterministic tests. That testability is the payoff of the boundary discipline from Loops and the plan milestone. View models, use-cases, and any non-trivial computed logic are the targets; SwiftUI layout is not.
A test that asserts nothing is worse than no test — it is a green check that lies. Every @Test
should have a #expect that would actually fail if the behavior regressed. If you cannot write
a failing case, you do not yet understand what the code should do.
Step 3 — a code-review subagent on every PR
Professional stage means AI reviews AI's work — with you as the final judge. Commit in small PRs and run a review pass on each:
"Review this diff against Swift 6 strict concurrency, our repository boundary (no storage types in the domain), retain cycles in closures, and tokens-only styling. List problems with file and line; do not rewrite."
Read the findings, decide which are real, and fix those. The subagent catches the mechanical
issues — a strong capture, an @unchecked with no justification, a raw color — so your attention
goes to the judgment calls. This is exactly the review muscle the Project 8 and 9 checkpoints
trained.
Step 4 — hooks that enforce the guardrails
Do not rely on remembering to format and test. Wire it into the workflow so it happens automatically:
- A pre-commit hook that runs the formatter and the test suite, blocking the commit on failure.
- A CI check on every push: build,
swift test, and the token/lint checks. - Optionally, a hook that flags risky patterns in a diff (
@unchecked Sendable,try!, an SDK import outside the allow-listed boundary files).
# .git/hooks/pre-commit (illustrative) swift format lint --strict . || exit 1 swift test || exit 1
The point is that quality is enforced by machinery. A human forgets to run the tests on a Friday; a hook does not. Automating the guardrails is what lets you delegate aggressively to AI without the quality drifting.
Never let a hook be bypassed casually. --no-verify exists for genuine emergencies, not for
"the test is annoying." If a hook fails, the fix is the underlying problem, not skipping the
hook. A capstone with green hooks you actually respect is worth more than one with hooks you
route around.
Checkpoint
Your app should now:
- Implement every in-scope user story as a working vertical slice, runnable on device.
- Have Swift Testing coverage on the view models and logic, all with real assertions, run against in-memory fakes.
- Have a git history of small PRs, each run through a code-review pass whose findings you triaged.
- Have hooks/CI that run format and tests automatically and are green.
Stretch goals
- Add a
/swift-reviewslash command tuned to your project's conventions and run it before every merge; note what it caught that you would have missed. - Add snapshot or accessibility tests for a critical screen so a layout regression fails CI.
- Set up a code-review subagent that comments inline on the PR rather than in chat, so the review lives with the diff.
Knowledge check
Q: Why build story-by-story (vertical slices) instead of layer-by-layer? Because a vertical slice keeps the app runnable at every step and scopes each AI delegation to a reviewable diff. Building all models, then all view models, then all views leaves the app non-functional for long stretches and produces large, hard-to-review changes where bugs hide.
Q: What is the role of hooks in a Professional AI workflow? They enforce the guardrails mechanically — format and tests run on every commit regardless of whether anyone remembers. That automation is what makes aggressive AI delegation safe: quality does not depend on discipline you might lapse on, it depends on machinery that always runs.