Skip to content

feat(video-studio): unblock owner saves, single-screen editor layout, UX pass - #441

Merged
adme-dev merged 8 commits into
mainfrom
feat/video-studio-rd
Aug 23, 2026
Merged

feat(video-studio): unblock owner saves, single-screen editor layout, UX pass#441
adme-dev merged 8 commits into
mainfrom
feat/video-studio-rd

Conversation

@adme-dev

@adme-dev adme-dev commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Why

R&D of /agency/audio/projects/:id (Video Studio). As an owner the editor could not save at all: God mode is always active for owners and every unregistered write route 503s (God mode mutation coordination required) — surfaced only as a tiny red "Save failed" pill. On top of that the 3-column workbench only activated at 2xl, so at laptop widths the inspector wrapped below the fold and the Produce/Review tabs looked dead; every plain click on a clip pushed a no-op edit and an autosave.

What

Blocker

  • Register the five DB-bound studio editing routes as God-mode mutation families (timeline PUT, versions POST, project POST/PATCH/DELETE) via the transaction-bound coordinator; handlers run …In(db) cores with replay paths; all frontend callers send Idempotency-Key. Render/upload/AI routes are intentionally left unregistered (not transaction-bound) — the UI now says why when a save fails.
  • Inventory ratchets (godModeIsolationInventory, godModeGateInventory) bumped deliberately for the one new route.

Editor layout

  • Fixed-height 3-column workspace at lg+ (assets | preview + selection controls | inspector), each column scrolling independently; mobile gets a 3-stop panel switcher.
  • Timeline + transport in a resizable bottom dock (drag handle, height remembered).
  • Duplicate clip/asset inspector removed; effects drawer + generated-clip panel moved under the preview so selecting a clip no longer shifts the timeline.
  • Timeline: pointer travel < 3px is a click, not a move (fixes no-op undo step + save on every selection).

UX

  • Save status pill with icons + toast/alert with server reason + Retry; beforeunload and route-leave guard that flushes first and asks before discarding.
  • Inline rename and client assignment in the header (new PATCH /api/agency/audio/projects/:id).
  • Keyboard: Space, Home, ←/→ (⇧×5), ⌘S; shortcuts popover.
  • Library rail: category chips + Filters popover (was 6 stacked rows), "Hide N failed" for failed AI jobs; generated assets titled from the prompt instead of the job UUID (new + legacy rows).
  • Empty lanes offer "+ Add footage / overlay / music…"; generate composer's validation only speaks after interaction; render strip shows a live spinner + copy while rendering.
  • Marketing: Video Studio added to /features, /features/video-studio, mega menu.

Verification

  • pnpm run test:run: 11978 passed; 1 failure is pre-existing on origin/main (nuxtUiUnheadCompatibilityhooks.hookOnce is not a function).
  • pnpm run build: passes.
  • Click-tested against a local dev server (prod DB): load, select (no PUT), play/Space, dock resize, rename (single PATCH), client popover, filters, hide-failed, Add music, Produce/Review tabs.

Render / upload / AI generation for owners (4th commit)

  • New server/utils/godMode/externalLedgerCoordinator.ts generalises the banner render ledger protocol (claim → reserve ids → dispatch checkpoint → result/ambiguous). Families registered for render-video, upload-media, generation/jobs; routes take reserved ids and replay from them; callers send Idempotency-Key. Ledger SQL dry-run against the real schema (rolled back).

Not done / follow-ups

  • Preview deployed from this branch: https://preview.agency-dashboard-6cm.pages.dev/agency/audio/projects/eca5685a-14bf-411b-ad35-53394f6bbb44 — sign in there as an owner and try Render / Footage upload / Generate. The owner path is unit-tested + SQL dry-run but not click-tested (no owner session available to Claude).
  • Banner Studio's coordinator now runs on the generic ledger too (5th commit; its 8 tests unchanged in intent).
  • Preview shows "Could not decode preview" locally (no R2 creds) — unchanged behaviour in prod.

🤖 Generated with Claude Code

adme-dev and others added 8 commits August 23, 2026 15:08
…utation families

Owners are always in God mode, and the God mode middleware refuses every
non-GET /api route without a registered mutation family — so an owner could
not autosave, snapshot, create or delete a studio project (503 'God mode
mutation coordination required', surfaced only as a tiny 'Save failed' pill).

Register the four DB-bound editing routes (timeline PUT, versions POST,
project POST/DELETE) as transaction-bound families, run their DB cores under
the execution-ledger coordinator, and send Idempotency-Key headers from every
frontend caller. Render / upload / AI routes stay unregistered (queue/R2/AI
fan-out is not transaction-bound).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-error UX, rename, shortcuts

- Workbench is now a fixed-height 3-column workspace (assets | preview | inspector)
  at lg+, each column scrolling independently; the old 2xl-only third column wrapped
  below the fold at common laptop widths, which made the Produce/Review tabs look dead.
- Timeline + transport live in a resizable bottom dock (drag handle, remembered per
  browser) so the editing surface is always on screen.
- Removed the duplicated clip/asset inspector from the preview column; the effects
  drawer and generated-clip panel moved under the preview so selecting a clip no
  longer shifts the timeline.
- A plain click on a clip emitted a zero-distance move-clip → no-op undo step +
  autosave on every selection. Ignore pointer travel under 3px.
- Save failures now surface as a toast + alert with the server's reason and a
  Retry action; the status pill shows unsaved/saving/saved/not-saved with icons.
- beforeunload + route-leave guard flushes pending edits, asks before discarding.
- Inline project rename (PATCH /api/agency/audio/projects/:id, God-mode coordinated).
- Keyboard: Space play/pause, Home, ←/→ nudge (⇧ ×5), ⌘S save now; shortcuts popover.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, empty-lane adds, client picker, marketing page

- Library rail: 9 category chips + a Filters popover (source/status/model/aspect,
  with a reset) replace six stacked filter rows; 'Hide N failed' tucks away failed
  AI jobs per browser (still reachable under the Failed filter).
- Generated assets are titled from the prompt's first clause (finalize.ts) and
  legacy 'Generated video <uuid>' rows display the same way.
- Empty timeline lanes show '+ Add footage / overlay / music…' and open the right
  picker or the Produce tab.
- Generate composer: validation speaks only after the user has typed or tried.
- Render strip: spinner + 'rendering now' copy while a job is active.
- Header: client assignment popover (PATCH clientId).
- Marketing: Video Studio feature on /features, /features/video-studio, mega menu.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…H route

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion via an external-provider ledger

Owners could edit (previous commits) but still could not render, upload footage,
or generate AI clips — those routes fan out to Queues / R2 / AI Gateway and were
left unregistered. Generalise the banner render ledger protocol into
server/utils/godMode/externalLedgerCoordinator.ts: claim by Idempotency-Key,
reserve durable ids before any side effect, checkpoint 'dispatched', store a
replay payload on success, mark post-dispatch failures ambiguous (never retried
blindly). Register families for render-video, upload-media and generation/jobs;
the routes accept reserved ids (createRenderJob.jobId, createVideoGenerationJob.id,
upload key suffix) and replay from them. Frontend callers send Idempotency-Key.
Banner's coordinator is left untouched (candidate to migrate onto the generic one).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…c external ledger

godModeRender.ts is now a thin adapter (exact error wording preserved via
per-family message overrides); the test harness only swaps its mocked SQL
substrings/field names to the generic statements.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ble columns, render progress, full owner coverage

- Preview: R2 presigned URLs carry no CORS headers, so canvas draws failed
  silently (black frame) and audio buffers 'went missing'. New same-origin,
  Range-aware media proxy (GET /api/agency/audio/projects/:id/media?key=…,
  key must be in the timeline; native MEDIA_BUCKET on Pages, S3 fallback).
  clip-sources now returns proxy URLs. Overlay iframes pin color-scheme so the
  dark app no longer paints an opaque backdrop over the frame. Loading/error
  states draw the clip name instead of pure black.
- Timeline: clip labels from library titles / cleaned file stems (never ids);
  snap guide line while dragging; ⌘-wheel zoom; '+ Add …' on empty lanes.
- Inspector: editable Start / Duration / End (one field per commit).
- Workbench: draggable column dividers (widths remembered); audio projects use
  the same docked layout.
- Versions: restore checkpoints the current draft first ('Before restoring …').
- Missing clips: Select / Remove actions; fixed the selection model so
  'Replace selected clip' can actually enable (selecting an asset used to
  clear the clip selection).
- Render progress: migration 398 adds media_render_jobs.progress; the worker
  reports per-format stage; the strip shows 'Rendering square_1x1 · 2 of 3'
  with a progress bar.
- God mode: 14 more studio write routes (publish, portal, save-asset, captions,
  masks, extract, voiceover, source assets, assembly, directives) on the generic
  ledger via withGodModeLedger(); every studio caller sends Idempotency-Key;
  a guard test scans the studio frontend for write calls and fails if any is
  unregistered or keyless.
- Tests: MediaTimeline interaction test (click vs drag, labels, add-to-track),
  proxy + Range parsing, progress reporting, timing inputs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The render captures overlay HTML in an output-size viewport (e.g. 1080×1920)
and composites it at 0,0 unscaled. The preview iframe was sized to the
displayed box, so a 300×600 banner covered ~74% of the frame instead of ~28%.
Lay the iframe out at output size and CSS-scale it to the box (ResizeObserver).
Measured: banner width fraction 0.276 in preview vs 0.278 in the render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@adme-dev
adme-dev merged commit 9c2c851 into main Aug 23, 2026
6 checks passed
adme-dev added a commit that referenced this pull request Aug 23, 2026
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