An AI ad interface can look like a form: enter a prompt, choose a presenter, select a ratio, and generate. For engineering and QA, that model is too flat. Each choice changes what should be required next, which errors are recoverable, and whether the exported asset still matches the original brief.
This article proposes a state model based only on public product-page descriptions. It is not a source-code review, an architecture claim, or a performance benchmark. The goal is to make a multi-channel video workflow discussable and testable.
Why a single isReady flag is not enough
Consider a user who selects Instagram, adds a square product image, writes a Story-oriented script, and then switches the channel to TikTok. The project may still contain valid data, but “valid” no longer means “ready.” The ratio, opening logic, caption density, and delivery expectations may need review.
A boolean cannot explain that distinction. It cannot tell the UI whether the script is missing, whether the selected ratio conflicts with the destination, whether generation failed, or whether an output exists but is awaiting human review.
A better state model keeps authoring, generation, and review separate:
type Channel = "instagram" | "tiktok";
type DraftState =
| { kind: "briefing"; channel?: Channel }
| { kind: "configuring"; channel: Channel; missing: string[] }
| { kind: "ready"; channel: Channel }
| { kind: "generating"; channel: Channel; requestId: string }
| { kind: "generated"; channel: Channel; assetId: string }
| { kind: "reviewRequired"; channel: Channel; assetId: string; checks: ReviewCheck[] }
| { kind: "approved"; channel: Channel; assetId: string }
| { kind: "failed"; channel: Channel; reason: "input" | "generation" | "export" };
type ReviewCheck = {
id: "product" | "claims" | "captions" | "audio" | "rights" | "disclosure";
status: "pending" | "pass" | "fail";
};
These types are a design proposal. They do not describe the implementation of any referenced product.
Make channel intent an input, not export metadata
If channel appears only when the user clicks Download, it cannot guide earlier decisions. Channel intent should shape the brief before generation.
The public Instagram Ad Maker page describes prompts, UGC-style scenes, AI influencers, voice selection, style variations, and ratios for Reels, Stories, and feed placements. That suggests an Instagram configuration needs a nested placement choice rather than a single generic “Instagram” value.
A useful model might be:
type InstagramPlacement = "reel" | "story" | "feed";
type InstagramBrief = {
channel: "instagram";
placement: InstagramPlacement;
openingFrame: string;
proofScene: string;
nextAction: string;
ratio: "9:16" | "4:5" | "1:1";
};
The ratio union is illustrative. The real UI and current placement rules must remain the source of truth.
Invalidate selectively when the channel changes
Changing from Instagram to TikTok should not necessarily erase the product asset, supported claims, or brand constraints. It should invalidate assumptions that depend on the channel.
The public AI TikTok Ad Generator page highlights scripts, hooks, visual patterns, presenters, voiceover, 9:16 output, and multiple variations. A channel switch could therefore preserve shared assets while marking the hook, ratio, and pacing plan for review.
function switchChannel(project: Project, next: Channel): Project {
return {
...project,
channel: next,
channelBrief: undefined,
review: markPending(project.review, ["captions", "audio"]),
outputs: [],
};
}
Again, this is an example contract. The important idea is explicit invalidation. Silent reuse can produce an export that is technically complete but strategically stale.
Keep generation success separate from approval
A returned video file is not the end state. The UI should distinguish “generated” from “approved.” This matters for AI presenters and UGC-style scripts, where a visually coherent result may still contain a product error, unsupported claim, caption issue, pronunciation problem, or disclosure concern.
The public workflow at UGCVideo.ai is useful as a visible example of combining creative inputs and generated variations. It does not prove campaign performance, internal system behavior, or legal readiness. A product team should resist encoding “generation succeeded” as “safe to publish.”
Review can be implemented as a gate with reasons. If a check fails, the user needs a route back to the relevant input, not a generic error toast. Caption failure should reopen caption or script controls; product-depiction failure may require a different reference asset or scene; rights failure may block export entirely.
Define variants as experiments
Variation generation becomes more useful when every child output records what changed. Instead of treating a batch as an array of opaque assets, attach an experiment dimension:
type VariantAxis = "hook" | "presenter" | "scene" | "voice" | "captionStyle";
type Variant = {
assetId: string;
parentBriefId: string;
axis: VariantAxis;
valueLabel: string;
inheritedFields: string[];
};
This does not measure ad effectiveness. It preserves the reason a variant exists. Performance data, if collected later under a valid campaign setup, belongs in a separate system with audience, placement, time, and version context.
A compact test matrix
At minimum, test these transitions:
- A new project cannot enter
readywithout a channel and complete brief. - Switching channels preserves shared product assets but invalidates channel-specific fields.
- A failed generation can retry without duplicating approved outputs.
- A generated asset cannot become
approvedwhile a required review check is pending or failed. - Editing a supported claim after generation marks the existing asset stale.
- Export records the approved asset version and intended placement.
- Navigating backward does not lose the failure reason or silently approve a replacement.
Boundary tests deserve equal attention: unsupported file types, unusually long scripts, missing voice selection, incompatible ratios, interrupted uploads, expired sessions, and duplicate clicks on Generate.
Instrument the decision path, not just the button
Analytics events such as generate_clicked are easy to count and hard to interpret. A more useful event model records state transitions and validation categories without storing sensitive creative content:
type WorkflowEvent = {
from: DraftState["kind"];
to: DraftState["kind"];
channel?: Channel;
validationCodes?: string[];
variantAxis?: VariantAxis;
};
This can reveal where users abandon the workflow or repeatedly revisit a field. It still cannot tell the team whether an ad “won.” Product telemetry explains interface behavior; campaign measurement explains market response.
The engineering payoff is explainability
The point of a state machine is not ceremony. It is to make every transition answerable: Why is generation blocked? What became stale after a channel switch? Which checks remain? What changed between variants? Which exact asset was approved?
When those questions have explicit answers, the UI can guide users without pretending that automation removes editorial judgment. The generator produces a candidate. The workflow produces a traceable decision.
Top comments (0)