<?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: Cathy Lai</title>
    <description>The latest articles on DEV Community by Cathy Lai (@cathylai).</description>
    <link>https://dev.to/cathylai</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F904121%2Fdeac3704-41b3-4e1b-a391-0257ed0e729f.jpeg</url>
      <title>DEV Community: Cathy Lai</title>
      <link>https://dev.to/cathylai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cathylai"/>
    <language>en</language>
    <item>
      <title>Spending Hours Designing the UI? Or Just Telling AI the Pain Story</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Sun, 31 May 2026 05:21:45 +0000</pubDate>
      <link>https://dev.to/cathylai/spending-hours-designing-the-ui-or-just-telling-ai-the-pain-story-58al</link>
      <guid>https://dev.to/cathylai/spending-hours-designing-the-ui-or-just-telling-ai-the-pain-story-58al</guid>
      <description>&lt;p&gt;Now that I get my &lt;a href="https://dev.to/cathylai/garden-visualizer-would-results-improve-using-example-images-screenshots-included-jn2"&gt;backend API services &lt;/a&gt;in place, it's time to design a frontend! Just describe the mood to AI, right? I did, and it looked... fine. But it didn't feel right. It didn't match the soul of why I was building it.&lt;/p&gt;

&lt;p&gt;So, instead of looking at font pairing blogs for three hours, I sat down and told Gemini the personal story and frustration behind the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outdated Garden Styles
&lt;/h2&gt;

&lt;p&gt;I explained that as a busy professional, I felt completely alienated by the gardening world. Every time I looked up gardening advice on YouTube or blogs to fix up my yard, I was met with slow acoustic country music, rambling lectures full of jargons, and an aesthetic that felt like a grandmother giving advice.&lt;/p&gt;

&lt;p&gt;It felt old fashioned, inefficient, and totally out of touch with how my peers and I live. For example:&lt;/p&gt;

&lt;p&gt;❌ Old fashion garden style&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F4j9r6z77u8lin9pcs0js.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F4j9r6z77u8lin9pcs0js.jpg" alt="Cottage Garden impossible to maintain" width="800" height="1050"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We don't want a chaotic cottagecore wildflower patch that takes six hours a week to prune. Our design references are the sleek tech offices we work in, the boutique hotels we visit, or the trendy cafes with clean gravel, square lawns, structural plants, and warm outdoor lighting. We just want a beautiful, low-maintenance outdoor lounge where we can turn on some music, host a weekend barbecue, and have drinks with friends on a summer evening.&lt;/p&gt;

&lt;p&gt;&lt;span&gt;✔&lt;/span&gt; Modern outdoor courtyard &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.amazonaws.com%2Fuploads%2Farticles%2Fh3fi2qi9rkhdu018bavw.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fh3fi2qi9rkhdu018bavw.jpg" alt="Modern garden" width="800" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I told Gemini: “I built a tool that takes a messy yard and instantly visualizes that clean, hotel-grade hosting space. Help me pick a font system that talks to these busy professionals, not their grandmothers.”&lt;/p&gt;

&lt;h2&gt;
  
  
  10 Seconds to the Right Font...!
&lt;/h2&gt;

&lt;p&gt;Main title font "Fraunces"&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F6nst6y2azx6w5pfhi7mj.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.amazonaws.com%2Fuploads%2Farticles%2F6nst6y2azx6w5pfhi7mj.png" alt="Font styles " width="800" height="898"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Subtitle and body text "Inter"&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F3aghe0vooew48d2wl3ut.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.amazonaws.com%2Fuploads%2Farticles%2F3aghe0vooew48d2wl3ut.png" alt="Font styles " width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailwind Changes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Load Fraunces from Fontshare in global.css
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="sx"&gt;url("https://api.fontshare.com/v2/css?f[]=fraunces@500,600,700&amp;amp;display=swap")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Register a Tailwind font family in global.css
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="nt"&gt;--font-inter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--font-inter-family&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nt"&gt;ui-sans-serif&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;system-ui&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;sans-serif&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nt"&gt;--font-fraunces&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Fraunces"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;ui-serif&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;Georgia&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;serif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Apply it on the page title (page.tsx)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font-fraunces text-5xl leading-[0.95] font-semibold tracking-tight text-[#1f321d] sm:text-6xl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nx"&gt;GardenViz&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font-inter mt-2 text-lg font-medium tracking-tight text-[#3a4f35] sm:text-xl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nx"&gt;Garden&lt;/span&gt; &lt;span class="nx"&gt;Design&lt;/span&gt; &lt;span class="nx"&gt;Visualizer&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;Busy&lt;/span&gt; &lt;span class="nx"&gt;Homeowners&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result&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.amazonaws.com%2Fuploads%2Farticles%2F30dev155rks3zpq0ylpq.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.amazonaws.com%2Fuploads%2Farticles%2F30dev155rks3zpq0ylpq.png" alt="App Title and font" width="780" height="199"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fcutd4c7nc2ijjb3455wa.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.amazonaws.com%2Fuploads%2Farticles%2Fcutd4c7nc2ijjb3455wa.png" alt="App screenshot with the new font" width="799" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think it looks quite good! :)&lt;/p&gt;

&lt;h2&gt;
  
  
  AI as a Collaborator
&lt;/h2&gt;

&lt;p&gt;By treating the AI as a creative collaborator and feeding it the context of a real human frustration rather than just a technical prompt, I got a design system that feels incredibly intentional.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>uidesign</category>
      <category>devjournal</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Garden Visualizer - Would Results Improve Using Example Images? (Screenshots Included)</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Thu, 28 May 2026 04:17:38 +0000</pubDate>
      <link>https://dev.to/cathylai/garden-visualizer-would-results-improve-using-example-images-screenshots-included-jn2</link>
      <guid>https://dev.to/cathylai/garden-visualizer-would-results-improve-using-example-images-screenshots-included-jn2</guid>
      <description>&lt;h2&gt;
  
  
  Perfect Prompts Needed?
&lt;/h2&gt;

&lt;p&gt;In creating a &lt;a href="https://dev.to/cathylai/how-i-stopped-despairing-over-the-backyard-mess-and-started-an-ai-side-project-3f9a"&gt;garden visualizer&lt;/a&gt; to help home owners with their landscape decisions, I assumed that I needed to "program" the text prompt perfectly in order to give good results. So spending time tweaking the text descriptions seems natural. &lt;/p&gt;

&lt;p&gt;However, after a few days &lt;a href="https://dev.to/cathylai/i-thought-my-one-sentence-is-enough-to-create-a-perfect-gpt-image-until-i-realized-it-had-been-3c20"&gt;learning about context engineering&lt;/a&gt;, I realised I could just send example images and describe what I like or dislike about them. That makes it a lot easier!! Both for me and for GPT!&lt;/p&gt;

&lt;h2&gt;
  
  
  "Before" Image
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2Faqa4q2repo5zds5j4nmx.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Faqa4q2repo5zds5j4nmx.jpeg" alt=" " width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Text prompt
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Could you give me an idea of how I could transform this area of the property to have a nicer garden?&lt;br&gt;
Please don't change any of the layout and proportions nor points of view. &lt;br&gt;
Please don't add extra buildings or structures either.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Result&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.amazonaws.com%2Fuploads%2Farticles%2Fajaw9yknf87mf2ou30kk.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fajaw9yknf87mf2ou30kk.jpeg" alt="Messy Garden" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conclusion: it looks very nice, but quite unrealistic... home owners probably won't be able to know where to start to achieve it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt with 2 Images as Positive and Negative Examples
&lt;/h2&gt;

&lt;p&gt;Positive example image&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.amazonaws.com%2Fuploads%2Farticles%2Ftf6j84433p1xlvowci7i.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Ftf6j84433p1xlvowci7i.jpeg" alt="Positive training image" width="799" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use it as a style guide, eg the realism style, the lawns (restricted and tidy), the sunlight, the homeliness, and simple yet elegant furniture. Not expensive. And not gardening magazine fantasy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Negative example image&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.amazonaws.com%2Fuploads%2Farticles%2F8sf3xl1rkoweou64xbu3.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F8sf3xl1rkoweou64xbu3.jpg" alt="Negative example image" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not match its unrealistic look, expensive furniture, perfect edges, storybook-like texture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With 2 images as part of the parameters, I got a more achievable, yet still inspiring image:&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.amazonaws.com%2Fuploads%2Farticles%2Fyacwqx6qi24oxpbdaryu.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fyacwqx6qi24oxpbdaryu.jpg" alt="More realistic garden after example images" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think it has picked up from the positive example: lawns, lighting, realism, and the simple furniture. From the negative, it got rid of the large qualities of flowers, and exclude expensive furniture. &lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;p&gt;To easily test different variations, I have a test script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;test-image-edit.mjs

// And run it like so:
npm run &lt;span class="nb"&gt;test&lt;/span&gt;:image-edit &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; public/images/messy_garden2.png &lt;span class="nt"&gt;--positive&lt;/span&gt; public/images/positive.jpeg &lt;span class="nt"&gt;--negative&lt;/span&gt; public/images/too_perfect.jpg &lt;span class="nt"&gt;-o&lt;/span&gt; scripts/output/test_output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables me to add images as parameters, so in the future I can play with different variations easily, without changing the source code constantly!&lt;/p&gt;

</description>
      <category>promptengineering</category>
      <category>ai</category>
      <category>devjournal</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>I Thought My One Sentence Is Creating a Perfect GPT Image; Until I Realized It Had Been Learning My "Taste" All Along</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Tue, 26 May 2026 10:57:20 +0000</pubDate>
      <link>https://dev.to/cathylai/i-thought-my-one-sentence-is-enough-to-create-a-perfect-gpt-image-until-i-realized-it-had-been-3c20</link>
      <guid>https://dev.to/cathylai/i-thought-my-one-sentence-is-enough-to-create-a-perfect-gpt-image-until-i-realized-it-had-been-3c20</guid>
      <description>&lt;p&gt;When I first started experimenting with GPT image generation for my Garden Visualizer project, I honestly thought the workflow would be simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload image&lt;/li&gt;
&lt;li&gt;Write one good prompt&lt;/li&gt;
&lt;li&gt;Receive perfect result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A prompt like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Add low-maintenance shrubs to this Auckland backyard.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And it works?&lt;/p&gt;

&lt;p&gt;Not exactly!…&lt;/p&gt;

&lt;p&gt;After weeks of experimenting with ChatGPT app with staging photos, gardens, landscaping concepts, and “future visualizations”, I slowly realized something surprising:&lt;/p&gt;

&lt;p&gt;The impressive results I was getting were not coming from a single prompt at all. They were coming from &lt;strong&gt;hidden context that ChatGPT had been accumulating&lt;/strong&gt; throughout the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strange Thing I Noticed
&lt;/h2&gt;

&lt;p&gt;At some point, I could type something incredibly vague like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Show after 3 years.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;…and GPT somehow knew:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which shrubs I meant&lt;/li&gt;
&lt;li&gt;Where the lemon tree it was&lt;/li&gt;
&lt;li&gt;That I disliked overcrowded gardens&lt;/li&gt;
&lt;li&gt;That I preferred realistic suburban styling&lt;/li&gt;
&lt;li&gt;That I wanted sparse planting&lt;/li&gt;
&lt;li&gt;That I preferred low-maintenance layouts &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the moment I realized: The “prompt” was not the whole product. The conversation itself had become part of the generation engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Wrong Assumption About the Image Edit API
&lt;/h2&gt;

&lt;p&gt;Initially, I assumed the Image Edit API worked similarly to ChatGPT conversations.&lt;/p&gt;

&lt;p&gt;I thought:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model would somehow “remember”&lt;/li&gt;
&lt;li&gt;the AI would learn over time&lt;/li&gt;
&lt;li&gt;previous generations would influence future ones automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But API calls are usually &lt;strong&gt;stateless&lt;/strong&gt; - which means every request is effectively isolated unless &lt;em&gt;you manually resend the context&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "users_image": "messy_backyard.jpg",
  "prompt": "plz generate a nicely layout idea for this garden"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…actually contains almost no useful information.&lt;/p&gt;

&lt;p&gt;The model has no idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How dense should be the plants&lt;/li&gt;
&lt;li&gt;What realism level&lt;/li&gt;
&lt;li&gt;What maintenance standard&lt;/li&gt;
&lt;li&gt;What climate&lt;/li&gt;
&lt;li&gt;What style direction&lt;/li&gt;
&lt;li&gt;Budget constraints &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API is not “remembering”. MY app has to carry the memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  How About Using Reference Images?
&lt;/h2&gt;

&lt;p&gt;Then I got curious. Instead sending the original image and a pure text prompt, what if I also include example images I liked?&lt;/p&gt;

&lt;p&gt;Suddenly the AI had a visual target. Not exact copying — but guidance. The reference image could influence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;density&lt;/li&gt;
&lt;li&gt;lighting mood&lt;/li&gt;
&lt;li&gt;realism&lt;/li&gt;
&lt;li&gt;staging style&lt;/li&gt;
&lt;li&gt;color palette&lt;/li&gt;
&lt;li&gt;suburban vs luxury feel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly, this may become one of the most important patterns in AI apps going forward.&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here is my magic prompt.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here is the style, taste, realism, and emotional direction I want you to follow.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;I started this project thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The better the prompt, the better the image.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now I think a more accurate statement is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The better the accumulated context, the better the direction.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that subtle difference completely changed how I think about building AI products.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>buildinpublic</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>How Adding People to My AI Garden Images Gave It a Personality (and Attracting the Right Users)</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Mon, 25 May 2026 06:42:30 +0000</pubDate>
      <link>https://dev.to/cathylai/how-putting-faces-literally-to-my-ai-garden-images-gave-it-a-personality-4ecm</link>
      <guid>https://dev.to/cathylai/how-putting-faces-literally-to-my-ai-garden-images-gave-it-a-personality-4ecm</guid>
      <description>&lt;h2&gt;
  
  
  Pure Illustrations
&lt;/h2&gt;

&lt;p&gt;To display rotating images during the long Image Edit API calls to transform users' messy backyards, I have started to create educational gardening tips. At first, they were purely about the landscape itself, like something out of a gardening textbook.&lt;/p&gt;

&lt;p&gt;I made before-and-after images like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lighting vs no lighting&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fr3m2391eseg9v3fl4ij6.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.amazonaws.com%2Fuploads%2Farticles%2Fr3m2391eseg9v3fl4ij6.png" alt="Garden lighting before and after" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Technically, the images were useful. But something felt missing.&lt;/p&gt;

&lt;p&gt;Then I realized: I wasn’t actually showing who the app was for. Then I tried adding characters into the scenes. Instead of a blank garden, I showed this instead:&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.amazonaws.com%2Fuploads%2Farticles%2Fwjjb780igkmt1fpt9fhw.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.amazonaws.com%2Fuploads%2Farticles%2Fwjjb780igkmt1fpt9fhw.png" alt="Solar lights improves backyard" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suddenly, the images stopped looking like generic landscaping diagrams and started feeling like lifestyle outcomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Specifically Am I Helping?
&lt;/h2&gt;

&lt;p&gt;I realized my target users were probably not hardcore gardeners. They are more likely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Busy professionals in their 30s&lt;/li&gt;
&lt;li&gt;First-time homeowners&lt;/li&gt;
&lt;li&gt;People with decent income but limited time&lt;/li&gt;
&lt;li&gt;People who want a beautiful space without spending every weekend doing hard labor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The garden itself is not really the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The product is&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less stress&lt;/li&gt;
&lt;li&gt;Pride in the home&lt;/li&gt;
&lt;li&gt;A relaxing lifestyle&lt;/li&gt;
&lt;li&gt;Confidence inviting friends over&lt;/li&gt;
&lt;li&gt;Avoiding expensive gardening mistakes years later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding characters with expressions — stressed, tired, peaceful, proud — made the consequences emotionally obvious within seconds.&lt;/p&gt;

&lt;p&gt;Ironically, this lesson had almost nothing to do with programming. But it may end up being one of the most important product decisions I’ve made so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Empathetic App Builder
&lt;/h2&gt;

&lt;p&gt;Because once I truly understand who I'm building for, everything becomes clearer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The design style&lt;/li&gt;
&lt;li&gt;The wording&lt;/li&gt;
&lt;li&gt;The examples&lt;/li&gt;
&lt;li&gt;The onboarding&lt;/li&gt;
&lt;li&gt;Which features matter and which do not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because instead of building for a vague audience, I was helping one &lt;strong&gt;specific&lt;/strong&gt; person solve real problems in their everyday life.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>devjournal</category>
      <category>buildinpublic</category>
      <category>ui</category>
    </item>
    <item>
      <title>How I Got Users to Willingly Wait 1 Minute for an API Call (Without Over-Engineering)</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Sun, 24 May 2026 04:43:34 +0000</pubDate>
      <link>https://dev.to/cathylai/how-i-got-users-to-willingly-wait-1-minute-for-an-api-call-without-over-engineering-2coc</link>
      <guid>https://dev.to/cathylai/how-i-got-users-to-willingly-wait-1-minute-for-an-api-call-without-over-engineering-2coc</guid>
      <description>&lt;h2&gt;
  
  
  AI is Great, But It Takes Time
&lt;/h2&gt;

&lt;p&gt;One of the most awkward parts of building my AI garden visualizer was not actually the AI itself — it was the waiting time.&lt;/p&gt;

&lt;p&gt;The image generation API I used could take close to a minute to return a result. From a developer’s perspective, the obvious solutions might be&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A loading spinner. &lt;/li&gt;
&lt;li&gt;Parallel processing&lt;/li&gt;
&lt;li&gt;Try to get the API to return progress during the wait&lt;/li&gt;
&lt;li&gt;Experiment with 3 different other models to see which one is the fastest&lt;/li&gt;
&lt;li&gt;Simplify the prompt, just show trivia changes &lt;/li&gt;
&lt;li&gt;Other clever solutions...??&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But none of these is ideal - some of them require days of work and research. But - I haven't even got any users for the app!&lt;/p&gt;

&lt;h2&gt;
  
  
  Is There Another Way?
&lt;/h2&gt;

&lt;p&gt;Most homeowners using this app are not garden experts. Many feel overwhelmed by messy backyards, overgrown plants, drainage issues, or simply not knowing where to begin. They do not necessarily want to study gardening for weeks. They want quick, practical guidance that helps them feel more confident immediately.&lt;/p&gt;

&lt;p&gt;So instead of trying to “hide” the waiting time, I decided to &lt;strong&gt;use&lt;/strong&gt; it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotating Tips
&lt;/h2&gt;

&lt;p&gt;During the AI processing phase, the app now displays simple illustrated garden tips every few seconds — almost like a lightweight PowerPoint presentation. Each screen is designed to be scannable within 7–10 seconds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why mulch matters&lt;/li&gt;
&lt;li&gt;How tree drip lines work&lt;/li&gt;
&lt;li&gt;Why plants fail when planted too close together&lt;/li&gt;
&lt;li&gt;Simple before/after landscape ideas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Something like this, &lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fiu4n74hkafex46hi69e0.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.amazonaws.com%2Fuploads%2Farticles%2Fiu4n74hkafex46hi69e0.png" alt="Garden Edge" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then use setInterval to display them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
    &lt;span class="c1"&gt;// use setInterval() to display an garden tip every 7 seconds&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInterval&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="nf"&gt;setTipVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// use setTimeout to fade in/out &lt;/span&gt;
      &lt;span class="nx"&gt;timeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&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="nf"&gt;setTipIndex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getRandomTipIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevIndex&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nf"&gt;setTipVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;7000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting thing is that users no longer feel like they are waiting. They are learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the App More Helpful
&lt;/h2&gt;

&lt;p&gt;And I think this taught me an important lesson about software development: sometimes the best solution is not deeper engineering complexity, but changing perspective. Instead of asking, “How do I technically eliminate the delay?”, I started asking, “What would make this minute genuinely useful for the user?”&lt;/p&gt;

&lt;p&gt;That shift completely changed the experience of the app.&lt;/p&gt;

&lt;p&gt;Ironically, the long API call became an opportunity to strengthen the product’s identity. The app stopped feeling like a simple image generator and started feeling more like a helpful gardening companion.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devjournal</category>
      <category>buildinpublic</category>
      <category>ux</category>
    </item>
    <item>
      <title>AI Assisted GUI Took Seconds</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Fri, 22 May 2026 07:13:38 +0000</pubDate>
      <link>https://dev.to/cathylai/ez-garden-visualiser-ai-assisted-gui-took-seconds-3b3a</link>
      <guid>https://dev.to/cathylai/ez-garden-visualiser-ai-assisted-gui-took-seconds-3b3a</guid>
      <description>&lt;p&gt;One of the biggest surprises in this project so far wasn’t the image generation itself — it was how quickly the UI came together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Imagine the GUI
&lt;/h2&gt;

&lt;p&gt;Once I've got the &lt;a href="https://dev.to/cathylai/hello-world-openai-api-script-for-image-edit-ez-garden-visualiser-dev-journal-1-13hi"&gt;command line script&lt;/a&gt; working, I wanted to imagine what the actual user experience of the app would look like. Normally, this is the stage where projects slow down. You spend hours adjusting spacing, choosing colors, moving buttons around, trying to make things “feel right,” or waiting for design ideas to form. But this time, using Cursor together with GPT, the process felt completely different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just Describe the Atmosphere!..
&lt;/h2&gt;

&lt;p&gt;I simplified the goal down to the absolute minimum: a clean interface with a single upload button. Then I described the atmosphere I wanted — modern, calm, visually polished, and easy for non-technical users to understand. Within seconds, the AI generated the page structure, title, subtitle, layout, styling, background, and even a surprisingly polished upload component. The spacing felt balanced. The typography matched the mood. The visual hierarchy made sense immediately.&lt;/p&gt;

&lt;p&gt;Prototype of the app. Before uploading:&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fyh0zhmot50yp7ug2mty6.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fyh0zhmot50yp7ug2mty6.jpg" alt=" " width="799" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After uploading&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F0rbz5sqsvhl2dhbwt03j.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F0rbz5sqsvhl2dhbwt03j.jpg" alt="GUI Prototype - After Uploading" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Momentum to Just Keep Going
&lt;/h2&gt;

&lt;p&gt;What amazed me most wasn’t just the speed, but the momentum it created. Instead of spending days stuck in “design paralysis,” trying to make the app look acceptable before continuing, I suddenly had something that already felt close to finished. That psychological effect is huge. Once the interface looked real, it became much easier to focus on the actual functionality and backend logic.&lt;/p&gt;

&lt;p&gt;I’m starting to understand why AI-assisted development feels so powerful for solo developers. It removes a lot of the friction between idea and execution. You no longer need to perfectly visualize every design decision in your head before starting. You can iterate rapidly, react to what you see, and improve from there. It doesn’t replace good design thinking entirely, but it dramatically lowers the barrier to creating interfaces that feel polished enough to build upon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did AI "Replaced" the Developers?
&lt;/h2&gt;

&lt;p&gt;The interesting part is that this project is gradually becoming less about “AI replacing developers” and more about &lt;strong&gt;AI removing bottlenecks&lt;/strong&gt; that used to slow developers down. In this case, the UI stopped being a multi-day obstacle and became a 10-second starting point — which meant I could move almost immediately into building the real product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;I will write a backend service with Next.js to call the OpenAI APIs. Excited about what this could become! 😀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>devjournal</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>"Hello World" OpenAI API Script for Image Edit</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Mon, 18 May 2026 06:48:09 +0000</pubDate>
      <link>https://dev.to/cathylai/hello-world-openai-api-script-for-image-edit-ez-garden-visualiser-dev-journal-1-13hi</link>
      <guid>https://dev.to/cathylai/hello-world-openai-api-script-for-image-edit-ez-garden-visualiser-dev-journal-1-13hi</guid>
      <description>&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;On the day 1 of my &lt;a href="https://dev.to/cathylai/how-i-stopped-despairing-over-the-backyard-mess-and-started-an-ai-side-project-3f9a"&gt;garden transformation project&lt;/a&gt;, my goal was simple: write a one-file plain JavaScript script that sends a “before” garden photo to the OpenAI Image API, adds a prompt, and generates an “after” photo. I wanted to get this working first as a command-line script before turning it into a proper front-end and back-end app.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bump in the Road...
&lt;/h2&gt;

&lt;p&gt;In reality, the first day was less about garden design and more about getting the API call to work at all. I kept running into errors: the wrong image model, input image format (need png), rate limits, and a requirement to verify my identity before using certain models. The documentation was not immediately clear to me about which models required verification, so I had to go through the identity verification process (need Driver's License, and to turn my head up and down and around in front of the laptop camera) and top up my account with $5 USD before I could continue testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How About Just a Hello World?
&lt;/h2&gt;

&lt;p&gt;Because of that, I simplified the problem even further. Instead of trying to edit a garden photo straight away, I wrote the smallest possible script: just call the API with a simplest one-line prompt. No uploaded image. No garden transformation yet. Just prove that my local JavaScript setup, API key, model name, and response handling were working.&lt;/p&gt;

&lt;p&gt;Finally, I got the simplest version working - just add a cat in the garden using the most basic image model "gpt-image-1.5".&lt;/p&gt;

&lt;p&gt;It really just the simplest script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node test_garden.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you're curious, here's my GitHub. Please note that OpenAPI API Key will be different for each person and I have mine in a private .env file.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pscientist" rel="noopener noreferrer"&gt;
        pscientist
      &lt;/a&gt; / &lt;a href="https://github.com/pscientist/garden-ai" rel="noopener noreferrer"&gt;
        garden-ai
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Ez Garden Visualisation - Command Line version
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;garden-ai&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Ez Garden Visualisation - Command Line version&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pscientist/garden-ai" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Before&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.amazonaws.com%2Fuploads%2Farticles%2F08j4mkqbltwhn0ii2mn1.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.amazonaws.com%2Fuploads%2Farticles%2F08j4mkqbltwhn0ii2mn1.png" alt="Messy, unmade garden" width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the prompt "Please add a cat walking in the garden.", I've got &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.amazonaws.com%2Fuploads%2Farticles%2Fp12dveu0ovnhjms7zghz.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fp12dveu0ovnhjms7zghz.jpeg" alt="Messy garden with a cat" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Happy with the Progress
&lt;/h2&gt;

&lt;p&gt;So Day 1 of the garden transformation script was really about reducing the problem until it became &lt;strong&gt;solvable&lt;/strong&gt;. Before building the full app, I needed to confirm the smallest moving part: can JavaScript call the OpenAI Image API and return an image? Now that the answer is yes, the next step is to write a proper app and to use real prompts.&lt;/p&gt;

&lt;p&gt;Another advantage of having this simple command line script is that experiments of the API calls is now easier - I can now play with different image models, prompts, and see which ones will return the best result in ths shortest amount of time. &lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;I will start drafting up a Next.js app to build a frontend so that the shape of the app will become clearer in my head:) I planned to write my own RESTful APIs to make calls to OpenAPI services!&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;p&gt;The project &lt;a href="https://dev.to/cathylai/how-i-stopped-despairing-over-the-backyard-mess-and-started-an-ai-side-project-3f9a"&gt;background story&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openai</category>
      <category>buildinpublic</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>From the Concept to Reality: Building "Ez Garden Visualizer" in Stages</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Wed, 13 May 2026 04:08:52 +0000</pubDate>
      <link>https://dev.to/cathylai/from-the-concept-to-reality-building-ez-garden-visualizer-in-stages-1mim</link>
      <guid>https://dev.to/cathylai/from-the-concept-to-reality-building-ez-garden-visualizer-in-stages-1mim</guid>
      <description>&lt;h2&gt;
  
  
  So Many Possibilities... So Little Time
&lt;/h2&gt;

&lt;p&gt;When I first thought about building an &lt;a href="https://dev.to/cathylai/how-i-stopped-despairing-over-the-backyard-mess-and-started-an-ai-side-project-3f9a"&gt;AI garden visualizer&lt;/a&gt; app, the full idea sounded much bigger than a weekend project: upload a garden photo, generate a transformed version, suggest plants, estimate cost, maybe save projects, maybe support users, maybe turn it into a mobile app. That is exciting, but also dangerous, because it is very easy to start building the “final architecture” before proving the core workflow. So I decided to build it in layers: first a tiny command-line prototype, then a simple Next.js app, then cloud functions and storage later.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Goal for Each Week
&lt;/h2&gt;

&lt;p&gt;For week one, the goal is only to prove the basic AI workflow with a plain JavaScript file. No frontend, login, database, nor a cloud storage. Just one image in, one AI garden concept out. The command line version might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node garden-transform.js ./input/backyard.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pseudocode is intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// garden-transform.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imagePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
Transform this garden into a tidy, low-maintenance,
affordable makeover concept.
Keep the original layout and proportions.
Suggest easy-care plants, mulch, edging, and simple seating.
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;describeImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imagePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gardenPlan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateTextPlan&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;imageDescription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$500-$600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tidy, homely, achievable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afterImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateGardenImage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;originalImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imagePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;gardenPlan&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;saveFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./output/garden-plan.txt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gardenPlan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;saveImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./output/garden-after.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For week two, I would wrap the prototype inside a very small Next.js app. The goal is not to build the whole product yet. It is just to make the prototype usable through a browser: upload a photo, click a button, see a result. The folder structure could stay very minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;garden-ai-app/
  app/
    page.tsx
    api/
      transform/
        route.ts
  components/
    ImageUploader.tsx
    ResultPreview.tsx
  lib/
    openai.ts
    prompts.ts
  public/
  package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend flow could be as simple as:&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="c1"&gt;// app/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;AI&lt;/span&gt; &lt;span class="nx"&gt;Garden&lt;/span&gt; &lt;span class="nx"&gt;Makeover&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Upload&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;garden&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;simple&lt;/span&gt; &lt;span class="nx"&gt;makeover&lt;/span&gt; &lt;span class="nx"&gt;concept&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ImageUploader&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ResultPreview&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&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;And the API route could reuse the logic from week one:&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="c1"&gt;// app/api/transform/route.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gardenPlan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateGardenPlan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afterImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateGardenImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gardenPlan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gardenPlan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;afterImage&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;What I like about this staged approach is that each week has a clear outcome. &lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Goals
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Week one proves the AI workflow. &lt;/li&gt;
&lt;li&gt;Week two proves the user experience. &lt;/li&gt;
&lt;li&gt;Week three can then move the image storage and processing into proper cloud services. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is also knowing what not to build yet: no authentication, no payment system, no project dashboard, no plant database, and no perfect architecture. At this stage, the goal is &lt;strong&gt;momentum&lt;/strong&gt;. Build the smallest useful layer, learn from it, then add the next layer only when the previous one works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;p&gt;See the app's &lt;a href="https://dev.to/cathylai/how-i-stopped-despairing-over-the-backyard-mess-and-started-an-ai-side-project-3f9a"&gt;background story&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dream Outcome
&lt;/h2&gt;

&lt;p&gt;Here's another inspiring ChatGPT transformation :) &lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F9in6cy8jrvxk6k636bpb.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F9in6cy8jrvxk6k636bpb.jpg" alt="Messy Garden before Edging" width="800" height="439"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F8o4fhwskhe6wouhv0lnu.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2F8o4fhwskhe6wouhv0lnu.jpeg" alt="Tidy garden with edging" width="799" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devjournal</category>
      <category>buildinpublic</category>
      <category>openai</category>
    </item>
    <item>
      <title>How I Stopped Despairing Over the Backyard Mess and Started an AI Side Project</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Mon, 11 May 2026 02:21:29 +0000</pubDate>
      <link>https://dev.to/cathylai/how-i-stopped-despairing-over-the-backyard-mess-and-started-an-ai-side-project-3f9a</link>
      <guid>https://dev.to/cathylai/how-i-stopped-despairing-over-the-backyard-mess-and-started-an-ai-side-project-3f9a</guid>
      <description>&lt;h2&gt;
  
  
  Back-breaking, Expensive Backyard Clean Up
&lt;/h2&gt;

&lt;p&gt;After spending weeks of labour and money cleaning up areas of the garden that had been poorly planned by the previous owner of the house, I started thinking: there must be a better way. It must feel exciting at first to buy plants for a blank backyard, but a few years down the track, things can become huge, messy, and expensive to maintain. Hundreds or even thousands of dollars may need to be spent just to begin fixing the problem. Some plants even come back more aggressively after being trimmed. With very little expertise in landscaping or garden design, I eventually ran to AI for help...&lt;/p&gt;

&lt;h2&gt;
  
  
  Could AI Help?
&lt;/h2&gt;

&lt;p&gt;What I did was feed a photo of the space into GPT and ask it to transform the garden — within a reasonable budget — into something tidy, hotel-like, but still warm and homely. What I got back was a very pleasant surprise.&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.amazonaws.com%2Fuploads%2Farticles%2Fm30atovvruh725zib4mj.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fm30atovvruh725zib4mj.jpeg" alt="messy backyard" width="800" height="1067"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fvrrwwbyuf88xq5iypovd.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.amazonaws.com%2Fuploads%2Farticles%2Fvrrwwbyuf88xq5iypovd.png" alt="ai backyard transformation illustration " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I didn’t expect was the emotional reaction it triggered: suddenly, I could actually picture this chic, functional outdoor space, and it felt achievable. It’s like what people say — “once you see it, you can’t unsee it.”&lt;/p&gt;

&lt;h2&gt;
  
  
  What If I Write an App from This Idea?!
&lt;/h2&gt;

&lt;p&gt;With the sense of adventure, I've decided to write an app which uses this ChatGPT functionality. I can see so many uses of such ideas: not just landscaping, but also house renovations, room decors, shopping help, etc...&lt;/p&gt;

&lt;p&gt;The simplest version of the tool will be: someone uploads a garden photo, describes the style or maintenance level they want, and receives both a visual concept and practical planting suggestions. This article is entry #0 — discovering the problem and realizing there may be a useful solution here. In the next few days, I’ll start experimenting with the OpenAI API tools and learning how to generate these visual transformations programmatically!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;See &lt;a href="https://dev.to/cathylai/hello-world-openai-api-script-for-image-edit-ez-garden-visualiser-dev-journal-1-13hi"&gt;Day 1&lt;/a&gt; of my development journey!&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>ai</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>A New Way to Look at “Failures”</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Mon, 30 Mar 2026 09:57:08 +0000</pubDate>
      <link>https://dev.to/cathylai/a-new-way-to-look-at-failures-1i9o</link>
      <guid>https://dev.to/cathylai/a-new-way-to-look-at-failures-1i9o</guid>
      <description>&lt;h3&gt;
  
  
  Pursuing Correctness as Developers
&lt;/h3&gt;

&lt;p&gt;As developers, we are trained from school to prioritize correctness. We focus on logic, clean architecture, and passing tests because, in code, being "right" leads to success while being "wrong" leads to failure. Over time, this conditioning causes us to tie our self-worth to being correct, which can become a major hurdle when we transition into building products for the real world.&lt;/p&gt;

&lt;p&gt;When we start creating content or apps, the predictable link between effort and results often disappears. We might build something technically perfect that nobody uses, or write something insightful that nobody reads. This feels like a failure in the traditional sense, but &lt;strong&gt;in the market, "errors" aren't signs of being wrong—they are simply data points.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A World with no Predefinded Correctness
&lt;/h3&gt;

&lt;p&gt;High-performing developers often fall into a trap where they view a lack of traction as a personal flaw. In programming, a bug is a mistake to be fixed, but in entrepreneurship, being wrong is the primary way you learn. &lt;strong&gt;It is important to shift our mindset from "I failed" to "That experiment didn't land,"&lt;/strong&gt; recognizing that frequent failure is a necessary part of the process.&lt;/p&gt;

&lt;p&gt;This requires a new definition of confidence. True confidence isn't the belief that you will always succeed; it’s being okay regardless of the outcome. While developers are used to seeing output as validation, we must learn to see it as an experiment. You can care deeply about the work without letting the market's reaction judge your personal worth.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Billion Dollar Product Emerges from Failures
&lt;/h3&gt;

&lt;p&gt;A classic example of this is the origin of Twitch. Justin Kan initially launched Justin.tv to stream his life 24/7, which didn't succeed as a mainstream concept. However, instead of taking it as a personal failure, he noticed users were interested in the streaming technology itself—specifically for gaming. By viewing the "failed" project as &lt;strong&gt;market feedback&lt;/strong&gt; &lt;strong&gt;rather than a dead end,&lt;/strong&gt; he was able to pivot into a billion-dollar company.&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.amazonaws.com%2Fuploads%2Farticles%2Fqn7lbrdqayszshxgsv77.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.amazonaws.com%2Fuploads%2Farticles%2Fqn7lbrdqayszshxgsv77.png" alt=" " width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mindset Shift
&lt;/h3&gt;

&lt;p&gt;Ultimately, we must undergo an identity shift: we are no longer someone who proves value by being right, but someone who creates value by running experiments. This means shipping imperfect things and accepting that we will be ignored or wrong often. By launching/publishing often and observing, we turn failures into data points that fuel long-term growth.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>todayilearned</category>
      <category>developers</category>
      <category>mentalhealth</category>
    </item>
    <item>
      <title>Why Can’t Apple Just Accept a Pull Request for My iOS App? (Top Over-The-Air Update Questions Answered:)</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Tue, 24 Mar 2026 18:57:50 +0000</pubDate>
      <link>https://dev.to/cathylai/why-cant-apple-just-accept-a-pull-request-for-my-ios-app-top-over-the-air-update-questions-39oo</link>
      <guid>https://dev.to/cathylai/why-cant-apple-just-accept-a-pull-request-for-my-ios-app-top-over-the-air-update-questions-39oo</guid>
      <description>&lt;p&gt;&lt;em&gt;App Store, EAS Builds, OTA Updates — Explained&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Over the Air Update - Concept
&lt;/h2&gt;

&lt;p&gt;OTA allows us to update our app &lt;strong&gt;without rebuilding and resubmitting a new version to the App Store or Play Store&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app fetches updated code/content from a server&lt;/li&gt;
&lt;li&gt;The update will be applied on launch (or in the background)&lt;/li&gt;
&lt;li&gt;Users won't need to click "Update".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Questions &amp;amp; Answers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❓ “Why can’t I just deploy the updates like Vercel?”
&lt;/h3&gt;

&lt;p&gt;Because mobile apps don’t run on the servers. &lt;/p&gt;

&lt;p&gt;On the web:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code lives on the server&lt;/li&gt;
&lt;li&gt;Users always get the latest version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On mobile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code is compiled into a &lt;strong&gt;binary&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It lives on the user’s phone&lt;/li&gt;
&lt;li&gt;Users may stay on old versions forever&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❓ “Why does Apple/Google need to review my app?”
&lt;/h3&gt;

&lt;p&gt;Because an app runs on the user’s device and can access: camera, photos, contacts &amp;amp; payments. Apple/Google isn’t reviewing the code like a PR. They’re reviewing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Is this app safe and trustworthy for users?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ❓ “Why not just review the code like GitHub?”
&lt;/h3&gt;

&lt;p&gt;Because code doesn’t fully represent behaviour. An app can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch remote data&lt;/li&gt;
&lt;li&gt;Enable features dynamically&lt;/li&gt;
&lt;li&gt;Change UI based on config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Apple/Google reviews &lt;strong&gt;what users experience&lt;/strong&gt;, not just static code.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❓ “Why is mobile deployment so slow?”
&lt;/h3&gt;

&lt;p&gt;Because mistakes are expensive.&lt;/p&gt;

&lt;p&gt;On web:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug → redeploy → fixed instantly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On mobile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug → shipped to thousands of devices&lt;/li&gt;
&lt;li&gt;Some users never update&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Bugs are &lt;strong&gt;persistent&lt;/strong&gt;, not temporary&lt;/p&gt;

&lt;h3&gt;
  
  
  ❓ “So what problem does OTA (Over-The-Air updates) solve?”
&lt;/h3&gt;

&lt;p&gt;OTA lets us update parts of our app &lt;strong&gt;without resubmitting to the App Store&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rebuild → resubmit → wait&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;push update → users get it on next app open&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 It brings mobile a bit closer to web-like iteration speed&lt;/p&gt;

&lt;h3&gt;
  
  
  ❓ “What is the limitation of OTA?”
&lt;/h3&gt;

&lt;p&gt;OTA updates &lt;strong&gt;cannot change the native layer&lt;/strong&gt;. If we add a new native library (e.g., a new camera plugin or a Bluetooth library), we &lt;strong&gt;must&lt;/strong&gt; submit a new build to the App Store.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❓ “Does OTA bypass App Review?”
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;OTA works &lt;strong&gt;within the boundaries of what Apple already approved&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;✅ Allowed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug fixes&lt;/li&gt;
&lt;li&gt;UI tweaks&lt;/li&gt;
&lt;li&gt;Text/content changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❌ Not allowed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New core features&lt;/li&gt;
&lt;li&gt;Changing app purpose&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❓ “So Apple/Google still controls everything?”
&lt;/h3&gt;

&lt;p&gt;Yes — but selectively. Think of it like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App Store = &lt;strong&gt;initial approval of your app’s behavior&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;OTA = &lt;strong&gt;small adjustments within that approved scope&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❓ “Why does OTA feel limited?”
&lt;/h3&gt;

&lt;p&gt;Because it was added after the original design. Mobile was only supposed to be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“build → review → install”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OTA is a layer on top trying to make it more flexible — but the App Store still enforces control.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❓ “Why is Expo so popular for OTA?”
&lt;/h3&gt;

&lt;p&gt;Because it makes OTA simple. Instead of building our own system, Expo gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosting&lt;/li&gt;
&lt;li&gt;Version control&lt;/li&gt;
&lt;li&gt;Compatibility checks&lt;/li&gt;
&lt;li&gt;Rollback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Using &lt;code&gt;eas update&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ❓ “How is this different from Flutter?”
&lt;/h3&gt;

&lt;p&gt;Flutter apps are compiled ahead of time. So instead of OTA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They rely more on app store updates&lt;/li&gt;
&lt;li&gt;and use remote config / feature flags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Less “code updates”, more “data-driven updates”&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;br&gt;
&lt;em&gt;While true that Flutter doesn't have an official, first-party OTA solution like Expo Updates, third-party tools (like Shorebird) now exist to bring OTA-style patching to Flutter by modifying the underlying engine.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Comparison with Web's "React Server Component (RSC)"
&lt;/h2&gt;

&lt;p&gt;RSC in web is trying to solve a similar problem, i.e. let server has more control over the code, not the client.&lt;/p&gt;
&lt;h3&gt;
  
  
  🌐 Web (Next.js)
&lt;/h3&gt;

&lt;p&gt;→ &lt;strong&gt;React Server Components&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  UI generated on server&lt;/li&gt;
&lt;li&gt;  streamed to browser&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  📱 Mobile (React Native / Flutter)
&lt;/h3&gt;

&lt;p&gt;→ &lt;strong&gt;Server-driven UI + Feature Flags + OTA&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sends configs that the app maps to pre-built native components&lt;/li&gt;
&lt;li&gt;OTA for small fixes &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  💡 One-line takeaway
&lt;/h2&gt;

&lt;p&gt;Mobile isn’t slower than web —&lt;br&gt;
it’s optimized for &lt;strong&gt;trust and stability&lt;/strong&gt;, not instant change.&lt;/p&gt;
&lt;h2&gt;
  
  
  Curious about how Expo OTA Works?
&lt;/h2&gt;

&lt;p&gt;Please watch a short demo below where I added a small paragraph to my entry screen, pushed it the App Store, and confirmed it on a relaunch on my phone. &lt;/p&gt;

&lt;p&gt;Step by step notes from the video &lt;a href="https://dev.to/cathylai/how-to-build-and-test-ios-apps-on-a-physical-phone-sending-updates-part-55-1gj2"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Sl7Y6f2oCnU?start=18"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>ota</category>
      <category>appstore</category>
      <category>reactnative</category>
      <category>mobile</category>
    </item>
    <item>
      <title>CI/CD for Mobile Apps Part 1/3 - Know Where Our Code Lives (Web vs Mobile Explained)</title>
      <dc:creator>Cathy Lai</dc:creator>
      <pubDate>Thu, 19 Mar 2026 17:41:06 +0000</pubDate>
      <link>https://dev.to/cathylai/cicd-for-mobile-apps-part-13-know-where-our-code-lives-web-vs-mobile-explained-59ah</link>
      <guid>https://dev.to/cathylai/cicd-for-mobile-apps-part-13-know-where-our-code-lives-web-vs-mobile-explained-59ah</guid>
      <description>&lt;p&gt;After a few days of working on CI/CD pipelines for mobile apps enabling OTA ("Over the Air" updates), I realised a few details that feels like just simple settings, but actually are resulted from a complete different underlying model which mobile apps operated under.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Code Resides
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web apps: The Single Source of Truth
&lt;/h3&gt;

&lt;p&gt;For web development, the code lives on the server, with the exception of browser cache where the code did not change from the last time. This means the newer code we pushed will always show up for the users, and every user will have the latest changes. &lt;/p&gt;

&lt;h3&gt;
  
  
  Mobile apps: Distributed Binaries
&lt;/h3&gt;

&lt;p&gt;Mobile apps, however, the code is compiled into binary and downloaded onto users' phones. The server just hosts the backend services like APIs, user management, and database queries and storage. &lt;/p&gt;

&lt;p&gt;When a new update is available (eg layout change, new camera features) the users must "download" it, else they stay on the old version. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Analogy
&lt;/h3&gt;

&lt;p&gt;So web apps are like &lt;strong&gt;electronic billboards&lt;/strong&gt;. There’s only one version, controlled by us. When we change it, every user sees the update instantly.&lt;/p&gt;

&lt;p&gt;Mobile apps, on the other hand, are like &lt;strong&gt;printed newsletters&lt;/strong&gt;. Every user holds their own copy. Some are on last month’s issue, some are on older ones. You can’t "remote delete" the paper in their hands; they have to go out and get the new edition. And if you send them content meant for a newer issue than what they have on hand... it might not make sense (crash).&lt;/p&gt;

&lt;h2&gt;
  
  
  The "runtime version" Config
&lt;/h2&gt;

&lt;p&gt;This is why we have something like this in our config&lt;br&gt;
app.json&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"expo"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"appVersion"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;---&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recipes-collect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;---&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is telling the update server how to determine which "issue" the user currently has.   &lt;/p&gt;

&lt;p&gt;There are also other ways to determine this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"policy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sdkVersion"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;---&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Expo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SDK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"policy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fingerprint"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;---&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;digital&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;signature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;native&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when we push an Over the Air (OTA) update, the app on a user's phone checks this configuration to decide: "Is this update compatible with the native code I have installed?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Diagram
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2Fevype71u3582e6trs5m7.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fevype71u3582e6trs5m7.jpeg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having this in mind, we will design our pipelines differently from web apps. In Part 2 tomorrow, we will use "EAS Workflow" from Expo to write scripts to automate the deployment.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>mobile</category>
      <category>ota</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
