fix(projects): report where a project actually is, not "Draft" forever - #26
Merged
Conversation
Every project card showed the same badge for its whole life. The badge rendered project.status, the column defaults to 'draft', and nothing in the app ever wrote it — six of the seven states in STATUS_LABELS were unreachable. The dashboard's only progress signal was a constant. The step nav had its own, different answer: it marked Compose finished as soon as any figure had a styled image, which says nothing about whether the figures were ever placed. Two definitions of "where is this project", neither of them right. Both now derive from the rows themselves. A stored status is a second copy of facts the figures, surface and composition rows already hold, and the copy drifts the moment a step is undone — so it is not written at all, and project.status is no longer displayed. 'export' is deliberately left underivable: nothing writes an exports row yet, and claiming the step was finished would be a guess. The steps and their labels move to lib/config as one list, so the nav and the badge cannot disagree again; the nav keeps only its icons. The list query fetches the ids it derives from in the same round trip — no N+1. The embedded tables all carry owner-scoped RLS policies, so an authenticated caller sees only their own rows, and guest callers were already scoped by user_id on the parent. verify: lint 0 errors (8 pre-existing warnings) · tsc clean · 84 tests pass Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014opKWKa65PXxn2MiWSKwwh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Every project card showed the same badge for its whole life. The badge rendered
project.status, the column defaults to'draft', and nothing in the app ever wrote it — six of the seven states inSTATUS_LABELSwere unreachable. The dashboard's only progress signal was a constant.The step nav had its own, different answer: it marked Compose finished as soon as any figure had a styled image, which says nothing about whether the figures were ever placed. Two definitions of "where is this project", neither of them right.
The fix
Both now derive from the rows themselves —
deriveProjectProgress()inlib/domain, one definition consumed by the badge and the nav, so they cannot disagree again.A stored status is a second copy of facts the figures / surface / composition rows already hold, and the copy drifts the moment a step is undone. So it isn't written at all, and
project.statusis no longer displayed.'export'is deliberately left underivable: nothing writes anexportsrow yet, and claiming the step was finished would be a guess.Steps and their labels move to
lib/config/project-steps.tsas one list (the nav keeps only its icons — the one genuinely UI-owned thing). This also removes a label map that lived in a component.Notes
figures(id), surfaces(id), compositions(id)) — no N+1.projectshas nosurface_id/composition_id, so there is exactly one relationship each way.for allRLS policies, so an authenticated caller sees only their own rows; guest callers use the service-role client already scoped byuser_idon the parent.compositionsrows were ever written, so "composed" had no honest signal.Verify
🤖 Generated with Claude Code
https://claude.ai/code/session_014opKWKa65PXxn2MiWSKwwh