Capstone · Milestone 2 — build and test with the full workflowswift-6.4/ios-26
Lesson 3 / 6
Unit 31 · Capstone

Capstone · Milestone 2 — build and test with the full workflow

Note

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(_:) to ItemListModel that 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.

Tip

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.

Note

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-review slash 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.