<?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: xianyu110</title>
    <description>The latest articles on DEV Community by xianyu110 (@xianyu110).</description>
    <link>https://dev.to/xianyu110</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%2F364812%2Ff1c84cb8-f678-4f66-af5e-cbfeb64561ae.png</url>
      <title>DEV Community: xianyu110</title>
      <link>https://dev.to/xianyu110</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xianyu110"/>
    <language>en</language>
    <item>
      <title>Shipping a Seasonal AI Side Project on Cloudflare Workers: Lessons from a Pet Halloween Photo App</title>
      <dc:creator>xianyu110</dc:creator>
      <pubDate>Tue, 06 Oct 2026 04:02:32 +0000</pubDate>
      <link>https://dev.to/xianyu110/shipping-a-seasonal-ai-side-project-on-cloudflare-workers-lessons-from-a-pet-halloween-photo-app-499f</link>
      <guid>https://dev.to/xianyu110/shipping-a-seasonal-ai-side-project-on-cloudflare-workers-lessons-from-a-pet-halloween-photo-app-499f</guid>
      <description>&lt;p&gt;Most side-project advice assumes you have time: launch, iterate, grow slowly. A Halloween product gets about three weeks. If people can't find it, trust it and pay for it in that window, the next chance is a year away.&lt;/p&gt;

&lt;p&gt;This October I shipped &lt;a href="https://hallowpaws.com?utm_source=devto" rel="noopener noreferrer"&gt;HallowPaws&lt;/a&gt;. You upload a photo of your dog or cat, pick a costume, and get an HD Halloween portrait that still looks like your pet. Below are the engineering and product decisions that mattered for a short seasonal window. Most of them would apply to any AI photo side project.&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%2Flqyce0lkcqz8h20ky5n6.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%2Flqyce0lkcqz8h20ky5n6.png" alt="Before and after examples: golden retriever as a vampire, black cat as a witch" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack: boring, cheap, scales to zero
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TanStack Start + React 19, deployed to &lt;strong&gt;Cloudflare Workers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D1&lt;/strong&gt; for data, &lt;strong&gt;R2&lt;/strong&gt; for images, a &lt;strong&gt;Cloudflare Queue&lt;/strong&gt; for generation jobs&lt;/li&gt;
&lt;li&gt;better-auth for accounts, Stripe Checkout for one-time packs&lt;/li&gt;
&lt;li&gt;An image-edit model behind an API aggregator, with a fallback model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a product that's busy for a few weeks and quiet the rest of the year, scale-to-zero pricing matters more than anything else on this list. Nothing sits idle and costs money in February.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 1: no prompt box, just presets
&lt;/h2&gt;

&lt;p&gt;The users are pet parents, not prompt engineers. So there is no free-text input anywhere. The only prompts that can run are 16 costume presets in one config file (vampire, witch, pumpkin, ghost, skeleton, pirate, wizard, mummy and so on). Each preset has just two creative fields, &lt;code&gt;costume&lt;/code&gt; and &lt;code&gt;scene&lt;/code&gt;, which get dropped into one shared template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edit the reference photo: create a Halloween costume portrait of the pet from the reference photo.
Keep the exact same animal identity: same species, breed, fur color and pattern,
markings, eye color, ear shape, face proportions and expression, so the owner
instantly recognizes their pet.
Dress the pet in {costume}. The costume must fit the animal's anatomy naturally...
Background: {scene}.
Style: photorealistic professional pet photography, sharp focus on the eyes,
detailed fur texture, shallow depth of field, vertical 4:5 portrait composition.
Do not add any humans, people, hands, text, captions, logos or brand marks...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The identity line comes before the costume line on purpose. For this kind of product, the quality bar isn't "nice AI art". It's whether a friend scrolling past would say "that's Max". Fixed presets also make results reproducible, so when a costume underperforms I fix one string in one file. Adding a costume or an SEO landing page means adding one entry, and the picker, sitemap and &lt;code&gt;llms.txt&lt;/code&gt; all update from that config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 2: watermark on the server, before storage
&lt;/h2&gt;

&lt;p&gt;The free tier is a low-res, watermarked preview. My first instinct was to ask the model to draw the watermark. Don't do that: the model treats it as a suggestion.&lt;/p&gt;

&lt;p&gt;Instead, previews get stamped inside the Worker with &lt;strong&gt;Photon (Rust compiled to WASM)&lt;/strong&gt; &lt;em&gt;before&lt;/em&gt; anything is written to R2. The image is downscaled to 768px wide and a tiled overlay is applied. The clean original never exists in storage, so there's no URL for anyone to guess. HD images skip this step completely.&lt;/p&gt;

&lt;p&gt;Two workerd gotchas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;.wasm&lt;/code&gt; file has to be imported as a precompiled module (&lt;code&gt;?module&lt;/code&gt;), because compiling from bytes at runtime is blocked.&lt;/li&gt;
&lt;li&gt;Load it lazily on first use, so a WASM problem can never break Worker startup for every other route.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Decision 3: image calls are slower than your proxy
&lt;/h2&gt;

&lt;p&gt;Image-edit calls regularly take longer than Cloudflare's ~100-second proxy timeout. Run them inside the request and users see HTTP 524. So generation became a job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;POST /generate&lt;/code&gt; validates the photos (JPEG/PNG/WebP, up to 10 MB each), &lt;strong&gt;reserves&lt;/strong&gt; credits, stores the photos in R2, inserts a job row, puts the job id on the queue, and returns right away.&lt;/li&gt;
&lt;li&gt;The queue consumer runs the job. A queue invocation can run far longer than an HTTP request.&lt;/li&gt;
&lt;li&gt;The client polls the job status every few seconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every failure path goes through one rule: mark the job failed and refund the reserved credits, exactly once. A sweeper fails and refunds anything stuck for more than 10 minutes. The provider wrapper also has a per-call timeout, so a hung primary model still leaves time to retry on the fallback model. Users never pay for a failed image, and that one rule removed a whole category of support emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 4: let people try before they sign up
&lt;/h2&gt;

&lt;p&gt;Asking for an account before showing any result kills a seasonal funnel. HallowPaws gives guests &lt;strong&gt;one free watermarked preview with no sign-up&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guests are identified by an httpOnly cookie, rate-limited per hashed IP per rolling 24 hours, and capped globally per day to protect AI spend.&lt;/li&gt;
&lt;li&gt;When the guest signs up, that preview is claimed into their new account as its free preview.&lt;/li&gt;
&lt;li&gt;Signing up also gives &lt;strong&gt;one free HD credit&lt;/strong&gt;, so a new user can unlock one clean, full-resolution portrait before paying anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decision 5: price for a spike, not a habit
&lt;/h2&gt;

&lt;p&gt;A monthly subscription for something people use for two weeks a year feels hostile. So HallowPaws sells one-time packs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treat&lt;/strong&gt;: $1.99 for 4 HD images (classic costumes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spooky&lt;/strong&gt;: $4.99 for 15 HD images (all 16 costumes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monster Party&lt;/strong&gt;: $9.99 for 40 HD images, plus group portraits of up to 3 pets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pack definitions live server-side as the single source of truth. The client only sends a pack key. Checkout ignores any price or credit count in the request body and records the server's numbers on the order, and that order is what the webhook grants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 6: honest demos
&lt;/h2&gt;

&lt;p&gt;Before/after examples are the most persuasive part of the landing page. They're also the easiest place to cut corners. The ones on HallowPaws come from royalty-free (CC0) pet photos, run through the same presets customers get, and the page says so. If your product is "it still looks like your pet", the demo can't use cherry-picked outputs from a different pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone building the next seasonal AI app
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ship the config, not the prompt box.&lt;/strong&gt; Curated presets beat flexibility for consumer users and are far easier to debug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume generation will outlive the request.&lt;/strong&gt; Design for queues and polling from day one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat credits like money from the start.&lt;/strong&gt; Reserve first, refund on every failure path, and make the refund idempotent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce the free tier on the server.&lt;/strong&gt; Watermarks and resolution limits belong in your code, not in the prompt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pick infrastructure that costs nothing in the off-season.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a dog or cat and five minutes, you can try a free preview at &lt;a href="https://hallowpaws.com?utm_source=devto" rel="noopener noreferrer"&gt;hallowpaws.com&lt;/a&gt;. I'm happy to answer questions about the Workers + Queue + Photon setup in the comments.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>ai</category>
      <category>sideprojects</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Keeping One Character Consistent Across a Whole Article's AI Illustrations</title>
      <dc:creator>xianyu110</dc:creator>
      <pubDate>Tue, 06 Oct 2026 04:02:08 +0000</pubDate>
      <link>https://dev.to/xianyu110/keeping-one-character-consistent-across-a-whole-articles-ai-illustrations-1p5l</link>
      <guid>https://dev.to/xianyu110/keeping-one-character-consistent-across-a-whole-articles-ai-illustrations-1p5l</guid>
      <description>&lt;p&gt;If you've tried to illustrate a long blog post with an image model, you know how it goes. You ask for five pictures and get five different art styles, three versions of your "mascot", and a stock-photo handshake you never asked for. Each image is fine by itself. Put them together in one post, though, and it looks like a ransom note.&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://inkdoo.app?utm_source=devto" rel="noopener noreferrer"&gt;InkDoo&lt;/a&gt;, a tool that takes an article and returns a full set of hand-drawn explainer illustrations, all starring the same character. This post covers the parts that actually made the set look consistent. Spoiler: most of it is boring prompt plumbing, not model magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline in one paragraph
&lt;/h2&gt;

&lt;p&gt;Article in. A planner LLM reads the text and returns JSON: which ideas are worth a picture, which paragraph each picture goes after, and a scene description for each. Then an image model draws every scene from a strict prompt template. Finally, an exporter drops the hosted image URLs back into your Markdown at the right spots. Three stages, and the consistency work happens in all three.&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%2Fk66gqoxxfmvaplog5jqh.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%2Fk66gqoxxfmvaplog5jqh.png" alt="Doo cranking a sieve that keeps only the ideas worth drawing" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Describe the character like a spec, not a vibe
&lt;/h2&gt;

&lt;p&gt;The biggest single win was writing each character as a short, very literal visual spec, then pasting that exact string into &lt;em&gt;every&lt;/em&gt; image prompt. Here is the default character, Mochi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Mochi", a round low squishy cat-like blob drawn ONLY as a hollow black
outline (white inside, never filled black): a wide soft dumpling-shaped body
sitting flat, two tiny triangle ears, two short sleepy horizontal line eyes,
a tiny 'w' mouth, small nub paws, one thin curly tail.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things I learned writing these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Count things.&lt;/strong&gt; "Two tiny triangle ears" and "ONE single large round eye" drift much less than "cute ears" or "big eye".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat the negative.&lt;/strong&gt; "Hollow outline, white inside, never filled black" appears in the description &lt;em&gt;and&lt;/em&gt; in the style block. Without it the model kept turning line-art characters into solid silhouettes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give the character a personality in three words&lt;/strong&gt; ("lazy-looking but surprisingly competent, deadpan"). It nudges poses without changing the design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;InkDoo ships six of these (Mochi, Doo, Blot, Stub, Folio, Puff), and they all follow the same spec format.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Make the character do the work
&lt;/h2&gt;

&lt;p&gt;A consistent character that just stands in the corner waving is decoration. The planner's system prompt has one rule that changed the output more than anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;${N} must PERFORM the core conceptual action of each image (pulling, carrying,
sieving, weighing, stitching, guarding, pushing, folding, unpacking...).
If the image still works without ${N}, ${N} is too decorative â€” rewrite.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the character is part of the metaphor, every image has to draw it at a size and angle where its features are readable. A tiny figure in the background is where likeness falls apart, so this rule helps consistency too.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Lock the style separately from the content
&lt;/h2&gt;

&lt;p&gt;Every image prompt has the same fixed "visual DNA" block: pure white background, minimalist black hand-drawn wobbly line art, at least 35% empty space, and sparse handwritten notes in only three colors. Each color has a job: orange for the main flow and arrows, red for the key warning or result, blue for side notes. It also carries a list of things to avoid: gradients, shadows, PPT infographics, cute-poster energy.&lt;/p&gt;

&lt;p&gt;Only a few fields change from image to image: theme, structure type, core idea, composition, and two to five short labels. The planner picks the structure from a small, fixed list (Workflow, Before/after, Concept metaphor, Route map, Mini comicâ€¦). It also has to invent a fresh, low-tech physical metaphor using one or two objects: a funnel, a scale, a drawer, a broken machine.&lt;/p&gt;

&lt;p&gt;Keeping the variable surface small is the real trick. The less each prompt is allowed to vary, the more the set reads as one series.&lt;/p&gt;

&lt;p&gt;(Credit where it's due: the illustration method, meaning one idea per image, white background and sparse colored annotations, is adapted from the MIT-licensed &lt;a href="https://github.com/helloianneo/ian-xiaohei-illustrations" rel="noopener noreferrer"&gt;Ian Xiaohei Illustrations&lt;/a&gt; project. The characters are original.)&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The planner never sees images, so give it a text twin
&lt;/h2&gt;

&lt;p&gt;Users can upload their own character as a reference picture. That creates an asymmetry. The image call can take the reference (InkDoo sends it to the edits endpoint), but the planner LLM is text-only. So a custom character gets two descriptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;desc&lt;/code&gt;, for the image model: "the user's own original character, exactly as shown in the attached reference imageâ€¦ keep its silhouette, proportions, face and distinctive featuresâ€¦ but redraw it in this minimalist black hand-drawn line-art styleâ€¦ ignore the reference background."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;planDesc&lt;/code&gt;, for the planner: a text-only version built from the name and any look description the user typed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;User text is sanitized (control characters, quotes and braces stripped, length capped) before it gets near a prompt. If the custom character is empty, the resolver quietly falls back to Mochi instead of producing a character-less set.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Switching characters without re-planning
&lt;/h2&gt;

&lt;p&gt;Planning is free in InkDoo, and generating images costs credits. So I didn't want people re-planning just because they picked a different character halfway through. The planner writes scenes in English with the character's name in them, so switching is a careful, Unicode-aware whole-word replace of the old name with the new one. Mochi's scene ("Mochi sits on the overflowing inbox") becomes Doo's scene, with no extra LLM call.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Put the images back where they belong
&lt;/h2&gt;

&lt;p&gt;The planner returns an &lt;code&gt;after&lt;/code&gt; anchor for each shot: the first ~20 characters of the paragraph the image should follow, copied verbatim. The exporter splits the article into blocks, normalizes whitespace, and matches each anchor to a block. Then it writes out Markdown (or WeChat-friendly HTML) with the hosted image URLs in place. Matching on a short prefix holds up much better than asking the model for paragraph numbers, which it miscounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that still bite
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Long jobs vs. request timeouts.&lt;/strong&gt; Image calls can take minutes, so generation runs as a background job on a Cloudflare Queue. The client polls, and a failed or stuck job refunds its credit automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-image redraws.&lt;/strong&gt; Sometimes one image in the set is off. Each card can redraw just that scene with an edited prompt, and the rest stay as they are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labels in the right language.&lt;/strong&gt; Handwritten notes follow the article language: Chinese articles get Chinese labels, English ones get English.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;If you want a consistent character across many AI images:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the character as a literal, countable spec and reuse the exact string.&lt;/li&gt;
&lt;li&gt;Make the character perform the key action so it's always drawn large enough to stay recognizable.&lt;/li&gt;
&lt;li&gt;Freeze the style block and keep the per-image variables few and structured.&lt;/li&gt;
&lt;li&gt;Keep a text twin for any reference image the planner can't see.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you'd rather not build the plumbing yourself, you can try the whole pipeline at &lt;a href="https://inkdoo.app?utm_source=devto" rel="noopener noreferrer"&gt;inkdoo.app&lt;/a&gt;. Paste a post or drop in a link or a .md/.docx file. New accounts get two free images. I'd love to hear how you're handling character consistency in your own projects.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>hi</title>
      <dc:creator>xianyu110</dc:creator>
      <pubDate>Tue, 28 Jul 2026 14:37:19 +0000</pubDate>
      <link>https://dev.to/xianyu110/hi-aik</link>
      <guid>https://dev.to/xianyu110/hi-aik</guid>
      <description>&lt;p&gt;hii&lt;/p&gt;

</description>
    </item>
    <item>
      <title>test</title>
      <dc:creator>xianyu110</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:50:06 +0000</pubDate>
      <link>https://dev.to/xianyu110/test-53lg</link>
      <guid>https://dev.to/xianyu110/test-53lg</guid>
      <description>&lt;p&gt;test&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
  </channel>
</rss>
