This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Nature apps have an awkward habit: they get you outside and then ask you to look at them. You lift the phone to identify a bird, the bird leaves, and you're still looking at the phone.
Paper Trail goes the other way. Tell it where you're walking and when. It finds the species people have actually reported there at this time of year, asks Gemma 3 1B, running inside your browser tab, for one line on how to recognise each, and gives you a single page to print: twelve species with checkboxes, a warning where Wikipedia gives a reason ("Toxic: don't taste"), the day's weather, the time to start by to finish in daylight, and a box to sketch whatever isn't on the card. On my Mac the screen part takes about twenty seconds. The phone can stay in your pocket for the rest of the afternoon.
It's for anyone who'd like to know what they're looking at without walking behind a screen: a family ticking boxes on a Sunday loop, a kid who just learned what a towhee is and wants to find another.
Demo
Live: paper-trail.mikey9220.workers.dev
Press See an Example for a finished card straight away. Your own card downloads Gemma 3 1B once (about 0.8 GB). In Chrome on my Mac the first card took 21 seconds of download and 17 to make; every card after that, about 17. Without WebGPU the page suggests quoting Wikipedia instead.
Code
mikey92
/
paper-trail
A printed field card for your walk: species people reported nearby, one line each written by Gemma 3 1B in your browser and checked against Wikipedia.
Paper Trail
A printed field card for this week's walk, written by Gemma in your browser.
Live: https://paper-trail.mikey9220.workers.dev · Write-up: on DEV · Evaluation: eval/EVAL.md · Hand review: eval/REVIEW.md
Tell it where and when. It finds the species people actually reported there in past years at this time of year (iNaturalist), reads what each one looks like (Wikipedia), and has Gemma 3 1B, running in a Web Worker on your own GPU, write one line on how to recognise each. Every line is checked against the Wikipedia sentences it was written from before it can reach the card; a line that fails is retried once with the rejected words named, and if it fails again the card quotes Wikipedia instead. You get one page with checkboxes, a box to sketch in, the weather and the time to start by to finish in daylight. Print it and put the phone…
How I Built It
where + when → iNaturalist: species reported within a few km, this month in past years
→ pick 12: birds, plants, a fungus and an insect, most-reported first
→ Wikipedia: the Description sentences about colour, shape, texture, sound
→ Gemma 3 1B in a Web Worker: one tip each
→ check against those sentences: fail → retry once → quote Wikipedia
→ Open-Meteo weather → one printable page
There is no backend: the page is static files on Cloudflare's free tier, and Gemma runs on your GPU. Transformers.js loads onnx-community/gemma-3-1b-it-ONNX-GQA (4-bit, 763 MB) on WebGPU, and decoding is greedy, so the same sentences always produce the same tip.
A 1B model is a careless witness
My first prompt asked for "ONE sentence of at most 15 words that tells a walker what to look or listen for", using "only words and facts from the source". I ran it on six places on three continents (72 species, the same inputs every time) and read every answer. Gemma 3 1B writes fluent, plausible tips, and gets the details wrong:
- Low smartweed: "Look for hairy stems…". Wikipedia: "The hairless, branching stems…"
- Great blue heron: "a rusty gray head". The neck is rusty-gray; the head is paler.
- A bracket fungus: "…and a mild taste". Wikipedia says so. No card should send anyone to taste a wild mushroom.
- Toyon, a shrub: "listen for the cheerful toyon song", borrowed from the robin in my example prompt.
- Common hawthorn: "Dark red fruit persists for 107.3 days, bearing 2.8 seeds." True. Useless on a walk.
What makes this fixable is that every tip is written from a handful of sentences I already have, so every tip can be checked against them. checkTip() in src/ground.ts passes a tip only if:
- every content word is in the source, after crude stemming (lobed and lobes, reddish and red); pointing words like look and observe are free;
- every colour, tone or pattern sits next to the same part in the source: within eight words, with no other colour in between. That catches the heron's rusty gray head, and green spores on sulphur tuft, whose gills turn green "as the blackish spores develop";
- there are no numbers, not even true ones, and nothing about taste;
- "listen for" comes with something to hear: a call, a song, drumming.
expect(checkTip("Look for yellow gills and green spores.", tuft).unsupported).toEqual(["green spores"]);
A failed tip gets one retry that names the rejected words (The source does not say: "gray leaves"…). If that fails too, the card quotes the article's most visual sentence in quotation marks. Every line on a card is either checked or quoted.
Four prompts, the same 72 species
| Prompt | What changed | Passed first try | Printed, with one retry | Quoted instead |
|---|---|---|---|---|
| v1 | 3 (4%) | 31 | 41 | |
| v2 | "Look for" or "Listen for"; a bird and a plant as examples; observe allowed; no numbers | 47 | 50 | 22 |
| v3 | No taste; "listen" needs a sound; no sentences comparing with another species; colour within eight words of its part | 46 | 49 | 23 |
| v4 | "Keep every colour with its own part"; no other colour in between | 29 | 39 | 33 |
v1's 4% wasn't Gemma being wrong 96% of the time: most first tries began "Observe the…", and observe isn't in any Wikipedia description. Reading the rejections, not just counting them, found that and most of the other fixes (full report). v4 prints fewer of Gemma's lines than v3 on purpose: I read every line both printed, and v3 had 12 wrong where v4 has 5. A quote is plain, but it's true.
What the check can't see
I read all 39 tips Gemma wrote in the final run next to their sources (every verdict): 29 right, 5 clumsy (glabrous, sori), 5 wrong. Two of the wrong ones:
- Wood duck: "listen for a rising whistle from the female". The rising whistle is the male's call.
- Toyon: "red fruits developing in December". They develop in August or September and ripen in December. That one is on the example card above; I left it there.
Every word is in the source and every colour is next to its part. What's wrong is whose or when, taken from the next clause, and a word-level check can't parse who a clause is about. It narrows what can go wrong from anything a model can imagine to true words put back together wrong, while the species list, warnings and weather stay plain data. A third was my fault, not Gemma's: the lyrebird article's tail section opens with "Adult males have tails up to 70 cm (28 in) long", my filter dropped that sentence for its measurement, and Gemma never learned whose tail had "two silvery median feathers".
Gemma 3 270M, a third of the download, isn't the default for a measured reason: on the same 72 species, one of its tips passed. It mostly copied the prompt's plant example onto everything ("bright white bark peeling in papery strips" for a sparrow and a hawk), and the check caught each one.
One deploy surprise
Everything worked on localhost; on the live site the model never loaded. Hugging Face answers 404, without a CORS header, to any request whose Referer is a *.workers.dev page (pages.dev and localhost are fine; I checked with curl). The page fell back to quoting Wikipedia, as designed, which is why I nearly missed it. The fix is one line in public/_headers: Referrer-Policy: no-referrer.
Why Does Open Innovation Matter?
It costs nothing to run, so it can stay free. A card is up to two dozen generations. On a hosted API that's a bill per card and a key to hide behind a server; here the model runs on the visitor's GPU and the site is static files.
Your location stays yours. The model never sees where you are, only Wikipedia sentences. A location from your device is rounded to about a kilometre before it goes to iNaturalist and Open-Meteo.
I could see and steer every step. Open weights let me run the same model, prompt and decoding in Node for a reproducible evaluation; for the example card it wrote the same twelve lines as the browser on WebGPU, but for one word. Retries are free, so being strict is free.
Where a closed model would be better: the writing. A frontier model would write livelier tips with fewer retries, but it would still need the same check, and it would put a server, a key and a bill between you and the walk.
The data is open too: iNaturalist's observations, Wikipedia's descriptions (CC BY-SA) and Open-Meteo's forecasts (CC BY). Paper Trail is glue and a small model on top of other people's open work.
Prize Categories
- Best Use of Gemma: Gemma 3 1B writes every tip in the visitor's browser on WebGPU, with no server, and most of this post is what it took to print a 1B model's sentences.


Top comments (0)