Skip to content

fix(playground): chat plan gating + studio UX - #2633

Merged
smakosh merged 3 commits into
mainfrom
fix/playground-chat-plan-ux
Jun 10, 2026
Merged

smakosh merged 3 commits into
mainfrom
fix/playground-chat-plan-ux

Conversation

@smakosh

@smakosh smakosh commented Jun 10, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes a batch of playground issues around the Chat plan context and studio UX:

Video studio: audio no longer disables models

Models like MiniMax Hailuo 2.3 (silent-only) and Grok Imagine (audio-only) were disabled in the model selector with "This mapping does not support audio/silent output" tooltips depending on the current audio toggle. Any model is now selectable:

  • The audio toggle adapts to the selection: it locks off for silent-only models, locks on for audio-only models (with an explanatory tooltip), and stays free when the model supports both.
  • The audio flag is clamped per model at request time, so mixed comparisons (e.g. Hailuo vs Grok) each get a valid mode.
  • Audio mode no longer filters supported sizes/durations since it can't exclude a mapping anymore.

Video studio: low-credits banner in Chat plan context

The "Low credits remaining. Top up…" banner showed in the Chat plan context where top-ups don't apply. The banner now reads chat plan credits from /chat-plans/status (source of truth, instead of the possibly stale org row) and, when truly out of credits in the Chat plan context, promotes the subscription plans ("View plans" → /pricing) instead of a top-up.

Chat: default org selection

The chat playground always defaulted to the Chat plan context. Now, when the user has no active chat plan and no credits on the Chat org, but does have a funded dashboard org, the server redirects to that org (?orgId=…) so generation runs against their existing credits. The Chat plan context remains the default for subscribers, or when no org has credits — so the plan upsell can do its job.

Chat: unsubscribed users could generate from the Chat plan context

Credit gating only ran when a dashboard org was selected (selectedOrganization is null in the Chat plan context), so unsubscribed users with zero credits could keep generating (e.g. via free models). Gating now resolves the billing org (selected org or the dedicated Chat org) and, when blocked in the Chat plan context, routes to /pricing instead of the top-up dialog.

Canvas UX

  • JSON spec panel hidden by default; the confusing "Preview"/"Editor" toggle is now an explicit Show JSON / Hide JSON button (the left-panel "Preview" label no longer looks like a duplicate of a clickable button).
  • New empty state with randomized prompt suggestions (same pattern as image/video studios) that generate on click.
  • Updated the default spec copy that referenced "the JSON on the left".

Testing

  • pnpm build passes (17/17 tasks)
  • vitest run apps/playground passes (13 tests, including updated video-gen.spec.ts)
  • Full pnpm test:unit failures are environmental only (ECONNREFUSED :5432 — local Postgres not running); no api/worker/gateway code touched

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Canvas empty state with interactive prompt suggestions and hidden JSON editor by default
    • New canvas suggestion presets for quicker UI prompts
  • Improvements

    • Stronger billing/credit gating for chat with clearer top-up/pricing redirects
    • Streamlined audio handling for video generation with clearer disable reasons shown in controls
    • Option to hide comparison UI in video header
  • Tests

    • Updated video generation tests to match API adjustments

- Video studio: models are no longer disabled by the current audio
  mode; the audio toggle adapts to the selection instead and the
  audio flag is clamped per model at request time
- Video studio: low-credits banner uses /chat-plans/status in the
  Chat plan context and promotes plans instead of top-ups
- Chat: default to a funded dashboard org for unsubscribed users;
  Chat plan context stays default only for subscribers or when no
  org has credits
- Chat: credit gating now also covers the Chat plan context
  (previously skipped when no org was selected), promoting plans
  when out of credits
- Canvas: JSON spec hidden by default with a clearer Show/Hide JSON
  toggle, plus an empty state with prompt suggestions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fdc49c54-e8d1-4292-833f-de32b6c271da

📥 Commits

Reviewing files that changed from the base of the PR and between f209067 and 71a6609.

📒 Files selected for processing (3)
  • apps/playground/src/app/playground-shell.tsx
  • apps/playground/src/components/playground/chat-page-client.tsx
  • apps/playground/src/components/playground/video-page-client.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/playground/src/components/playground/chat-page-client.tsx
  • apps/playground/src/app/playground-shell.tsx
  • apps/playground/src/components/playground/video-page-client.tsx

Walkthrough

This PR enhances the playground with an empty-state canvas UI, implements credit/billing gating at shell and handler levels, removes global audio eligibility logic from video helpers to enable per-model audio control, and updates video component props accordingly.

Changes

Canvas UI, Billing Gating, and Video Audio Changes

Layer / File(s) Summary
Canvas empty state UI and suggestions
apps/playground/src/lib/hero-suggestions.ts, apps/playground/src/components/playground/canvas-page-client.tsx, apps/playground/src/lib/canvas/templates.ts
CanvasEmptyState component displays animated prompt suggestions sampled from a new canvasSuggestions constant; JSON editor is hidden by default; default spec text guides users to use the prompt instead of editing JSON.
Billing and chat-plan access gating
apps/playground/src/app/playground-shell.tsx, apps/playground/src/components/playground/chat-page-client.tsx, apps/playground/src/components/playground/video-page-client.tsx
Early chat-plan access check in shell redirects to funded org when Chat is unavailable; ensureBillableContext() centralizes credit gating in chat handlers; video page fetches and uses chat-plan status to show context-specific credit warnings.
Video request helper API: remove audioEnabled parameter
apps/playground/src/lib/video-gen.ts
mappingSupportsVideoRequest and four exported compatibility helpers (getSupportedVideoSizesForSelection, getSupportedVideoDurationsForSelection, getSupportedVideoRequestOptions, getNormalizedVideoRequestSelection) remove the audioEnabled parameter; audio-gating branches are deleted.
Video helper tests: update for audioEnabled removal
apps/playground/src/lib/video-gen.spec.ts
Test invocations of video compatibility helpers are updated to remove the trailing audioEnabled boolean argument; existing assertions about supported sizes, durations, and mode-specific behavior remain unchanged.
Video page: per-model audio control and integration
apps/playground/src/components/playground/video-page-client.tsx
Audio handling refactors from global effectiveAudioEnabled to per-model getAudioForModel() computation; replaces audio-selection constraints with selectionAudioSupport and model-lock conditions; video request payload uses per-model audio; dependencies and VideoControls props updated to expose lock state and reason.
Video controls and header component prop updates
apps/playground/src/components/playground/video-controls.tsx, apps/playground/src/components/playground/video-header.tsx
VideoControlsProps adds optional audioToggleDisabledReason string for contextual disable messages; VideoHeaderProps removes model-option-disabling callbacks and adds hideCompare boolean to conditionally hide comparison UI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • theopenco/llmgateway#2307: Overlaps canvas page UI/behavior changes in apps/playground/src/components/playground/canvas-page-client.tsx.
  • theopenco/llmgateway#2333: Related shared hero-suggestions usage and changes that affect canvasSuggestions sampling and display.
  • theopenco/llmgateway#2306: Overlaps renderPlaygroundShell changes in apps/playground/src/app/playground-shell.tsx (SEO vs. chat/org resolution and gating).
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(playground): chat plan gating + studio UX' directly summarizes the main changes: Chat plan credit gating fixes and studio UX improvements across video and canvas components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/playground-chat-plan-ux

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/playground/src/app/playground-shell.tsx (1)

141-175: ⚡ Quick win

Move this redirect ahead of the Chat-org fetch.

This check runs after Line 87 has already fetched "/playground/chat-org", but the nearby comment says that org is created on demand. Users who should be redirected straight into a funded dashboard org will still provision/touch the Chat org first. Run the /chat-plans/status fallback before fetching the Chat org, then resolve chatOrg only if the request actually stays in the personal context.

Based on PR objectives, this redirect is supposed to choose the funded dashboard org before the request continues in the Chat-plan context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/playground/src/app/playground-shell.tsx` around lines 141 - 175, The
redirect logic that checks chat plan access should run before fetching the
chat-org so we don't provision/touch a Chat org for users who should land in a
funded dashboard org; move the block that calls
fetchServerData("GET","/chat-plans/status") and computes hasChatPlanAccess
(using chatPlanStatus, Number(chatPlanStatus.regularCredits)) to execute before
the code that fetches "/playground/chat-org" (and before any call that may
create chatOrg), and only proceed to resolve chatOrg if hasChatPlanAccess
remains true; if hasChatPlanAccess is false and you find a funded organization
from organizations (using Number(o.credits) > 0), build nextParams from
searchParams and call redirect(`/?${nextParams.toString()}`) as currently
implemented.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/playground/src/components/playground/chat-page-client.tsx`:
- Around line 1125-1146: The gate in ensureBillableContext currently lets
hasChatPlanCredits satisfy billing even when selectedOrganization is set; change
it so dashboard org gating is checked first: if selectedOrganization exists and
Number(selectedOrganization.credits) <= 0 (and isChatPlanStatusLoaded &&
!hasChatPlanCredits) then trigger setShowTopUp(true) and return false, otherwise
if no selectedOrganization resolve to chatOrg and apply the existing chat-plan
gating (use Number(chatOrg.credits) and hasChatPlanCredits) before redirecting
router.push("/pricing"); ensure this aligns with ensureCurrentChat which creates
chats under selectedOrganization?.id ?? chatOrg?.id.

In `@apps/playground/src/components/playground/video-page-client.tsx`:
- Around line 293-318: The aggregation for selectionAudioSupport is wrong: using
some(...) allows mixed selections to leave the toggle enabled even when every
selected model is forced to one mode; change the two some(...) calls to
selected.every(...) so selectionAudioSupport.audio = selected.every(model =>
model.supportsVideoAudio !== false) and selectionAudioSupport.silent =
selected.every(model => model.supportsVideoWithoutAudio === true) (keep the
empty-selection special case), which will make audioToggleLocked /
effectiveAudioEnabled / audioToggleLockedReason behave correctly for forced-only
model sets.

---

Nitpick comments:
In `@apps/playground/src/app/playground-shell.tsx`:
- Around line 141-175: The redirect logic that checks chat plan access should
run before fetching the chat-org so we don't provision/touch a Chat org for
users who should land in a funded dashboard org; move the block that calls
fetchServerData("GET","/chat-plans/status") and computes hasChatPlanAccess
(using chatPlanStatus, Number(chatPlanStatus.regularCredits)) to execute before
the code that fetches "/playground/chat-org" (and before any call that may
create chatOrg), and only proceed to resolve chatOrg if hasChatPlanAccess
remains true; if hasChatPlanAccess is false and you find a funded organization
from organizations (using Number(o.credits) > 0), build nextParams from
searchParams and call redirect(`/?${nextParams.toString()}`) as currently
implemented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fbddc46a-78d3-44ea-beb5-1a897fb79a89

📥 Commits

Reviewing files that changed from the base of the PR and between 7f211be and f209067.

📒 Files selected for processing (10)
  • apps/playground/src/app/playground-shell.tsx
  • apps/playground/src/components/playground/canvas-page-client.tsx
  • apps/playground/src/components/playground/chat-page-client.tsx
  • apps/playground/src/components/playground/video-controls.tsx
  • apps/playground/src/components/playground/video-header.tsx
  • apps/playground/src/components/playground/video-page-client.tsx
  • apps/playground/src/lib/canvas/templates.ts
  • apps/playground/src/lib/hero-suggestions.ts
  • apps/playground/src/lib/video-gen.spec.ts
  • apps/playground/src/lib/video-gen.ts
💤 Files with no reviewable changes (2)
  • apps/playground/src/lib/video-gen.spec.ts
  • apps/playground/src/components/playground/video-header.tsx

Comment thread apps/playground/src/components/playground/chat-page-client.tsx
Comment thread apps/playground/src/components/playground/video-page-client.tsx
@smakosh smakosh self-assigned this Jun 10, 2026
smakosh and others added 2 commits June 10, 2026 20:10
- ensureBillableContext: dashboard orgs gate on their own credits
  only — chat plan credits live on the Chat org and never fund
  dashboard-org requests
- Video audio toggle: lock unless every selected model supports
  both modes, so the toggle never appears actionable for
  forced-only selections
- playground-shell: run the funded-org redirect before the
  chat-org fetch so redirected users never get a Chat org
  provisioned

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@smakosh
smakosh enabled auto-merge June 10, 2026 19:24
@smakosh
smakosh added this pull request to the merge queue Jun 10, 2026
Merged via the queue into main with commit b47f8e4 Jun 10, 2026
11 checks passed
@smakosh
smakosh deleted the fix/playground-chat-plan-ux branch June 10, 2026 19:35
pull Bot pushed a commit to soitun/llmgateway that referenced this pull request Jun 17, 2026
## Summary

Three related Chat-playground fixes for the out-of-credits experience in
the Chat plan context.

### 1. Image & Audio studios: false low-credits banner in the Chat plan
context
The Image and Audio studios computed remaining credits straight off the
`selectedOrganization` row passed from the server, which can be stale
and doesn't reflect live plan status. The **"Low credits remaining. Top
up…"** banner therefore showed in the Chat plan context (where top-ups
don't apply) even when no chat plan was active.

Both studios now mirror the **Video** studio (fixed in theopenco#2633):
- Read remaining credits from `/chat-plans/status` (source of truth)
when in the Chat plan context (`org.isChat`).
- When out of credits in that context, the banner promotes the
subscription plans (**"View plans" → `/pricing`**) instead of a top-up.

### 2. Video studio: generic "Video creation failed" instead of the real
gateway error
The gateway returns an OpenAI-compatible envelope — `{ error: { message,
… } }` — but `getGatewayErrorMessage` only unwrapped `error` when it was
a **string**, so it always fell through to the fallback. It now also
unwraps the nested `error.message`, so the exact gateway error reaches
the toast/gallery. Added `utils.spec.ts`.

### 3. Credit-error generation failures upsell a plan in the Chat plan
context
With #2 landed, the insufficient-credits **402** (`"Video generation
requires at least $1.00 in available credits. Please add credits and try
again."`) surfaced verbatim — wrong for the Chat plan context where
top-ups don't apply. When a studio generation fails for lack of credits
**and** the org is the Chat org, the toast/gallery error now upsells a
subscription:
- not subscribed → **"Subscribe to a plan to continue generating
videos/images/audio."**
- subscribed but out of plan credits → **"You've used all your plan
credits. Upgrade your plan to continue generating …"**

Non-credit gateway errors (e.g. unsupported image size) are unchanged
and still surface verbatim. Added shared `lib/credit-error.ts`
(`isInsufficientCreditsError`, `chatPlanCreditErrorMessage`) +
`credit-error.spec.ts`; hoisted the chat-plan status query above the
generate callbacks so they can branch on it.

## Testing
- Unit: `credit-error.spec.ts` (6) + `video/utils.spec.ts` (6) → 12/12
pass
- Build: `turbo run build --filter=playground` → 13/13
- **Runtime (Playwright, recorded):** logged in against the seeded stack
(Chat org, 0 credits, no plan) and confirmed end-to-end:
- `/image` & `/audio` banners → "Upgrade to a plan" + **View plans →
/pricing** (no Top Up)
- `/video` & `/image` generation → toast/gallery shows **"Subscribe to a
plan to continue generating …"**, not the "$1.00 / add credits" message
- control: `/api/video` still returns the raw 402 gateway message
(rewording is client-side)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved gateway error handling by extracting nested gateway error
messages when available.
* Standardized “insufficient credits” generation failures with
chat-plan-specific, user-facing messaging.
* **New Features**
* Added chat-plan credit status fetching to drive accurate credit/upsell
behavior for audio, image, and video.
* Updated low-credits UI: chat-plan contexts now show a dedicated
ChatPlanUpsell; non-chat contexts keep existing banner/top-up behavior.
* **Tests**
* Added unit tests for gateway error extraction and insufficient-credits
detection and messaging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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