<?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: Jack Li</title>
    <description>The latest articles on DEV Community by Jack Li (@ksh24029).</description>
    <link>https://dev.to/ksh24029</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%2F4103862%2Fa7df51c1-8cc4-462c-b016-d5d90b7a6b5c.jpg</url>
      <title>DEV Community: Jack Li</title>
      <link>https://dev.to/ksh24029</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ksh24029"/>
    <language>en</language>
    <item>
      <title>Why Your AI Image App Needs Workflow Definitions, Not Just Prompt Templates</title>
      <dc:creator>Jack Li</dc:creator>
      <pubDate>Tue, 01 Sep 2026 07:55:16 +0000</pubDate>
      <link>https://dev.to/ksh24029/why-your-ai-image-app-needs-workflow-definitions-not-just-prompt-templates-4gfd</link>
      <guid>https://dev.to/ksh24029/why-your-ai-image-app-needs-workflow-definitions-not-just-prompt-templates-4gfd</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbgn090nsbjsq64d0u5o2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbgn090nsbjsq64d0u5o2.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An AI image wrapper looks deceptively simple at first glance: upload an image, pass an instruction to a model API, and render the output. That works fine for a weekend prototype, but it falls apart quickly when building an actual product.&lt;/p&gt;

&lt;p&gt;While developing &lt;a href="https://imaginemyhouse.com/" rel="noopener noreferrer"&gt;ImagineMyHouse&lt;/a&gt;, I gradually added more specialized tools: room redesign, virtual staging, object removal, relighting, floor-plan visualization, and panorama generation.&lt;/p&gt;

&lt;p&gt;From a high level, every tool shared the exact same loop:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Image in → Pipeline processing → Image out&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By the time the project grew to 16 workflows, I ran into an architectural fork:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build 16 separate mini-apps and maintain endless duplicate code.&lt;/li&gt;
&lt;li&gt;Force 16 completely different user intents into a single, vague "AI image generator" prompt box.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The workable middle ground was clear: &lt;strong&gt;share the workspace infrastructure, but model each user intent as a distinct workflow configuration.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Users Don't Think in "Image Generation"
&lt;/h2&gt;

&lt;p&gt;Developers usually describe these products by their technical mechanism: &lt;em&gt;"The user uploads a picture and the model transforms it."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Users describe the job with specific constraints and expectations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Redesign this living room, but don't move the windows or walls."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Clean up the clutter on the floor, keep everything else."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Furnish this empty listing photo for a staging preview."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Change the lighting from noon to dusk without altering materials."&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying pipeline might share components, but the user contract is fundamentally different for each task.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workflow&lt;/th&gt;
&lt;th&gt;What May Change&lt;/th&gt;
&lt;th&gt;What Must Remain Stable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Room Redesign&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Furniture, materials, colors, lighting&lt;/td&gt;
&lt;td&gt;Camera perspective and room geometry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Room Cleanup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Movable objects and clutter&lt;/td&gt;
&lt;td&gt;Flooring, walls, door frames, windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Virtual Staging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Added furniture and decor&lt;/td&gt;
&lt;td&gt;Empty-room boundary and lighting angle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Relighting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Light source, direction, atmosphere&lt;/td&gt;
&lt;td&gt;Objects, textures, room composition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Floor-Plan Render&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visual style and rendering textures&lt;/td&gt;
&lt;td&gt;Room boundaries and plan dimensions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Panorama&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Field of view and projection&lt;/td&gt;
&lt;td&gt;Scene identity and visual continuity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A workflow isn't just a prompt template with a label. It defines what the user expects the system to preserve versus what they want transformed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Defining Workflows Through Explicit Constraints
&lt;/h2&gt;

&lt;p&gt;When building the first few tools, creating dedicated pages and forms felt fast. But as the number of features scaled, fixing layout bugs or updating API wrappers across a dozen slightly different components quickly became unsustainable.&lt;/p&gt;

&lt;p&gt;To fix this, I abstracted the workspace into two distinct layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Shared Workspace Infrastructure (Reusable)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asset upload validation and image compression&lt;/li&gt;
&lt;li&gt;Authentication and rate limits&lt;/li&gt;
&lt;li&gt;Asynchronous task polling and job status recovery&lt;/li&gt;
&lt;li&gt;History persistence, exports, and common error boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Workflow Definition Layer (Specific)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Targeted input constraints and guidance copy&lt;/li&gt;
&lt;li&gt;Active UI controls (e.g., style selector vs. lighting slider)&lt;/li&gt;
&lt;li&gt;Intent-specific preservation parameters and negative constraints&lt;/li&gt;
&lt;li&gt;Tailored example galleries and contextual failure messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation keeps the codebase maintainable while ensuring each tool remains focused on a single job.&lt;/p&gt;




&lt;h2&gt;
  
  
  Normalizing Intent Before Provider APIs
&lt;/h2&gt;

&lt;p&gt;Another helpful boundary was decoupling the workflow intent from third-party AI APIs.&lt;/p&gt;

&lt;p&gt;The frontend communicates with an internal domain 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;WorkflowRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;workflowId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;room-redesign&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="s1"&gt;virtual-staging&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="s1"&gt;cleanup&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="s1"&gt;relighting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sourceImage&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;preservationLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;strict&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="s1"&gt;moderate&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="s1"&gt;loose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;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 backend maps this normalized payload into provider-specific parameters (masks, control conditioning weights, or prompt templates).&lt;/p&gt;

&lt;p&gt;If a model provider changes or a better fine-tune is deployed, the frontend workflow contract remains untouched.&lt;/p&gt;




&lt;h2&gt;
  
  
  Preservation Is a Product Requirement, Not a Prompt Suffix
&lt;/h2&gt;

&lt;p&gt;Tacking &lt;code&gt;"preserve original room layout"&lt;/code&gt; to the end of a prompt rarely solves structural drift.&lt;/p&gt;

&lt;p&gt;Different workflows live along a preservation spectrum:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Relighting → Cleanup → Staging → Redesign → Plan-to-Render&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;(Strict structural lock → High creative latitude)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For every tool, you have to decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which anchor points define the image's identity?&lt;/li&gt;
&lt;li&gt;Where is drift acceptable, and where does it break user trust?&lt;/li&gt;
&lt;li&gt;What conditioning signals (edge maps, depth, segmentation) are required?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In interior redesign, moving a structural pillar or shifting a door frame is an immediate failure, regardless of how good the aesthetic looks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reliable Job States Build User Trust
&lt;/h2&gt;

&lt;p&gt;AI generation takes anywhere from 5 to 15 seconds. Users refresh tabs, hit network drops, or trigger concurrent jobs.&lt;/p&gt;

&lt;p&gt;A resilient UX treats generation as an &lt;strong&gt;asynchronous background job&lt;/strong&gt; rather than a transient React state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Acknowledge request receipt immediately with a unique job ID.&lt;/li&gt;
&lt;li&gt;Maintain job state in the database so progress survives tab reloads.&lt;/li&gt;
&lt;li&gt;Ensure billing and credit deduction are idempotent to prevent double-charging on network retries.&lt;/li&gt;
&lt;li&gt;Provide clear error reasons rather than generic infinite spinners.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Does a Workflow Deserve a Dedicated Route?
&lt;/h2&gt;

&lt;p&gt;With a unified workspace engine, spinning up new routes is cheap. But creating pages for minor keyword tweaks creates shallow experiences that confuse users and dilute SEO.&lt;/p&gt;

&lt;p&gt;A separate route makes sense only when at least two of these factors change:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Input Type&lt;/strong&gt;: A 2D blueprint requires different validation and guidance than a wide-angle bedroom photo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control Requirements&lt;/strong&gt;: Staging requires empty room presets; cleanup needs brush/mask tools rather than style cards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Definition of Success&lt;/strong&gt;: The criteria for evaluating a clean floor differ entirely from a multi-style re-render.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the input, controls, and output expectations are identical, it belongs as a preset inside an existing workflow—not a separate URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Evaluating Workflows by User Action, Not Just HTTP 200
&lt;/h2&gt;

&lt;p&gt;An API returning a &lt;code&gt;200 OK&lt;/code&gt; doesn't mean the user got what they wanted.&lt;/p&gt;

&lt;p&gt;Rather than looking solely at API completion rates, evaluate tools against post-generation behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep / Download Rate&lt;/strong&gt;: Did the output match the user's intent closely enough to save?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immediate Re-roll Rate&lt;/strong&gt;: Are users constantly re-generating due to bad default weights?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow Abandonment&lt;/strong&gt;: Are users dropping off at the configuration stage because of irrelevant settings?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tracking quality at the workflow level reveals which specific transformation promises need tighter tuning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Scaling from a prototype to a multi-tool product isn't about stringing together more API endpoints. It comes down to decoupling generic infrastructure from domain-specific workflows and treating preservation rules as core application logic.&lt;/p&gt;

&lt;p&gt;For developers building multi-tool AI apps: what is your threshold for keeping features in a unified workspace versus splitting them into separate bounded micro-apps?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
