--- name: fal-studio-mobile-concurrency-errors description: Patterns for mobile-first fal-studio UX — concurrent generation jobs, preserving compatible reference uploads across model switches, and normalizing fal provider errors. version: 1.0.0 author: Hermes Agent license: MIT metadata: hermes: tags: [fal-studio, mobile-ui, concurrency, fal-ai, error-handling, gallery, vite, react] related_skills: [systematic-debugging, vps-app-deployment, hetzner-s3-storage] --- # fal-studio mobile concurrency + error handling Use this for fal-studio-style apps where users compare many image/video models on mobile and need to launch multiple jobs without losing references. ## When to use - Mobile-first Vite/React app with one create screen and many AI models - Users switch between similar models and expect references to persist - A single global `generating` spinner blocks exploration - Provider errors are coming back as nested JSON / `[object Object]` - Generated assets auto-save to gallery/storage after completion ## Core patterns ### 1. Replace single-result state with a lightweight job queue Do not use a single global `result` + `generating` pair if the user may launch another request before the first finishes. Use frontend job state like: - `jobQueue: [{ id, modelId, modelName, prompt, status, result, error, createdAt }]` - `activeJobId` - derived `activeJob` - derived `activeResult` Statuses: - `queued` - `running` - `completed` - `failed` Flow: 1. Build `FormData` from the current prompt/model/uploads 2. Push a new job into `jobQueue` 3. Fire the request in an async closure without blocking model switching 4. On success: attach `result`, mark `completed`, refresh gallery/credits 5. On error: attach normalized error text, mark `failed` 6. Let the user tap job chips to inspect past/current jobs This is enough for real multi-request concurrency in a standard Express + fal app without needing backend queues. ### 2. Preserve only compatible upload buckets across model switches Users hate losing uploaded references when comparing nearby models. Use upload buckets keyed by semantic purpose, not model-specific IDs: - `generalReferences` - `firstFrame` - `lastFrame` - `storyboardReference` On model switch: 1. Compute old upload sections and new upload sections 2. Keep only keys that exist in the next model 3. Trim preserved files/previews to the new section's `maxFiles` 4. Clear only truly incompatible buckets This works well for: - image edit → image edit - first-frame video → first-frame video - first/last-frame video → another first/last-frame video - storyboard-capable models with the same section key ### 3. Normalize fal errors before showing them to users fal responses may include nested objects, arrays, or structured validation payloads. If you directly surface them, the UI may show `[object Object]` or `object object object`. Implement a shared formatter that unwraps, in order: - `error` - `detail` - `message` - `msg` - `reason` - arrays of issues joined with ` • ` - validation items with `loc` + `type` - final JSON.stringify fallback Recommended mapping: - `401` → invalid key or missing access - `403` → likely safety/policy/provider restriction - `422` → invalid parameters or blocked request - `429` → rate limited - `5xx` → provider-side failure Use the same formatter on: - backend fal proxy route - frontend fetch wrapper / generation handler ### 4. Keep the generate bar visually stable on mobile A bottom action bar that is directly `position: fixed` and also changes height/state can appear to jump. Use: - outer fixed shell with safe-area padding - inner constrained bar with stable padding - gradient backdrop - enough `.content` bottom padding so content never collides with the CTA - touch-friendly `min-height: 48px` buttons on mobile Recommended structure: - `.generate-bar-shell` → fixed full width + safe area + backdrop - `.generate-bar` → centered constrained content width ### 5. Auto-save means result state and gallery state are different concerns For fal-studio-like apps: - generation response can include `previewUrl` from fal - saved asset can be a local fallback URL or S3 URL - gallery should use the saved asset URL - result preview can prefer `previewUrl` So keep helpers like: - `getPrimaryResult(result)` → prefer `previewUrl`, then saved URL - `getAssetUrl(item)` → prefer gallery-safe local/S3 URL - `getResultKind(result)` → trust explicit `resultKind` first ## Verification checklist After changes: 1. Launch one job, switch models, launch another — both must complete 2. Switch between two compatible reference-based models — uploads should remain 3. Trigger a provider rejection/moderation-ish failure — error should be readable, not object spam 4. Confirm generated assets still auto-save to gallery 5. Check mobile layout on a narrow viewport / iPhone screenshot 6. Build, restart PM2, verify `dist/` timestamps, and spot-check other VPS apps ## Good fit example This pattern was used successfully on `fal-studio` when Alex wanted: - automatic gallery saves - multiple image/video jobs at once - preserved references while comparing similar models - cleaner mobile-first behavior - clearer Kling/fal error messages instead of `object object object`