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.