DEV Community

mT41Gzp73rc6
mT41Gzp73rc6

Posted on

2026 Video Generation UI: Capability-Gated Forms, Async Status, and Moderation Gates

Short answer: make moderation coverage a prerequisite for publication, while the video generation form and job status remain separate, server-owned contracts. The form can hide unsupported controls, but only the backend can decide whether a rendered asset is safe to publish.

That distinction matters in a B2B SaaS video dashboard. A customer may submit a prompt, reference image, or soundtrack that passes one check while the final encoded file fails another. Treating “rendered” as “ready” creates a policy hole. I have seen the same shape in email systems: an accepted OTP request is not proof of delivery. Video queues need that humility too.

What should a capability-gated media form promise during a job?

The form should be generated from a versioned capability snapshot. Include accepted input modes, duration limits, aspect ratios, output media types, and moderation stages. Keep the user's intent separate from the effective request: if a model change invalidates a selected ratio, show the conflict and ask for a new choice. Silently coercing it makes the audit trail misleading.

Moderation coverage needs more precision than a boolean. Model coverage for prompt text, reference assets, generated frames, audio, and the final container, along with the policy revision and possible outcomes. If the tenant requires checks for all five stages and the selected capability covers only three, submission should be blocked with a field-level explanation. A checkbox cannot repair a missing control.

The browser is not an authorization boundary. It is a useful editor.

On submit, send the capability revision and an idempotency key. The server revalidates both. A stale revision returns a conflict with fresh capabilities; it does not create a job that will fail later. A repeated request returns the original job. This is the boring detail that saves support tickets after a mobile browser retries a timed-out request.

How do rendering, moderation, and publication stay separate?

Use a finite public state vocabulary such as queued, running, succeeded, failed, and canceled. Keep moderation_state and publishable as separate fields. succeeded means that an output exists; publishable means required moderation and media validation also passed. Do not invent a giant state name like rendered_but_scan_pending for every combination.

The critical path is a durable job record followed by an outbox event. The transaction commits the job and its event together; a relay publishes the event and marks it delivered. Workers consume idempotently. If the browser loses the response, it retries with the same key and then reads the canonical job. A polling error leaves the last known state visible with a stale marker. Cancellation is a request until the worker confirms it.

Here is the failure sequence worth drawing on a whiteboard. A user opens the dashboard at 09:00, loads capability revision r17, chooses a 16:9 clip, and submits just as the service publishes revision r18 with that ratio removed. The server must reject the stale combination before queueing work. Now imagine the network drops after the database commits but before the response reaches the browser: a second request with the same idempotency key must return the existing job, not create a second render. Later, a worker finishes encoding and emits version 4, while a delayed version 3 event arrives from a retry. The client compares versions and keeps version 4. Finally, moderation rejects the audio track after the video has rendered. The job remains succeeded, moderation_state becomes a terminal rejection, and publishable stays false. Each boundary is explicit, so operators can explain what happened without reading a stack trace or guessing which button the customer pressed.

That is the whole point.

from dataclasses import dataclass
from typing import Literal

JobState = Literal["queued", "running", "succeeded", "failed", "canceled"]


@dataclass(frozen=True)
class JobView:
    job_id: str
    state: JobState
    version: int
    moderation_state: str
    publishable: bool


def should_apply_update(current: JobView, incoming: JobView) -> bool:
    """Reject late events and updates for a different job."""
    return current.job_id == incoming.job_id and incoming.version > current.version
Enter fullscreen mode Exit fullscreen mode

That version check is small, but it prevents an old tab or delayed event from moving a completed job backward. Push events can reduce latency; polling is fine at moderate scale when intervals use jitter and terminal jobs stop polling. Either way, reconnect by fetching the canonical representation.

Which architecture fits a moderation-first dashboard?

Option Coverage evidence Refresh recovery Operational trade-off
Blocking request Easy to conflate render and scan Poor Simple prototype, fragile timeouts
Client timer over a request Usually implicit Partial Better feedback, unreliable coordinator
Capability snapshot plus job API Explicit policy revision and stages Strong More state-machine and contract work
Workflow engine behind a job API Explicit, branchable evidence Strong More storage, versioning, and operator overhead

The third option is the default for a typical dashboard. Choose a workflow engine when generation, scanning, transcoding, and publication each have independent retries or human review. The catch is ownership: a workflow platform becomes another production system to operate.

This architecture is not suitable when the dashboard already fronts a durable workflow API. Adapt that API's identifiers, versions, and outcomes instead of creating a second coordinator. Stick with a blocking call only for short internal tools where refresh recovery and job history are explicitly out of scope.

Failure tests that expose the real boundary

Test the transition graph as data. Include duplicate submissions, a capability revision changing between load and submit, cancellation racing with completion, repeated worker delivery, moderation rejection after render success, and a lower-version update arriving late. Record tenant, logical request, job, capability revision, policy revision, source artifact, and rendition identifiers in logs.

For media delivery, validate the actual container, codec, and MIME type combinations in the browser matrix; a filename extension is not evidence of playback support. Keep the validated source artifact and derive previews from it, so an optimized thumbnail never replaces the source needed for audit or reprocessing.

I'm not sure one moderation taxonomy will fit every generator. Your mileage may vary. A normalized set of coverage stages at the dashboard boundary, with adapter-specific mappings tested against each integration, is the practical compromise.

References

Top comments (0)