Project 8 · Rubric and defend-your-code
Unit 28 · Project 8 · Rubric. Score your own Trackcast honestly, then answer the defend-your-code prompts out loud or in writing. If you can't defend a choice, that is the choice to revisit.
The rubric is not a checklist to game; it is the review an enterprise iOS engineer would give your player. Media code earns its keep by surviving interruption, so most of the weight is on correctness under real conditions, not on features.
Rubric
| Area | Not yet | Solid | Excellent |
|---|---|---|---|
| Playback core | play/pause works | seek is exact, speed control, no observer leak | transport disabled until item ready, clean deinit |
| Library & discover | one hard-coded episode | subscribed podcasts grid, show pages, .searchable discover |
reuses one model; no duplicated fetch paths |
| Chapters & queue | none | chapters derived from the play head (seek on tap); an up-next queue | queue auto-advances at end-of-item |
| Sleep timer | absent | a timer that pauses playback when it fires | cancels cleanly; survives re-arm |
| Isolation | compiles | PlayerModel is @MainActor @Observable |
every cross-domain hop justified, no @unchecked |
| Background | plays foreground only | plays backgrounded and on lock | interruptions pause and resume correctly |
| Lock screen | title shows | artwork + self-advancing scrubber + remote commands | commands stay in sync with app state |
| Downloads | downloads to disk | background session, file moved synchronously | cancel with resume data, race-free progress |
| Live Activity (elective) | absent | Lock Screen + Dynamic Island render | rate-limited updates, clamped progress, clean end |
| Craft | builds | light/dark correct, VoiceOver on transport | no raw colors, no magic numbers, tokens only |
Aim for every row at "Solid" before you ship, with the isolation and downloads rows at "Excellent" — those are the ones the AI checkpoint proved are easy to get subtly wrong.
Defend your code
Answer these as if in a code review. The goal is that your reasons are load-bearing, not decoration.
Why is PlayerModel marked @MainActor? What specifically would break if it were not,
given that it both feeds SwiftUI and calls AVPlayer? Name the concrete race or crash.
Where does the periodic time observer's closure capture self, and why weakly? Trace the
retain cycle that a strong capture would create, object by object, and say what removing the
observer in deinit prevents.
Your download progress lives behind an actor. Why not a lock, or a @MainActor class? What
does the actor buy you over each alternative, and what is the cost you accepted (the async
access) in exchange?
Why is the temp file moved inside didFinishDownloadingTo rather than in a follow-up task?
What is the lifetime of that file, and what happens if you defer the move?
On the lock screen, why set MPNowPlayingInfoPropertyPlaybackRate? What does the OS do with
it, and what visible bug appears if you forget it on pause?
The AI-usage reflection
You are in Professional stage. Write two or three sentences on each:
- What you delegated. Which components did you hand to Claude Code wholesale, and why were they good candidates (broad boilerplate, well-specified contract)?
- What you rejected or rewrote. The download-concurrency checkpoint was one seeded bug — did you catch anything similar in your own AI-produced diffs? What did you send back?
- What you would configure differently. If you set up a
/swift-reviewpass or a hook to flag@unchecked Sendablein diffs, would this project have gone faster? Say how.
The strongest answer to "what did you reject" is specific: "Claude gave me a time observer that
captured self strongly; I caught it because a leaked observer is a known trap, and I changed
it to [weak self] and added the deinit removal." Specificity is the difference between
using AI and being used by it.
Knowledge check
Q: Two rows are called out as "get to Excellent." Which, and why those two? Isolation and downloads. Both are places where AI-generated code compiles while hiding a concurrency bug (a wrong actor boundary, a raced progress dictionary), so they are where your review judgment, not the compiler, is the last line of defense.
Q: What makes a good defend-your-code answer versus a weak one?
A good answer names the concrete failure a choice prevents — the specific race, retain cycle,
or deleted file — rather than restating the choice. "It's @MainActor because UI code should
be on the main thread" is weak; naming the exact cross-actor mutation it eliminates is strong.