<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zhe</title>
    <description>The latest articles on DEV Community by Zhe (@zhebuildsthings).</description>
    <link>https://dev.to/zhebuildsthings</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4082397%2F96de2550-29f6-4687-b77a-c32c962f5609.jpg</url>
      <title>DEV Community: Zhe</title>
      <link>https://dev.to/zhebuildsthings</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zhebuildsthings"/>
    <language>en</language>
    <item>
      <title>Designing an LLM Leaderboard That Can Survive Change</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:16:28 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/designing-an-llm-leaderboard-that-can-survive-change-4p7o</link>
      <guid>https://dev.to/zhebuildsthings/designing-an-llm-leaderboard-that-can-survive-change-4p7o</guid>
      <description>&lt;h1&gt;
  
  
  Designing an LLM Leaderboard That Can Survive Change
&lt;/h1&gt;

&lt;p&gt;An LLM comparison page is easy to sketch and hard to keep honest. The first version usually has a table, a score column, and a sort button. The second version has multiple model families, benchmark updates, pricing changes, provider outages, and users asking why two “top” models disagree.&lt;/p&gt;

&lt;p&gt;This is an interface-level design memo, not a description of private implementation. The goal is to make the data contract visible enough that a product team can discuss it, test it, and revise it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a signal taxonomy
&lt;/h2&gt;

&lt;p&gt;The first design decision is not the color of the score badge. It is deciding which signals belong together and which must remain separate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ks274jux19zszcdvizy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ks274jux19zszcdvizy.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The public &lt;a href="https://www.llmboard.ai/" rel="noopener noreferrer"&gt;LLMBoard AI&lt;/a&gt; homepage is a useful reference point because it exposes separate paths for overall capability, coding, reasoning, math, knowledge, instruction following, pricing, speed, provider reliability, and benchmark detail. That separation suggests a simple taxonomy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;capability signals describe what a model did on a defined task family;&lt;/li&gt;
&lt;li&gt;deployment signals describe cost, latency, availability, and operating constraints;&lt;/li&gt;
&lt;li&gt;evidence signals describe the benchmark, date, coverage, and measurement scope;&lt;/li&gt;
&lt;li&gt;catalog metadata describes the model identity, provider, modality, and status.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these are collapsed into one score too early, the interface becomes persuasive but difficult to audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model a row as a record, not a number
&lt;/h2&gt;

&lt;p&gt;A leaderboard row needs more than the fields model_name and score. A useful conceptual record might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stable model identifier;&lt;/li&gt;
&lt;li&gt;provider and model family;&lt;/li&gt;
&lt;li&gt;evaluation category and benchmark name;&lt;/li&gt;
&lt;li&gt;score and metric unit;&lt;/li&gt;
&lt;li&gt;evaluation date and freshness status;&lt;/li&gt;
&lt;li&gt;price basis and currency, when price is shown;&lt;/li&gt;
&lt;li&gt;runtime measurement scope, when speed is shown;&lt;/li&gt;
&lt;li&gt;source or methodology reference;&lt;/li&gt;
&lt;li&gt;confidence or comparability note.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters when a provider renames a model, a benchmark changes version, or a price is quoted per million tokens in one place and per request in another. The UI should not make two records look identical when the measurement contracts differ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the comparison state explicit
&lt;/h2&gt;

&lt;p&gt;Users should be able to answer: “What exactly am I looking at?” A compact state summary near the table can show the selected modality, task category, benchmark, date range, price basis, and sort field.&lt;/p&gt;

&lt;p&gt;The phrase &lt;a href="https://www.llmboard.ai/" rel="noopener noreferrer"&gt;LLM Leaderboard&lt;/a&gt; is often treated as if it names one universal table. In product design, it is better treated as a family of views sharing a model catalog. The selected view should be part of the URL or a restorable filter state so a reader can share the same comparison with a teammate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for disagreement between views
&lt;/h2&gt;

&lt;p&gt;The most informative user moment is often not when one model wins every table. It is when the leader changes by task, price, or speed. Instead of hiding that disagreement, make it readable.&lt;/p&gt;

&lt;p&gt;Useful interface patterns include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a comparison drawer that keeps the selected models while the user changes the benchmark;&lt;/li&gt;
&lt;li&gt;a “why this row?” panel showing the metric, date, and source;&lt;/li&gt;
&lt;li&gt;a freshness badge that distinguishes current, aging, and unavailable data;&lt;/li&gt;
&lt;li&gt;a separate deployment view so price and latency do not masquerade as capability;&lt;/li&gt;
&lt;li&gt;a clear empty state when models cannot be compared under the same conditions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Test the failure states first
&lt;/h2&gt;

&lt;p&gt;The happy path is a sorted table. The product quality appears in the awkward cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a benchmark has no score for a model;&lt;/li&gt;
&lt;li&gt;two records use different versions of the same benchmark;&lt;/li&gt;
&lt;li&gt;the provider changes a model ID;&lt;/li&gt;
&lt;li&gt;an official price is missing or has a different unit;&lt;/li&gt;
&lt;li&gt;a runtime number was measured under a different context length;&lt;/li&gt;
&lt;li&gt;a user opens a shared link after the dataset has changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each case, write the expected user-visible behavior before writing UI copy. “Not available” is different from zero. “Not comparable” is different from a low rank. “Last updated” is not the same as “verified today.”&lt;/p&gt;

&lt;h2&gt;
  
  
  A small review contract
&lt;/h2&gt;

&lt;p&gt;Before shipping a new ranking view, ask four questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can a reader identify the task and metric without opening documentation?&lt;/li&gt;
&lt;li&gt;Can a reviewer find the measurement date and source?&lt;/li&gt;
&lt;li&gt;Can a model be removed or renamed without changing historical meaning?&lt;/li&gt;
&lt;li&gt;Can the team explain what the score does not measure?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to any question is no, the interface needs more context, not a brighter badge.&lt;/p&gt;

&lt;p&gt;One final engineering habit helps: keep the raw measurement and the editorial presentation separate. A card may show a rounded score for scanning, while the detail view preserves the original unit, source, and timestamp. That makes a redesign less likely to rewrite the meaning of an old result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;An LLM leaderboard is a data product. Its credibility comes from the boundaries around each number: what was measured, when, under which conditions, and how the result should be used. Design those boundaries as carefully as the ranking itself, and the page can remain useful even as models and benchmarks keep moving.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ux</category>
      <category>productdesign</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Ranking Page Is a Decision Interface, Not a Database</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Fri, 04 Sep 2026 02:40:51 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/a-ranking-page-is-a-decision-interface-not-a-database-413m</link>
      <guid>https://dev.to/zhebuildsthings/a-ranking-page-is-a-decision-interface-not-a-database-413m</guid>
      <description>&lt;p&gt;A Ranking Page Is a Decision Interface, Not a Database&lt;/p&gt;

&lt;p&gt;An AI-tool directory can be implemented as a collection of cards. A useful one behaves more like a decision interface.&lt;/p&gt;

&lt;p&gt;That distinction changes the data model, the UI states, and the tests. A card that says “best” without an audience, task, date, or limitation is not a recommendation; it is an unscoped label. The engineering problem is to help a reader move from a broad query to a defensible shortlist without hiding uncertainty behind a score.&lt;/p&gt;

&lt;p&gt;This is an interface-level design memo. It uses public page behavior and common directory requirements as a starting point; it does not claim access to any private codebase or internal ranking pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the unit of comparison
&lt;/h2&gt;

&lt;p&gt;The first mistake is treating a tool as the only entity. A directory actually compares a tool in a context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ToolCandidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bestFor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;strengths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;limitations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;pricingSignal&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lastChecked&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bestFor&lt;/code&gt; and &lt;code&gt;workflow&lt;/code&gt; are not decorative copy. They prevent a broad platform from being compared as if it were a narrow utility. &lt;code&gt;limitations&lt;/code&gt;keeps the page useful after the first click. &lt;code&gt;lastChecked&lt;/code&gt;gives the publisher a reason to revisit a volatile claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate ranking from filtering
&lt;/h2&gt;

&lt;p&gt;Readers arrive with different intents. One wants an image workflow; another wants a research assistant; a third wants a tool that fits a small budget. A single global order creates false precision.&lt;/p&gt;

&lt;p&gt;Use two layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Filtering&lt;/strong&gt;: reduce the catalog by category, audience, task, access, or integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ranking&lt;/strong&gt;: order the remaining candidates using an explained editorial method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This lets the interface say, “Here are strong options for your job,” rather than implying that number four is universally worse than number one.&lt;/p&gt;

&lt;p&gt;The public category pages on &lt;a href="https://www.best10.ai/" rel="noopener noreferrer"&gt;Best10.ai&lt;/a&gt; illustrate the editorial pattern: products are grouped into practical categories and discussed through workflow fit, intended audience, capability notes, pricing signals, and limitations. Those fields give a frontend more meaningful states than a score badge alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the comparison contract explicit
&lt;/h2&gt;

&lt;p&gt;The page should answer four questions before the reader opens a vendor site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What job is this candidate meant to support?&lt;/li&gt;
&lt;li&gt;Who is likely to benefit from that fit?&lt;/li&gt;
&lt;li&gt;What should be checked before committing?&lt;/li&gt;
&lt;li&gt;When was the volatile information last reviewed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That contract can be represented as a view model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ComparisonRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolCandidate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;matchReasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;checkBeforeChoosing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editorial&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-reported&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;confidence&lt;/code&gt; field is intentionally modest. It distinguishes a publisher’s editorial synthesis from a verified external fact or an unknown that needs checking. Do not turn it into a percentage unless there is a reproducible method behind the number.&lt;/p&gt;

&lt;p&gt;Use search intent as an input state&lt;/p&gt;

&lt;p&gt;The phrase &lt;a href="https://www.best10.ai/" rel="noopener noreferrer"&gt;best AI tools&lt;/a&gt; is not one intent. A search box, category route, or article should preserve the qualifier that made the query useful: for students, for coding, for presentations, for free access, or for a particular workflow.&lt;/p&gt;

&lt;p&gt;If a user removes the qualifier, show a prompt to add one instead of silently presenting an arbitrary universal ranking. A good empty state can ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What are you trying to finish, and what constraint matters most?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Possible choices include output type, experience level, budget, collaboration, privacy, and export needs. These inputs do not have to become a complex recommender. Even a static category link is better than pretending the system knows the user’s priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the states users actually encounter
&lt;/h2&gt;

&lt;p&gt;Completed cards are the easy screenshot. The harder states are the ones that protect trust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a category has too few current entries;&lt;/li&gt;
&lt;li&gt;a pricing signal is missing or stale;&lt;/li&gt;
&lt;li&gt;a vendor link returns an error;&lt;/li&gt;
&lt;li&gt;a tool changes category after an update;&lt;/li&gt;
&lt;li&gt;a limitation is longer than the headline benefit;&lt;/li&gt;
&lt;li&gt;two candidates share the same name;&lt;/li&gt;
&lt;li&gt;a user filters to a group with no result;&lt;/li&gt;
&lt;li&gt;an editorial note has an unknown confidence level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI should fail locally. A broken vendor link should not make the whole comparison page unusable. A missing price should read “check vendor pricing,” not a guessed zero. A stale review should be queued for review rather than quietly presented as current.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep promotional pressure out of the schema
&lt;/h2&gt;

&lt;p&gt;A directory becomes harder to trust when sponsored placement and editorial order are indistinguishable. If commercial relationships exist, represent them explicitly in data and display. Do not let payment silently mutate the meaning of “best.”&lt;/p&gt;

&lt;p&gt;Likewise, avoid a conversion event that requires the reader to accept a ranking before seeing the caveat. The page can offer a vendor link, but the decision should remain inspectable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small QA checklist
&lt;/h2&gt;

&lt;p&gt;For each category page, verify:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every ranking has a stated audience or job.&lt;/li&gt;
&lt;li&gt;Each card contains at least one limitation or a clear unknown.&lt;/li&gt;
&lt;li&gt;Volatile claims have a review date or a recheck instruction.&lt;/li&gt;
&lt;li&gt;Filters preserve the user’s context when the URL changes.&lt;/li&gt;
&lt;li&gt;Empty, stale, and broken-link states are readable on mobile.&lt;/li&gt;
&lt;li&gt;Screen readers receive the candidate name before its score or badge.&lt;/li&gt;
&lt;li&gt;A score never claims external verification unless a separate verification process exists.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The engineering goal is not to expose every internal editorial decision. It is to expose enough context that a reader can make the next decision responsibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this model ages better
&lt;/h2&gt;

&lt;p&gt;Tool catalogs change. A context-rich record can survive a feature rename because the underlying question—what job, for whom, with what trade-off—remains stable. A score-only record becomes misleading as soon as the market shifts.&lt;/p&gt;

&lt;p&gt;Build the page as a map of decisions, not a warehouse of names. The result will be more useful to readers and easier for the content team to maintain.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>productdesign</category>
      <category>ux</category>
    </item>
    <item>
      <title>Designing an AI YouTube Thumbnail Workflow That Keeps Its Context</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Wed, 02 Sep 2026 09:56:31 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/designing-an-ai-youtube-thumbnail-workflow-that-keeps-its-context-4mg5</link>
      <guid>https://dev.to/zhebuildsthings/designing-an-ai-youtube-thumbnail-workflow-that-keeps-its-context-4mg5</guid>
      <description>&lt;p&gt;A video-thumbnail interface can look like one upload button and one prompt field. The product behavior behind those controls is a sequence of contracts: an image is accepted, a concept is interpreted, a style is selected, candidates are generated, and a creator decides whether the result represents the video.&lt;/p&gt;

&lt;p&gt;This memo is based on a public interface and general engineering patterns. It does not describe a private API, model, or production implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the source from the brief
&lt;/h2&gt;

&lt;p&gt;An uploaded frame is evidence. A prompt is intent. A style is a visual constraint. Keep them separate so the user can correct one without losing the others.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;youtubeLink&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;concept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ThumbnailBrief&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gaming&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;podcast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tutorial&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vlog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;other&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;style&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;emotion&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;calm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;curious&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shocked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hype&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shortText&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;aspect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;16:9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;9:16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mustAvoid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public &lt;a href="https://www.thumbs.ai/ai-youtube-thumbnail-maker" rel="noopener noreferrer"&gt;AI YouTube Thumbnail Maker&lt;/a&gt; page visibly supports an image upload or YouTube-link start, a written prompt, style selection, emotional cues, multiple variations, and 16:9 or 9:16 layouts. It lists PNG, JPG, JPEG, and WebP uploads up to 4 MB. These are useful observed inputs; they do not reveal the internal generation pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate inputs without erasing intent
&lt;/h2&gt;

&lt;p&gt;A file validator should return a row-level error for unsupported format or size. It should not clear the subject, action, or text that a creator has already written.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;InputState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;empty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;validating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ThumbnailBrief&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ThumbnailBrief&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ThumbnailBrief&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The distinction between source and brief also helps with YouTube links. A link may provide context for a video, while the creator’s brief specifies which moment to emphasize. Treating the URL itself as a complete creative instruction invites generic results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve provenance through every transformation
&lt;/h2&gt;

&lt;p&gt;Store the source kind, upload name, prompt revision, style choice, emotion choice, aspect ratio, and candidate identifier. This is not a hidden model log; it is a user-readable change history. A creator should be able to answer “which frame led to this cover?” without opening an old browser tab.&lt;/p&gt;

&lt;p&gt;When a candidate uses a generated face treatment, label it as an edit. When an image is upscaled, keep the original available for comparison. When a background is replaced, record that the new setting is creative direction rather than recorded evidence. These distinctions are especially helpful when an editor hands the asset to a client or another team.&lt;/p&gt;

&lt;p&gt;The contract can also include a review deadline and an approval owner. That prevents an unreviewed candidate from being mistaken for the version ready to publish, even if its file name says final.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep batch work observable
&lt;/h2&gt;

&lt;p&gt;Batch input changes the user’s expectations. If ten URLs are pasted, the interface should show ten rows, not one spinner followed by a mixed folder. Each row can expose the normalized video identity, the quality attempted, the preview state, and the next action. A partial result is useful when it is labeled; a silent omission is not.&lt;/p&gt;

&lt;p&gt;Cancellation deserves a specific contract too. When a user starts a second request, the first request must not overwrite the current brief or candidate list. Attach a request identifier to every result and discard responses that are no longer current. This is a small implementation detail with a large effect on trust.&lt;/p&gt;

&lt;p&gt;Finally, keep user-facing language precise. “Source unavailable,” “lower-resolution source found,” and “preview ready” each describe a different state. Avoid collapsing them into “success” merely to make the dashboard look cleaner.&lt;/p&gt;

&lt;p&gt;Observability also applies to the creative brief itself. Show the normalized subject, event, and proof next to every candidate so a reviewer can see whether a variation changed the story or only the styling. If the prompt is revised, increment its version and keep the earlier candidate linked to the earlier text. That makes a disagreement concrete: the team can point to a prompt revision, a crop change, or an approval decision instead of arguing from memory.&lt;/p&gt;

&lt;p&gt;For a production queue, emit structured events such as sourceAccepted, briefUpdated, candidateReady, candidateRejected, and exportCompleted. These events can feed a small activity panel without exposing model internals. They also make support conversations easier because a creator can report where the workflow stopped. A recoverable error should offer the next action—replace the file, edit the link, retry the candidate, or return to review—rather than a generic red banner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make variations comparable
&lt;/h2&gt;

&lt;p&gt;“Generate more” is not a useful experiment unless the system records what changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;VariationSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subjectScale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;emotion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textTreatment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;ThumbnailBrief&lt;/span&gt;&lt;span class="p"&gt;)[];&lt;/span&gt;
  &lt;span class="nl"&gt;candidateIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the axis is emotion, keep the source, subject, and text stable. If the axis is aspect, compare 16:9 and 9:16 while preserving the focal subject and safe area. Candidate review then produces information instead of an unstructured preference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model review and export separately
&lt;/h2&gt;

&lt;p&gt;A candidate can be visible without being approved. It can be approved without being downloaded. Use states that make the distinction explicit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReviewState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reviewing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;needsRevision&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exporting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;candidateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exported&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;candidateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The review screen should ask whether the subject appears in the video, whether the text adds information, whether the generated expression is clearly a design treatment, and whether the image is usable for the intended platform. UI safe zones are useful layout guidance, not proof that every platform crops content identically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test recovery paths
&lt;/h2&gt;

&lt;p&gt;Illustrative tests should cover invalid files, cancelled requests, stale responses, empty prompts, and a candidate that fails the content check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid image preserves the prepared brief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/ai-youtube-thumbnail-maker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speaker holding the prototype&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Source image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setInputFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fixtures/too-large.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toHaveValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speaker holding the prototype&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The labels are illustrative contracts, not verified selectors from the public page. Also test keyboard-only review, aspect-ratio switching, one candidate rejected while others remain, and export retry.&lt;/p&gt;

&lt;p&gt;When a creator moves from a video brief into original design work, &lt;a href="https://www.thumbs.ai/" rel="noopener noreferrer"&gt;Thumbs.ai&lt;/a&gt; is the broader workspace context. The system should carry the permitted source and explicit brief while making generated edits distinguishable from source evidence.&lt;/p&gt;

&lt;p&gt;The core engineering lesson is modest: a thumbnail pipeline becomes easier to trust when every creative choice has a visible state, a recoverable error, and a review question.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>ux</category>
      <category>ai</category>
    </item>
    <item>
      <title>Designing a Recoverable GTA 5 Thumbnail Brief Instead of a Mega-Prompt</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Thu, 27 Aug 2026 01:59:12 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/designing-a-recoverable-gta-5-thumbnail-brief-instead-of-a-mega-prompt-4mjj</link>
      <guid>https://dev.to/zhebuildsthings/designing-a-recoverable-gta-5-thumbnail-brief-instead-of-a-mega-prompt-4mjj</guid>
      <description>&lt;h1&gt;
  
  
  Designing a Recoverable GTA 5 Thumbnail Brief Instead of a Mega-Prompt
&lt;/h1&gt;

&lt;p&gt;“Make a cinematic GTA 5 police-chase thumbnail with my custom car” sounds like a useful request. It is a poor application contract. It mixes video type, source evidence, featured subject, location, mood, text, and constraints into one blob. If the result contains the wrong vehicle or invents an event, the UI has no precise way to preserve what was right and repair what was wrong.&lt;/p&gt;

&lt;p&gt;This is a public-UI design exercise, not a description of any private API or implementation. The point is to turn a creative request into state that can be validated, resumed, compared, and reviewed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate source evidence from the communication brief
&lt;/h2&gt;

&lt;p&gt;The raw screenshot and the intended story answer different questions. Model both.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;templateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gameplayImage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;capturedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;descriptionOnly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Brief&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roleplay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;race&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;carMeet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;funny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;character&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vehicle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;location&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shortText&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mustPreserve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;mustAvoid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Source&lt;/code&gt; answers “what can this composition draw from?” &lt;code&gt;Brief&lt;/code&gt; answers “what must the viewer understand?” The split lets a creator replace a blurry frame without losing the statement that the featured object is a black Sultan RS, the action is a bridge escape, and the event is fictional gameplay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make template switching explicit
&lt;/h2&gt;

&lt;p&gt;The public &lt;a href="https://www.thumbs.ai/gta-5-thumbnail" rel="noopener noreferrer"&gt;GTA 5 thumbnail&lt;/a&gt; page offers scene directions for RP, live streams, casino heists, bank robberies, car meets, funny moments, story mode, police chases, racing, parkour, trolling, and 1v1 videos. It also visibly invites an uploaded image or concept and lists image-format limits.&lt;/p&gt;

&lt;p&gt;Rather than treating these as decorative choices, a UI can give each template a contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;supports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Brief&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][];&lt;/span&gt;
  &lt;span class="nl"&gt;requiredSlots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pressure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;proof&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[];&lt;/span&gt;
  &lt;span class="nl"&gt;optionalSlots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;route&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opponent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[];&lt;/span&gt;
  &lt;span class="nl"&gt;safeTextAreas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switching from &lt;code&gt;chase&lt;/code&gt; to &lt;code&gt;carMeet&lt;/code&gt; should preserve the source image and featured vehicle, but warn that a &lt;code&gt;route&lt;/code&gt; constraint may no longer have meaning. Silent field loss is the kind of failure that makes creator tools feel unpredictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generation has more than two states
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;isLoading&lt;/code&gt; is too small for an upload-and-review workflow. Model recovery deliberately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EditorState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;choosingSource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editingBrief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Source&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;validatingUpload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Brief&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Brief&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reviewing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;candidateIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Brief&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recoverableError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;resumeAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exportReady&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;candidateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The visible page states that PNG, JPG, JPEG, and WebP uploads are accepted up to 4 MB. A rejected upload should return the user to source selection while retaining the written brief. A cancelled request must invalidate its &lt;code&gt;requestId&lt;/code&gt;, so a late response cannot replace a newer set of candidates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review semantic accuracy before visual preference
&lt;/h2&gt;

&lt;p&gt;File validation is deterministic. Whether the image truthfully packages the video requires a human confirmation step. Use structured review questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the character, car, or featured object actually in the footage?&lt;/li&gt;
&lt;li&gt;Does the implied event match the episode, rather than a more dramatic hypothetical?&lt;/li&gt;
&lt;li&gt;Does the map or location help identify the scene?&lt;/li&gt;
&lt;li&gt;Is the short text a useful hook rather than a duplicate of the title?&lt;/li&gt;
&lt;li&gt;Can the subject and action be named at a small preview size?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answers are not a claim detector. They form a release checklist and create useful revision reasons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RejectionReason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wrong_vehicle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unclear_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text_unreadable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;misleading_event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;identity_lost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CandidateSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;variation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subjectScale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textTreatment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;focalPoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;constantFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Brief&lt;/span&gt;&lt;span class="p"&gt;)[];&lt;/span&gt;
  &lt;span class="nl"&gt;candidateIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Candidate sets should vary one dimension at a time. “Try again” becomes less useful than “keep the car, change the crop, and remove text.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the boundaries, not only the happy path
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid upload preserves a prepared brief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/gaming-thumbnail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;escape the bridge roadblock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Featured vehicle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;black Sultan RS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Source image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setInputFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fixtures/too-large.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toHaveValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;escape the bridge roadblock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also test stale requests, template switches, narrow review cards, keyboard flow, useful image alternatives, and duplicate export attempts. Do not make an assurance about clicks, reach, or performance part of the success condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persist decisions without turning them into hidden prompts
&lt;/h2&gt;

&lt;p&gt;Creative tools often save only the final image. That makes a future revision needlessly opaque. Save a small, inspectable record alongside the candidate: the selected source ID, template ID, normalized brief, variation axis, rejection reasons, and export timestamp. The record should contain user-visible constraints, not a secret model transcript that a creator cannot review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DecisionRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;templateId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Brief&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;selectedCandidateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;rejected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;candidateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RejectionReason&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
  &lt;span class="nl"&gt;chosenBecause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes “reuse the last car-meet look” a legitimate product action. The UI can offer the previous text treatment and color system while asking the user to choose a new subject and proof detail. It can also prevent accidental reuse of an old screenshot by surfacing its capture time and source label.&lt;/p&gt;

&lt;p&gt;Permission checks belong in this record as well. A boolean cannot establish ownership, but an explicit acknowledgement—“I may use this source image”—keeps the decision visible at the moment of export. The same review surface can remind a user that a fictional gameplay image should not be framed as real-world evidence.&lt;/p&gt;

&lt;p&gt;Versioning matters when a creator returns after publishing. Exports should receive a new version rather than mutate an approved candidate. That makes it possible to compare a later crop or corrected text treatment with the original decision and to roll back without reconstructing the whole brief from scratch.&lt;/p&gt;

&lt;p&gt;Keep the version label visible in the review grid, export filename, and decision record so collaboration never depends on memory alone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.thumbs.ai/" rel="noopener noreferrer"&gt;Thumbs.ai&lt;/a&gt; is the wider creation context when a user leaves a game-specific template family. The important product behavior is continuity: preserve their permitted source and the brief, while removing only GTA-specific assumptions.&lt;/p&gt;

&lt;p&gt;The design goal is not a longer prompt. It is a creator-controlled sequence of choices that survives errors, makes candidates comparable, and keeps the visual promise tied to the actual video.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>ux</category>
    </item>
    <item>
      <title>Designing a Style-Preset Thumbnail Flow Without Building a Clone Button</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Tue, 25 Aug 2026 10:17:06 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/designing-a-style-preset-thumbnail-flow-without-building-a-clone-button-2g5m</link>
      <guid>https://dev.to/zhebuildsthings/designing-a-style-preset-thumbnail-flow-without-building-a-clone-button-2g5m</guid>
      <description>&lt;h1&gt;
  
  
  Designing a Style-Preset Thumbnail Flow Without Building a Clone Button
&lt;/h1&gt;

&lt;p&gt;“Make it look like this creator” sounds like a simple product requirement. In an interface, it is not. The request bundles several different jobs: identify a composition, preserve the user’s subject, infer an emotional direction, generate variations, and prevent the result from becoming a copy of someone else’s identity.&lt;/p&gt;

&lt;p&gt;This post is a black-box UX exercise based on visible controls, not a description of any company’s private implementation. The goal is to design a flow that treats a creator-style preset as a structural starting point rather than a one-click clone button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the inputs by responsibility
&lt;/h2&gt;

&lt;p&gt;A reference image, a YouTube URL, a selfie, a template, and a text prompt should not collapse into one untyped payload. Each input has a different role and failure mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;templateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;emotion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;surprised&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;excited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;focused&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;custom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Brief&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;premise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;focalObject&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mustAvoid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The separation makes the UI easier to explain. The reference supplies structure. The subject supplies the person who belongs in the thumbnail. The brief supplies the video-specific story. &lt;code&gt;mustAvoid&lt;/code&gt; gives users a place to state boundaries such as “no logos,” “no copied face,” or “do not add money.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the workflow visible
&lt;/h2&gt;

&lt;p&gt;The public &lt;a href="https://www.thumbs.ai/mrbeast-thumbnail-generator" rel="noopener noreferrer"&gt;Mrbeast thumbnail&lt;/a&gt; page shows several template options, reference upload and YouTube-link routes, a selfie input, emotional directions, and generation of multiple versions. That suggests a useful staged flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose the structural reference.&lt;/li&gt;
&lt;li&gt;Add the user-owned subject.&lt;/li&gt;
&lt;li&gt;Describe the actual video premise.&lt;/li&gt;
&lt;li&gt;Generate candidates.&lt;/li&gt;
&lt;li&gt;Review for clarity and originality.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not hide all five stages behind one empty text box. Progressive disclosure reduces uncertainty, and each stage can display a specific recovery action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EditorState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;choosingReference&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addingSubject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Reference&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;writingBrief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Reference&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reviewing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Candidate&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recoverableError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;retryFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Step&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why raw TypeScript should stay inside fenced blocks on DEV.to. Without the fence, the browser can interpret angle brackets or collapse indentation, turning a useful model into broken prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate references without pretending they are permissions
&lt;/h2&gt;

&lt;p&gt;An uploaded image should be validated for type and size before submission. A URL should be parsed with the platform’s URL API, not split with a fragile string expression. But technical acceptance is not legal or ethical permission.&lt;/p&gt;

&lt;p&gt;Add plain-language copy beside the reference field: “Use images you own or are allowed to reference. The reference guides composition; it does not grant rights to names, faces, logos, or artwork.” This is more useful than burying the boundary in terms no one sees during creation.&lt;/p&gt;

&lt;p&gt;For creator-named presets, state that no affiliation is implied. The generated output should be reviewed for accidental likenesses and protected branding, especially when the reference contains a famous face.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep errors attached to the failed step
&lt;/h2&gt;

&lt;p&gt;Avoid a single &lt;code&gt;error&lt;/code&gt; state. Users need different actions for an unsupported file, an unreachable URL, an empty brief, and a generation timeout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ErrorCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unsupported_file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file_too_large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid_reference_url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brief_too_vague&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generation_timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;candidate_unavailable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An unsupported file should return the user to upload. A vague brief should preserve the reference and subject while highlighting the premise field. A timeout should offer retry without duplicating the request. Store a client request ID and ignore late responses after the user begins a new generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review candidates against a rubric
&lt;/h2&gt;

&lt;p&gt;Four visually different outputs are not necessarily four useful alternatives. Present each candidate with the same review questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the focal subject recognizable at mobile size?&lt;/li&gt;
&lt;li&gt;Does the image express the real premise of the video?&lt;/li&gt;
&lt;li&gt;Does text add information instead of repeating the title?&lt;/li&gt;
&lt;li&gt;Are important elements away from vulnerable edges?&lt;/li&gt;
&lt;li&gt;Does the design contain the user’s own identity?&lt;/li&gt;
&lt;li&gt;Does it accidentally reproduce a real person, logo, or distinctive artwork?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Allow rejection reasons. “Face changed,” “wrong object,” and “too similar to reference” can guide a targeted revision. The product should never imply that a preset guarantees clicks or virality; performance depends on the video, audience, packaging, distribution, and many other factors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export only after the boundary check
&lt;/h2&gt;

&lt;p&gt;Before export, show a compact confirmation: the user owns or can use uploaded assets; names and logos have been reviewed; the thumbnail represents the video accurately; and the candidate has been checked at small size. This is not a substitute for policy enforcement, but it puts the decision at the moment it matters.&lt;/p&gt;

&lt;p&gt;If the user wants to move beyond one named preset, &lt;a href="https://www.thumbs.ai/" rel="noopener noreferrer"&gt;Thumbs.ai&lt;/a&gt; provides the wider creation context. In product architecture, keep the generic workspace distinct from a creator-style entry page so the user can graduate from reference-driven exploration into a reusable channel system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test transitions, not screenshots
&lt;/h2&gt;

&lt;p&gt;The highest-value tests exercise state recovery. Upload a valid selfie, fail the reference URL, fix it, and confirm the selfie remains. Start generation, cancel, and verify that a late response cannot replace the current state. Reject a candidate for similarity, revise the brief, and confirm the rejection reason is carried forward.&lt;/p&gt;

&lt;p&gt;A style preset is useful when it teaches structure and accelerates exploration. It becomes risky when the interface treats identity as a filter. Explicit input roles, recoverable state, visible originality checks, and honest performance language keep the workflow on the productive side of that line.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>typescript</category>
      <category>ux</category>
    </item>
    <item>
      <title>Designing a Multi-Model Answer Grid Without Hiding Uncertainty</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Mon, 24 Aug 2026 09:59:53 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/designing-a-multi-model-answer-grid-without-hiding-uncertainty-57e6</link>
      <guid>https://dev.to/zhebuildsthings/designing-a-multi-model-answer-grid-without-hiding-uncertainty-57e6</guid>
      <description>&lt;p&gt;A multi-model interface sounds simple: send one prompt, render several answers, and let the user compare them. The difficult part is not the columns. It is representing independent, asynchronous work without implying that agreement equals correctness.&lt;/p&gt;

&lt;p&gt;This article sketches an interface contract for that problem. It is an illustrative design exercise based on public behavior, not a description of any private implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model each panel independently
&lt;/h2&gt;

&lt;p&gt;If three providers receive a prompt, they will not finish together. One may stream immediately, one may pause, and one may fail. A single global loading boolean cannot represent those outcomes.&lt;/p&gt;

&lt;p&gt;Start with a per-panel state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PanelStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streaming&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancelled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AnswerPanel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;panelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;modelLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PanelStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;completedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The collection can now contain two complete answers and one error without pretending that the entire request failed. The UI can preserve readable results while offering a retry only on the failed panel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep prompt identity outside the panels
&lt;/h2&gt;

&lt;p&gt;Comparison is meaningful only when the displayed answers belong to the same prompt version. Store the prompt as an immutable turn record and associate every panel with that turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ComparisonTurn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;turnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;panels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnswerPanel&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user edits the prompt, create a new turn. Do not silently replace the text above old responses. A tiny “edited” badge is not enough when the main purpose is comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design the grid for partial progress
&lt;/h2&gt;

&lt;p&gt;The grid should answer four questions at a glance:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which model is represented by this panel?&lt;/li&gt;
&lt;li&gt;Is it still working?&lt;/li&gt;
&lt;li&gt;Is the visible text complete?&lt;/li&gt;
&lt;li&gt;What action is available next?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skeleton loaders are poor once real tokens exist. Render the available text, retain a visible streaming indicator, and announce status changes to assistive technology without reading every token aloud. When a response completes, move focus only if the user initiated an action that requires it.&lt;/p&gt;

&lt;p&gt;On narrow screens, columns become stacked cards. Preserve the shared prompt above them and keep model labels sticky or repeated. “Answer 1” and “Answer 2” are insufficient after cards move vertically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid a false consensus feature
&lt;/h2&gt;

&lt;p&gt;It is tempting to count matching phrases or display “3/3 models agree.” That indicator can overstate what the system knows. Similar wording is not independent fact checking, and different wording is not necessarily substantive disagreement.&lt;/p&gt;

&lt;p&gt;A safer comparison layer helps users inspect rather than declares a verdict. Possible features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manual highlighting of claims;&lt;/li&gt;
&lt;li&gt;a user-created verification checklist;&lt;/li&gt;
&lt;li&gt;per-answer copy controls that retain the model label;&lt;/li&gt;
&lt;li&gt;a prompt for “What assumption differs across these answers?”;&lt;/li&gt;
&lt;li&gt;a clear reminder to verify consequential claims with primary sources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The public multi-model chat at &lt;a href="https://www.wikis.ai/" rel="noopener noreferrer"&gt;Wikis AI&lt;/a&gt; demonstrates the core interaction: a shared prompt goes to selected models, responses stream independently in separate panels, and the reader can continue with a useful model. This public behavior is enough to motivate the state model above; it does not expose the service's internal architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate discovery from focused follow-up
&lt;/h2&gt;

&lt;p&gt;A broad question often needs orientation before conversation. A topic guide can provide the vocabulary, common distinctions, and paths for follow-up.&lt;/p&gt;

&lt;p&gt;The public &lt;a href="https://www.wikis.ai/" rel="noopener noreferrer"&gt;AI Wiki&lt;/a&gt; is presented as a collection of structured guides with source-answer comparison and follow-up options across AI topics. From a product-design perspective, that suggests two entry modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;browse mode:&lt;/strong&gt; select a topic and inspect organized perspectives;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;question mode:&lt;/strong&gt; send a specific prompt and compare live responses.
Keep those modes connected but distinct. A guide is not merely a frozen chat, and a chat is not automatically a maintained reference page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Make failure local and recoverable
&lt;/h2&gt;

&lt;p&gt;Provider errors, timeouts, and cancellations should not erase successful answers. Give each panel its own recovery action and preserve the original turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;retryPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComparisonTurn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;panelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;panels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;panels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;panelId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;panelId&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;panel&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, retry semantics also need an idempotency strategy, cancellation support, and protection against duplicated billing or duplicated events. Those responsibilities belong in the real service boundary, not in a copy-pasted client helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test transitions, not screenshots alone
&lt;/h2&gt;

&lt;p&gt;A visual snapshot of three completed panels misses the risky states. Test the timeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all panels queued;&lt;/li&gt;
&lt;li&gt;one streaming while another completes;&lt;/li&gt;
&lt;li&gt;one fails after emitting partial text;&lt;/li&gt;
&lt;li&gt;user cancels one panel;&lt;/li&gt;
&lt;li&gt;retry affects only its target;&lt;/li&gt;
&lt;li&gt;prompt editing creates a new turn;&lt;/li&gt;
&lt;li&gt;mobile stacking preserves labels and reading order;&lt;/li&gt;
&lt;li&gt;keyboard navigation reaches every panel action;&lt;/li&gt;
&lt;li&gt;status announcements are concise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also test the product language. “Compared” is accurate. “Verified” is not, unless a separate verification process actually occurred.&lt;/p&gt;

&lt;p&gt;The best multi-model grid does not choose a winner by decoration. It keeps provenance, progress, failure, and uncertainty visible enough that a person can make the next decision deliberately.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>design</category>
      <category>frontend</category>
      <category>llm</category>
    </item>
    <item>
      <title>Designing a Domain Lookup UI Around Evidence, Not a Green Badge</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Mon, 24 Aug 2026 06:17:12 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/designing-a-domain-lookup-ui-around-evidence-not-a-green-badge-35lb</link>
      <guid>https://dev.to/zhebuildsthings/designing-a-domain-lookup-ui-around-evidence-not-a-green-badge-35lb</guid>
      <description>&lt;p&gt;A domain checker looks like a tiny frontend problem: one input, one button, one result. The public interface is simple because the uncertainty has been compressed, not because it has disappeared.&lt;/p&gt;

&lt;p&gt;This article does not inspect private source code or architecture. It uses the visible behavior of a public lookup page to build a reusable design checklist for any asynchronous search UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the result as evidence with provenance
&lt;/h2&gt;

&lt;p&gt;“Available” should not be a boolean floating without context. A useful result model records what answered, what it answered about, and how confident the interface may be.&lt;/p&gt;

&lt;p&gt;At minimum, distinguish states such as idle, loading, available, registered, for-sale, needs-confirmation, and error. Preserve a source label for registry RDAP, public registration data, pricing source, or registrar checkout. If one extension fails, do not collapse the entire batch into a generic error.&lt;/p&gt;

&lt;p&gt;The public &lt;a href="https://www.wikis.ai/" rel="noopener noreferrer"&gt;Check Domain&lt;/a&gt; page describes registry-backed RDAP checks for supported extensions and separates availability, record, and cost information. That visible separation is a strong UI lesson: different evidence types deserve different components and different copy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LookupState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;requestedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;registered&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;for-sale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This type is an illustrative design proposal, not a claim about the site’s implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render a batch as progressive, independent rows
&lt;/h2&gt;

&lt;p&gt;A user may submit one label but request many extensions. Those lookups will not necessarily resolve at the same time. The page should keep completed rows stable while pending rows continue to load. Sorting must also be deliberate: dynamically moving results can make users click the wrong item.&lt;/p&gt;

&lt;p&gt;For each row, expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the full candidate domain;&lt;/li&gt;
&lt;li&gt;current lookup state;&lt;/li&gt;
&lt;li&gt;evidence source or a plain-language explanation;&lt;/li&gt;
&lt;li&gt;an action appropriate to that state;&lt;/li&gt;
&lt;li&gt;a last-checked time when freshness matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid using color as the only signal. “Available” and “registered” need visible text, and errors should explain whether retrying is reasonable. Keyboard focus must remain predictable when new rows arrive.&lt;/p&gt;

&lt;p&gt;Input handling deserves its own contract. Decide whether the field accepts a bare label, a complete domain, uppercase characters, trailing dots, or internationalized names. Normalize for lookup without silently changing what the user typed, and show the normalized candidate before any external purchase action. An explicit submit action is easier to reason about when one search fans out across many registries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep price data separate from registration state
&lt;/h2&gt;

&lt;p&gt;An available domain is not necessarily the cheapest option. A discounted first year and a higher renewal are two values with different meanings. Put them in separately labeled fields rather than combining them into one ambiguous “from” price.&lt;/p&gt;

&lt;p&gt;When building a &lt;a href="https://www.wikis.ai/" rel="noopener noreferrer"&gt;domain name search&lt;/a&gt;, the product contract should specify currency, price freshness, promotional conditions, and what happens when a registrar has no comparable quote. A missing renewal price should render as unknown, not zero.&lt;/p&gt;

&lt;p&gt;The final purchase happens outside the lookup interface. A clear boundary message—availability and price must be confirmed at registrar checkout—prevents the product from implying a reservation it cannot provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test uncertainty, not only happy paths
&lt;/h2&gt;

&lt;p&gt;The most valuable tests are rarely “typing example returns a green row.” Cover partial and changing states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One extension resolves while several remain pending.&lt;/li&gt;
&lt;li&gt;A registry times out and a retry later succeeds.&lt;/li&gt;
&lt;li&gt;A result changes between lookup and checkout.&lt;/li&gt;
&lt;li&gt;Registration price exists but renewal price is missing.&lt;/li&gt;
&lt;li&gt;A registered domain returns redacted public data.&lt;/li&gt;
&lt;li&gt;A complete domain is entered instead of a bare label.&lt;/li&gt;
&lt;li&gt;Screen-reader output announces state changes without flooding the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Illustrative Playwright pseudocode could assert stable semantics rather than guessed production selectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keeps partial results understandable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Selectors and response fixtures must be adapted to the real application.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/domain/i&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;northstar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/search/i&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/checking/i&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/confirm at registrar checkout/i&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Make the boundary part of the product
&lt;/h2&gt;

&lt;p&gt;Good uncertainty copy is not legal debris below the fold. It is a functional part of the result. Explain that registry data is time-sensitive, public records may be redacted, pricing can change, and a domain check is not trademark clearance.&lt;/p&gt;

&lt;p&gt;The frontend’s job is not to make a complicated system look certain. It is to make each source, state, and next action understandable enough that a user can decide what to verify next.&lt;/p&gt;

&lt;p&gt;That principle also improves observability. Track source-level failures without retaining unnecessary search history. Measure timeouts and unsupported extensions separately from negative results, because reliability work depends on knowing whether the registry, pricing source, network, or interface contract failed.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>ux</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Designing a Meme Template Picker That Preserves Context</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Fri, 21 Aug 2026 10:33:49 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/designing-a-meme-template-picker-that-preserves-context-561h</link>
      <guid>https://dev.to/zhebuildsthings/designing-a-meme-template-picker-that-preserves-context-561h</guid>
      <description>&lt;p&gt;A meme template picker has a difficult information-architecture problem: users recognize some formats by sight, others by name, and many only within a cultural context. A grid of images may look efficient while hiding meaning, age, source, and intended use.&lt;/p&gt;

&lt;p&gt;This article is based on public interface text. It is not a source-code, private-API, or production-architecture review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat a template as metadata plus slots
&lt;/h2&gt;

&lt;p&gt;The public &lt;a href="https://www.thumbs.ai/youtube-meme-thumbnail-maker" rel="noopener noreferrer"&gt;YouTube meme thumbnail&lt;/a&gt; page presents reaction formats, split comparisons, gaming memes, Shorts-oriented layouts, face inputs, text, emojis, and stylized effects. A frontend model should represent more than an image preview.&lt;/p&gt;

&lt;p&gt;A template may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a name and accessible description;&lt;/li&gt;
&lt;li&gt;supported aspect ratios;&lt;/li&gt;
&lt;li&gt;face, screenshot, headline, or comparison slots;&lt;/li&gt;
&lt;li&gt;a cultural-context note;&lt;/li&gt;
&lt;li&gt;source or rights information when available;&lt;/li&gt;
&lt;li&gt;compatibility with faceless or face-led workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search and filtering should use those properties rather than forcing users to understand every visual reference in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep input types separate
&lt;/h2&gt;

&lt;p&gt;A selfie upload, a gameplay screenshot, and a short headline have different validation and privacy implications. The interface should name what each asset will be used for, show upload progress, and preserve the brief when one input fails.&lt;/p&gt;

&lt;p&gt;Face-related actions deserve clear consent and review states. The product should not imply that a face swap is accurate, endorsed, or safe simply because generation completed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model selection as a reversible decision
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.thumbs.ai/" rel="noopener noreferrer"&gt;Thumbs.ai&lt;/a&gt; publicly describes creation, recreation, face-expression, style, and template tools. When a user changes templates, the UI needs a contract for compatible inputs. Does the uploaded face remain? Is the text remapped? Does a vertical layout crop the reference differently?&lt;/p&gt;

&lt;p&gt;Useful states include &lt;code&gt;browsing&lt;/code&gt;, &lt;code&gt;editingBrief&lt;/code&gt;, &lt;code&gt;uploading&lt;/code&gt;, &lt;code&gt;generating&lt;/code&gt;, &lt;code&gt;reviewing&lt;/code&gt;, &lt;code&gt;recoverableError&lt;/code&gt;, and &lt;code&gt;exporting&lt;/code&gt;. Preserve the chosen template, assets, and text across recoverable failures. If switching layouts will discard work, explain that before the transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test interpretation, not only rendering
&lt;/h2&gt;

&lt;p&gt;Automation can verify template selection, required fields, ratios, error recovery, and file export. It cannot decide whether a meme is understood by the audience or whether its context is appropriate.&lt;/p&gt;

&lt;p&gt;Questions for human review include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the visual reference support the video’s actual setup?&lt;/li&gt;
&lt;li&gt;Is the punchline dependent on an outdated or harmful context?&lt;/li&gt;
&lt;li&gt;Does the face or character still resemble the intended subject?&lt;/li&gt;
&lt;li&gt;Are text, screenshots, and labels accurate?&lt;/li&gt;
&lt;li&gt;Are source and usage rights sufficiently understood?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Avoid outcome language in the interface
&lt;/h2&gt;

&lt;p&gt;Terms such as “viral” or “high CTR” describe hoped-for outcomes, not deterministic UI states. A successful export does not prove that a thumbnail will perform. Product copy, success messages, and analytics should keep that boundary clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compact implementation checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Store template meaning and slots as structured metadata.&lt;/li&gt;
&lt;li&gt;Make previews accessible without relying on image recognition.&lt;/li&gt;
&lt;li&gt;Separate face, screenshot, text, and effect validation.&lt;/li&gt;
&lt;li&gt;Preserve compatible inputs across template changes.&lt;/li&gt;
&lt;li&gt;Support partial generation failures and retries.&lt;/li&gt;
&lt;li&gt;Show aspect ratio and crop before export.&lt;/li&gt;
&lt;li&gt;Keep cultural, legal, and editorial review outside binary automation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not merely to help users find a familiar image. It is to help them understand what will be preserved, changed, and communicated when that image becomes part of their own video context.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>productdesign</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Designing an AI UGC Ad Workflow: Five Interface Contracts to Make Explicit</title>
      <dc:creator>Zhe</dc:creator>
      <pubDate>Wed, 19 Aug 2026 06:37:46 +0000</pubDate>
      <link>https://dev.to/zhebuildsthings/designing-an-ai-ugc-ad-workflow-five-interface-contracts-to-make-explicit-22j8</link>
      <guid>https://dev.to/zhebuildsthings/designing-an-ai-ugc-ad-workflow-five-interface-contracts-to-make-explicit-22j8</guid>
      <description>&lt;p&gt;AI video tools are often described as if the interface has one job: collect a prompt and return a video. Creator-style advertising exposes why that model is too small. The user is actually coordinating a format, product evidence, presenter, script, voice, delivery, aspect ratio, and review decision.&lt;/p&gt;

&lt;p&gt;This article is a black-box analysis of a public product page. It does not describe private source code, architecture, model behavior, or production metrics. The goal is to extract reusable interface contracts that a frontend or product team can test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contract 1: Format Selection Must Change the Required Inputs
&lt;/h2&gt;

&lt;p&gt;A Talking Review, Product-in-Hand demonstration, Unboxing POV, and App Showcase are not cosmetic presets. Each implies different required evidence.&lt;/p&gt;

&lt;p&gt;For example, an App Showcase needs readable screen content, while Product-in-Hand needs a product image or asset that can be placed inside the performance. If the interface presents every format as the same form with a new thumbnail, users will discover missing requirements late.&lt;/p&gt;

&lt;p&gt;A useful UI should make three things visible when a format changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what assets are required;&lt;/li&gt;
&lt;li&gt;what the default scene structure will emphasize;&lt;/li&gt;
&lt;li&gt;what limitations or unsupported combinations apply.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The state transition should be explicit enough that a user can predict what the next screen will ask for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contract 2: Product Evidence Must Be Paired With Script Claims
&lt;/h2&gt;

&lt;p&gt;The script editor and asset picker should not behave like unrelated panels. If a line promises “easy unboxing,” the workflow needs a place to connect that claim to packaging imagery or a demonstration beat.&lt;/p&gt;

&lt;p&gt;The point is to make the contract discussable: a spoken benefit may need a visible counterpart, and the UI should help users notice when that counterpart is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contract 3: Presenter, Voice, and Delivery Are Separate Decisions
&lt;/h2&gt;

&lt;p&gt;It is tempting to collapse “creator” into one selector. In practice, the presenter’s appearance, voice source, emotional direction, and gesture choices answer different questions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.nextify.ai/" rel="noopener noreferrer"&gt;Nextify.ai&lt;/a&gt; publicly positions itself as an AI creative production platform that works with product assets, scripts, avatar video, UGC creatives, and other marketing outputs. That broader context is useful because it frames the presenter as one component of a creative workflow, not the entire product.&lt;/p&gt;

&lt;p&gt;For interface design, separate controls reduce accidental coupling. Changing the presenter should not silently reset the script. Changing the voice source should reveal any new validation requirements. Changing emotion or delivery should preserve a clear baseline so the user can compare versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contract 4: Output Framing Needs an Early Preview
&lt;/h2&gt;

&lt;p&gt;Aspect ratio is often placed at the end of a generator. That is too late for vertical social video. A 9:16 composition affects subject scale, caption width, product placement, and safe areas from the first scene.&lt;/p&gt;

&lt;p&gt;A good workflow should expose the intended frame before generation and provide at least a lightweight composition preview. If preview fidelity is limited, label it as a guide rather than implying pixel-perfect correspondence.&lt;/p&gt;

&lt;p&gt;The public &lt;a href="https://www.nextify.ai/ugc-ad-generator" rel="noopener noreferrer"&gt;ugc ads&lt;/a&gt; page describes a three-step flow: choose a creator-style setup, select or create a presenter and add the script or supported audio, then set an output such as 9:16 and generate. That sequence provides a useful starting model, but an implementation still needs to preserve state and explain dependencies across steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contract 5: Review Is a Product State, Not an Afterthought
&lt;/h2&gt;

&lt;p&gt;Generation completion should not equal campaign readiness. The interface needs a review state where the user can inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether the spoken line matches the visible product moment;&lt;/li&gt;
&lt;li&gt;whether captions are readable;&lt;/li&gt;
&lt;li&gt;whether the first seconds establish the intended hook;&lt;/li&gt;
&lt;li&gt;whether product and presenter remain inside the safe frame;&lt;/li&gt;
&lt;li&gt;whether the ad implies an unsupported testimonial or result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That final check is especially important for creator-style formats. A generated presenter should not be framed as an independent customer who personally used the product unless the evidence supports that claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Small Test Matrix
&lt;/h2&gt;

&lt;p&gt;Before shipping a workflow like this, test transitions rather than only screens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Change format after adding assets. Verify which inputs persist and which are invalidated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change presenter after editing delivery controls. Verify that the script remains intact and reset behavior is explained.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switch from landscape to vertical. Verify captions, product placement, and preview safe areas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove a product asset linked to a demonstration beat. Verify that the missing evidence is surfaced before generation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return from review to edit. Verify that the system preserves the selected concept instead of creating an accidental duplicate.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing Principle
&lt;/h2&gt;

&lt;p&gt;The most useful AI creative interfaces do not hide complexity by pretending it does not exist. They organize complexity into decisions the user can understand, reverse, and review.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://www.nextify.ai/ugc-ad-generator" rel="noopener noreferrer"&gt;UGC-style advertising&lt;/a&gt;, the core design problem is state coherence: keeping format, evidence, script, presenter, delivery, and frame aligned as the idea changes. Make those contracts explicit, and both the interface and the resulting brief become easier to trust.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>ux</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
