<?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: Dev3 ^~^</title>
    <description>The latest articles on DEV Community by Dev3 ^~^ (@shashank03dev).</description>
    <link>https://dev.to/shashank03dev</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%2F4164403%2F567fe475-6428-41a8-9187-cb026fd21371.png</url>
      <title>DEV Community: Dev3 ^~^</title>
      <link>https://dev.to/shashank03dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shashank03dev"/>
    <language>en</language>
    <item>
      <title>I built an AI that tells you to leave the leaves alone</title>
      <dc:creator>Dev3 ^~^</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:27:22 +0000</pubDate>
      <link>https://dev.to/shashank03dev/i-built-an-ai-that-tells-you-to-leave-the-leaves-alone-20cn</link>
      <guid>https://dev.to/shashank03dev/i-built-an-ai-that-tells-you-to-leave-the-leaves-alone-20cn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Autumn yard work starts with a plan that's mostly habit: rake everything. Some of it is real work, like leaves on the steps you walk down every morning. Some of it you could skip. Penn State Extension notes that leaves in garden beds can stay where they fall to insulate roots, and the National Wildlife Federation describes the leaf layer as foraging ground and nesting material for birds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RakeLess&lt;/strong&gt; takes one ground-level photo of a yard and splits it into a 3×3 grid. You mark the patches you were planning to clear. It hands back a &lt;strong&gt;chore receipt&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Planned cleanup worth reconsidering:&lt;/strong&gt; leaves on a bed or under trees, seen clearly, with no site concerns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access work to keep:&lt;/strong&gt; leaves on a path or steps, whether you planned it or not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect before deciding:&lt;/strong&gt; lawns (a photo can't show how thick the layer is), mixed or unclear patches, anything outside the rules' scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No leaf task observed:&lt;/strong&gt; nothing on the ground, so it doesn't count as work saved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every line shows what the model saw, which rule fired, and any correction you made. The counts are grid cells. RakeLess doesn't turn them into square metres, minutes, or "birds helped".&lt;/p&gt;

&lt;p&gt;The lazy part is the point: do the clearing that matters, skip some of the rest, and have a written reason for each patch you skip.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live (hosted Gemma):&lt;/strong&gt; &lt;a href="https://rakeless.vercel.app" rel="noopener noreferrer"&gt;https://rakeless.vercel.app&lt;/a&gt;. The landing page replays a saved real run, labelled &lt;code&gt;RECORDED REPLAY · not a new model call&lt;/code&gt;. New photos go to Google's Gemini API, and the page says so before you upload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local run with the network cut off:&lt;/strong&gt; &lt;a href="https://github.com/shashank03-dev/RakeLess/blob/main/eval/results/OFFLINE.md" rel="noopener noreferrer"&gt;&lt;code&gt;eval/results/OFFLINE.md&lt;/code&gt;&lt;/a&gt;. Ollama and the RakeLess server ran inside a Linux network namespace with only loopback. The page records the network probe, the health check (provider, model digest), the single Ollama request, and the API response: live, uncached, 112.6 s.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also tried to record a captioned browser video of that local run. It didn't work on a 4-core VM: video capture slowed inference about tenfold, and all four attempts timed out (the first at my own 280 s cap, the rest at Ollama's ~5 minute request limit). The failed runs are in the same folder.&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/shashank03-dev" rel="noopener noreferrer"&gt;
        shashank03-dev
      &lt;/a&gt; / &lt;a href="https://github.com/shashank03-dev/RakeLess" rel="noopener noreferrer"&gt;
        RakeLess
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;RakeLess 🍂&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Mark the yard patches you were going to rake. RakeLess shows which of that work is worth reconsidering, which access work to keep, and which patches you need to look at first.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Fallen leaves in garden beds and under trees give shelter to insects and the birds that eat them. Leaves on a path still need clearing. RakeLess splits one ground-level photo into nine patches, has an open-weight Gemma model describe each patch with a few fixed labels, and runs nine plain rules over those labels. You can correct any label; the plan and the chore receipt update without another model call.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every result is a suggestion from a photo. It cannot see leaf depth, plant disease, or local rules. Walk the patch before you act.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://rakeless.vercel.app" rel="nofollow noopener noreferrer"&gt;https://rakeless.vercel.app&lt;/a&gt; (hosted Gemma through the Gemini API, see &lt;a href="https://github.com/shashank03-dev/RakeLess#privacy-and-retention" rel="noopener noreferrer"&gt;Privacy&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local mode:&lt;/strong&gt; the same app with Gemma 4 in Ollama…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shashank03-dev/RakeLess" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. &lt;code&gt;npm test&lt;/code&gt; covers the rules (including all 1,080 combinations of context and labels), the receipt, the model contract, the request deadline and retries, cache expiry, readiness recovery, and the provider HTTP contracts with mocks. &lt;code&gt;npm run eval&lt;/code&gt; reruns the evaluation below.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The model only looks. Plain rules decide.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;photo → server: decode, rotate, strip EXIF, 3×3 grid + 9 crops
      → Gemma labels each cell: surface · leaves on ground · visibility · one sentence of evidence
      → Zod: exactly nine valid cells (server and browser)
      → 9 ordered rules → map + chore receipt → you correct → rules re-run, no new model call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemma never gives advice. For each cell it picks from fixed labels: surface (bed/tree base, lawn, path/steps, mixed, unknown), fallen leaves (yes, no, unclear), and visibility (clear, unclear). Nine rules in &lt;code&gt;shared/rules.ts&lt;/code&gt; turn those labels into &lt;em&gt;leave for now&lt;/em&gt;, &lt;em&gt;clear access&lt;/em&gt;, &lt;em&gt;review&lt;/em&gt;, or &lt;em&gt;no leaf task&lt;/em&gt;. The same file runs on the server and in the browser, so a correction is instant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A bug that shaped the rules.&lt;/strong&gt; An earlier version checked "is it a path with leaves?" before "is the photo clear?". So a blurry patch the model &lt;em&gt;thought&lt;/em&gt; was a path became a confident "clear this". Uncertainty now runs first: unclear, mixed, or unknown patches go to review before any other rule. A test enumerates every combination and asserts that &lt;em&gt;leave&lt;/em&gt;, &lt;em&gt;clear&lt;/em&gt;, and &lt;em&gt;no task&lt;/em&gt; only appear where the rules allow them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two providers, one contract.&lt;/strong&gt; A small provider interface has two implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hosted:&lt;/strong&gt; &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; through the Gemini API. This is what the Vercel demo uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local:&lt;/strong&gt; any vision-capable Gemma tag in Ollama. I used &lt;code&gt;gemma4:e4b&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both receive the same gridded overview plus nine labelled crops. Both are validated by the same Zod schema and feed the same rules. The server picks the provider; the browser can't. There is no silent fallback from local to cloud. Each result records the provider, the model, the version (or the Ollama manifest digest for local runs), the prompt and rules versions, the image hash, model time, server time, the number of upstream calls, and cache status.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixing the failure paths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One deadline covers each request: 52 s on Vercel, inside the 60 s function limit. A retry only happens for invalid JSON or a transient error, and only if at least 10 s remain. Every retry counts against the daily budget.&lt;/li&gt;
&lt;li&gt;If the browser goes away, the upstream call is aborted.&lt;/li&gt;
&lt;li&gt;The readiness check describes the selected model provider, not just the web server. It retries with backoff and has a "Check again" button, so one slow probe no longer disables analysis until you reload.&lt;/li&gt;
&lt;li&gt;The old cache kept whole responses, base64 photos included, with no expiry. Now it keeps only the nine labels for at most 10 minutes. The privacy copy describes exactly that, including where Google's API terms apply.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the evaluation showed
&lt;/h2&gt;

&lt;p&gt;I wanted a number that couldn't come from one photo and its rotations. So I collected &lt;strong&gt;seven distinct, licensed photos&lt;/strong&gt; from Wikimedia Commons: a New Jersey lawn and sidewalk, a Maryland driveway, English garden steps, woodland leaf litter, an Italian sidewalk, a dark leaf pile, and a woodland floor in January. I aimed for 8–12, but Commons rate-limited the downloads. The coding agent wrote reference labels for every cell &lt;strong&gt;before&lt;/strong&gt; any model saw these photos, and committed them first. These are proxy labels, not expert ground truth.&lt;/p&gt;

&lt;p&gt;With local &lt;code&gt;gemma4:e4b&lt;/code&gt; on a 4-core CPU:&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;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Valid nine-cell answers&lt;/td&gt;
&lt;td&gt;7 / 7, no retries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time per photo&lt;/td&gt;
&lt;td&gt;95–226 s (median 124 s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cells sent to review&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;61 / 63&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reference leave/clear cells reached&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1 / 7&lt;/strong&gt; (1 / 26 if every photo were treated as in scope)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong &lt;em&gt;leave&lt;/em&gt; or wrong &lt;em&gt;no task&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 / 63&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's an honest middle result. The rule layer did its job: a weak model never produced confident bad advice. But a tool that sends almost everything to review is only a little lazier than raking. The failures were specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Canopy read as lawn.&lt;/strong&gt; On the New Jersey photo, the three tree-canopy cells came back as &lt;em&gt;"The grass shows scattered fallen leaves."&lt;/em&gt; In the inspector you change "on the ground" to &lt;em&gt;No leaves on ground&lt;/em&gt;, and each becomes &lt;em&gt;no leaf task&lt;/em&gt;. That's one click per cell and no second model call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A sharp driveway called "unclear".&lt;/strong&gt; On the Maryland close-up, all nine cells were unclear. Correcting the surface and confirming you looked turns them into &lt;em&gt;clear access&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate.&lt;/strong&gt; On two scenes the model repeated one sentence for all nine cells, so it clearly wasn't using the per-cell crops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I couldn't evaluate hosted Gemma because I had no API key in the build environment, so I'm not putting a hosted accuracy or speed number here. The one saved hosted run (16.7 s, one retry) is a single scene.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;What open weights changed here is concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I could take the model off the network.&lt;/strong&gt; The same Gemma family that powers the hosted demo ran on a CPU with external networking disabled, and the API answered with a live, uncached result tagged with the exact weights' digest. For a photo of someone's yard, being able to say "this never left the machine" matters. It isn't free: a 6.6 GB download, two to four minutes per photo on this hardware, and electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could swap the model without touching the judgement.&lt;/strong&gt; Moving from hosted 26B to local E4B changed nothing in the rules, the receipt, or the tests. The evaluation then showed exactly what that swap cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every judgement is readable.&lt;/strong&gt; The prompt, the label schema, the nine rules, and the sources behind them are in the repo. If you think lawns shouldn't always be "review", you can change R7, cite a better source, and rerun &lt;code&gt;npm run eval&lt;/code&gt; on the same seven photos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A proprietary vision API could fill the same "looks" slot. The rule layer doesn't care. What it couldn't give me is the offline run, or the chance to pin and inspect the exact weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope and limits
&lt;/h2&gt;

&lt;p&gt;The rules were written for temperate North American autumn yards. Anywhere else, every leafy patch becomes review-only. RakeLess can't measure leaf depth, spot disease or pests, know local fire or leaf rules, or judge whether a path is safe. Each cell of the 3×3 grid can mix surfaces, and when it does it's sent to review. A &lt;em&gt;leave for now&lt;/em&gt; is a suggestion to walk the patch first, not a certificate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Photos: Michael Gil (CC BY 2.0), Famartin, Mark Schellhase, Acabashi (CC BY-SA 4.0), Anna.Massini (CC BY 4.0), McKay Savage (CC BY 2.0), and Bob Embleton (CC BY-SA 2.0), via Wikimedia Commons. Rule sources: Penn State Extension, National Wildlife Federation, University of Minnesota Extension, and Joan Nassauer's work on cues to care. They inform a prototype and don't endorse it. Built with Gemma 4, Ollama, React, Vite, Zod, and sharp, with help from an AI coding agent.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma does all of the vision work, hosted through the Gemini API on the public demo, and locally through Ollama, including a run with the network cut off.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>gemma</category>
    </item>
    <item>
      <title>Paper Trail: Gemma writes my neighbourhood into a paper zine, so my phone can stay home</title>
      <dc:creator>Dev3 ^~^</dc:creator>
      <pubDate>Sun, 11 Oct 2026 02:37:56 +0000</pubDate>
      <link>https://dev.to/shashank03dev/paper-trail-gemma-writes-my-neighbourhood-into-a-paper-zine-so-my-phone-can-stay-home-3jhj</link>
      <guid>https://dev.to/shashank03dev/paper-trail-gemma-writes-my-neighbourhood-into-a-paper-zine-so-my-phone-can-stay-home-3jhj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr0c3nha50uubhilovqqy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr0c3nha50uubhilovqqy.gif" alt="A blank sheet gets printed into an 8-page zine, folds into a booklet, and the phone slides off the desk under a red stamp that says PHONE STAYS HOME" width="760" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most "go outside" apps want to come with you. They want a photo of the tree to prove you saw it, a check-in at the bench, a notification after ten minutes outside. You end up in the park looking at the same screen you left on your desk.&lt;/p&gt;

&lt;p&gt;I wanted an app you use for two minutes before a walk and two minutes after, and that stays at home in between.&lt;/p&gt;

&lt;p&gt;When I was thinking about it I kept coming back to the old I-Spy spotter books. Kids carried a little paper book, ticked off what they saw, and posted the finished book back for a reward. No battery, no signal, and the fun part happened outside.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Paper Trail&lt;/strong&gt;: an open model writes your real neighbourhood into an 8-page paper zine, you walk with paper and a pencil, and when you're back it reads your pencil marks from one photo.&lt;/p&gt;

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

&lt;p&gt;You pick a starting point, how long you want to walk and who's coming. Paper Trail pulls the real streets and places around you from OpenStreetMap, plans a loop, and Gemma writes the zine. It prints on one sheet of US Letter or A4. One cut, a few folds, and it's a pocket booklet.&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%2F0e6yi3jx2ffkhaa91i9g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0e6yi3jx2ffkhaa91i9g.jpg" alt="The setup page: pick a start point, a length and who's coming, then Fold my trail" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The eight pages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cover&lt;/strong&gt;: a title written for your spot, today's weather and a PHONE STAYS HOME stamp&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map&lt;/strong&gt;: your real streets, hand-drawn in code, with the loop and numbered stops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route&lt;/strong&gt;: directions by landmark, not turn-by-turn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I-Spy&lt;/strong&gt;: 8 to 10 things to tick off, at least six of them real mapped places on your route&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listen&lt;/strong&gt;: stand still for a minute and write down the loudest, the quietest and a new sound&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Riddle&lt;/strong&gt;: the answer is a real stop on your route&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sketch&lt;/strong&gt;: a drawing prompt tied to something you'll pass&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bring me home&lt;/strong&gt;: a QR code and a note on which model wrote it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the zine Gemma wrote tonight for Dolores Park in San Francisco, exactly as it prints. The top row is upside down on purpose: that's how a one-sheet zine folds.&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%2Fk585suqn55llb9w416wl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk585suqn55llb9w416wl.jpg" alt="The printed sheet: Listen, I-Spy, The Route and The Map upside down on the top row; The Riddle, Sketch, Bring me home and the cover " width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you leave. When you come back, you unfold the sheet and take one photo. The browser finds the four black corner squares, flattens the photo and checks every box for pencil. Gemma reads your handwriting, judges your riddle answer and describes your sketch. You get a Field Report with a postmark, a Trail Book of every walk, and a counter of time on the screen against time outside.&lt;/p&gt;

&lt;p&gt;It's for anyone who wants a reason to go outside without bringing the feed along: kids, families, someone walking a dog, or a teacher who wants thirty different trails starting at the school gate.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Try it live:&lt;/strong&gt; &lt;a href="https://paper-trail-t6mp.onrender.com" rel="noopener noreferrer"&gt;paper-trail-t6mp.onrender.com&lt;/a&gt;. No sign-up. If you'd rather not share your location, pick one of the example parks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15-second launch video:&lt;/strong&gt; &lt;a href="https://github.com/shashank03-dev/Paper-Trail-WEEK-1/blob/main/video/out/paper-trail-launch-1920x1080.mp4" rel="noopener noreferrer"&gt;watch it here&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;While Gemma writes, the sheet prints itself page by page, so you're not staring at a spinner:&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%2Ftka3ix0gdpxc6vqrec7j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftka3ix0gdpxc6vqrec7j.jpg" alt="Writing your trail: Gemma is planning the trail, a page counter shows 1 of 8, and the map page has already printed" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The read-back, on the live site
&lt;/h3&gt;

&lt;p&gt;To test the return trip end to end, I took that Dolores Park sheet and added the kind of marks you'd make on a walk: six ticks out of nine, three sounds, the riddle answer and a quick tree. I drew the marks in code and tilted and shadowed the sheet like a phone photo. That's the photo I uploaded:&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%2Fw40h2135c8komeev2w1d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw40h2135c8komeev2w1d.jpg" alt="A tilted photo of the sheet on a wooden table, with pencil ticks, handwriting and a small tree sketch" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The browser found the corners on its own:&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%2Fkqpu4s7l263ez776b186.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqpu4s7l263ez776b186.jpg" alt="Welcome back: Corners found, with the four markers outlined on the photo and a Read my sheet button" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;28 seconds later, the Field Report:&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%2Feare4n6kleho8kuzsyht.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feare4n6kleho8kuzsyht.jpg" alt="The Field Report for Golden Hydrants and Hilltop Views: a red Trail Office stamp for 110 points, spotted 6 of 9, the I-Spy list, and a note from the Trail Office" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It got all six ticks and all three untouched boxes right. It read "golden fire hydrant" and marked the riddle &lt;strong&gt;Right!&lt;/strong&gt;. It described the sketch as "a simple tree with a straight trunk and two overlapping circles for its leafy canopy", which is exactly what I drew. The Trail Office note even quoted my sounds back to me.&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/shashank03-dev" rel="noopener noreferrer"&gt;
        shashank03-dev
      &lt;/a&gt; / &lt;a href="https://github.com/shashank03-dev/Paper-Trail-WEEK-1" rel="noopener noreferrer"&gt;
        Paper-Trail-WEEK-1
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Every touch-grass app wants to come with you. This one stays home.
    &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;Paper Trail&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Every touch-grass app wants to come with you. This one stays home.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Paper Trail turns your real neighbourhood into a one-sheet, 8-page paper zine written by an open model (Gemma 4 — on your own machine, or through Ollama Cloud or the Gemini API), so your phone can stay home. When you get back, Gemma reads your pencil ticks, handwriting and sketches from one photo and stamps your Trail Book.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/shashank03-dev/Paper-Trail-WEEK-1/docs/media/hero.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshashank03-dev%2FPaper-Trail-WEEK-1%2FHEAD%2Fdocs%2Fmedia%2Fhero.gif" alt="TODO: hero GIF — the sheet printing panel by panel, then folding"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An entry for the DEV Hacktoberfest Open-Source AI Challenge, Week 1: &lt;strong&gt;Touch Grass&lt;/strong&gt;. MIT licensed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt; Setup ─► OpenStreetMap features + streets ─► route.ts picks a 5–6 stop loop (no AI)
   │      Open-Meteo weather                         │
   │                                                 ▼
   │                       Gemma writer: plan  ─►  cover · route · listen · back
   │                       (JSON schema → zod → grounding check → retry → template)
   ▼                                                 │
 IndexedDB  ◄──── the trail ◄──── one SVG sheet, mm-exact, drawn with rough.js&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shashank03-dev/Paper-Trail-WEEK-1" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. Next.js 15, TypeScript, Tailwind 4, rough.js for every wobbly line, three.js for the folding sheet on the landing page, and Dexie for storage in the browser. There are 90 unit tests and 12 browser tests that run the whole flow, setup to stamp, against a mock model.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; setup ─► OpenStreetMap places + streets ─► planner picks a 5–6 stop loop (no AI)
                                                  │
                         Gemma: plan ─► cover · route · listen · back
                         (JSON schema → zod → grounding check → retry → template)
                                                  │
                         one SVG sheet in millimetres ─► print, cut, fold, walk
                                                  │
 Field Report ◄─ combine ◄─ Gemma reads 4 page crops ◄─ warp ◄─ corner markers ◄─ one photo
                    ▲
                    └── ink count inside every printed checkbox (no AI)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gemma writes, but it doesn't navigate
&lt;/h3&gt;

&lt;p&gt;A plain planner picks the loop: the most interesting nearby places that fit your time at walking pace. Gemma gets that route plus a short list of real places, each with an id, and writes in two steps: first a plan (title, I-Spy list, riddle, sketch prompt), then the text for each page.&lt;/p&gt;

&lt;p&gt;Every answer goes through a JSON schema, then zod, then checks that the model can't talk its way past. Every place has to exist. At least six I-Spy items have to be real places. The riddle's answer has to be a stop on the route. Here's the riddle check, trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;riddleProblems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Riddle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WriterContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&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;waypoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;featureId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;wp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetFeatureId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`riddle targetFeatureId "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetFeatureId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" must be one of the WAYPOINT ids: ...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nf"&gt;idLeaks&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;fitsEverywhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;riddleText&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;riddle text is too long for its panel; make it shorter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The text also has to fit the page, and a page is 67 mm wide. SVG doesn't wrap text, so I generated width tables from the actual font files and measure every line before it's accepted. If any check fails, Gemma gets the exact problem back and another try. If it keeps failing, a template built from the same real places takes over, so you always get a zine.&lt;/p&gt;

&lt;p&gt;The checks earned their place tonight. Writing the Dolores Park zine took 5 calls, and 2 of them (the route page and the listen page) failed a check the first time and passed on the retry:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Live run, Dolores Park, 20 minutes&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Gemma 4 26B A4B, on the Gemini API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to write the whole zine&lt;/td&gt;
&lt;td&gt;41.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;13.8 tokens/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calls that needed a retry&lt;/td&gt;
&lt;td&gt;2 of 5 (route, listen)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I-Spy items tied to real mapped places&lt;/td&gt;
&lt;td&gt;7 of 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to read the photo back&lt;/td&gt;
&lt;td&gt;28.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ticks read correctly&lt;/td&gt;
&lt;td&gt;9 of 9 boxes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The paper is one SVG, in millimetres
&lt;/h3&gt;

&lt;p&gt;Everything on the sheet is drawn in code: the map, the weather icon, the stamp, the QR code. There are no stock images and no generated images. One layout file holds every measurement, so the preview on screen is the print, and the reader knows exactly where every checkbox was printed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading is where the open model earns its keep
&lt;/h3&gt;

&lt;p&gt;One photo of a whole sheet is a lot to ask of a model, so the browser does the geometry first, in plain TypeScript with no OpenCV: find the four corner markers, solve a homography, warp the photo flat and cut out the pages. Gemma then gets four small crops (I-Spy, Listen, Riddle, Sketch), each with a prompt saying what should be on it.&lt;/p&gt;

&lt;p&gt;For the ticks there's a second opinion that needs no model at all. Because I know where every box was printed, I measure how much pencil is inside it. When Gemma and the ink count agree, the answer stands. When they don't, the report marks the row in amber and you tap to fix it.&lt;/p&gt;

&lt;p&gt;On 30 synthetic phone photos (tilted, rotated, shadowed, blurred), the corner markers were found every time, and the ink count alone got every tick right. Real crumpled paper in bad light is harder than that, which is why every result can be fixed with one tap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugs I hit along the way
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My first sheet didn't fold right.&lt;/strong&gt; I'd offset the page grid by the print margin, so the real folds landed 3 mm off the page edges. Pages are now exact quarters of the sheet, with the margins inside each page. There's a &lt;code&gt;/fold-test&lt;/code&gt; page with giant page numbers to check a printer before printing the real thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma on the Gemini API thinks before it answers.&lt;/strong&gt; It spent its token budget on a hidden thought block and sometimes ran out before the JSON. Requests now ask for minimal reasoning, and any thought text that still arrives is stripped before parsing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real output leaked internal ids.&lt;/strong&gt; The prompt gives places ids like &lt;code&gt;n9000024&lt;/code&gt;, and Gemma sometimes printed them in the text ("the fountain n9000024"). Now every piece of text is checked for ids and sent back for a retry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same API takes no system prompt and no JSON schema for Gemma.&lt;/strong&gt; The client folds the system prompt into the request and puts the schema in the prompt, and the zod checks do the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running it
&lt;/h3&gt;

&lt;p&gt;Locally it runs on Ollama with &lt;code&gt;gemma4:e4b&lt;/code&gt;, or &lt;code&gt;gemma4:e2b&lt;/code&gt; on smaller machines. The live site runs on Render as one Docker web service in Singapore, and it uses Gemma 4 26B A4B through the Gemini API, with Gemma on Ollama Cloud as the fallback. Every default in the config is a Gemma model. Render redeploys on every push to &lt;code&gt;main&lt;/code&gt;. My last change went live 2 minutes 48 seconds after the push.&lt;/p&gt;

&lt;p&gt;Since the site is public, it has guard rails: a daily cap on model calls, at most four model calls at once with a queue, a health check that's cached so the model badge doesn't spend quota, and a block on other sites calling the API.&lt;/p&gt;

&lt;p&gt;Even the launch video is open source end to end. It's drawn in code from the same zine renderer and the landing page's fold model, and the voice is Kokoro-82M, an Apache-2.0 text-to-speech model that runs on a CPU.&lt;/p&gt;

&lt;p&gt;I built Paper Trail with an AI coding agent doing a lot of the typing while I made the calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Paper Trail works with two things I don't want to hand out lightly: a map of where I live, and a photo of my handwriting.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It can run entirely on my own machine.&lt;/strong&gt; With Gemma in Ollama next to the app, my location and my notes never leave my computer. There are no accounts and no analytics, and trails live in the browser. On the hosted site, the zine prints which provider wrote it, and the "your location never left this machine" line only appears when that's true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I can swap the model with one environment variable.&lt;/strong&gt; E2B for a small machine, E4B on a laptop, 26B or 31B hosted. Same prompts, same checks, same zine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There's always a way to run it yourself.&lt;/strong&gt; If a hosted provider rate-limits, the next one serves the same open model. If you don't trust any provider, you run it on your own hardware. A school, a library or a scout group can run it for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could make a small model reliable instead of hoping a big one is.&lt;/strong&gt; Because I control the whole loop, I could split the photo into four focused crops, add an ink count as a second opinion, and check every answer against the real map. A closed API would give me one big answer back and my fingers crossed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Where closed is better: a big hosted model writes a little more colourful copy, and it's faster than a small model on a CPU. For an app you open for two minutes before a walk, that's a fine trade.&lt;/p&gt;

&lt;p&gt;And once it's printed, it works with no signal and no battery. It's paper.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it can't do (yet)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenStreetMap coverage varies. Where a place is thinly mapped, the I-Spy list leans on things like "a passing dog" or "a leaf that's two colours".&lt;/li&gt;
&lt;li&gt;Hard light hurts. A shadow across the checkboxes can fool both the ink count and the model, which is why every row is one tap to fix.&lt;/li&gt;
&lt;li&gt;Small models still misread handwriting sometimes. The report never hides that.&lt;/li&gt;
&lt;li&gt;The screen-versus-outside counter only times the outside part if you print from the app.&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 does both halves of the app. It writes every zine (grounded in real places, schema-checked, fitted to the page) and reads every sheet that comes back (handwriting, riddle answers and sketch descriptions from four focused crops). It runs as E2B or E4B on Ollama locally, and as 26B A4B on the Gemini API with 31B on Ollama Cloud as the fallback for the live site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render:&lt;/strong&gt; the live site is a Docker web service on Render that redeploys on every push to &lt;code&gt;main&lt;/code&gt;, with a daily model budget, a concurrency gate and a cached health check so a public demo can't run up the bill. The repo also includes a Render Blueprint that runs Gemma itself in an Ollama private service with a persistent model disk, next to the web app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built solo.&lt;/p&gt;

&lt;p&gt;Print one this weekend, leave your phone at home, and tell me in the comments what you spotted.&lt;/p&gt;

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