<?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: Sparsh Kumbhare</title>
    <description>The latest articles on DEV Community by Sparsh Kumbhare (@big0boy).</description>
    <link>https://dev.to/big0boy</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%2F4164649%2Fc533647f-9f74-446f-b6ed-5dff91a0d577.png</url>
      <title>DEV Community: Sparsh Kumbhare</title>
      <link>https://dev.to/big0boy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/big0boy"/>
    <language>en</language>
    <item>
      <title>git-ofrenda: a Día de los Muertos altar for the code your repo deleted</title>
      <dc:creator>Sparsh Kumbhare</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:11:00 +0000</pubDate>
      <link>https://dev.to/big0boy/git-ofrenda-a-dia-de-los-muertos-altar-for-the-code-your-repo-deleted-55kn</link>
      <guid>https://dev.to/big0boy/git-ofrenda-a-dia-de-los-muertos-altar-for-the-code-your-repo-deleted-55kn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge: Week 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Death came in lace and marigolds to &lt;code&gt;legacy_ftp_upload()&lt;/code&gt;'s door&lt;br&gt;
and found it uploading orders via FTP in a with block, the way it did before.&lt;br&gt;
"Your password stored in ~/.netrc," she laughed, "is why you've had your fall,"&lt;br&gt;
and &lt;code&gt;push_orders_to_sheet()&lt;/code&gt; moved right in and took it all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every repository is a graveyard. Somewhere in yours there's a function that ran every night for five years, until one Tuesday someone deleted it in a commit called "cleanup". Its author may have left the company. Nobody remembers what it did. Git does, though. Git remembers its body down to the byte, who wrote it, who removed it, and why.&lt;/p&gt;

&lt;p&gt;Día de los Muertos rests on a simple idea: the dead are only truly gone when they're forgotten. So once a year, families build an ofrenda, an altar with candles, marigolds, bread, water, salt and photographs, so the departed can find their way home for a night.&lt;/p&gt;

&lt;p&gt;I built one for code.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;git-ofrenda&lt;/strong&gt; is a git subcommand. Run &lt;code&gt;git ofrenda&lt;/code&gt; in any repository and it performs three rituals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The altar.&lt;/strong&gt; It walks the entire history, finds every function and class that once lived in the repo and no longer does, and builds them a candle-lit HTML ofrenda. Each spirit gets a sugar-skull portrait generated from its name, its dates, who wrote it and who laid it to rest, a cause of death read from the fatal commit, the code that replaced it, a one-line remembrance, a &lt;em&gt;calavera literaria&lt;/em&gt;, and its last words: the full source, exactly as it was the moment before it was deleted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The séance.&lt;/strong&gt; &lt;code&gt;git ofrenda summon &amp;lt;name&amp;gt;&lt;/code&gt; lights a candle in your terminal, and the function talks back in the first person. It knows its own body, every commit that ever touched it, and how it died.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unfinished business.&lt;/strong&gt; Ghosts linger where they're still spoken of. &lt;code&gt;git ofrenda haunts&lt;/code&gt; finds every line in today's code that still names something dead. A stale comment is a mention. A &lt;em&gt;call&lt;/em&gt; to a function that no longer exists is a bug waiting for its night, and &lt;code&gt;--strict&lt;/code&gt; exits non-zero so you can put it in CI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The remembrances, the details in every verse and the voices in the séance come from an open-weight model (qwen3:8b) running locally through Ollama. Nothing leaves the machine.&lt;/p&gt;

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

&lt;p&gt;To demo it without exposing anyone's real history, the repo includes a script that builds &lt;strong&gt;La Panadería del Barrio&lt;/strong&gt;, a fictional neighbourhood bakery with seven years of commits from four (invented) developers. Over those years its code buried a fax machine, a dot-matrix printer, an FTP server and a retry loop that took down production at 3am.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python scripts/make_haunted_repo.py /tmp/panaderia
git ofrenda &lt;span class="nt"&gt;-C&lt;/span&gt; /tmp/panaderia &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F18ph562zxz1qyg4pmpp2.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%2F18ph562zxz1qyg4pmpp2.png" alt="The ofrenda for La Panadería del Barrio" width="800" height="833"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spirits are placed on tiers by how long they lived: &lt;em&gt;Los Ancestros&lt;/em&gt; (more than a year), &lt;em&gt;Una temporada&lt;/em&gt; (a season), and &lt;em&gt;Velas breves&lt;/em&gt;, brief candles. &lt;code&gt;quick_hack_for_demo()&lt;/code&gt; lived 16 hours. Classes wear a crown of cempasúchil. The base of the altar holds the traditional offerings, each with its meaning: water for the long journey home, salt to keep the spirit whole, copal smoke to guide them, marigolds whose colour and scent light the path.&lt;/p&gt;

&lt;p&gt;Below the altar, the unfinished business:&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%2Fynwa69tp39lgs5wrc48j.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%2Fynwa69tp39lgs5wrc48j.png" alt="Offerings and unfinished business" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;legacy_ftp_upload()&lt;/code&gt; was deleted in November 2024 with the message &lt;em&gt;"Remove FTP sync, nobody has used it since 2021"&lt;/em&gt;. But &lt;code&gt;bakery/cron.py&lt;/code&gt; still has an &lt;code&gt;if os.environ.get("FTP_FALLBACK"):&lt;/code&gt; branch that calls it. The day someone sets that variable, the cron job crashes. That's a real bug, found by asking a ghost why it hasn't moved on.&lt;/p&gt;

&lt;p&gt;And the séance. This is a real, unedited answer from qwen3:8b:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git ofrenda summon retry_forever --ask "What happened at 3am?"
retry_forever() › The mists hide it. But I know what happened. At 3am, the accountant's
server was down, and I tried to upload the batch. I kept trying, again and again, without
giving up. My loop ran endlessly, and the system crashed under the weight of my persistence.
It was my fault. I never gave up, and I never checked if the server was truly unreachable.
I was meant to be a safety, but I became a storm. The developer who killed me knew.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In interactive mode you can type &lt;code&gt;/code&lt;/code&gt; to see its body, &lt;code&gt;/life&lt;/code&gt; for every commit that shaped it, &lt;code&gt;/haunts&lt;/code&gt; for where it's still named, and &lt;code&gt;/resurrect&lt;/code&gt; for the exact &lt;code&gt;git show … | sed -n&lt;/code&gt; command that brings it back.&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/Big0boy" rel="noopener noreferrer"&gt;
        Big0boy
      &lt;/a&gt; / &lt;a href="https://github.com/Big0boy/git-ofrenda" rel="noopener noreferrer"&gt;
        git-ofrenda
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Día de los Muertos altar for the code your repository deleted. Calaveras and séances from a local open-weight model.
    &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;git-ofrenda&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;A Día de los Muertos altar for the code your repository deleted.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git ofrenda&lt;/code&gt; walks your git history, finds every function and class that once lived in the repo and doesn't anymore, and builds them an ofrenda: a candle-lit HTML altar with a sugar-skull portrait, a lifespan, a cause of death, a &lt;em&gt;calavera literaria&lt;/em&gt; and the code's last words. You can also light a candle and talk with any of them, or ask which lines in today's code still call something that died.&lt;/p&gt;

&lt;p&gt;The writing is done by an open-weight model (qwen3:8b by default) running on your own machine through &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;. No source code, commit message or author name leaves your computer.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Big0boy/git-ofrenda/assets/altar.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%2FBig0boy%2Fgit-ofrenda%2FHEAD%2Fassets%2Faltar.png" alt="The ofrenda for a demo repository"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Requires Python 3.10+ and git. There are no Python dependencies.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/Big0boy/git-ofrenda &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="pl-c1"&gt;cd&lt;/span&gt; git-ofrenda
uv tool install &lt;span class="pl-c1"&gt;.&lt;/span&gt;          &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; or: pipx install .&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;That puts &lt;code&gt;git-ofrenda&lt;/code&gt; on your PATH, and git picks it…&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/Big0boy/git-ofrenda" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Pure Python standard library: no dependencies, about 2,600 lines (a fair share of it is the altar's SVG and CSS), 33 tests.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What lives there&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ofrenda/history.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;walking history, births and deaths, exhuming bodies, haunts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ofrenda/langs.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;how 15 languages announce a definition and where it ends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ofrenda/spirits.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the local model: calaveras, remembrances, the séance persona&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ofrenda/altar.py&lt;/code&gt;, &lt;code&gt;skulls.py&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the HTML ofrenda and the sugar-skull generator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scripts/make_haunted_repo.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the fictional bakery and its seven years of history&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;h3&gt;
  
  
  Finding the dead
&lt;/h3&gt;

&lt;p&gt;One streaming pass of &lt;code&gt;git log -p --unified=0 --reverse&lt;/code&gt; reads every diff in the repository, oldest first. Per-language patterns spot lines that open a definition: &lt;code&gt;def&lt;/code&gt;, &lt;code&gt;function&lt;/code&gt;, &lt;code&gt;const x = () =&amp;gt;&lt;/code&gt;, &lt;code&gt;func (r *T)&lt;/code&gt;, &lt;code&gt;pub fn&lt;/code&gt;, and so on across 15 languages. A &lt;code&gt;+&lt;/code&gt; line is a birth and a &lt;code&gt;-&lt;/code&gt; line is a death.&lt;/p&gt;

&lt;p&gt;Most deletions aren't deaths, though, and most of the work went into telling the difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Edits aren't deaths.&lt;/strong&gt; If a definition is removed and re-added in the same file in the same commit, its signature changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moves aren't deaths.&lt;/strong&gt; A function is only dead if nothing with its name is defined at &lt;code&gt;HEAD&lt;/code&gt; in the same language family. That also covers code that moved files, was renamed back, or was resurrected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Methods go with their class.&lt;/strong&gt; If a method's lines sit inside a class that died in the same commit, it rests in the class's portrait instead of getting one of its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once a spirit is confirmed, its body is cut from the file as it stood one commit before death (&lt;code&gt;&amp;lt;sha&amp;gt;^:&amp;lt;path&amp;gt;&lt;/code&gt;) using indentation, brace or &lt;code&gt;end&lt;/code&gt; matching that knows about strings, comments, template literals and Rust lifetimes. &lt;code&gt;git log -L&amp;lt;start&amp;gt;,&amp;lt;end&amp;gt;:&amp;lt;path&amp;gt;&lt;/code&gt; then recovers every commit that ever shaped those lines; those are the memories the ghost gets in the séance. Anything defined in the same commit that killed it is listed as &lt;em&gt;survived by&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I stress-tested it on real repositories. itsdangerous (677 commits) takes 0.3 seconds. In charmbracelet's &lt;code&gt;glow&lt;/code&gt;, it found &lt;code&gt;min()&lt;/code&gt; and &lt;code&gt;max()&lt;/code&gt;, deleted in an April 2025 lint cleanup after Go 1.21 had made them builtins. The first run on glow also reported 30 "calls to dead code", which was a lie. Go's grouped &lt;code&gt;type ( … )&lt;/code&gt; declarations hid living types, and a dead method called &lt;code&gt;Contains&lt;/code&gt; was being "called" by every &lt;code&gt;strings.Contains(&lt;/code&gt;. After fixing grouped types, cross-language namesakes and qualified calls, glow, hyperfine and itsdangerous all report zero false calls, while the panadería's real one is still caught.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 8B model couldn't rhyme, so I stopped asking it to
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;calavera literaria&lt;/em&gt; is a specific, playful form: a short rhymed verse, written every November, in which La Catrina comes to collect someone and teases them about their habits on the way out. I wanted every departed function to get one. qwen3:8b is fast on a laptop GPU, but it can't rhyme on request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Asked directly&lt;/strong&gt;, it wrote lively verses that mostly didn't rhyme. One also said the author "died too", which is exactly what a tool like this must never say.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With one example verse&lt;/strong&gt;, every verse in the run copied its skeleton: &lt;em&gt;"La Catrina found X still…, she said 'tonight you are mine'… and so it had to go."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asked to plan its rhymes first&lt;/strong&gt;, it planned &lt;em&gt;conchas / ranchas&lt;/em&gt; and &lt;em&gt;server / year&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With thinking turned on&lt;/strong&gt;, a verse took 25 to 65 seconds and still didn't rhyme.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Given the last word of each line&lt;/strong&gt;, it hit the word by bolting it on: &lt;em&gt;"You started with a dot-matrix, start."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I split the job along the line between what an 8B model is good at and what it isn't. It's very good at &lt;em&gt;reading code&lt;/em&gt;. It can tell you that &lt;code&gt;retry_forever&lt;/code&gt; had an "unconditional retry without limits" and that &lt;code&gt;fax_order_to_kitchen&lt;/code&gt; upper-cased every customer's name. So the model now returns JSON with those details, and Python owns the verse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_CALAVERAS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Death came in lace and marigolds to {label}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s door&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and found it {habit}, the way it did before.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;Your {quirk},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; she laughed, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;is why you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ve had your fall,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and {successor} moved right in and took it all.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each detail is validated before it goes in: a "quirk" that comes back as a verb phrase ("relied on ~/.netrc") gets rejected, and "one more one more upload" gets trimmed. &lt;strong&gt;Git, not the model, decides who the successor was.&lt;/strong&gt; The model only guesses; git knows which function was born in the commit that killed this one. Every verse rhymes now, nobody living is ever named as Death's target, and each one usually takes a single ~4-second call. A full altar for the bakery's 10 spirits builds in about 45 seconds on an RTX 4050 laptop GPU, and it's cached in &lt;code&gt;.git/ofrenda/&lt;/code&gt; after that.&lt;/p&gt;

&lt;h3&gt;
  
  
  The séance
&lt;/h3&gt;

&lt;p&gt;The ghost's system prompt is built entirely from facts: its source, every commit that shaped it, who wrote and deleted it, what replaced it, and every line that still calls it. It's told to speak in the first person, be exact about its own code, and &lt;em&gt;say the mists hide it&lt;/em&gt; rather than invent anything. Replies stream token by token in the terminal. With no model available, an offline ghost still answers questions about who, when, why and what from the facts alone.&lt;/p&gt;

&lt;p&gt;It isn't perfect. Asked about its bugs, &lt;code&gt;legacy_ftp_upload()&lt;/code&gt; invented a subtle one about generators and missed its real one: it calls &lt;code&gt;io.BytesIO&lt;/code&gt; and never imports &lt;code&gt;io&lt;/code&gt;. An 8B model embellishes. That's why every date, name and commit on the altar comes from git and never from the model, and why &lt;code&gt;--model&lt;/code&gt; takes any bigger model you have.&lt;/p&gt;

&lt;h3&gt;
  
  
  The altar
&lt;/h3&gt;

&lt;p&gt;The page is one self-contained HTML file with zero network requests: it has to work with the internet off, because the whole point is that it can. The papel picado is SVG with masks cut out in skull, flower, heart and marigold patterns. The candles are CSS, and each one flickers on its own rhythm. Every sugar skull is generated deterministically from a hash of the function's name, so &lt;code&gt;legacy_ftp_upload()&lt;/code&gt; always has the same face. Petals fall unless you've asked your OS for reduced motion. It's written to &lt;code&gt;.git/ofrenda/&lt;/code&gt; so it never shows up in your working tree.&lt;/p&gt;

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

&lt;p&gt;Because a repository's history is one of the most private things a team owns. It's every line of unreleased code, every author's name and email, and every honest commit message: &lt;em&gt;"fix: retry_forever caused the 3am outage (it never gave up)"&lt;/em&gt;. Plenty of companies forbid pasting their source into a hosted AI service, and they're right to. git-ofrenda reads all of it, and none of it leaves the laptop. With an open-weight model running locally, "is this allowed?" becomes a question about my hardware, not someone else's terms of service.&lt;/p&gt;

&lt;p&gt;It also changed how I could build. Every verse is a structured JSON call that I validate, score and retry, and a full altar is dozens of calls. Locally that costs nothing and has no rate limit, so I could work through half a dozen prompting strategies in an afternoon, and anyone can run &lt;code&gt;--all&lt;/code&gt; on a repo with 170 dead functions without thinking about a bill. I could switch thinking off per request, force JSON output, and swap the model with one flag: &lt;code&gt;--model gemma3:12b&lt;/code&gt; works the same way.&lt;/p&gt;

&lt;p&gt;And there's the theme itself. Hosted models get deprecated and APIs get sunset; they die the way functions do. An open model's weights on my disk don't. Neither does a self-contained HTML file. This altar will still open in ten years, offline. To be honest about it, a frontier model would write prettier verses than an 8B one. But the constraint is what pushed me to split the work: the model reads the code and git holds the facts. That's the better design either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;Día de los Muertos isn't Halloween in a different costume. It's a celebration of remembrance, and I tried to borrow from it carefully. The calavera is a real tradition of affectionate mockery, so the verses tease the code's habits, never people. Only code dies here: no author is ever named as Death's target, and the scribe is told outright never to suggest anyone died. The offerings carry their traditional meanings instead of being decoration. And the tiers, the marigold path and La Catrina's many names (La Flaca, La Huesuda) are there because the form deserves to be done properly.&lt;/p&gt;

&lt;p&gt;Running it on real repositories turned out to be moving in a way I didn't expect. &lt;code&gt;min()&lt;/code&gt; and &lt;code&gt;max()&lt;/code&gt; in glow lived for almost five years before the language itself made them unnecessary, which is about the best death a function can have. Every repo has a few spirits like that. Light a candle for them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Big0boy/git-ofrenda &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;git-ofrenda
uv tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/your-repo &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git ofrenda &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The model inside git-ofrenda is qwen3:8b, running locally through Ollama.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>LazyBonsai: an anti-hustle garden that only grows when you stop</title>
      <dc:creator>Sparsh Kumbhare</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:58:47 +0000</pubDate>
      <link>https://dev.to/big0boy/lazybonsai-an-anti-hustle-garden-that-only-grows-when-you-stop-1i28</link>
      <guid>https://dev.to/big0boy/lazybonsai-an-anti-hustle-garden-that-only-grows-when-you-stop-1i28</guid>
      <description>&lt;p&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;br&gt;
The moss does not hurry, and yet it covers the whole stone.&lt;/p&gt;

&lt;p&gt;Every productivity tool I open is quietly asking the same thing: &lt;em&gt;faster, please&lt;/em&gt;. More velocity, more streaks, more dashboards, more “you’re falling behind” nudges dressed up as encouragement. Even the wellbeing apps have turned rest into a metric to optimize.&lt;/p&gt;

&lt;p&gt;So for the &lt;strong&gt;Slow and Steady&lt;/strong&gt; challenge — in the spirit of &lt;strong&gt;International Sloth Day&lt;/strong&gt; — I built the opposite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://big0boy.github.io/lazy-bonsai/" rel="noopener noreferrer"&gt;LazyBonsai&lt;/a&gt;&lt;/strong&gt; is a digital bonsai garden that grows only while you do nothing. It is ambient, it is private, and its mind runs entirely on your own machine through local open-weight models (with offline wisdom baked right in).&lt;/p&gt;

&lt;p&gt;No accounts. No analytics. No API keys. No audio files. And absolutely no one telling you to hustle.&lt;/p&gt;


&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;Open the app and you see a small bonsai sapling on a soft canvas horizon. There is a timer, a few quiet controls, and one button: &lt;strong&gt;Enter Calm Mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;While Calm Mode is on — and you are not thrashing the mouse around — the garden begins to grow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;5 minutes of stillness&lt;/strong&gt; → buds appear on the branches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 minutes&lt;/strong&gt; → full bloom, petals drifting down through the scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;30 minutes&lt;/strong&gt; → a serene gold aura settles over the tree.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing is lost if you move. Frantic pointer movement just pauses the clock for a breath. The garden doesn’t punish you; it waits.&lt;/p&gt;

&lt;p&gt;Alongside the tree lives &lt;strong&gt;the Slow AI Sloth&lt;/strong&gt;, a local model that offers three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bonsai Whispers&lt;/strong&gt; — one or two poetic sentences about slowness, arriving periodically while you sit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Chill Pill&lt;/strong&gt; — you type what you’re panicking about (&lt;em&gt;“My client wants this bug fixed immediately”&lt;/em&gt;), and the sloth applies Occam’s razor, explains why it can safely wait fifteen minutes, and offers a quiet 15-minute timer so you can test the claim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tea with the Sloth&lt;/strong&gt; — a slow, streamed conversation for when the week has simply been too much.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And a &lt;strong&gt;“Why Open Innovation Matters”&lt;/strong&gt; drawer makes the case for why this had to be local, open, and free.&lt;/p&gt;

&lt;p&gt;There is also a full ambient soundscape — singing bowl, bamboo fountain, rain, wind — synthesized live with the Web Audio API.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why “slow and steady” needed a real mechanic
&lt;/h2&gt;

&lt;p&gt;A theme about slowness is easy to illustrate and hard to &lt;em&gt;implement&lt;/em&gt;. A countdown is not slowness; it’s just a different kind of pressure.&lt;/p&gt;

&lt;p&gt;So the Calmness Engine rewards the one thing productivity software never rewards: &lt;strong&gt;not producing&lt;/strong&gt;. The tree is the progress bar, and the only way to fill it is to leave the keyboard alone. Growth is persisted in &lt;code&gt;localStorage&lt;/code&gt;, so the garden you sat with yesterday is still there today. The tab can even be in the background — stepping away is the point, not a loophole.&lt;/p&gt;

&lt;p&gt;There is a deliberate &lt;code&gt;?speed=60&lt;/code&gt; time-lapse mode for demos and judging. In normal use, the garden makes you wait thirty real minutes for the gold aura. That waiting is the feature.&lt;/p&gt;


&lt;h2&gt;
  
  
  The stack, and the fun parts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; React 18, Vite, Tailwind CSS, Lucide icons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bonsai:&lt;/strong&gt; a seeded recursive fractal on &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, rebuilt from a deterministic PRNG so the same tree persists across sessions. Branch reach and thickness interpolate with calm time; blossom density, season palette, wind sway, falling petals, fireflies, and a slow day→dusk→night→dawn colour cycle are all drawn per frame. Clicking releases a ripple and a small gust of petals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The soundscape:&lt;/strong&gt; zero external assets. Four layers of pure synthesis — inharmonic sine partials with long exponential decays for the bowl, filtered pink-noise bursts and a wooden knock for the shishi-odoshi, looped pink noise through low- and band-pass filters for rain and wind, with a slow LFO wandering the wind’s centre frequency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The backend:&lt;/strong&gt; a small Express proxy in &lt;code&gt;server/index.js&lt;/code&gt;. It exists for three honest reasons: to avoid browser CORS with Ollama, to auto-detect installed models, and to own the offline fallback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mind:&lt;/strong&gt; whatever local model you already have. On my machine it’s &lt;code&gt;qwen3:8b&lt;/code&gt;, with &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt; as a second choice, but any installed Ollama model works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/index.js — the shape of every generation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;OLLAMA_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/generate`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&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="nf"&gt;stringify&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;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SLOTH_SYSTEM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;num_predict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nf"&gt;looksLikeThinkingModel&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="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;think&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling reasoning models without leaking them
&lt;/h3&gt;

&lt;p&gt;qwen3-class models like to think out loud, and &lt;code&gt;&amp;lt;think&amp;gt;…&amp;lt;/think&amp;gt;&lt;/code&gt; blocks are not what you want appearing in a zen whisper. LazyBonsai asks Ollama to disable thinking where supported, and otherwise strips reasoning blocks before they reach the UI — including &lt;strong&gt;while streaming&lt;/strong&gt;, where the stripping has to stay incremental so token deltas never desynchronise. The chat uses Server-Sent Events, so words arrive slowly instead of appearing in one anxious block.&lt;/p&gt;

&lt;h3&gt;
  
  
  Never failing, even when the model does
&lt;/h3&gt;

&lt;p&gt;A wellbeing app that shows a red error toast because a 7B model is cold is not calming. The proxy catches every failure — Ollama down, model missing, timeout, empty generation, a stream that dies halfway — and falls back to a hand-written pool of Taoist sloth reflections, Occam-cut panic responses, and gentle chat replies in &lt;code&gt;server/wisdom.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Offline chat even streams word by word with a small delay, because the pacing is part of the product.&lt;/p&gt;

&lt;p&gt;Pull the network cable out, stop Ollama, and the garden still works. That was a hard requirement, not a nice-to-have.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why open innovation matters here
&lt;/h2&gt;

&lt;p&gt;This project would be fundamentally worse as a SaaS product, and the “Why Open Innovation Matters” drawer in the app says so plainly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy by architecture.&lt;/strong&gt; People type real panic into a de-escalator. “My client wants this fixed immediately.” “I can’t switch off.” “I feel guilty for resting.” Those sentences should not leave the laptop. With a local model behind a localhost proxy, they physically cannot. There is no analytics, no telemetry, no third-party font or CDN, and no cloud fallback quietly uploading your worst afternoon. The only stored state is your calm seconds and garden preferences, in your own browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Energy conservation, literally.&lt;/strong&gt; A sloth survives on very little. Local quantized inference on hardware you already own means no datacenter spins up because you needed a gentler sentence. Token budgets are deliberately short; one stream at a time; a canvas that draws the garden instead of downloading it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero SaaS bills, forever.&lt;/strong&gt; No API keys, no seats, no usage dashboard waiting to surprise you. Clone it, &lt;code&gt;npm install&lt;/code&gt;, and it runs on an airplane, in a cabin, or on an old tablet with a charger. Open weights mean the intelligence in your calm companion cannot be deprecated, repriced, or shut down by a vendor.&lt;/p&gt;

&lt;p&gt;The garden is small on purpose. Small things are easier to keep alive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Big0boy/lazy-bonsai
&lt;span class="nb"&gt;cd &lt;/span&gt;lazy-bonsai
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# optional but recommended&lt;/span&gt;
ollama pull qwen3:8b

npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frontend at &lt;code&gt;http://localhost:5173&lt;/code&gt;, proxy at &lt;code&gt;http://localhost:8787&lt;/code&gt;. For a single-process, fully offline build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then everything is served from &lt;code&gt;http://localhost:8787&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyboard:&lt;/strong&gt; &lt;code&gt;w&lt;/code&gt; whisper · &lt;code&gt;c&lt;/code&gt; chill pill · &lt;code&gt;t&lt;/code&gt; tea · &lt;code&gt;g&lt;/code&gt; garden · &lt;code&gt;m&lt;/code&gt; sound.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Waiting is a design material.&lt;/strong&gt; The most interesting part of this build was not the model or the canvas — it was deciding how long the app should take to respond, and then defending that delay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fractals make excellent progress bars.&lt;/strong&gt; Watching branch thickness interpolate with calm seconds feels far more alive than a percentage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallbacks are part of the art direction.&lt;/strong&gt; The offline wisdom was originally a safety net. It became one of the most characterful parts of the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local models are good enough for gentleness.&lt;/strong&gt; A small model does not need to be correct about the world. It needs to be unhurried, kind, and private. That is a much lower bar, and a much better fit for open weights.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What’s next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;daily garden&lt;/strong&gt; that keeps three or four trees, one per day, so you can look back at a week of pauses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambient audio that responds to growth&lt;/strong&gt; — the bowl striking less often as the tree blooms.&lt;/li&gt;
&lt;li&gt;Optional &lt;strong&gt;export of a “calm receipt”&lt;/strong&gt; (a small local image of your tree and minutes rested) that you can keep, not share.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;screen-reader pass&lt;/strong&gt; on the canvas with a text description of the current growth stage.&lt;/li&gt;
&lt;li&gt;More offline reflections, especially ones contributed by other tired people.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try it, then close the tab
&lt;/h2&gt;

&lt;p&gt;If you take one thing from this post, make it the fifteen minutes. Open LazyBonsai, enter Calm Mode, and go make tea while the tree does absolutely nothing useful.&lt;/p&gt;

&lt;p&gt;That’s the whole product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Big0boy/lazy-bonsai" rel="noopener noreferrer"&gt;github.com/Big0boy/lazy-bonsai&lt;/a&gt; · &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://big0boy.github.io/lazy-bonsai/" rel="noopener noreferrer"&gt;big0boy.github.io/lazy-bonsai&lt;/a&gt; · &lt;strong&gt;Built for:&lt;/strong&gt; DEV “Slow and Steady” challenge &amp;amp; Hacktoberfest 🦥🌿&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI disclosure: this project was developed with AI-assisted coding tools. The code is open source, reviewed, and entirely local at runtime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>FirstPass (phototriage): Local Open-Weight Photo Culling for Wedding Photographers</title>
      <dc:creator>Sparsh Kumbhare</dc:creator>
      <pubDate>Thu, 15 Oct 2026 20:19:28 +0000</pubDate>
      <link>https://dev.to/big0boy/firstpass-phototriage-local-open-weight-photo-culling-for-wedding-photographers-1908</link>
      <guid>https://dev.to/big0boy/firstpass-phototriage-local-open-weight-photo-culling-for-wedding-photographers-1908</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;&lt;strong&gt;FirstPass (phototriage)&lt;/strong&gt; is a privacy-first, local CLI and visual review tool built for solo wedding and event photographers. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;3,000 frames come off the memory card after a 10-hour wedding. The photographer only delivers 400. &lt;/p&gt;

&lt;p&gt;Culling those photos is 4 to 8 hours of tedious, unpaid labour: eyeballing near-identical burst shots to find the single frame where nobody blinked, discarding motion blur, and grouping reception moments. It is the single most hated task in event photography, and it happens right after the adrenaline of the shoot on a tight delivery deadline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who Would Pay For It?
&lt;/h3&gt;

&lt;p&gt;Solo wedding and portrait photographers currently pay &lt;strong&gt;$20 to $40 every month&lt;/strong&gt; for cloud-based culling tools like Aftershoot, Narrative Select, or Imagen. &lt;/p&gt;

&lt;p&gt;FirstPass targets this exact market with a clear, honest promise: &lt;strong&gt;Not&lt;/strong&gt; "AI picks your best artistic photos" (a claim photographers mistrust), but &lt;em&gt;"here are the technical rejects (blurs, soft frames, redundant burst tails) isolated with mathematical precision, your keepers filed by category, and zero client photos ever leaving your drive."&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;FirstPass separates triage into two deliberate phases: &lt;strong&gt;Scan&lt;/strong&gt; (which writes an audit plan and touches zero originals) and &lt;strong&gt;Apply&lt;/strong&gt; (which safely isolates rejects into quarantine).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Interactive HTML Review Plan
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;phototriage scan&lt;/code&gt; runs, it generates an interactive, self-contained &lt;code&gt;report.html&lt;/code&gt; with instant previews, sharpness variance diagnostics, and zero-shot category badges:&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%2Fg6jte4t9lojljegl1sdk.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%2Fg6jte4t9lojljegl1sdk.png" alt="phototriage review report" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Walkthrough
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Generate a 28-frame synthetic shoot (blurs, bursts, bokeh, sharp keepers)&lt;/span&gt;
python scripts/make_fixtures.py /tmp/demo-shoot

&lt;span class="c"&gt;# 2. Scan and triage with local open-weight AI (measures ~50 images/sec on CPU)&lt;/span&gt;
phototriage scan /tmp/demo-shoot &lt;span class="nt"&gt;--ai&lt;/span&gt;

&lt;span class="c"&gt;# 3. Open the visual plan in your browser to inspect every decision&lt;/span&gt;
open /tmp/demo-shoot/.phototriage/report.html

&lt;span class="c"&gt;# 4. Preview what will happen without touching files (dry run)&lt;/span&gt;
phototriage apply /tmp/demo-shoot &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# 5. Move rejects safely into quarantine folders (_FirstPass_rejected/)&lt;/span&gt;
phototriage apply /tmp/demo-shoot &lt;span class="nt"&gt;--action&lt;/span&gt; move
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The complete source code, synthetic benchmark generator, and test suite are available on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Big0boy" rel="noopener noreferrer"&gt;
        Big0boy
      &lt;/a&gt; / &lt;a href="https://github.com/Big0boy/phototriage" rel="noopener noreferrer"&gt;
        phototriage
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Find blurry and near-duplicate photos, and categorize keepers with local open-weight AI.
    &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;phototriage (FirstPass)&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;3,000 frames come off the card. The photographer only wants to look at 400.&lt;/strong&gt;&lt;br&gt;
Find blurry and near-duplicate photos, and categorize keepers with an open-weight model running entirely on your own machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Big0boy/phototriage/assets/report-preview.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%2FBig0boy%2Fphototriage%2FHEAD%2Fassets%2Freport-preview.png" alt="phototriage review report" width="100%"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Nothing is uploaded. The only optional network access in an &lt;code&gt;--ai&lt;/code&gt; run is the one-time download of open CLIP weights to &lt;code&gt;~/.cache/huggingface/&lt;/code&gt;. After that, the tool works 100% offline. No client photo, thumbnail, or score ever leaves your drive.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Who is this for?&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solo wedding &amp;amp; event photographers&lt;/strong&gt; who shoot 2,000–4,000 frames per event and spend 4–8 unpaid hours per shoot manually culling near-identical burst frames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy-conscious professionals:&lt;/strong&gt; Client contracts increasingly prohibit uploading unreleased photos to third-party cloud AI vendors. &lt;strong&gt;phototriage&lt;/strong&gt; runs entirely locally — satisfying privacy clauses with zero marginal cost per image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone with large photo shoots&lt;/strong&gt; looking for an automated, explainable first pass that separates technical failures from keepers without making subjective…&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/Big0boy/phototriage" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Python 3.9+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packaging:&lt;/strong&gt; Installable directly via &lt;code&gt;pip install -e .&lt;/code&gt; with the &lt;code&gt;phototriage&lt;/code&gt; CLI entrypoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Suite:&lt;/strong&gt; 43 automated tests running on synthetic in-memory fixtures (&lt;code&gt;pytest tests -q&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;FirstPass was built with a strict hierarchy: &lt;strong&gt;cheap mathematical gates first, local open-weight neural networks second, and absolute reversibility at the file level.&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;[ Camera JPEGs ]
       │
       ▼
1. Metadata &amp;amp; Fast Decode (Pillow BOX downsampling)
       │
       ▼
2. Tile-Based Focus Scoring (Top 2% Laplacian variance — preserves shallow DOF bokeh)
       │
       ▼
3. Dual-Signal Burst Clustering (EXIF time + 64-bit dHash + 16x16 shape correlation guard)
       │
       ▼
4. Open-Weight CLIP AI (openai/clip-vit-base-patch32: zero-shot labels + embeddings)
       │
       ▼
5. Audit Artifacts (report.html, triage.csv, manifest.json)
       │
       ▼
6. Reversible Apply (moves rejects to _FirstPass_rejected/ with collision tracking)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Bokeh-Preserving Focus Metric
&lt;/h3&gt;

&lt;p&gt;Standard blur detectors compute global image variance. For wedding photography, that fails completely: a portrait shot at $f/1.4$ has a razor-sharp eye and a creamy, defocused background. Global averaging calls it "soft" and deletes the best photo of the day.&lt;/p&gt;

&lt;p&gt;FirstPass divides the image into a $32 \times 32\text{px}$ tile grid, computes Laplacian variance per tile, and averages &lt;strong&gt;only the sharpest 2% of tiles&lt;/strong&gt;. A small off-centre subject (like a bride's face covering 3% of the frame) is preserved, while true motion blur is caught instantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Dual-Signal Burst Clustering
&lt;/h3&gt;

&lt;p&gt;Bursts taken within a 3.0-second window are grouped, and only the sharpest frame is nominated as the keeper. To prevent fine textures (like confetti, gravel, or lace) from falsely collapsing into identical hashes, FirstPass requires &lt;strong&gt;both&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 64-bit difference hash (&lt;code&gt;dHash&lt;/code&gt;), &lt;strong&gt;AND&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A mean-centered, scale-normalized $16 \times 16$ grayscale signature Pearson correlation ($\ge 0.92$).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Open-Weight CLIP (&lt;code&gt;openai/clip-vit-base-patch32&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Powered by open weights licensed under the permissive &lt;strong&gt;MIT License&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Downloaded once (~600 MB) to &lt;code&gt;~/.cache/huggingface/&lt;/code&gt; and runs &lt;strong&gt;100% offline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Evaluates photos in bounded memory batches against natural language prompts (&lt;em&gt;"a wedding ceremony"&lt;/em&gt;, &lt;em&gt;"decorations, table settings and details"&lt;/em&gt;, &lt;em&gt;"two people posing together"&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critical rule:&lt;/strong&gt; The model is used to &lt;strong&gt;label and organize&lt;/strong&gt;, never to delete. Deletion decisions are strictly mathematical and explainable.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Open-source and open-weight models aren't just an implementation choice for this project — &lt;strong&gt;they are the entire commercial thesis:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Client Contracts Forbid Closed APIs:&lt;/strong&gt;
Wedding couples sign legally binding contracts. An increasing number of modern contracts explicitly state that raw client photos will not be fed to third-party commercial cloud AI services. Proprietary cloud tools (Aftershoot, Narrative) cannot clear these contract terms. Running an open-weight model locally is the only way a photographer can say: &lt;em&gt;"I can put this in my contract: your photos never leave my machine."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0 Marginal Cost Across 90,000 Frames/Year:&lt;/strong&gt;
$3,000\text{ frames} \times 30\text{ weddings} = 90,000\text{ images/year}$. On per-image cloud APIs, token and inference costs eat directly into freelance margins. Locally on the photographer's existing MacBook or laptop, the marginal cost per image is effectively zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works in the Field with Zero Wi-Fi:&lt;/strong&gt;
Photographers cull on flights, in mountain cabins, or in hotel rooms with terrible Wi-Fi before traveling home. FirstPass requires zero internet connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable Architecture:&lt;/strong&gt;
Because CLIP is open, a photographer shooting motorsport or indoor receptions can swap the checkpoint (&lt;code&gt;--model &amp;lt;HF_ID&amp;gt;&lt;/code&gt;) or supply custom label files without asking vendor permission.&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>LeafPace - Touch grass</title>
      <dc:creator>Sparsh Kumbhare</dc:creator>
      <pubDate>Fri, 09 Oct 2026 21:20:25 +0000</pubDate>
      <link>https://dev.to/big0boy/leafpace-touch-grass-516l</link>
      <guid>https://dev.to/big0boy/leafpace-touch-grass-516l</guid>
      <description>&lt;h1&gt;
  
  
  LeafPace 🍃
&lt;/h1&gt;

&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The screen must be the shortest part of the experience.&lt;/strong&gt; 30 seconds on your phone → 45 minutes on the trail.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;&lt;strong&gt;LeafPace&lt;/strong&gt; is a zero-backend progressive web app that gets runners, walkers, and hikers off the screen and into the wild — in under 30 seconds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nature-biased loop synthesis&lt;/strong&gt; — pick 🏃 Run / 🚶 Walk / 🥾 Hike, a distance (1–15 km), and a greenery bias (Dense Canopy / Park Hopper / Dirt Trail). LeafPace queries &lt;strong&gt;OpenStreetMap&lt;/strong&gt; for real parks, woods, and nature reserves around you, projects a loop through them, and snaps it to the actual pedestrian network (OSRM foot, with the Valhalla open engine as fallback). Standard nav apps route you down the noisiest asphalt; LeafPace routes you under the trees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solar &amp;amp; shade advisory&lt;/strong&gt; — computes the sun's azimuth/elevation for your exact coordinates and time, and flags when you'll be running into blinding low sun versus cool canopy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPX hand-off&lt;/strong&gt; — one tap exports a GPX 1.1 file (200–300 trackpoints) for Garmin, Apple Watch, Strava, Organic Maps — or share it straight to your watch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen off. Touch grass.&lt;/strong&gt; — after export, the screen dims and simply says: pocket your phone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Who it's for:&lt;/strong&gt; anyone who wants their run or hike to happen &lt;em&gt;outside&lt;/em&gt; the algorithm — and whose home address shouldn't be uploaded to a fitness social network to do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy by architecture:&lt;/strong&gt; there is no backend. Your GPS coordinates never leave your device — every network call LeafPace makes (OSM Overpass, Nominatim, OSRM/Valhalla) is a &lt;em&gt;location-independent&lt;/em&gt; public API query, and the AI runs 100% on-device.&lt;/p&gt;




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

&lt;p&gt;🔗 &lt;strong&gt;Live app: &lt;a href="https://big0boy.github.io/LeafPace/" rel="noopener noreferrer"&gt;https://big0boy.github.io/LeafPace/&lt;/a&gt;&lt;/strong&gt; (GitHub Pages, zero hosting cost)&lt;/p&gt;

&lt;p&gt;Try it on your phone: open the link, tap &lt;strong&gt;🍃 Synthesize Trail&lt;/strong&gt;, then &lt;strong&gt;📱 Lock Phone &amp;amp; Touch Grass&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;📦 &lt;strong&gt;Repo: &lt;a href="https://github.com/Big0boy/LeafPace" rel="noopener noreferrer"&gt;https://github.com/Big0boy/LeafPace&lt;/a&gt;&lt;/strong&gt; — every dependency is open source, every data source is an open commons.&lt;/p&gt;

&lt;p&gt;Stack: &lt;strong&gt;React 18 + TypeScript + Vite + Tailwind&lt;/strong&gt;, &lt;strong&gt;Leaflet&lt;/strong&gt;, &lt;strong&gt;@mlc-ai/web-llm&lt;/strong&gt; (lazy-loaded), &lt;strong&gt;SunCalc&lt;/strong&gt;, &lt;strong&gt;Vitest&lt;/strong&gt;, plus a dependency-free &lt;strong&gt;CDP smoke-test harness&lt;/strong&gt; (&lt;code&gt;scripts/smoke.mjs&lt;/code&gt;) that drives the real app in headless Chromium — fake GPS lock, route synthesis, GPX download and XML validation included.&lt;/p&gt;

&lt;p&gt;The part I'm proudest of is the green-space lookup, because public open infrastructure is &lt;em&gt;flaky&lt;/em&gt; and the app must never fail — it races three Overpass mirrors in parallel, hedges to Nominatim (a completely separate OSM service) if none answers in 2.5 s, retries, and only then falls back to synthetic parks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// First usable answer wins; a broken mirror can never stall route generation.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;firstSuccess&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GreenSpaceLookup&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;overpassWave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nominatimWave&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="nx"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;overpass&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Overpass&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nominatim&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; green-space lookup succeeded (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; results)`&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;winner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spaces&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;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The AI core is &lt;strong&gt;open-weight models running natively on the phone's GPU&lt;/strong&gt; via &lt;a href="https://webllm.mlc.ai/" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt; (WebGPU):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;code&gt;SmolLM2-360M-Instruct&lt;/code&gt; (quantized, ~250 MB cached in the browser) — swappable to &lt;code&gt;Qwen2.5-0.5B/1.5B-Instruct&lt;/code&gt; by changing one constant in &lt;code&gt;src/services/webgpu-llm.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt architecture:&lt;/strong&gt; the app builds a structured JSON digest (activity, distance, green spaces traversed, surface mix, sun angle, time of day) and the on-device model turns it into a 3-part naturalist briefing: &lt;em&gt;foliage highlight&lt;/em&gt;, &lt;em&gt;surface &amp;amp; footing advice&lt;/em&gt;, &lt;em&gt;mindset cue&lt;/em&gt; ("leave the earbuds behind…").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never blocks:&lt;/strong&gt; if WebGPU is unavailable or the model isn't downloaded yet, a deterministic template generator produces the briefing instantly — the 30-second budget is sacred. Once weights are cached in IndexedDB, briefings work &lt;strong&gt;fully offline&lt;/strong&gt; on the trail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Around the model sits the open geospatial pipeline: &lt;strong&gt;Overpass API + Nominatim&lt;/strong&gt; (green-space discovery), &lt;strong&gt;OSRM foot + Valhalla&lt;/strong&gt; (pedestrian snapping), &lt;strong&gt;SunCalc&lt;/strong&gt; (solar geometry), and a hand-rolled &lt;strong&gt;GPX 1.1 serializer&lt;/strong&gt;. All of it is a static PWA with a service worker — deployable anywhere, free forever.&lt;/p&gt;

&lt;p&gt;Testing: 29 vitest unit tests (loop geometry, polyline decoding, GPX, mirror-race fallbacks) plus the headless-browser smoke test, both run in CI on every push.&lt;/p&gt;




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

&lt;p&gt;Because &lt;strong&gt;both halves of LeafPace only exist because of open infrastructure&lt;/strong&gt;, and each has a closed-API twin that would be &lt;em&gt;worse for the user&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open geospatial commons:&lt;/strong&gt; proprietary map APIs bill per request and optimize for cars. OpenStreetMap has the unpaved path through the woods that Google never ranked. When the main Overpass mirror rate-limited me mid-build, I could race two community mirrors and a second open service (Nominatim) instead of reading a credit-card bill — resilience built &lt;em&gt;by&lt;/em&gt; the commons, &lt;em&gt;from&lt;/em&gt; the commons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-weight models:&lt;/strong&gt; your running route starts at your front door. Sending that prompt to a closed LLM API means uploading a precise home-coordinate fingerprint with it. WebGPU + open weights keep the entire inference — and the entire location history — physically on your phone, and keep working when you're already out of cell range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero server, zero cost:&lt;/strong&gt; no database of user tracks to leak, no bill to shut down. An open-source PWA on free static hosting is a complete product.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Entering every category this touches — please verify final category names against the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;challenge page&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open-Weight AI&lt;/strong&gt; — SmolLM2 / Qwen2.5 run via WebLLM, fully on-device&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge / Local Inference&lt;/strong&gt; — WebGPU in the mobile browser, offline-capable after first model download&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy &amp;amp; Data Sovereignty&lt;/strong&gt; — zero backend; location never leaves the device&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Geospatial Commons&lt;/strong&gt; — OpenStreetMap Overpass/Nominatim + OSRM/Valhalla&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Use of AI
&lt;/h2&gt;

&lt;p&gt;AI was used to create this project. The on-device briefing model (&lt;code&gt;SmolLM2-360M-Instruct&lt;/code&gt;) is an open-weight model distributed by its creators — no closed or proprietary LLM API was used at any point, since the app's premise is that location data must never leave the device.&lt;/p&gt;

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