Dev-seed fixture states
Dev-seed fixture states
The studio dev-seed scenarios seed deterministic rows under fixed
slugs so a QA pass (Playwright layout suite, dogfood walks, ad-hoc
browser checks) can land on a known state without burning a real
generation. This page is the single source of truth for which
scenario exercises which state, which @brioso.test account owns
the rows, and how to reseed cleanly.
Persona: who to log in as
The canonical studio QA persona is creator
(/dev/login?persona=creator&redirect=/studio). Every studio:*
fixture is seeded under creator@brioso.test.
persona=agency is kept as a thin alias that mints the same session
(older runbooks and curl examples still say “agency”). Use creator
in any new docs.
Other personas (free-new, pro-new, free-returning,
pro-returning) own the onboarding state matrix — they only have
subscriptions / users.onboardingCompletedAt rows, no jobs.
billing:pro / billing:free cover subscription-only states.
States
Two state machines live on top of the same jobs row:
JobState (DB column)
Authoritative list: packages/contract/src/jobStates.ts. All seven
values are valid at the DB level (the column is plain text, no CHECK
constraint). Studio fixtures only seed a subset; runtime code can
move a job through the rest.
| State | Meaning |
|---|---|
unverified |
OTP requested, not yet verified — never visible in the studio |
new |
Verified, queued for a runner |
composing |
Claimed by a runner, generation in progress |
paid |
Complete, tracks delivered — “success” |
failed |
Generation failed (terminal) |
paused |
User-stopped mid-generation (resumable) |
cancelled |
User-deleted (soft-delete; excluded from /jobs/mine) |
JobLifecycle (UI render bucket)
Authoritative list: packages/verticals/studio/src/components/canvas/lineage.ts
toLifecycleState(). Three values — what the canvas node actually
renders:
| Lifecycle | Renders as | Triggers |
|---|---|---|
complete |
TrackList + waveform + Remix/Use prompt actions | state === "paid" |
generating |
GeneratingProposals placeholder | anything not in the stopped set |
stopped |
Inline alert ("This generation stopped before the tracks were ready…") |
failed / paused / cancelled |
composing and new both render as generating; the difference is
visible only to the polling loop (isTerminalJob in
hooks/useJobPolling.ts), which keeps polling composing jobs and
stops on terminal states.
Scenarios
All under creator@brioso.test unless noted. Slugs are stable so a
Playwright spec can deep-link to them. Reseed with
mise run db:seed -- <scenario>.
| Scenario | Slug | JobState | Lifecycle | Notes |
|---|---|---|---|---|
studio:empty |
(none) | — | — | Account has zero jobs. New-brief landing screen. |
studio:thread-short |
dev-feedback-mock-0001 |
paid |
complete |
Single resolved job, 1 track. The /studio feedback icon row. |
studio:thread-long |
dev-seed-thread-long (plus 21 turns) |
paid x 22 |
complete x 22 |
Forces feed overflow + the fade mask. |
studio:thread-longword |
dev-seed-thread-longword + -t1 |
paid x 2 |
complete x 2 |
Thread of 2 takes; the second has a 200-char unbroken token for the mobile width-overflow bug. |
studio:thread-composing |
dev-seed-thread-composing |
composing |
generating |
Single-turn composing state — the loading state. |
studio:thread-failed |
dev-seed-thread-failed |
failed |
stopped |
Single-turn failed state — the reduceConversationMessages failed branch. |
studio:thread-paused |
dev-seed-thread-paused |
paused |
stopped |
Worker-stopped mid-generation; renders the same stopped node as failed but a distinct DB state. |
studio:thread-composing-mid |
dev-seed-thread-composing-mid + -t1 |
paid then composing |
complete + generating |
Mid-thread generation: paid take plus an in-progress follow-up. Exercises the live-polling node next to a completed one. |
studio:thread-failed-mid |
dev-seed-thread-failed-mid + -t1 |
paid then failed |
complete + stopped |
Failure-after-progress: paid take plus a failed follow-up. Exercises the stopped node next to a completed one. |
studio:at-limit |
dev-seed-at-limit-{0,1,2} under onboarding-free-returning@brioso.test |
paid x 3 |
complete x 3 |
Free-plan quota exhausted (FREE_THREAD_CAP=3) — renders AtLimitCta instead of the composer. |
sharing:link |
dev-seed-sharing-link |
paid |
complete |
Resolved job plus a share-link token (/s/dev-seed-share-token). |
billing:pro / billing:free |
(no jobs) | — | — | subscriptions rows only. |
Reseed
# One-shot, idempotent (each scenario upserts by slug):
mise run db:seed -- studio:thread-long studio:thread-failed \
studio:thread-composing-mid studio:thread-failed-mid
# Reset every dev-seed-* row (jobs and share links; user accounts are
# separate — use `mise run db:reset-users` if those need clearing too):
mise run db:seed -- --reset
# List all scenarios:
mise run db:seed -- --list
The --reset flag deletes every jobs.slug LIKE 'dev-seed-%' row and
every share_links.jobSlug LIKE 'dev-seed-%' row; it intentionally
does not touch dev-feedback-mock-0001 (no dev-seed- prefix) or
the dev-seed-at-limit-* set under onboarding-free-returning@.
Why this doc exists
The previous dev-seed comment header listed scenarios but said
nothing about the state each one exercises. The 2026-08-23 session
saw three generations show “This generation stopped before the
tracks were ready…” under agency@brioso.test — those were stale
rows under a persona that owned no fixtures, not a UI regression.
Adding the state mapping here and pointing all skills/docs at
creator makes the next person stop chasing a phantom bug.