Skip to content

fix(projects): report where a project actually is, not "Draft" forever - #26

Merged
catomean merged 1 commit into
mainfrom
fix/project-progress-truth
Aug 28, 2026
Merged

fix(projects): report where a project actually is, not "Draft" forever#26
catomean merged 1 commit into
mainfrom
fix/project-progress-truth

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

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

The fix

Both now derive from the rows themselves — deriveProjectProgress() in lib/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.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.

Steps and their labels move to lib/config/project-steps.ts as 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

  • The list query fetches the ids it derives from in the same round trip (figures(id), surfaces(id), compositions(id)) — no N+1.
  • Embedding is unambiguous: projects has no surface_id/composition_id, so there is exactly one relationship each way.
  • All three embedded tables carry owner-scoped for all RLS policies, so an authenticated caller sees only their own rows; guest callers use the service-role client already scoped by user_id on the parent.
  • This only became derivable last commit — before fix(compose): keep the scene background instead of losing it on refresh #25 no compositions rows were ever written, so "composed" had no honest signal.

Verify

  • lint: 0 errors, 8 warnings (all pre-existing)
  • tsc: clean
  • tests: 84 pass · 0 fail (was 78; 6 new cover the derivation, including the two old wrong answers)

🤖 Generated with Claude Code

https://claude.ai/code/session_014opKWKa65PXxn2MiWSKwwh

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
@catomean
catomean merged commit 2dfdc39 into main Aug 28, 2026
1 check passed
@catomean
catomean deleted the fix/project-progress-truth branch August 28, 2026 04:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant