feat(video-studio): unblock owner saves, single-screen editor layout, UX pass - #441
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 at2xl, 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
…In(db)cores with replay paths; all frontend callers sendIdempotency-Key. Render/upload/AI routes are intentionally left unregistered (not transaction-bound) — the UI now says why when a save fails.godModeIsolationInventory,godModeGateInventory) bumped deliberately for the one new route.Editor layout
lg+(assets | preview + selection controls | inspector), each column scrolling independently; mobile gets a 3-stop panel switcher.UX
beforeunloadand route-leave guard that flushes first and asks before discarding.PATCH /api/agency/audio/projects/:id)./features,/features/video-studio, mega menu.Verification
pnpm run test:run: 11978 passed; 1 failure is pre-existing onorigin/main(nuxtUiUnheadCompatibility—hooks.hookOnce is not a function).pnpm run build: passes.Render / upload / AI generation for owners (4th commit)
server/utils/godMode/externalLedgerCoordinator.tsgeneralises the banner render ledger protocol (claim → reserve ids → dispatch checkpoint → result/ambiguous). Families registered forrender-video,upload-media,generation/jobs; routes take reserved ids and replay from them; callers sendIdempotency-Key. Ledger SQL dry-run against the real schema (rolled back).Not done / follow-ups
🤖 Generated with Claude Code