<?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: danfodev</title>
    <description>The latest articles on DEV Community by danfodev (@seyin).</description>
    <link>https://dev.to/seyin</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%2F4182400%2Fb75065a6-84df-4123-90bf-3598a7325c8c.png</url>
      <title>DEV Community: danfodev</title>
      <link>https://dev.to/seyin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seyin"/>
    <language>en</language>
    <item>
      <title>Oja: One Product Photo In, a Naija Pidgin Instagram Listing Out (Open-Weight Gemma)</title>
      <dc:creator>danfodev</dc:creator>
      <pubDate>Fri, 16 Oct 2026 03:33:01 +0000</pubDate>
      <link>https://dev.to/seyin/oja-one-product-photo-in-a-naija-pidgin-instagram-listing-out-open-weight-gemma-2fle</link>
      <guid>https://dev.to/seyin/oja-one-product-photo-in-a-naija-pidgin-instagram-listing-out-open-weight-gemma-2fle</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;If you sell on Instagram or WhatsApp in Nigeria, you've typed this caption before: &lt;em&gt;"Available. DM to order."&lt;/em&gt; For the fifth product that day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Oja&lt;/strong&gt; ("market" in Yoruba) writes the listing for you. Upload one to three photos, add a hint if you like (what it is, its condition, the price you have in mind), pick a voice, and get back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a title&lt;/li&gt;
&lt;li&gt;an Instagram caption with hashtags&lt;/li&gt;
&lt;li&gt;a longer description&lt;/li&gt;
&lt;li&gt;a WhatsApp status, with an &lt;strong&gt;Open in WhatsApp&lt;/strong&gt; button&lt;/li&gt;
&lt;li&gt;a rough Naira price guide (a guide, never a promise)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The voices are &lt;strong&gt;Clear English&lt;/strong&gt;, &lt;strong&gt;Naija Pidgin&lt;/strong&gt; and &lt;strong&gt;Yoruba-flavoured English&lt;/strong&gt;. Every piece has a copy button, because the people who'd use this are on their phones, between customers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Oja in numbers&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live at &lt;strong&gt;oja-ng.tech&lt;/strong&gt;, with the code on GitHub under the MIT licence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 real sellers&lt;/strong&gt; tried it, and all 3 said they'd pay. Two named a figure: about ₦10,000 and ₦15,000 a month.&lt;/li&gt;
&lt;li&gt;About &lt;strong&gt;150 to 215 s&lt;/strong&gt; per listing running Gemma 3 locally on a CPU-only laptop, and about &lt;strong&gt;7 to 34 s&lt;/strong&gt; hosted with Gemma 4 (different models, details below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;115 tests&lt;/strong&gt;, and 0 axe accessibility violations in every state I tested.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;I built it for Nigerian sellers because most tools I see are made with another market in mind. I wanted a seller to open something that already feels like theirs: products they recognise, a market they know, and a voice that sounds like how they talk to customers. If English isn't where they're most at home, Pidgin might be, and I wanted them to feel heard, not translated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Yoruba-flavoured" means English with light Yoruba touches. It is not full Yoruba.&lt;/strong&gt; Real Yoruba output is the next thing I want to build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who would pay?&lt;/strong&gt; Anyone who sells online and posts new stock regularly. All three sellers who tried Oja said they would pay. Two gave amounts (about ₦10,000 and ₦15,000 a month), and the third said it depends on the value of what he sells (details in &lt;em&gt;My First User&lt;/em&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  The moment I knew the prompt was wrong
&lt;/h3&gt;

&lt;p&gt;An early hosted Pidgin test was a striped tote bag. The description came back like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This tote bag get beautiful rust color with yellow stripes and teal polka dots. E simple but e get style well well. You fit carry am go market, work, or even outing. We dey deliver for Lagos, Abuja and Port Harcourt…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good Pidgin. But the WhatsApp status from the same run came back in plain English, and the caption was only half in voice. Only the description was fully in character. I rewrote the prompt so every field follows the chosen style, with a worked example per style. Here is a Pidgin listing for a jar of cherry jam, from the saved example on the live site (written by &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; on 16 October 2026, with the prompt as it was after that fix):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caption:&lt;/strong&gt; Oya see fine Cherry Jam! Blue Pearl brand, e sweet well well. DM to order.&lt;br&gt;
&lt;strong&gt;Description:&lt;/strong&gt; This na Blue Pearl Cherry Jam inside glass jar. E get white label with cherry picture and red patterned lid. DM to order, we dey deliver for Lagos, Abuja and Port Harcourt.&lt;br&gt;
&lt;strong&gt;WhatsApp status:&lt;/strong&gt; Blue Pearl Cherry Jam don land! Sweet jam inside glass jar. DM to order, we dey deliver nationwide.&lt;br&gt;
&lt;strong&gt;Price guide:&lt;/strong&gt; ₦2,500 to ₦4,500, "Price fit change based on jar size"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live: &lt;a href="https://oja-ng.tech" rel="noopener noreferrer"&gt;https://oja-ng.tech&lt;/a&gt;&lt;/strong&gt; (a paid Render instance, so it doesn't sleep)&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/oaQ9pkC1Vhw" width="710" height="399"&gt;
  &lt;/iframe&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fev89cexmll46k2ycp9iy.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%2Fev89cexmll46k2ycp9iy.png" alt="Oja showing a Naija Pidgin listing for a jar of cherry jam, with title, Instagram caption, description, WhatsApp status and Naira price guide" width="499" height="917"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hosted demo uses a free-tier model endpoint, which sometimes has a bad moment. If you see "The AI service had a hiccup", press the button again, or press &lt;strong&gt;Try an example&lt;/strong&gt; to see a saved result straight away. (The saved examples were written before my last prompt change.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/SeyinAlao" rel="noopener noreferrer"&gt;
        SeyinAlao
      &lt;/a&gt; / &lt;a href="https://github.com/SeyinAlao/oja" rel="noopener noreferrer"&gt;
        oja
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Oja turns one product photo into Instagram and WhatsApp listings for Nigerian sellers, in English, Naija Pidgin or Yoruba-flavoured English. Open-weight Gemma, OpenAI-compatible endpoint, runs hosted or local.
    &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;Oja&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Listing writer for small Nigerian sellers on Instagram and WhatsApp. Upload 1 to 3 product photos, add optional hints, pick a voice (Clear English, Naija Pidgin or Yoruba-flavoured English) and get a title, caption with hashtags, description, WhatsApp status and a rough price guide, ready to copy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://oja-ng.tech" rel="nofollow noopener noreferrer"&gt;https://oja-ng.tech&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo video:&lt;/strong&gt; &lt;a href="https://www.youtube.com/shorts/oaQ9pkC1Vhw" rel="nofollow noopener noreferrer"&gt;https://www.youtube.com/shorts/oaQ9pkC1Vhw&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Built for the Hacktoberfest 2026 Open-Source AI Challenge, Week 2 (Side Hustle).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Built with Next.js (App Router), TypeScript (strict), Tailwind CSS and Framer Motion. The AI is any open-weight vision model behind an OpenAI-compatible endpoint; the default is &lt;code&gt;gemma3:4b&lt;/code&gt; on local Ollama.&lt;/p&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/SeyinAlao/oja/docs/mobile.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FSeyinAlao%2Foja%2FHEAD%2Fdocs%2Fmobile.png" alt="Oja on a wide screen: a Naija Pidgin listing for a jar of cherry jam, with copy buttons and a price guide" width="300"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/SeyinAlao/oja/docs/result.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FSeyinAlao%2Foja%2FHEAD%2Fdocs%2Fresult.png" alt="Oja on a phone: a Naija Pidgin listing for an Ankara tote bag, with copy buttons and a price guide" width="300"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Both screenshots are real generations from hosted &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; through Google's OpenAI-compatible endpoint (the model badge in each shows it), not mock-ups.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run locally&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ollama pull gemma3:4b
ollama serve          &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; skip if Ollama is already running&lt;/span&gt;
pnpm install
pnpm dev              &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; http://localhost:3000&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Other scripts: &lt;code&gt;pnpm test&lt;/code&gt;, &lt;code&gt;pnpm lint&lt;/code&gt;, &lt;code&gt;pnpm typecheck&lt;/code&gt;, &lt;code&gt;pnpm build&lt;/code&gt;…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SeyinAlao/oja" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;MIT licensed. The README covers running it locally with Ollama, pointing it at another endpoint, deploying to Render, model licences and known limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Two ways to run the same app&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Time per listing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local, Ollama, CPU only&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma3:4b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;148 to 157 s on the server; 170 to 215 s through the UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosted, Google AI Studio free tier&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;about 7 to 11 s in my benchmark runs; 20 to 34 s in runs through the UI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are different models, so please don't read this as a same-model speed test. It shows what each setup feels like to a seller. I haven't worked out why the UI numbers are higher than the server benchmark. Google's model picker describes Gemma 4 26B A4B as a mixture-of-experts model that activates about 4B parameters per inference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App.&lt;/strong&gt; Next.js (App Router), TypeScript in strict mode, Tailwind v4, Framer Motion and zod. One route, &lt;code&gt;/api/generate&lt;/code&gt;, talks to any OpenAI-compatible &lt;code&gt;chat/completions&lt;/code&gt; endpoint, and three environment variables choose the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Hosted: Gemma 4 on Google AI Studio&lt;/span&gt;
&lt;span class="nv"&gt;AI_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://generativelanguage.googleapis.com/v1beta/openai/
&lt;span class="nv"&gt;AI_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gemma-4-26b-a4b-it
&lt;span class="nv"&gt;AI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...

&lt;span class="c"&gt;# Local: Gemma 3 on Ollama, nothing leaves the machine&lt;/span&gt;
&lt;span class="nv"&gt;AI_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:11434/v1
&lt;span class="nv"&gt;AI_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gemma3:4b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Prompting.&lt;/strong&gt; The system prompt is written for Nigerian commerce: Naira formatting, "DM to order", delivery wording for Lagos, Abuja and Port Harcourt. Each style has its own voice rules and a worked example. The model first lists what it can see in the photos (colours, patterns, handles, straps), which pushes the copy toward concrete details. The reply must be strict JSON, validated with zod, with one repair call if it isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy.&lt;/strong&gt; Photos are resized in the browser, sent once to the model and never stored. There's no database. The route has a rate limit and a request size cap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality.&lt;/strong&gt; 115 unit tests (vitest) and an axe-core check with 0 violations in every state I tested, including the saved-example mode. I have not done manual screen-reader testing yet.&lt;/p&gt;
&lt;h3&gt;
  
  
  How do I know the Pidgin isn't wrong?
&lt;/h3&gt;

&lt;p&gt;One of my testers asked exactly this, and the honest answer is that Oja can't know. Here is what I do and don't have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What I have:&lt;/strong&gt; a worked example per style in the prompt, an automated check that voice markers appear in all three text fields, and my own reading of sample outputs. I'm a Yoruba speaker, so I read the Yoruba-flavoured outputs myself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What that can't tell me:&lt;/strong&gt; a marker check can see that "dey" appears. It can't tell whether a sentence sounds natural or fits the product. In some runs the Pidgin tote listings suggested "summer wear" and "beach", which the photo doesn't support, and they still passed the voice check. Pidgin and Yoruba also differ by region, age and context, and one reviewer is not a test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design choice:&lt;/strong&gt; Oja shows the draft for the seller to read before posting, because they're the best judge of their own voice. The results page now says so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next:&lt;/strong&gt; a "doesn't sound right" button for sellers, and a set of Pidgin and Yoruba listings reviewed by native speakers, which is the data you'd need to fine-tune an open-weight model.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Suggestions are a feature. Claims are not.
&lt;/h3&gt;

&lt;p&gt;My first prompt told the model never to invent details, and it kept adding one short use suggestion anyway ("enjoy it with bread or pancakes" for jam). I liked that. It's what a good shop assistant says. So I changed my mind: the prompt now allows &lt;strong&gt;one short, generic use suggestion&lt;/strong&gt;, and forbids stating facts the photos and hints don't support: material, size, brand, authenticity, condition, battery details, longevity and the like. That matters most for categories like perfume and used phones, where an invented "original" or "no scratches" could mislead a buyer.&lt;/p&gt;

&lt;p&gt;I measured it with 10 live generations per style on two images (the jam jar photo, and a flat illustration of a tote bag), before and after the change:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Old prompt&lt;/th&gt;
&lt;th&gt;New prompt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runs with an unsupported claim&lt;/td&gt;
&lt;td&gt;2 of 60&lt;/td&gt;
&lt;td&gt;0 of 60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs with a sensible use suggestion&lt;/td&gt;
&lt;td&gt;32 of 60&lt;/td&gt;
&lt;td&gt;57 of 60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valid JSON on the first try&lt;/td&gt;
&lt;td&gt;60 of 60&lt;/td&gt;
&lt;td&gt;60 of 60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice check passed&lt;/td&gt;
&lt;td&gt;59 of 60&lt;/td&gt;
&lt;td&gt;59 of 60&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two old unsupported claims were a "sturdy" handle and "orange fabric" on the tote bag. My coding agent read all 120 outputs and counted them, rather than relying only on a regex. It's a small sample, and going from 2 to 0 is not a guarantee. That's why Oja now shows a line under the results: &lt;em&gt;"Read it through and fix anything that isn't true for your product before you post."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What went wrong on the way
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smaller images made it worse.&lt;/strong&gt; To cut latency I tried 768 px photos instead of 1024 px. In my test, 0 of 9 responses were valid JSON on the first try, and 2 failed outright. I kept 1024 px.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A latency setting that wasn't available.&lt;/strong&gt; Google's endpoint rejected a reasoning-effort option for this model (HTTP 400 or 500).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I leaked my own endpoint.&lt;/strong&gt; The "model unreachable" error panel echoed the model server URL to the browser. The error-path tests I had my coding agent write caught it before any visitor did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A lost backslash.&lt;/strong&gt; The agent's own edit made &lt;code&gt;Retry-After: 1&lt;/code&gt; wait 0 ms. A test caught that too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My guard was too strict.&lt;/strong&gt; The "no sellable product" check rejected a flat illustration of a tote bag as "not a real product", though the same image had produced a listing earlier. It works on real photos, but it can refuse mockups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buttons didn't look clickable.&lt;/strong&gt; Tailwind v4 stopped giving buttons a pointer cursor. I noticed when I hovered over mine and nothing happened.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Reliability, honestly
&lt;/h3&gt;

&lt;p&gt;The free hosted endpoint sometimes returned HTTP 500, 502 and 503. In a batch of 9 calls with my early prompt, 2 failed. On the live site, some listings worked at once and some needed up to four tries. I added one automatic retry on 429, 5xx and network errors, plus an optional fallback model. &lt;strong&gt;The fallback never triggered in my testing&lt;/strong&gt;, so I can't claim it helped. By design it only fires after the retry fails on a 429, 5xx or network error, not on timeouts or invalid JSON. I also added a &lt;strong&gt;Try an example&lt;/strong&gt; button that shows a saved result instantly, labelled as saved and not live, so a bad moment on the free tier doesn't leave a visitor with nothing.&lt;/p&gt;
&lt;h3&gt;
  
  
  What it can't do yet
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Yoruba is "flavoured" English, not Yoruba.&lt;/li&gt;
&lt;li&gt;It may refuse illustrations and mockups.&lt;/li&gt;
&lt;li&gt;Local mode is far too slow on my CPU-only laptop to recommend today.&lt;/li&gt;
&lt;li&gt;In hosted mode, the photo goes to the model provider. I haven't verified what Google's free tier does with prompts, so check before using real customer photos.&lt;/li&gt;
&lt;li&gt;The price guide is a model's estimate, not market data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One app, two ways to run it.&lt;/strong&gt; I moved Oja from local Ollama to Google's hosted Gemma by changing three environment variables. With a closed API, "where do my seller's photos go?" has one answer. With open weights it has two: a hosted endpoint when speed matters, or the seller's own machine when privacy does. Local mode was too slow on my laptop, but it works, and a machine with a GPU would change that. I haven't tested that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No lock-in.&lt;/strong&gt; If a provider changes its price or has a bad week, the plan is a config change, not a rewrite. I built the architecture for that but haven't proven it in a real outage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A market a general model ignores.&lt;/strong&gt; Today Pidgin and Yoruba come from prompting alone. Open weights are what make fine-tuning on reviewed Pidgin and Yoruba listings possible, so the voice is trained instead of prompted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not every "open" is the same.&lt;/strong&gt; Google's Gemma 4 license page is the Apache License 2.0. The Gemma 3 model I use locally comes under Google's own Gemma Terms of Use, which include a prohibited-use policy. Both let me run the weights myself, but the terms differ, so the README links both. Oja doesn't redistribute any weights, and anyone building a business on either should read the terms first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where open did not win.&lt;/strong&gt; Running it locally is too slow on my laptop for a real seller, and the free hosted tier is flaky. I also didn't benchmark a closed API, so I can't claim Oja is cheaper or better than one. The hosted tier cost me nothing during the challenge, but I haven't worked out what a listing would cost at paid rates, and that's the number I'd need before charging sellers.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built Oja with Claude Code, an AI coding agent. The sessions are saved with DevRelay:&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Building Oja: a Nigerian listing writer on open-weight AI (Gemma 3 + Ollama)&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Build a brand-new project called Oja: a listing writer for small Nigerian sellers on Instagram and WhatsApp. Upload 1-3 product photos, optional hints, pick a style (Clear English, Naija Pidgin, Yoruba-flavoured English) and get ready-to-post copy. Next.js + TypeScript strict + Tailwind + Framer Motion, no DB, no auth. AI layer must call any OpenAI-compatible /v1/chat/completions endpoint (default local Ollama gemma3:4b), return strict JSON validated with zod, retry once with a repair prompt, log generation time. Verify against local Ollama and record the real generation time. Work end to end without asking questions.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Checking the environment first: Node, pnpm, git, Ollama and whether gemma3:4b is pulled.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-powershell"&gt;PowerShell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node -v; pnpm -v; ollama list; Invoke-WebRequest http://localhost:11434/api/tags&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;node -v; pnpm -v; ollama list; Invoke-WebRequest http://localhost:11434/api/tags&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Node v22.20.0, pnpm 11.1.2, gemma3:4b present, Ollama responding 200&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Scaffolding by hand rather than create-next-app to avoid interactive prompts. Installing Next 16, React 19, zod, framer-motion, Tailwind v4, vitest.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-powershell"&gt;PowerShell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm add next react react-dom zod framer-motion; pnpm add -D typescript tailw...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;pnpm add next react react-dom zod framer-motion; pnpm add -D typescript tailwindcss @tailwindcss/postcss eslint eslint-config-next vitest&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Installed. First attempt failed because Set-Content wrote a BOM into package.json; regenerated it with node.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Writing the AI layer: zod schema (with coercion of '25,000' strings and hashtag normalisation), a brace-balanced JSON extractor that strips code fences, per-style voice prompts, an OpenAI-compatible client with a timeout, and generateListing() which retries once with a repair prompt naming the invalid fields.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/schema.ts, lib/extract-json.ts, lib/prompts.ts, lib/ai-client.ts, lib/gen...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;lib/schema.ts, lib/extract-json.ts, lib/prompts.ts, lib/ai-client.ts, lib/generate.ts, lib/rate-limit.ts, app/api/generate/route.ts&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Files created&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Writing vitest coverage for fence stripping, extraction edge cases (braces and escaped quotes inside strings, truncated objects), validation, the repair flow, and the rate limiter. Then the UI: dropzone with client-side 1024px resize, segmented style picker, skeleton loading with rotating messages, result cards with copy buttons, WhatsApp link, error panels.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm typecheck; pnpm test; pnpm lint&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm typecheck; pnpm test; pnpm lint&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;typecheck clean, 19 tests passed. Lint initially crashed: typescript-eslint does not support TypeScript 7, so I pinned typescript to 6.0.3; lint then clean.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Build passes. No sample image existed, so I generated a synthetic Ankara-style tote bag with System.Drawing, started the production server and called the API once per style against real Ollama.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node scripts/smoke.mjs english | pidgin | yoruba&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node scripts/smoke.mjs english | pidgin | yoruba&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;english 148.7s, pidgin 148.0s, yoruba 157.2s on 100% CPU gemma3:4b; all returned valid JSON on the first attempt&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;About 150s per listing is too close to the 180s default timeout, so I raised the default to 300s and changed the last loading message to set expectations. Then drove the real UI with headless Edge: upload, resize, pick Pidgin, generate, copy. It completed in 170s with the Copied confirmation, and the only console error was a missing favicon, which I fixed.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;playwright-core UI run (outside the repo)&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ playwright-core UI run (outside the repo)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;UI wall 170.6s, copied label shown, 1 favicon 404&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Finished: README with env vars, endpoint swapping, Render notes, measured timings and a Why open models section; 8 conventional commits with no AI attribution; lint, typecheck, tests and build green.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        8 of 8 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Shipping Oja: model badge, error-path tests, Render prep, a11y and a public repo&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Oja already works (Next.js, TypeScript strict, Tailwind v4, Framer Motion, OpenAI-compatible AI layer, default local Ollama gemma3:4b). Do, in order, with small conventional commits: 1) a model badge showing the model and whether it runs locally or on a hosted endpoint, via a /api/health route that never exposes keys or the URL, plus an elapsed counter for local runs; 2) exercise the 429, 413, timeout and unreachable paths with tests and fix what is broken; 3) a README table for Ollama, OpenRouter, Google AI Studio and generic providers, verified, and make sure the client works with and without AI_API_KEY; 4) render.yaml with pinned pnpm and Node, health check, sync:false env vars, manual deploy and domain steps; 5) metadata, OG image and an axe-core accessibility pass with keyboard check; 6) real screenshots; 7) create the public GitHub repo and push; 8) final lint, typecheck, test, build. No questions, no Claude attribution, never print keys.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Added endpointInfo() (model plus a local flag from the base URL hostname, with a lookalike-host test), a /api/health route, a ModelBadge, and an elapsed-seconds counter in the loading state that only shows for local models.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/ai-client.ts (endpointInfo), app/api/health/route.ts, components/ModelBad...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ts &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpointInfo&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;health&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ModelBadge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;LoadingState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Wrote route-handler tests with fetch stubbed: 429 with Retry-After after 11 requests, 413 by header and by real body size, 504 on timeout, 502 on unreachable, 502 after one repair attempt on garbage. One test failed: the unreachable message echoed AI_BASE_URL back to the browser. That is a real leak for hosted endpoints, so I changed the message to a generic one and kept the test as a regression guard.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm test&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm test&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;46 tests passed after the fix (1 failed before it)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Provider facts were fetched from each provider's docs rather than written from memory: OpenRouter base URL and Bearer auth, Google's OpenAI-compatible base URL with image_url data URLs, Render blueprint fields, Node version precedence and DNS records. Google's Gemma page lists two Gemma IDs on the Gemini API, but nothing confirms them on the OpenAI-compatible path, so the README labels them as unverified examples.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-webfetch"&gt;WebFetch&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;provider and Render documentation pages&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;provider and Render documentation pages&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;OpenRouter https://openrouter.ai/api/v1; Google .../v1beta/openai/; Render region frankfurt valid; apex ALIAS/ANAME or A 216.24.57.1; www CNAME&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Render prep: packageManager pinned to &lt;a href="mailto:pnpm@11.1.2"&gt;pnpm@11.1.2&lt;/a&gt;, corepack in the build command, and --prod=false because Render's NODE_ENV during build is undocumented. Verified in a clean clone with NODE_ENV=production that a frozen-lockfile install and next build succeed and that allowBuilds in pnpm-workspace.yaml is honoured. This was a Windows run, not a Render run, and the README says so.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git clone; NODE_ENV=production pnpm install --frozen-lockfile --prod=false; p...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git clone; NODE_ENV=production pnpm install --frozen-lockfile --prod=false; pnpm build&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;install and build succeeded&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Accessibility: scripts/a11y.mjs runs axe-core on the empty, loading, result and error states with the API mocked, then tabs through the form. First run found small card headings below contrast, an h1 to h3 jump, and a duplicate tab stop on the hidden file input. Fixed with a darker ink token, h2 headings and tabIndex -1. All four states now report 0 violations.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node scripts/a11y.mjs http://localhost:3010&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node scripts/a11y.mjs http://localhost:3010&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;empty/loading/result/error: 0 violations; focus order: Add photo, three inputs, style radio, Write my listing; A11Y OK&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Generated the OG image from a script in the app palette, captured mobile and result screenshots (the result is one real gemma3:4b generation: 215 s through the UI on CPU), scanned the repo for secrets and attribution, then created the public repo and pushed. Lint, typecheck, 46 tests and the production build pass.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;gh repo create SeyinAlao/oja --public --source . --push&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ gh repo create SeyinAlao/oja --public --source . --push&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;https://github.com/SeyinAlao/oja, main pushed&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        7 of 7 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Getting Oja ready for Hacktoberfest: licence, honest README, secret scan&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Project: "Oja" (Windows, PowerShell), a working Next.js + TypeScript strict app (Tailwind v4, pnpm, vitest), public on GitHub at SeyinAlao/oja, live at &lt;a href="https://oja-ng.tech" rel="noopener noreferrer"&gt;https://oja-ng.tech&lt;/a&gt;, entered in the Hacktoberfest 2026 Open-Source AI Challenge Week 2 (Side Hustle). Work without asking questions. Do NOT add Claude as a co-author or add any Claude or Claude Code attribution anywhere. Never print, log or commit API keys. Keep any code and documentation clean and professional, with no filler comments. Use small conventional commits and push to origin main.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add an MIT LICENSE file (year 2026, holder "Seyin Alao") and the matching license field in package.json.&lt;/li&gt;
&lt;li&gt;README: add near the top a short block with the live demo (&lt;a href="https://oja-ng.tech" rel="noopener noreferrer"&gt;https://oja-ng.tech&lt;/a&gt;), the demo video (&lt;a href="https://www.youtube.com/shorts/oaQ9pkC1Vhw" rel="noopener noreferrer"&gt;https://www.youtube.com/shorts/oaQ9pkC1Vhw&lt;/a&gt;) and one line saying it was built for the Hacktoberfest 2026 Open-Source AI Challenge Week 2 (Side Hustle). Add a "Known limitations" section stating plainly: the free-tier hosted model endpoint sometimes returns HTTP 5xx errors and some listings need a retry; the optional fallback model has not been observed to trigger in testing; "Yoruba-flavoured English" is English with light Yoruba touches, not full Yoruba; manual screen-reader (NVDA/VoiceOver) testing has not been done; in hosted mode photos are sent to the model provider, so users should check that provider's terms; local CPU inference takes minutes. Make sure the timings table in the README clearly labels which model each number came from. Confirm docs/mobile.png and docs/result.png exist, are referenced correctly, and contain no people or private information.&lt;/li&gt;
&lt;li&gt;If the gh CLI is authenticated, run: gh repo edit SeyinAlao/oja --description "..." --homepage &lt;a href="https://oja-ng.tech" rel="noopener noreferrer"&gt;https://oja-ng.tech&lt;/a&gt; --add-topic hacktoberfest --add-topic gemma --add-topic open-weight-models --add-topic ollama --add-topic nextjs --add-topic typescript --add-topic nigeria --add-topic ai. If gh is not authenticated, give me the exact manual steps for the repo page.&lt;/li&gt;
&lt;li&gt;Security check: confirm .env* files are gitignored, and search tracked files and the full git history for anything resembling an API key (Google API keys typically start with "AIza") or a pasted .env value. Report only file names and commit hashes, never the matched text. If anything is found, stop and tell me what to rotate.&lt;/li&gt;
&lt;li&gt;Run lint, typecheck, tests and build, push, and reply with a short summary.&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I'll work in the Oja repo. Starting by looking at the repo state.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git status &amp;amp;&amp;amp; git log --oneline -8 &amp;amp;&amp;amp; git remote -v &amp;amp;&amp;amp; ls -la &amp;amp;&amp;amp; ls docs &amp;amp;&amp;amp; c...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git status &amp;amp;&amp;amp; git log --oneline -8 &amp;amp;&amp;amp; git remote -v &amp;amp;&amp;amp; ls -la &amp;amp;&amp;amp; ls docs &amp;amp;&amp;amp; cat package.json &amp;amp;&amp;amp; cat .gitignore&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;On branch main, up to date with origin/main, working tree clean.
Latest: 6c2f936 docs: refresh screenshots with real products photo
docs/: mobile.png, result.png
package.json: scripts dev/build/start/lint/typecheck/test/a11y/og; no license field.
.gitignore: node_modules, .next, .env, .env.local, *.tsbuildinfo, next-env.d.ts, test-assets/, .shots/, .shots-server.log&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;cat README.md &amp;amp;&amp;amp; git ls-files&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ cat README.md &amp;amp;&amp;amp; git ls-files&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;README (about 16 KB): intro, screenshots credited to local gemma3:4b, Run locally, Environment variables, hosted providers, AI layer, Decisions, Measured generation time (local and hosted tables), Accessibility, Privacy, Deploy on Render, Why open models.
Tracked files: app/, components/, lib/, scripts/, docs/*.png, .env.example and config files. .env.local is not tracked.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Security check first, since a finding would stop everything else.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git check-ignore -v .env .env.local .env.production .env.development.local
gi...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git check-ignore -v .env .env.local .env.production .env.development.local
git ls-files | grep -i '\.env'
git log --all --name-only --pretty=format: | grep -i '\.env' | sort -u
git grep -l -E 'AIza[0-9A-Za-z_-]{20,}'
for every commit in git rev-list --all: git grep -l -E 'AIza[0-9A-Za-z_-]{20,}'
for every commit: git grep -l -I -E '(sk-...|sk-or-v1-...|ghp_...|github_pat_|hf_...|gsk_...|AI_API_KEY=\S+|AI_FALLBACK_API_KEY=\S+|BEGIN PRIVATE KEY)'&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Ignored: .env, .env.local (not .env.production or .env.development.local)
Tracked env files: .env.example only, ever.
AIza in tracked files: none. AIza in history: none.
Other patterns: README.md in commits 2f38c88, 55e7679, b4812cf, fad5e09.
32 commits scanned.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;No AIza keys anywhere. Four old README versions matched a broader pattern; checking which pattern and whether it's a placeholder, without printing the value.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;for c in 2f38c88 55e7679 b4812cf fad5e09: classify each match by prefix and r...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ for c in 2f38c88 55e7679 b4812cf fad5e09: classify each match by prefix and remaining length only&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;All four: line 38, prefix AI_API_KEY=, remaining length 5, an angle-bracket placeholder.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Those four are a 5-character &amp;lt;...&amp;gt; placeholder in a docs example, not a key. Checking every historical .env.example value and long high-entropy strings.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;for each .env.example version: print variable names and value lengths only
fo...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ for each .env.example version: print variable names and value lengths only
for every commit: git grep -l -I -E '[A-Za-z0-9_-]{39,}' (excluding lockfile and images)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;4 versions of .env.example: AI_API_KEY and AI_FALLBACK_API_KEY always length 0.
No long opaque tokens in any commit.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git log --all --format=%B | grep -c -E 'AIza|sk-|AI_API_KEY='
file docs/*.png&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git log --all --format=%B | grep -c -E 'AIza|sk-|AI_API_KEY='
file docs/*.png&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;0 matches in commit messages.
docs/mobile.png: PNG 499 x 917
docs/result.png: PNG 780 x 5116&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;docs/mobile.png, docs/result.png&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;docs/mobile.png, docs/result.png&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;[images viewed] mobile.png: wide two-column layout, a Naija Pidgin listing for a jar of Blue Pearl cherry jam, model badge gemma-4-26b-a4b-it (hosted). result.png: phone layout, a Naija Pidgin listing for an Ankara tote bag (drawn illustration), model badge gemma-4-26b-a4b-it, 'Written in 27.8s'.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Neither screenshot shows a face or any personal data, but the README's description of them is out of date: both now show hosted gemma-4-26b-a4b-it, and mobile.png is a wide-screen result, not an empty phone state. Zooming in on the jar photo to check for hands or anything personal.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;crop the product thumbnail from docs/mobile.png and enlarge it 6x (System.Dra...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ crop the product thumbnail from docs/mobile.png and enlarge it 6x (System.Drawing) into a scratch file&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;[image viewed] Part of a hand holding the jar at the left edge; no face, too low resolution to identify anyone.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The jar photo has part of a hand holding it at the left edge. No face and not identifiable, but it is part of a person, so I'll flag it in the summary rather than altering the image. Checking gh auth.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;gh auth status | grep -v -i token&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ gh auth status | grep -v -i token&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Logged in to github.com account SeyinAlao (keyring). Active account: true.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now the edits. First the licence.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;LICENSE: standard MIT text, Copyright (c) 2026 Seyin Alao&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;LICENSE: standard MIT text, Copyright (c) 2026 Seyin Alao&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;package.json: after "private": true, add "license": "MIT",&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;package.json: after "private": true, add "license": "MIT",&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Updated.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git add LICENSE package.json &amp;amp;&amp;amp; git commit -m "chore: add MIT licence" -m "Th...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git add LICENSE package.json &amp;amp;&amp;amp; git commit -m "chore: add MIT licence" -m "The repository is public and entered in an open-source challenge, so it needs an explicit licence. Adds the MIT text (2026, Seyin Alao) and the matching license field in package.json."&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;dbc9d6e chore: add MIT licence&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Widening the ignore rule so .env.production, .env.development.local and similar files are covered too.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;sed: replace '.env' with '.env*' and '.env.local' with '!.env.example' in .gi...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ sed: replace '.env' with '.env*' and '.env.local' with '!.env.example' in .gitignore
git check-ignore -q .env.production &amp;amp;&amp;amp; git check-ignore -q .env.local &amp;amp;&amp;amp; ! git check-ignore -q .env.example &amp;amp;&amp;amp; echo OK&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;.gitignore now has .env* and !.env.example
OK&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git commit -am "chore: ignore every .env file except the example" -m "Only .e...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git commit -am "chore: ignore every .env file except the example" -m "Only .env and .env.local were ignored, so a .env.production or .env.development.local holding a key could be committed by mistake. Checked with git check-ignore: .env.local and .env.production are ignored, .env.example is still tracked."&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;8fb7191 chore: ignore every .env file except the example&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now the README changes: a links block at the top, accurate screenshot text, model labels on the timings, and the limitations section.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;README.md, top: add
- **Live demo:** https://oja-ng.tech
- **Demo video:** ht...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;README.md, top: add
- **Live demo:** https://oja-ng.tech
- **Demo video:** https://www.youtube.com/shorts/oaQ9pkC1Vhw
- Built for the Hacktoberfest 2026 Open-Source AI Challenge, Week 2 (Side Hustle).
Screenshot alt text now describes each image (cherry jam listing on a wide screen; Ankara tote listing on a phone), and the caption says both are real generations from hosted gemma-4-26b-a4b-it, as each model badge shows.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Updated.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;README.md, Measured generation time: add 'Two models were timed. Every number...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;README.md, Measured generation time: add 'Two models were timed. Every number below names the model it came from.' Add a Model column to the local table (gemma3:4b (local, CPU)) and the hosted table (gemma-4-26b-a4b-it). The two loose browser-UI timings (215 s, 27.8 s) now name their model.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Updated.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;README.md: add '## Known limitations' before Accessibility:
- Hosted endpoint...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;README.md: add '## Known limitations' before Accessibility:
- Hosted endpoint errors: the free-tier endpoint sometimes returns HTTP 5xx; Oja retries once, but some listings still need the seller to try again.
- Fallback model untested in practice: covered by unit tests, never observed to trigger in real testing.
- Yoruba-flavoured English is English with light Yoruba touches, not full Yoruba.
- No manual screen-reader testing (NVDA, VoiceOver).
- In hosted mode photos go to the model provider; check that provider's terms.
- Local CPU inference takes minutes per listing.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Updated.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Splitting the README change into separate commits.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;printf 'y\nn\nn\n' | git add -p README.md &amp;amp;&amp;amp; git commit -m "docs: link the li...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ printf 'y\nn\nn\n' | git add -p README.md &amp;amp;&amp;amp; git commit -m "docs: link the live demo and video, and describe the screenshots accurately"
printf 'y\nn\n' | git add -p README.md &amp;amp;&amp;amp; git commit -m "docs: name the model behind every timing"
git add README.md &amp;amp;&amp;amp; git commit -m "docs: add a known limitations section"&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;1c723b7 docs: link the live demo and video, and describe the screenshots accurately
062c1a1 docs: name the model behind every timing
6c1fd8d docs: add a known limitations section
Working tree clean.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Commits are in. Running lint, typecheck, tests and build.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm lint; pnpm typecheck; pnpm test; pnpm build&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm lint; pnpm typecheck; pnpm test; pnpm build&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;lint exit 0
typecheck exit 0
Test Files 11 passed (11), Tests 102 passed (102)
build exit 0 (routes: / static, /api/generate and /api/health dynamic)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;All green. Pushing, then updating the repo metadata.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git push origin main &amp;amp;&amp;amp; gh repo edit SeyinAlao/oja --description "Oja turns o...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git push origin main &amp;amp;&amp;amp; gh repo edit SeyinAlao/oja --description "Oja turns one product photo into Instagram and WhatsApp listings for Nigerian sellers, in English, Naija Pidgin or Yoruba-flavoured English. Open-weight Gemma, OpenAI-compatible endpoint, runs hosted or local." --homepage https://oja-ng.tech --add-topic hacktoberfest --add-topic gemma --add-topic open-weight-models --add-topic ollama --add-topic nextjs --add-topic typescript --add-topic nigeria --add-topic ai &amp;amp;&amp;amp; gh repo view SeyinAlao/oja --json description,homepageUrl,repositoryTopics,licenseInfo&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;6c2f936..6c1fd8d  main -&amp;gt; main
licenseInfo: MIT License; homepage https://oja-ng.tech; description set; topics: ai, gemma, hacktoberfest, nextjs, nigeria, ollama, open-weight-models, typescript&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Everything is done and pushed to origin/main. GitHub now detects the repo as MIT, and the description, homepage and all 8 topics are set.&lt;/p&gt;

&lt;p&gt;Security check: nothing to rotate. .env.local was never tracked; the only env file ever committed is .env.example, and its key fields were empty in all 4 versions. No AIza key in tracked files, any of the 32 commits, or commit messages; no other common key formats or long opaque tokens. 4 old commits (2f38c88, 55e7679, b4812cf, fad5e09) matched in README.md, but each is a 5-character &amp;lt;...&amp;gt; placeholder. Only .env and .env.local were ignored, so the rule is now .env* (keeping .env.example).&lt;/p&gt;

&lt;p&gt;Commits: chore: add MIT licence; chore: ignore every .env file except the example; docs: link the live demo and video, and describe the screenshots accurately; docs: name the model behind every timing; docs: add a known limitations section.&lt;/p&gt;

&lt;p&gt;Checks: lint, typecheck, tests (102 passed) and build all pass.&lt;/p&gt;

&lt;p&gt;One thing to decide: in docs/mobile.png, part of a hand holding the jam jar shows at the left edge of the product photo. No face and not identifiable, but it is part of a person. If 'no people' should hold strictly, retake that screenshot with the jar on a surface. docs/result.png shows only a drawn tote bag and no personal information.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Oja: a saved-example button, and measuring a prompt change before and after&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Project: "Oja" (Windows, PowerShell), a working Next.js (App Router) + TypeScript strict app (Tailwind v4, Framer Motion, zod, pnpm, vitest), deployed on Render at &lt;a href="https://oja-ng.tech" rel="noopener noreferrer"&gt;https://oja-ng.tech&lt;/a&gt;. It turns 1-3 product photos into an Instagram caption, description, WhatsApp status and rough Naira price guide in Clear English, Naija Pidgin or Yoruba-flavoured English. POST /api/generate calls an OpenAI-compatible chat completions endpoint (AI_BASE_URL, AI_MODEL, AI_API_KEY; currently Google's hosted gemma-4-26b-a4b-it via .env.local; never print, log or commit keys). Work without asking questions. Do NOT add Claude as a co-author or add any Claude or Claude Code attribution anywhere. Use small conventional commits and push to origin main. Code quality bar: clean, scalable, mid-level Google engineer standard, with proper separation of concerns, derived state instead of needless effects, shared types reused from the existing zod schema, and no filler or "vibe-coded" comments.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SAVED EXAMPLE. A product photo of a jam jar should exist at public/examples/jam-jar.jpg. If it does not, stop and tell me. Otherwise: resize it to at most 1024 px on the long side and compress it. Write a one-off script (scripts/generate-example.ts) that sends this photo through the real configured model service in all three styles, validates each result with the existing zod schema, and writes content/example.json with, per style: the full result, plus the model name, generation time and date. Re-run it if the JSON is invalid or the style is not followed in every field, and read the outputs yourself to check that they match the photo, contain no invented details, and carry no personal information.&lt;/li&gt;
&lt;li&gt;EXAMPLE BUTTON. In the empty state, add a secondary "Try an example" button. It shows the jam jar thumbnail and the saved result for the currently selected style instantly, with no network call, and switching style swaps between the three saved results. Label it "Saved example, written by &amp;lt;model&amp;gt; on &amp;lt;date&amp;gt;, not a live generation". Starting a real generation, or adding a photo, leaves the example mode. Keep it accessible and keep the example data separate from the live-generation logic.&lt;/li&gt;
&lt;li&gt;TESTS. Validate content/example.json against the zod schema in a test, and add a test showing that example mode does not call /api/generate. Re-run the axe and keyboard script for the new state and fix any findings.&lt;/li&gt;
&lt;li&gt;COVER IMAGE. Using the existing OG image script and palette, generate docs/cover.png at 1000x420 with the Oja wordmark and the line "One photo in. A Pidgin listing out."&lt;/li&gt;
&lt;li&gt;README: mention the saved example and how to regenerate it. Run lint, typecheck, tests and build, push, and give me a short summary.&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;First, checking that the jam jar photo exists.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;ls public/examples; file public/examples/*&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ ls public/examples; file public/examples/*&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;jam-jar.jpg: JPEG, Exif, 1080x1920, 490 KB&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;public/examples/jam-jar.jpg + list EXIF tag names (no values)&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;public/examples/jam-jar.jpg + list EXIF tag names (no values)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;[image viewed] A jar of Blue Pearl cherry jam on a grey surface; no people. EXIF tags: Width, Height, ExifIFD, Orientation; no GPS.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;No people and no location data, and re-encoding will drop the EXIF anyway. Reading the code: components/Studio.tsx is already 283 lines, so example mode can't just be added to it.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;wc -l app/*.tsx components/*.tsx lib/*.ts; cat lib/schema.ts lib/model-servic...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ wc -l app/*.tsx components/*.tsx lib/*.ts; cat lib/schema.ts lib/model-service.ts lib/generate.ts components/Studio.tsx components/ResultCards.tsx scripts/a11y.mjs scripts/make-og.mjs&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Read the schema (listingSchema, STYLES), the model service (generateWithFallback), Studio's Outcome union, ResultCards, the Playwright + axe script and the OG renderer. vitest runs in node without a DOM. jiti 2.7.0 is already installed transitively; Node 22.20 has process.loadEnvFile.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-powershell"&gt;PowerShell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scripts/resize-sample.ps1 -Max 1024 (via a temp file)&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;scripts/resize-sample.ps1 -Max 1024 (via a temp file)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;576x1024, 63335 bytes; JFIF only, EXIF gone&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/example-schema.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;savedExampleSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/examples/jam-jar.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STYLES&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listingSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;seen&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="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;generatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt; &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="nx"&gt;over&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;exhaustive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;so&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="nx"&gt;fails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scripts/generate-example.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;generate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Loads&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sends&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="nx"&gt;through&lt;/span&gt; &lt;span class="nx"&gt;generateWithFallback&lt;/span&gt; &lt;span class="nx"&gt;once&lt;/span&gt; &lt;span class="nx"&gt;per&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="nx"&gt;times&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt; &lt;span class="nx"&gt;fails&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;WhatsApp&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="nx"&gt;misses&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;voice&lt;/span&gt; &lt;span class="nx"&gt;markers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validates&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;whole&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;writes&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Naming&lt;/span&gt; &lt;span class="nf"&gt;styles &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pnpm&lt;/span&gt; &lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="nx"&gt;english&lt;/span&gt; &lt;span class="nx"&gt;yoruba&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;regenerates&lt;/span&gt; &lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;those&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm add -D jiti@2.7.0; add "example": "jiti scripts/generate-example.ts"; li...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm add -D jiti@2.7.0; add "example": "jiti scripts/generate-example.ts"; list AI_* variable names in .env.local (names only)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;AI_BASE_URL= AI_MODEL= AI_API_KEY= (values not printed)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm example&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm example&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;english: ok, gemma-4-26b-a4b-it, 16529 ms
pidgin: ok, 6638 ms
yoruba: ok, 7995 ms&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Read against the photo: Pidgin is acceptable. English invents uses ("Perfect for bread, pancakes, or yogurt"), which the prompt at the time forbade. Yoruba invents uses too, and misuses ẹ̀wà (beauty). Re-running only those two styles.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm example english yoruba (x4), then up to 8 more tries each filtered for b...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm example english yoruba (x4), then up to 8 more tries each filtered for bread|pancake|toast|snack|breakfast|taste&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Every one of 13 English and 13 Yoruba runs suggested a use or made a taste claim.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The model always adds a serving suggestion despite the prompt. Kept the candidates with the least of it and recorded it as a known limitation rather than changing the prompt in this task.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/example-schema.test.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Validates&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;checks&lt;/span&gt; &lt;span class="nx"&gt;every&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="nx"&gt;ships&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="sr"&gt;/, and that a file missing a style is rejected. 4 passed.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git commit (x2)&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git commit (x2)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;feat(example): add a script that generates the saved example
feat(example): add the jam jar photo and its saved listings&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;components/HintInputs.tsx; Studio uses one HintFields state&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;components/HintInputs.tsx; Studio uses one HintFields state&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;refactor(ui): move the hint fields out of Studio (283 -&amp;gt; 229 lines). Same ids, labels and help text.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/example.ts, components/ExampleResult.tsx, components/EmptyState.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;lib/example.ts, components/ExampleResult.tsx, components/EmptyState.tsx&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;lib/example.ts imports the JSON and only the schema's type, so zod stays out of the client bundle. ExampleResult renders the saved listing for the current style with a next/image thumbnail and the label "Saved example, written by &amp;lt;model&amp;gt; on &amp;lt;date&amp;gt;, not a live generation". EmptyState gets a btn-outline "Try an example" button.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;components/ResultCards.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ResultCards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Optional&lt;/span&gt; &lt;span class="nf"&gt;title &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your listing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;optional&lt;/span&gt; &lt;span class="nx"&gt;intro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optional&lt;/span&gt; &lt;span class="nf"&gt;onRegenerate &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;components/Studio.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Studio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Outcome&lt;/span&gt; &lt;span class="nx"&gt;gains&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt; &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt; &lt;span class="nx"&gt;shown&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;derived&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;selected&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;so&lt;/span&gt; &lt;span class="nx"&gt;switching&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="nx"&gt;swaps&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;no&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;Adding&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="nx"&gt;sets&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;outcome&lt;/span&gt; &lt;span class="nx"&gt;back&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;replaces&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Live&lt;/span&gt; &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="nx"&gt;announces&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Example loaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Example switched to &amp;lt;style&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Focus&lt;/span&gt; &lt;span class="nx"&gt;moves&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="nx"&gt;via&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scripts/a11y.mjs: new example section&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;scripts/a11y.mjs: new example section&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Opens the example by keyboard (Tab from the generate button, Enter), checks focus, live region, label, thumbnail, no Regenerate, axe, the Pidgin swap and its announcement, that adding a photo leaves example mode, and counts requests to /api/generate.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm build; pnpm start -p 3010; node scripts/a11y.mjs&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm build; pnpm start -p 3010; node scripts/a11y.mjs&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;empty/loading/result/no-product/error/example: 0 violations
ok: example mode made no request to /api/generate
A11Y OK&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Proving the network check can fail: temporarily adding a fetch to /api/generate in showExample and rebuilding.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node scripts/a11y.mjs (with the deliberate fetch)&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node scripts/a11y.mjs (with the deliberate fetch)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;FAIL: example mode called /api/generate 1 time(s)
A11Y FAILED (1)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The check catches it. Restored Studio, rebuilt, A11Y OK again.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;components/ExampleResult.test.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ExampleResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="nx"&gt;stubbed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;called&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="nx"&gt;caption&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;shown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not a live generation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="nx"&gt;appear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;there&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;Regenerate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="nx"&gt;tests&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scripts/make-og.mjs renders a list of images&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;scripts/make-og.mjs renders a list of images&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;public/og.png re-renders byte-identical; docs/cover.png 1000x420 with the awning mark and "One photo in. / A Pidgin listing out." (each sentence on its own line after the first render wrapped badly).&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;README.md&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight markdown"&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight markdown"&gt;&lt;code&gt;Saved example section (button behaviour, files, pnpm example [styles], what the script checks and what it cannot), pnpm og, and a known-limitation entry about the serving suggestions.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm lint; pnpm typecheck; pnpm test; pnpm build; git push origin main&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm lint; pnpm typecheck; pnpm test; pnpm build; git push origin main&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;all pass, 110 tests; 6c1fd8d..ea403d9 main -&amp;gt; main&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Done and pushed in 8 commits. One miss against the brief: the English and Yoruba saved listings each still suggest a use the photo doesn't show ("bread or pancakes", "bread or snacks"); 13 runs per style all did this. Recorded under Known limitations.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Intended behaviour, which the current prompt contradicts: the copy MAY include one short, generic, low-risk suggestion about how a product is used, served, worn or paired (for example "enjoy it on bread or pancakes" for jam). This is a feature that sellers like. What the model must NOT do is state facts that the photos or the seller's hints do not support: material or fabric, size, weight, quantity, ingredients, brand or model, storage or battery details, authenticity ("original", "authentic", "genuine", "NAFDAC approved"), condition ("brand new", "UK used", "no scratches"), performance or longevity ("long-lasting", "waterproof", "lasts all day"), origin, or any health or safety claim. If the seller's hints give such a fact, the copy may use it exactly as given. Friendly sign-offs in the Yoruba-flavoured style are intended and must NOT be banned. Test only with product photos, never photos of people.&lt;/p&gt;

&lt;p&gt;Time-box this to one pass:&lt;br&gt;
1. Rewrite the relevant part of the system prompt, keeping all voice rules and the JSON schema. Add one short positive and one negative example per style using a neutral product that is not the jam jar.&lt;br&gt;
2. Add a single short line under the results card, accessible and not a live-region announcement: "Read it through and fix anything that isn't true for your product before you post."&lt;br&gt;
3. Measure with 10 live generations per style on public/examples/jam-jar.jpg and test-assets/sample.jpg, before and after the change. Report per style how many outputs contained an unsupported factual claim (read the outputs yourself, do not rely only on a regex), and how many still contained a sensible generic use suggestion. Keep the change only if unsupported claims drop without removing the suggestions from most runs and without hurting the voice checks or first-try JSON validity; otherwise revert it.&lt;br&gt;
4. Regenerate content/example.json only if a saved example contains an unsupported factual claim. Show me all three saved listings either way and flag anything questionable.&lt;br&gt;
5. Update the README's Known limitations (use suggestions are intentional, read every line before posting, final numbers). Add a "Model licences" line: Gemma 4 (hosted) is Apache 2.0 per &lt;a href="https://ai.google.dev/gemma/apache_2" rel="noopener noreferrer"&gt;https://ai.google.dev/gemma/apache_2&lt;/a&gt;; Gemma 3 (local gemma3:4b) is under the Gemma Terms of Use (&lt;a href="https://ai.google.dev/gemma/terms" rel="noopener noreferrer"&gt;https://ai.google.dev/gemma/terms&lt;/a&gt;), which include a Prohibited Use Policy; Oja does not redistribute model weights.&lt;br&gt;
6. Update unit tests, run lint, typecheck, tests, axe and build, push, and give me a short before/after summary.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;cat lib/prompts.ts lib/prompts.test.ts; ls test-assets&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ cat lib/prompts.ts lib/prompts.test.ts; ls test-assets&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;FACTS said: 'Never invent a brand, a "collection", sizes, materials, quantities, occasions or use cases'. The Pidgin and Yoruba examples used 'brown leather' sandals. test-assets/sample.jpg is a drawn Ankara-style tote (no people).&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scratch measurement harness (not committed)&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;scratch measurement harness (not committed)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;10 runs x 3 styles x 2 photos through generateWithFallback with no hints; records the listing, attempts (first-try validity), voice markers per field and slang.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Baseline run (60 generations on the current prompt) in the background. It loaded the prompt at startup, so the prompt can be edited meanwhile.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/prompts.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;FACTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keep&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be concrete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;surface&lt;/span&gt; &lt;span class="nx"&gt;look&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;every&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;fact&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;photos&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;hints&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;support &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;full&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="nx"&gt;phrases&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;printed&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;such&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;brand&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="nx"&gt;counts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;shown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;hint&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt; &lt;span class="nx"&gt;exactly&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;given&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;MAY&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;ONE&lt;/span&gt; &lt;span class="nx"&gt;short&lt;/span&gt; &lt;span class="nx"&gt;generic&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;serve&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;wear&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;pair&lt;/span&gt; &lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Each&lt;/span&gt; &lt;span class="nx"&gt;voice&lt;/span&gt; &lt;span class="nx"&gt;gets&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;wrong&lt;/span&gt; &lt;span class="nx"&gt;caption&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;brown&lt;/span&gt; &lt;span class="nx"&gt;sandals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;wrong&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt; &lt;span class="nx"&gt;fails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Old&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;leather&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;examples&lt;/span&gt; &lt;span class="nx"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;Yoruba&lt;/span&gt; &lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="nx"&gt;keeps&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;O ṣeun&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;off&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/prompts.test.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Tests&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;unsupported&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;facts&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;hint&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;allowed&lt;/span&gt; &lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;wrong&lt;/span&gt; &lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="nx"&gt;per&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;mentions&lt;/span&gt; &lt;span class="nx"&gt;jam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="nx"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;components/ResultCards.tsx + test&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ResultCards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Plain&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="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-sm text-muted&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;after&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Read it through and fix anything that isn’t true for your product before you post.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;Test&lt;/span&gt; &lt;span class="nx"&gt;asserts&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;live&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git commit&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git commit&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;feat(ui): remind sellers to check the listing before posting&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-webfetch"&gt;WebFetch&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;https://ai.google.dev/gemma/apache_2 and https://ai.google.dev/gemma/terms&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;https://ai.google.dev/gemma/apache_2 and https://ai.google.dev/gemma/terms&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;apache_2 is titled 'Apache License 2.0' and linked as the 'Gemma 4 license'. The Terms of Use cover Gemma 1-3n and variants, say Gemma 4 has its own licence, and incorporate the Gemma Prohibited Use Policy (section 3.2). Both updated 2026-04-01.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;print the 60 baseline outputs + totals&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ print the 60 baseline outputs + totals&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;60/60 calls ok, 60/60 valid JSON first try, voice 59/60. Read by hand: unsupported claims 2 (tote, Yoruba: 'sturdy brown handle', 'orange fabric'). Use suggestions: jam English 10, Pidgin 2, Yoruba 10; tote English 0, Pidgin 0, Yoruba 10 (32/60).&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;whats the update&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The run on the new prompt is at 50 of 60 with no failures, no repair calls and no voice-check misses so far. The baseline is done: 2 of 60 outputs had an unsupported claim, 32 of 60 had a use suggestion. The reminder line is committed; the prompt waits for the comparison.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;print the 60 new-prompt outputs + totals&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ print the 60 new-prompt outputs + totals&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;60/60 calls ok, 60/60 valid JSON first try, voice 59/60. Read by hand: unsupported claims 0. Use suggestions: jam 10/10/10 ('perfect for bread or pancakes', 'E go sweet for bread or pancake', 'Ó dára with bread or toast'); tote English 7 ('pair it with casual outfits', 'carry your essentials'), Pidgin 10 ('carry am go market or outing'), Yoruba 10 ('pair with a simple summer dress'). 57/60.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Unsupported claims 2 -&amp;gt; 0, use suggestions 32 -&amp;gt; 57, JSON and voice unchanged. Keeping the change.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git commit&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git commit&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;feat(prompt): allow one use suggestion and forbid unsupported facts (commit body carries the measurements)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Saved example: none of the three states a fact from the list, so it was not regenerated. Flags: English says the label shows 'fresh cherries' (describes the picture, but ambiguous); 'delicious'/'sweet' are taste words, not counted; the Yoruba caption spells 'Ẹ jọ̀ọ́' where elsewhere it is 'E jọ̀ọ́'. All three predate the prompt change, which the README now says.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;README.md&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight markdown"&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight markdown"&gt;&lt;code&gt;AI-layer bullet describes the allowed suggestion and the forbidden unsupported facts; Known limitations: 'Read every line before posting' with the before/after numbers; Saved example notes which prompt produced it; new 'Model licences' section.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm lint; pnpm typecheck; pnpm test; pnpm build; node scripts/a11y.mjs; git ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm lint; pnpm typecheck; pnpm test; pnpm build; node scripts/a11y.mjs; git push origin main&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;all pass, 115 tests; axe 0 violations in every state including example; ea403d9..925950c main -&amp;gt; main&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Before -&amp;gt; after (10 runs per style per photo, read by hand): unsupported claims 2/60 -&amp;gt; 0/60; sensible use suggestion 32/60 -&amp;gt; 57/60; first-try valid JSON 60/60 -&amp;gt; 60/60; voice check 59/60 -&amp;gt; 59/60. Per style (claims, suggestions): jam English (0,10)-&amp;gt;(0,10), Pidgin (0,2)-&amp;gt;(0,10), Yoruba (0,10)-&amp;gt;(0,10); tote English (0,0)-&amp;gt;(0,7), Pidgin (0,0)-&amp;gt;(0,10), Yoruba (2,10)-&amp;gt;(0,10). 2 to 0 in 60 runs is a small difference and no guarantee; the README says so. Oddities in the new outputs: 'summer wear'/'beach' in Pidgin tote listings, and 'E kú àbọ̀' instead of 'E káàbọ̀' in two Yoruba jam runs.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        7 of 6 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;They cover the build, the deploy prep (including the error-path tests that caught my endpoint leak), the repo release and, in the last one, the saved-example button and the prompt change with its before-and-after measurements.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;Three sellers tried Oja with their own products: a clothing brand owner who sells on Instagram, a perfume reseller, and a vendor who sells used phones at lower prices. At their request, I haven't named them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked.&lt;/strong&gt; Between them they liked how it packages everything in one go, the captions, how it captured the local market, and the UI. The perfume reseller was surprised by how accurate the price guide was. The phone vendor liked the description and caption and that he could post to his WhatsApp status straight from the site. For some products, the price range matched what they'd expect.&lt;/p&gt;

&lt;p&gt;Their agreement is a sign it's plausible, not proof it's right. The range is a model's guess, and I haven't checked it against real sales.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What they asked.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Gemma was used in the app.&lt;/li&gt;
&lt;li&gt;Whether they'd have to download something to run Gemma. In the hosted version at oja-ng.tech, no. It's a website, and Gemma runs on Google's servers. Running it locally means installing Ollama and downloading a model of a few GB, and on my CPU-only laptop that took minutes per listing, so I don't recommend it to sellers yet.&lt;/li&gt;
&lt;li&gt;How to tell if the Pidgin has gone off course or is wrong (see above).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What didn't work.&lt;/strong&gt; Reliability. Sometimes it worked at once and sometimes it needed several tries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I changed because of it.&lt;/strong&gt; Nothing yet in response to their questions, since I only put it in their hands this week. The retry, fallback and saved-example button came from the reliability problems. Their Pidgin question shaped the section above and my next steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Would they pay?&lt;/strong&gt; All three said yes. The perfume reseller said about ₦10,000 a month, and the phone vendor about ₦15,000 a month. The clothing brand owner said it depends on the value of what he sells, which points to pricing by product value, not a flat fee. That's stated willingness from three people I know, not revenue, and I haven't tested any price.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A "doesn't sound right" button, and a reviewed set of Pidgin and Yoruba listings.&lt;/li&gt;
&lt;li&gt;Fine-tuning an open-weight model on that data, and real Yoruba output.&lt;/li&gt;
&lt;li&gt;Testing local mode on a machine with a GPU.&lt;/li&gt;
&lt;li&gt;Trying pricing by product value, as one tester suggested.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 4 26B A4B IT is the hosted model, and Gemma 3 runs locally through Ollama.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render:&lt;/strong&gt; deployed as a Render web service from a &lt;code&gt;render.yaml&lt;/code&gt; Blueprint, on a paid instance so it stays awake, with a &lt;code&gt;/api/health&lt;/code&gt; check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of a .tech Domain:&lt;/strong&gt; the live product runs on &lt;strong&gt;oja-ng.tech&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;AI disclosure: I built Oja with Claude Code, an AI coding agent, and I used Claude to help draft and edit this post from my own notes, test results and tester feedback. The listings shown are real model outputs, and the testers' feedback is theirs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you speak Pidgin or Yoruba and any of Oja's copy sounds off, tell me in the comments. Would love to hear your thoughts and feedback.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
