DEV Community

Diiiii
Diiiii

Posted on

Designing an AI Ad-Creative Workflow as a Testable State Machine

AI ad generators are often described as prompt boxes with a download button. That mental model is too small for the product and too vague for reliable QA. The more useful abstraction is a stateful workflow: a brief becomes one or more generation jobs, outputs enter review, and only approved assets become eligible for export.

This article is a black-box design exercise based on publicly visible product-page workflows. It does not claim access to private source code, APIs, model architecture, queues, or performance data. The types and tests below are proposals for discussing a similar system.

Turn the creative brief into typed input

A testable workflow starts before generation. Free-form prompts are useful, but the system also needs explicit fields for decisions that affect validation.

type Format = "image" | "video";
type AspectRatio = "16:9" | "9:16";
type Concept = "product-demo" | "talking-actor" | "unboxing-pov" | "app-showcase";

interface CreativeBrief {
  productAssetId: string;
  audience: string;
  promise: string;
  proofToShow: string;
  prompt: string;
  format: Format;
  aspectRatio: AspectRatio;
  concept: Concept;
  variantDimension: "hook" | "body" | "cta" | "visual-style";
  outputCount: number;
}
Enter fullscreen mode Exit fullscreen mode

The important field is variantDimension. Without it, a batch may change the hook, presenter, background, and CTA at once. The files will be different, but the experiment will be difficult to interpret. A system can warn when a requested batch changes multiple controlled fields.

The public AI Facebook Ads page describes a visible sequence of choosing an ad video or image template, supplying a product reference and prompt, setting aspect ratio, duration, and output quantity, then generating, previewing, and regenerating. Those visible steps suggest distinct validation boundaries even though they do not reveal implementation details.

Model jobs and assets separately

One brief can create several outputs, while one output may be regenerated. Treating a job and an asset as the same record makes retries and audit history awkward.

type JobState =
  | "draft"
  | "validating"
  | "queued"
  | "generating"
  | "review-ready"
  | "failed"
  | "cancelled";

type ReviewState = "unreviewed" | "changes-requested" | "approved" | "rejected";

interface GeneratedAsset {
  id: string;
  jobId: string;
  revision: number;
  reviewState: ReviewState;
  changedDimension: CreativeBrief["variantDimension"];
  exportUrl?: string;
}
Enter fullscreen mode Exit fullscreen mode

exportUrl is optional because generation completion should not automatically mean approval. A product can allow preview while withholding a final export action until required review checks pass. Whether that gate is appropriate depends on the application, but representing it explicitly makes the policy testable.

Make transitions boring and predictable

A state machine is valuable when it prevents impossible UI combinations. A draft can be edited. A queued job can be cancelled. A generating job may expose progress without accepting changes that silently alter the active request. A failed job should retain enough context for a deliberate retry.

Proposed transitions:

draft -> validating -> queued -> generating -> review-ready
                     |            |              |
                     v            v              v
                 cancelled      failed     review decision
Enter fullscreen mode Exit fullscreen mode

Key invariants include:

  1. A job cannot enter queued without a product asset, prompt, supported aspect ratio, and positive output count.
  2. A retry creates a new attempt or revision rather than erasing failure history.
  3. Every asset points to the brief snapshot that produced it.
  4. Approval records the reviewer, timestamp, and checklist version.
  5. Changing the brief after generation marks existing outputs as based on an older revision.

These rules are more useful than assertions about a particular loading spinner. They survive UI redesigns.

UGC-style output needs its own review contract

The public AI UGC Ads page lists concepts including Talking Actors, Unboxing POV, and App Showcase, and describes inputs or controls for a reference image, script or prompt, aspect ratio, clarity, output count, gestures, and emotions. A proposed schema should preserve those choices, but QA must go beyond checking that a file exists.

UGC-style output can look like a personal account, so review should detect unsupported testimonial language and require a human decision. The UI can surface questions rather than pretending to solve policy automatically:

  • Does the script imply that the synthetic presenter personally bought or used the product?
  • Does it state an outcome that the brief cannot substantiate?
  • Is required disclosure present for the intended market and placement?
  • Are likeness, voice, product, music, and source-asset rights documented?
  • Does the visible demonstration actually support the spoken claim?

These checks should be versioned. Advertising rules and internal policies change; an approval tied to an unnamed checklist becomes difficult to audit later.

Test behavior at three layers

1. Schema tests

Test supported enum values, missing inputs, output-count limits, and incompatible format settings. Property-based tests are useful for combinations: for every valid brief, serialization and deserialization should preserve the controlled variable.

2. Transition tests

Feed events into a pure reducer and assert the resulting state. Examples:

  • GENERATE from an invalid draft returns validation errors.
  • JOB_SUCCEEDED from generating creates review-ready assets.
  • APPROVE from unreviewed requires a completed checklist.
  • stale completion events cannot overwrite a newer job revision.
  • cancelling a terminal job is rejected or safely idempotent.

3. Interface tests

Use browser automation to verify user-visible contracts, but keep selectors semantic. The following is illustrative, not a verified selector contract for any public site:

test("a missing product asset blocks generation", async ({ page }) => {
  await page.getByRole("textbox", { name: /prompt/i }).fill("Show the product in a morning routine");
  await page.getByRole("button", { name: /generate/i }).click();
  await expect(page.getByRole("alert")).toContainText(/product image|required/i);
});
Enter fullscreen mode Exit fullscreen mode

Also test keyboard navigation, focus after validation, accessible job-status announcements, mobile preview sizing, and whether a refresh restores the current job without duplicate submission.

Design regeneration as a new decision

“Regenerate” sounds like a single action, but it may mean at least three things: rerun the same brief, edit one field and create a revision, or branch from an existing asset. The interface should name the difference.

For controlled creative testing, branching is particularly useful. A reviewer can keep the body and CTA fixed while changing only the hook. The system can display a small diff of the brief, allowing the exported assets to carry a machine-readable description of what changed.

This turns asset history into a creative experiment graph rather than a pile of filenames. It also lets downstream teams group results correctly.

The product boundary still matters

Nextify presents the supplied ad-generation pages inside a broader creative-tool site, but public pages cannot tell us how persistence, rendering, moderation, retries, or analytics are implemented. Those are questions for hands-on testing and internal documentation.

The design lesson is independent of any one implementation: make inputs explicit, separate jobs from outputs, preserve revisions, gate approval with human review, and test transitions as carefully as rendering. An AI creative tool becomes dependable when every button corresponds to a clear state change—and when a generated asset is treated as a candidate, not an unquestioned result.

Top comments (0)