---
path: /studio/generation/music
surface: studio
domain: generation
auth: signed-in + creator (proxy-gated; not anonymous, not search-indexed)
source: apps/oshun/web/src/app/studio/generation/music/page.tsx
status: walked
last_walked: '2026-06-03 automated runtime probe (Playwright headless) — prod build + mock BFF; render, HTTP 200, server-rendered <h1>, 0 page errors verified. Evidence: WALKTHROUGH/results/walkthrough-runtime-probe-2026-06-03.md'
---

# Studio · Generation · Music

## Purpose

The approved music generation surface (§24.6 ambient-audio / §24.8). Renders the
rich `MusicCard` bound to the server's approved music workflow classes
(`GET /v1/isis/music/workflow-classes`) and submits through
`POST /v1/isis/music/generate`. The backend runs the workflow-class envelope
guard + ambient-audio entitlement, then enqueues — fail-closed 503 until a music
provider is configured. The client mirrors the same envelope guard
(`evaluateMusicRequestEnvelope`) so out-of-envelope requests are surfaced before
submit; every outcome is reported honestly.

## Entry points

- **Direct URL / bookmark** — `/studio/generation/music` (proxy-gated to authed
  creators; `robots: noindex/nofollow/noarchive/nosnippet`)
- **Studio shell nav** — `active="studio"` (shortcut `6`), generation sub-area
- **Sibling generation surfaces** — cross-linked from curated-cards / nyx-3d /
  living-scene

## Layout regions

- **Header**: shell header (`ShellLayout active="studio"`)
- **Main**: `<section data-music-studio>`
  - **`<h1>Music Generation</h1>`** — page heading, rendered in all three states
    (error → inside `data-music-studio-error`; loading → inside
    `data-music-studio-loading`; populated → top of the studio section), plus a
    lede `<p>` "Generate ambient and contemplative music bound to the approved
    workflow classes. Inputs are envelope-guarded; outputs are
    provenance-tracked."
  - **Consent panel** (`.panel`, `data-music-consent`) — single rights/terms
    confirmation checkbox; gates the `consentId` passed to the card
  - **`MusicCard`** (via `CardShell`) — workflow-class picker + music inputs +
    live envelope verdict + preflight meta + Generate
  - **Status line** (`.panel`, `data-music-status`) after a submit attempt

## States

- [ ] **Loading** — `data-music-studio-loading` panel "Loading approved music
      workflow classes…" while the GET is in flight, or when the response lacks
      a `card` / `provenance`
- [ ] **Empty** — N/A: the approved workflow-class set is server-authoritative
- [ ] **Populated** — consent panel + `MusicCard` with the approved workflow
      classes filtered against `MUSIC_WORKFLOW_CLASSES`
- [ ] **Envelope deny (client)** — when the current inputs violate
      `WORKFLOW_CLASS_ENVELOPES[workflowClass]`, a `data-card-issues` list shows
      the violations and Generate is disabled (`inputsValid === false`)
- [ ] **Entitlement-denied / feature-unavailable** — handled by `CardShell`
      (`data-card-status`), as on curated-cards
- [ ] **Submit accepted** — "Accepted — queued for music generation."
- [ ] **Submit rejected (503)** — "Music generation provider is not configured
      for this deployment yet."
- [ ] **Submit rejected (403)** — "Not available: <message>"
- [ ] **Submit rejected (422)** — "The request is outside the approved envelope
      for this workflow class."
- [ ] **Error (load failed)** — `data-music-studio-error` "Music generation is
      unavailable right now: <message>"
- [ ] **Offline** — load GET fails → error panel; submit POST → "Submission
      failed."
- [ ] **Gated** — proxy (creator) + ambient-audio entitlement (server)
- [ ] **Standalone PWA** — renders within shell

## Interactions

- [ ] **Rights/terms consent** (checkbox, `data-music-consent-checkbox`)
  - Function: toggles `consentAccepted`; sets `consentId` to
    `consent-music-<userId>` when accepted (empty otherwise), and builds the
    `ConsentConfirmation` for preflight
  - Keyboard: Space toggles; first in tab order
  - Screen reader: announces "I confirm I have rights to any reference inputs
    and accept the curated-generation terms."

`MusicCard` inputs (all re-evaluate the envelope verdict live):

- [ ] **Workflow class (approved)** (select, `data-field="workflowClass"`) —
      options filtered to the intersection of the server's approved classes and
      `MUSIC_WORKFLOW_CLASSES`; selecting one resets tempo/duration defaults to
      the class envelope mid-points
- [ ] **Mood** (select, `data-field="mood"`) — calm / reflective / invitational
      / cinematic / devotional
- [ ] **Tempo (BPM)** (number, `data-field="tempoBpm"`) — `min`/`max` bound to
      `[envelope.minTempoBpm, envelope.maxTempoBpm]`; label shows the envelope
- [ ] **Duration (s)** (number, `data-field="durationSeconds"`) — bound to
      `[envelope.minDurationSeconds, envelope.maxDurationSeconds]`
- [ ] **Loopable** (checkbox, `data-field="loopable"`) — disabled unless
      `envelope.loopable === 'optional'`; label shows the envelope rule
- [ ] **Key signature** (select, `data-field="keySignature"`) — C-major /
      A-minor / D-major / G-major / E-minor / F-major / D-minor
- [ ] **Direction** (textarea) — regenerate steer
- [ ] **Generate** (button, `data-card-action="generate"`)
  - Function: `submit(request)` → `POST /v1/isis/music/generate` with the full
    `MusicGenerationRequest` (`workflowClass`, `mood`, `tempoBpm`,
    `durationSeconds`, `loopable`, `keySignature`, `tenantId`, `userId`,
    `consentId`)
  - Disabled when: envelope deny / preflight blocked / no consent / busy
- [ ] **Regenerate with direction**, **Variant compare**, **Send to editorial**,
      **Takedown** — shared `CardShell` controls (the last two are
      `() => undefined` here)

## Data & contracts

- **Reads**: `GET /v1/isis/music/workflow-classes` →
  `{ workflowClasses: MusicWorkflowClass[], creatorTier, card: CardSpec | null, provenance: ProvenancePreview | null }`
- **Writes**: `POST /v1/isis/music/generate` — `MusicGenerationRequest`;
  fail-closed 503 until a provider is configured (Suno is the registered
  provider when `OSHUN_SUNO_API_KEY` is set)
- **Realtime**: _None._
- **Caching**: client fetch on mount
- **Auth/role check**: proxy (creator) + ambient-audio entitlement (server) +
  workflow-class envelope guard (`evaluateMusicRequestEnvelope`) client + server
- **Contracts**: `@oshun-web/isis-music-generation` (`MUSIC_WORKFLOW_CLASSES`,
  `WORKFLOW_CLASS_ENVELOPES`, `evaluateMusicRequestEnvelope`,
  `MusicGenerationRequest`, `MusicWorkflowClass`),
  `@oshun-web/isis-curated-cards` (`CardSpec`, `ConsentConfirmation`,
  `ProvenancePreview`, `PENDING_COST_COEFFICIENTS`)

## Cross-references

- Parent: [`../studio-overview.md`](../studio-overview.md)
- Siblings:
  [`studio-generation-curated-cards.md`](./studio-generation-curated-cards.md),
  [`studio-generation-nyx-3d.md`](./studio-generation-nyx-3d.md),
  [`studio-generation-living-scene.md`](./studio-generation-living-scene.md)
- Component sources:
  `apps/oshun/web/src/components/studio/generation/MusicStudio.tsx`,
  `MusicCard.tsx`, `CardShell.tsx`

## Open questions / known gaps

- [ ] `tenantId` is `''` and `userId` defaults to `'me'` in `MusicStudio` —
      confirm both are meant to resolve from the session/proxy.
- [ ] `onSendToEditorial` / `onTakedown` are `() => undefined`; editorial
      routing from this surface is not wired.
