Build lifecycle¶
What this answers: what states does a Build move through, what triggers each transition, and how does compat evaluation flow when the Build changes?
State machine¶
stateDiagram-v2
[*] --> Empty
Empty --> Partial : add component
Partial --> Empty : clear all
Partial --> Complete : fill last slot
Complete --> Partial : remove component
Empty --> SavedAnon : save (cookie)
Partial --> SavedAnon : save (cookie)
Complete --> SavedAnon : save (cookie)
SavedAnon --> SavedAccount : sign in & claim
SavedAccount --> Shared : generate share slug
SavedAnon --> Shared : generate share slug
Shared --> [*] : delete
SavedAccount --> [*] : delete
Saved → Shared is bidirectional in practice — sharing doesn't change the save state, it adds a public slug. The state diagram simplifies for clarity.
States explained¶
| State | Meaning | Where the data lives |
|---|---|---|
Empty |
Build exists conceptually (someone opened /build) but no slots filled. |
Nowhere persisted; URL state is empty. |
Partial |
At least one slot filled, not yet complete. | URL params (?cpu=...&gpu=...) for the in-progress browser session. |
Complete |
All 8 slots filled. | Same as Partial — URL params. |
SavedAnon |
Cookied to the visitor's browser via signed build_session cookie, 1y expiry. |
Cookie holds the slot map (or a server-side session ID if too large). See ADR-0003. |
SavedAccount |
Persisted to the Build table, owned by a User. Cross-device. |
Postgres. |
Shared |
Has a public unlisted slug. Can be SavedAnon + Shared or SavedAccount + Shared. |
Postgres Build.shareSlug. |
Transition triggers¶
add component/remove component— user picks or clears a slot in/build. URL updates immediately.save (cookie)— explicit user action via the "Save" button. Cookie is signed server-side.sign in & claim— Auth.js completes; the same response that finalises sign-in claims the cookied build into the user's account, then clears the cookie. See ADR-0003.generate share slug— explicit "Share" button. Creates a random unlisted slug, persists alongside the Build.
Compatibility evaluation¶
Whenever the Build moves between any of the in-progress states (Empty / Partial / Complete), every compat rule re-evaluates:
flowchart TD
Start([Build state changes]) --> Iter[For each compat rule]
Iter --> Eval{Evaluate rule}
Eval -->|pass| Next[Next rule]
Eval -->|warn| W[Annotate slot: warn]
Eval -->|block| Bk[Annotate slot: block]
Eval -->|info| I[Annotate slot: info]
W --> Next
Bk --> Next
I --> Next
Next --> More{More rules?}
More -->|yes| Iter
More -->|no| Done([Surface annotations + overall status])
Rules in scope today (M1):
- CPU socket vs Motherboard socket
- PSU wattage vs CPU TDP + GPU TDP (basic sum)
- GPU length vs Case clearance
Rules planned for #22:
- RAM type (DDR4/DDR5) vs Motherboard
- Cooler height vs Case max cooler height
Verdicts: each rule produces pass, warn, block, or info. The Build's overall status is the max severity across all rules. block does not prevent saving — UC-9 explicitly allows incomplete or incompatible builds (per Use cases).
Implementation: pure functions in src/rules/, aggregated in src/lib/build.ts:computeCompatibility.
Notes¶
- The cookie save state is decided by ADR-0003 — every saved build is shareable, regardless of ownership.
- "Compatibility" is a derived attribute, not a state — it's recomputed on every change rather than stored. This avoids the need to invalidate cached compat results when rules change.
- Soft delete vs hard delete on saved builds is undecided. Hard delete is simpler; soft delete preserves the share URL longer.