Project 1 · Rubric and defend your code
Unit 21 · Project 1 · Rubric. Grade your own build against this before starting Project 2. The defend-your-code prompts are the real test: shipping working code is table stakes; explaining why it is built this way is the skill.
A rubric is not a formality. On this ladder, "it works" is the floor. What separates a warm-up from a portfolio piece is whether the choices were deliberate and whether you can say why. Score yourself honestly — a red row you can explain beats a green row you got by luck.
Rubric
| Area | Meets bar | Falls short |
|---|---|---|
| State | Only stored what can't be derived; totals are computed | Stored total/tip in @State and updated them by hand |
| Counter floor | Clamped at 0; minus/Reset disabled at 0 | Count goes negative, or button enabled but inert |
| Money type | Decimal throughout |
Double anywhere in the money path |
| Empty state | Blank subtotal shows a prompt, not $0.00 |
Wall of zeros, or a crash on empty input |
| Divide-by-zero | Party clamped in the stepper and the math | Relies on only one guard, or none |
| Formatting | .currency(code:), locale-aware |
Manual string building, "$" + ... |
| Accessibility | Icon buttons carry accessibilityLabels |
Icon-only controls VoiceOver can't name |
| Light/dark | Correct in both; no hard-coded colors | Only checked light; unreadable in dark |
Accessibility is graded
Turn on VoiceOver (or the Accessibility Inspector) and tab through both screens. Every
control must announce what it does. An icon-only + that reads as "plus button" fails; it
should read "Increment by 2, button." The tip result should read as a label/value pair.
This is not extra credit on Segue — an app a blind user can't operate is not done.
Light and dark are graded
You wrote no color literals, so both modes should be correct for free — but verify it.
Toggle dark mode and look for anything you hard-coded without realizing (a white background,
a black text color). If you used only semantic colors (.primary, .secondary,
Color.accentColor, system materials), both modes are handled. If something breaks, that is
a hard-coded value hiding somewhere.
The fastest way to catch a light/dark bug is Xcode's canvas: pin a preview in .dark and one
in .light side by side with .preferredColorScheme. Anything that only works in one is a
literal you need to replace with a semantic color.
Defend your code
Answer these out loud or in a comment block. If an answer is "I don't know," that is the part to revisit.
Why
@Stateand not a view model /@Observabletype here? The data is owned by a single view, never shared, and doesn't outlive the view.@Stateis the right-sized tool; an@Observablemodel would be ceremony with no payoff. When does that change? (Answer: when state is shared across views or must persist — Project 2.)What happens when party size is 0? It can't reach 0 — the stepper is bounded
1...50andperPersondivides bymax(partySize, 1). Explain why you kept both guards even though either alone would do.Why is
totalcomputed rather than stored? Because it is a pure function of the inputs. Storing it introduces a second source of truth you must keep in sync; computing it makes staleness impossible.Why
Decimalinstead ofDoublefor money? Walk through0.1 + 0.2inDoubleand explain what that does to a running bill total.Where does the "can't decrement below zero" rule live, and why there? In the clamp at the assignment (
max(0, count - step)) and the.disabled(count == 0). Explain the division of labor: one enforces the invariant, the other communicates it.
Definition of done
- Every rubric row is green, or you can articulate exactly why a row is amber and what you'd do with more time.
- You answered all five defend-your-code prompts without looking at the milestone lessons.
- VoiceOver reads both screens sensibly; both color schemes are correct.
When that holds, you have earned Project 2 — where the data finally outlives the app launch.
Knowledge check
Q: The app works perfectly but you stored total in @State and update it in each button
action. Does it pass?
No. It may behave correctly today, but it violates the single-source-of-truth rule the
project exists to teach — the total is derivable, so it must be computed. Working code with
the wrong shape doesn't meet the bar.
Q: You only tested in light mode and it looks fine. Is light/dark satisfied? No — you have to verify dark. "Looks fine in light" tells you nothing about dark, where a hard-coded white background or black text would fail. Untested is not the same as correct.