<?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: Yash Dharme</title>
    <description>The latest articles on DEV Community by Yash Dharme (@yash_dharme).</description>
    <link>https://dev.to/yash_dharme</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%2F4155661%2F4bbda09d-5581-4730-b139-e1b1974401a0.jpg</url>
      <title>DEV Community: Yash Dharme</title>
      <link>https://dev.to/yash_dharme</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yash_dharme"/>
    <language>en</language>
    <item>
      <title>git-séance: I let the ghosts of deleted code speak at their own funeral</title>
      <dc:creator>Yash Dharme</dc:creator>
      <pubDate>Wed, 28 Oct 2026 17:31:02 +0000</pubDate>
      <link>https://dev.to/yash_dharme/git-seance-i-let-the-ghosts-of-deleted-code-speak-at-their-own-funeral-1anb</link>
      <guid>https://dev.to/yash_dharme/git-seance-i-let-the-ghosts-of-deleted-code-speak-at-their-own-funeral-1anb</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 — Ghost in the Machine&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Every repository is a graveyard.&lt;/p&gt;

&lt;p&gt;Files are born in a commit like &lt;code&gt;add new benchmarks (to match koa)&lt;/code&gt;. People fix them, extend them and argue about them for years. Then one day someone writes &lt;code&gt;chore: remove benchmarks directory&lt;/code&gt;, and they're gone: not from history, just from everyone's memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;git-séance&lt;/strong&gt; holds a séance for them. It's a small command-line tool that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;finds every file that ever died&lt;/strong&gt; in a repository's history;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lets each ghost speak&lt;/strong&gt;: who created it and when, what it used to do, how long it lived, who cared for it, and the exact commit that ended it;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;builds an *ofrenda&lt;/strong&gt;*, a &lt;code&gt;GRAVEYARD.md&lt;/code&gt; memorial page for your repo's longest-lived departed code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's spooky, and it's also my take on Día de los Muertos: a small altar for the code that held the project up before anyone now on the team arrived.&lt;/p&gt;

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

&lt;p&gt;I pointed it at the &lt;a href="https://github.com/expressjs/express" rel="noopener noreferrer"&gt;Express&lt;/a&gt; repository: 6,183 commits and 16 years of history.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git-seance --repo express

🪦  684 files rest in the history of express.

   The longest-lived:

   ⚰️  benchmarks/run                              15.7 years   ✝ 2026-01-17
   ⚰️  benchmarks/Makefile                         12.1 years   ✝ 2026-01-17
   ⚰️  benchmarks/middleware.js                    12.1 years   ✝ 2026-01-17
   ⚰️  Contributing.md                             11.1 years   ✝ 2025-06-09
   ⚰️  examples/multipart/index.js                 10.8 years   ✝ 2023-05-18
   ⚰️  test/mocha.opts                             10.2 years   ✝ 2022-02-21
   …
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That listing takes about 0.3 seconds. Then you summon one:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git-seance summon lib/view/partial.js --repo express

👻  lib/view/partial.js

   I was partial.js. Tj Holowaychuk brought me into this world on 2011-02-03,
   with the words "moved files from lib/express/* to lib/". I used to resolve
   partial object names from the view path, resolving them into a human-readable
   name. I walked these halls for 8 months, and 2 commits passed through me. By
   the end I was 40 lines. Then, on 2011-10-07, Tj Holowaychuk wrote the words
   that ended me: "re-working view system".

   ── its last words ──
   │ …the file's final contents, straight out of git…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And this is part of the ofrenda it wrote for Express (&lt;code&gt;git-seance ofrenda --n 6&lt;/code&gt;, about 35 seconds on a laptop CPU):&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  ⚰️ &lt;code&gt;Contributing.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;2014-05-12 – 2025-06-09 · 11.1 years&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was Contributing.md. Roman Shtylman brought me into this world on 2014-05-12, with the words "add Contributing.md". I used to guide newcomers through the process of contributing to the Express.js project, guiding them towards involvement while minimizing unnecessary hurdles. I walked these halls for 11.1 years, and 33 commits passed through me. By the end I was 246 lines. Then, on 2025-06-09, Sebastian Beltran wrote the words that ended me: "fix(docs): move documentation and charters to the discussions and .github … (#6427)". Tell Ulises Gascón, Blake Embrey and Douglas Christopher Wilson I remember them.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚰️ &lt;code&gt;test/mocha.opts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;2011-12-20 – 2022-02-21 · 10.2 years&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was mocha.opts. Tj Holowaychuk brought me into this world on 2011-12-20, with the words "mocha.opts". [...] I walked these halls for 10.2 years, and 2 commits passed through me. By the end I was 3 lines. Then, on 2022-02-21, Douglas Christopher Wilson wrote the words that ended me: "tests: remove usage of should".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every name, date, count and commit message in there is real, pulled straight from git.&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/Yash11778" rel="noopener noreferrer"&gt;
        Yash11778
      &lt;/a&gt; / &lt;a href="https://github.com/Yash11778/git-seance" rel="noopener noreferrer"&gt;
        git-seance
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Summon the ghosts of deleted code from your git history, with an open-weight model (Gemma) running on your own machine.
    &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-séance&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Every repository is a graveyard. Files are born, worked on for years, and then one
commit (&lt;code&gt;chore: remove benchmarks directory&lt;/code&gt;) ends them. &lt;strong&gt;git-séance&lt;/strong&gt; finds them
lets each ghost tell its story, and builds an &lt;em&gt;ofrenda&lt;/em&gt;, a memorial page, for your
repo's departed code.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine&lt;/a&gt;.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;$ git-seance --repo express
🪦  684 files rest in the history of express.

   The longest-lived:

   ⚰️  benchmarks/run                              15.7 years   ✝ 2026-01-17
   ⚰️  benchmarks/Makefile                         12.1 years   ✝ 2026-01-17
   ⚰️  Contributing.md                             11.1 years   ✝ 2025-06-09
   ⚰️  examples/multipart/index.js                 10.8 years   ✝ 2023-05-18
   ⚰️  test/mocha.opts                             10.2 years   ✝ 2022-02-21
   …

$ git-seance summon lib/view/partial.js --repo express

👻  lib/view/partial.js

   I was partial.js. Tj Holowaychuk brought me into this world on 2011-02-03,
   with the words "moved files from lib/express/* to lib/". I used to resolve
   partial object names from the view path, resolving them&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/Yash11778/git-seance" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. About 280 lines of plain Node.js, no build step. Point it at any git repository you have locally:&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/Yash11778/git-seance &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;git-seance &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install
&lt;/span&gt;node bin/git-seance.mjs &lt;span class="nt"&gt;--repo&lt;/span&gt; /path/to/any/git/repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git log --diff-filter=D  ──► every deleted path (skipping any that were brought back)
git log --diff-filter=A  ──► when and by whom each one was born
git rev-list / shortlog  ──► how many commits touched it, who cared for it most
git show &amp;lt;death&amp;gt;^:&amp;lt;path&amp;gt; ──► its last words (the final contents)
                         ──► Gemma reads the last code ──► "I used to …"
                         ──► the ghost's speech: git facts + Gemma's one line
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The lesson: a ghost may be eerie, but it may not lie
&lt;/h3&gt;

&lt;p&gt;My first version gave Gemma a list of true facts about each file and asked it to write the whole eulogy. The voice was perfect and the history was invented. A file changed in exactly two commits became &lt;em&gt;"a steady flow of updates"&lt;/em&gt;. A file that was simply deleted &lt;em&gt;"faded away slowly, shrinking with each line deleted"&lt;/em&gt;. And when it didn't feel like using a fact, it dodged: &lt;em&gt;"I was created on a specific date."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's the one thing a tool about honouring the past can't do. So the work is split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The code writes every fact into the speech&lt;/strong&gt; (names, dates, lifespans, line counts, commit messages), so they're always true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma gets the one job only it can do&lt;/strong&gt;: read the dead file's last code and say, in the ghost's voice, what it used to do.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Here is the last version of a deleted file called "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&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;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
"""

In one short sentence, say what this file used to do, spoken by its ghost in first
person and past tense ("I used to ..."). Base it only on the code above: name the
concrete thing it did. Be a little eerie. Reply with the sentence only.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grounded in the code, the lines are specific and right. &lt;code&gt;lib/view/partial.js&lt;/code&gt; &lt;em&gt;"used to resolve partial object names from the view path"&lt;/em&gt;, and the old jQuery copy says &lt;em&gt;"I was a jQuery JavaScript Library, version 1.3.2"&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A smaller lesson: you can't tell a 1B model what &lt;em&gt;not&lt;/em&gt; to say
&lt;/h3&gt;

&lt;p&gt;Gemma loved the word &lt;strong&gt;"meticulously"&lt;/strong&gt;: four of the first six ghosts did something meticulously. I added &lt;em&gt;"Do not use the words meticulously, diligently or carefully"&lt;/em&gt; to the prompt, and the very next ghost was still &lt;em&gt;"diligently processing requests"&lt;/em&gt;. Small models don't follow negative instructions well. So the filler words are now stripped in code after generation, a three-line regex, and it works every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Matters Here
&lt;/h2&gt;

&lt;p&gt;A repository's history is its team's private memory: names, commit messages, and code that was deleted on purpose. &lt;strong&gt;A tool like this only makes sense if none of that leaves your machine&lt;/strong&gt;, especially for a company's private repos.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 3 1B runs locally&lt;/strong&gt; through &lt;a href="https://huggingface.co/docs/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt; and onnxruntime-node, on an ordinary laptop CPU. No code, names or commit messages are sent anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works with the lights (and the internet) off.&lt;/strong&gt; After a one-time ~860 MB download, it's fully offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing, and it won't disappear.&lt;/strong&gt; No API key and no hosted service that can be shut down. That feels right for a tool about remembering things that are gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is swappable.&lt;/strong&gt; Set &lt;code&gt;SEANCE_MODEL&lt;/code&gt; to summon with a bigger model.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Summon deleted functions&lt;/strong&gt;, not just whole files, by walking &lt;code&gt;git log -p&lt;/code&gt; for definitions that vanished.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A "family tree"&lt;/strong&gt; that follows renames, so a file's ancestors attend its funeral too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;git-seance haunt&lt;/code&gt;&lt;/strong&gt;: a commit hook that writes a short epitaph whenever you delete a file.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 3 1B is the only model in the project. It runs locally and reads every ghost's last code to say what it used to do.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with AI assistance (Claude Code) for scaffolding, testing and debugging. The demo uses the public Express repository's history; every ghost's facts come straight from its git log.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
      <category>git</category>
    </item>
    <item>
      <title>Sloth Sort: I let a local AI clean my Downloads folder so I never have to</title>
      <dc:creator>Yash Dharme</dc:creator>
      <pubDate>Tue, 20 Oct 2026 12:18:45 +0000</pubDate>
      <link>https://dev.to/yash_dharme/sloth-sort-i-let-a-local-ai-clean-my-downloads-folder-so-i-never-have-to-3a72</link>
      <guid>https://dev.to/yash_dharme/sloth-sort-i-let-a-local-ai-clean-my-downloads-folder-so-i-never-have-to-3a72</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;Everyone has a Downloads folder like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;download.pdf            download (1).pdf        scan0003.pdf
Document.pdf            e-ticket_8F3K2.pdf      statement_092026.pdf
untitled.md             notes.txt               export.csv
Screenshot from 2026-10-02 21-14-07.png        project-final-FINAL.zip
code-1.105.0-amd64.deb  lecture-recording.mp4   …
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Somewhere in there is the bill you need to pay, the internship offer you need to sign, and the flight ticket you'll be frantically searching for at the airport gate. Cleaning it up means opening every &lt;code&gt;download (1).pdf&lt;/code&gt; to find out what it is. So nobody does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sloth Sort&lt;/strong&gt; does it for you. It's a small command-line tool:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sloth-sort plan  ~/Downloads   &lt;span class="c"&gt;# look at everything, decide, move nothing&lt;/span&gt;
sloth-sort apply ~/Downloads   &lt;span class="c"&gt;# do exactly what you just reviewed&lt;/span&gt;
sloth-sort undo  ~/Downloads   &lt;span class="c"&gt;# put it all back&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The interesting part is that it &lt;strong&gt;reads your documents&lt;/strong&gt; and gives them names a human would search for, using &lt;strong&gt;Gemma 3 1B running on your own machine&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;This is real output from my test folder of 19 made-up files (the repo has a script that generates the same folder, so you can try it safely):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📁 Documents/Bank &amp;amp; Finance/
   statement_092026.pdf  →  2026-10 Riverbank Savings Account.pdf  ✨

📁 Documents/Bills &amp;amp; Receipts/
   download.pdf  →  2026-10 Sunrise Telecom Tax Invoice.pdf  ✨
   export.csv  →  2026-10 Groceries &amp;amp; Electricity Bills.csv  ✨

📁 Documents/Health/
   Document.pdf  →  Paracetamol Dosage.pdf  ✨

📁 Documents/Study/
   download (1).pdf  →  Normalization Database Management Systems.pdf  ✨
   untitled.md  →  Paging and Segmentation.md  ✨

📁 Documents/Travel/
   e-ticket_8F3K2.pdf  →  Pune to Bengaluru flight ticket.pdf  ✨
   notes.txt  →  Goa Trip Essentials.txt  ✨

📁 Documents/Work &amp;amp; Career/
   scan0003.pdf  →  Brightleaf Software Internship Offer.pdf  ✨

📁 Screenshots/2026-10/
   Screenshot from 2026-10-02 21-14-07.png
   Screenshot from 2026-10-11 09-02-55.png
…
19 files planned (9 read by Gemma ✨, the rest by type) in 19s. Nothing has moved yet.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then &lt;code&gt;apply&lt;/code&gt; moved all 19, and &lt;code&gt;undo&lt;/code&gt; put every one of them back exactly where it was. I checked that by comparing a hash of the file list before and after.&lt;/p&gt;
&lt;h3&gt;
  
  
  The lazy payoff
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;19 seconds&lt;/strong&gt; to plan 19 files on a laptop CPU, with no GPU (after the model's one-time download).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9 documents read for me&lt;/strong&gt;, and every one of them landed in the right folder with a name that says what it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One command to review, one to apply.&lt;/strong&gt; The alternative is opening nine &lt;code&gt;download (1).pdf&lt;/code&gt;-style files one by one. I'd guess that's 5–10 minutes of clicking for this folder. For a real Downloads folder with a few hundred files, it's the afternoon I've been putting off for two years.&lt;/li&gt;
&lt;/ul&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/Yash11778" rel="noopener noreferrer"&gt;
        Yash11778
      &lt;/a&gt; / &lt;a href="https://github.com/Yash11778/sloth-sort" rel="noopener noreferrer"&gt;
        sloth-sort
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Tidy a messy Downloads folder with an open-weight model (Gemma) that runs on your own machine.
    &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;🦥 Sloth Sort&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Tidy a messy Downloads folder with an open-weight model that runs &lt;strong&gt;on your own machine&lt;/strong&gt;
It reads your documents, files them into sensible folders with names you can actually
search for, shows you the plan, and only moves things when you say so. One command undoes it all.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge, Week 3: Slow and Steady&lt;/a&gt;.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;$ sloth-sort plan ~/Downloads

📁 Documents/Travel/
   e-ticket_8F3K2.pdf  →  Pune to Bengaluru flight ticket.pdf  ✨
📁 Documents/Work &amp;amp; Career/
   scan0003.pdf  →  Brightleaf Software Internship Offer.pdf  ✨
📁 Documents/Bills &amp;amp; Receipts/
   download.pdf  →  2026-10 Sunrise Telecom Tax Invoice.pdf  ✨
📁 Screenshots/2026-10/
   Screenshot from 2026-10-02 21-14-07.png
...
19 files planned (9 read by Gemma ✨, the rest by type) in 19s. Nothing has moved yet.

$ sloth-sort apply ~/Downloads     # do it
$ sloth-sort undo ~/Downloads      # changed your mind
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Obvious files are sorted by&lt;/strong&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Yash11778/sloth-sort" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. About 500 lines of plain Node.js (ES modules), including the demo generator, with no build step.&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/Yash11778/sloth-sort &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;sloth-sort &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run demo                                 &lt;span class="c"&gt;# makes a messy folder of made-up files&lt;/span&gt;
node bin/sloth-sort.mjs plan demo-downloads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;every file ──► hidden / half-downloaded / changed in the last 2 min?  ──► leave alone
           ──► image, video, installer, archive, code, font?          ──► sort by type (instant)
           ──► document ──► read the first ~1,500 characters (PDF, Word, text, CSV)
                        ──► clear keywords? ("PNR", "account statement", "prescription")
                               yes ─► that folder
                               no  ─► Gemma picks one of 8 folders
                        ──► Gemma writes a title, shaped for that folder
           ──► plan saved to disk ──► you review ──► apply ──► undo log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lesson 1: the model is the last resort, not the first
&lt;/h3&gt;

&lt;p&gt;Most of a Downloads folder doesn't need AI. A &lt;code&gt;.png&lt;/code&gt; named &lt;code&gt;Screenshot from …&lt;/code&gt; goes in &lt;code&gt;Screenshots/&amp;lt;month&amp;gt;&lt;/code&gt;, and a &lt;code&gt;.deb&lt;/code&gt; goes in &lt;code&gt;Installers&lt;/code&gt;. So only documents ever reach the model. In the demo, that's 9 of 19 files.&lt;/p&gt;

&lt;p&gt;Even for documents, my first version asked Gemma to pick one of eight folders, and it got three of nine wrong: the flight e-ticket went to &lt;em&gt;Bills&lt;/em&gt;, the bank statement to &lt;em&gt;Bills&lt;/em&gt;, and the internship offer letter to &lt;em&gt;Other&lt;/em&gt;. A 1B model choosing among eight similar categories is guessing. But "PNR" or "boarding pass" in a document is not ambiguous, so clear keywords now decide first:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RULES&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;travel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;pnr|boarding pass|e-&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;ticket|itinerary|flight|departure|…&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/i&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;health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;prescription|diagnosis|lab report|physician|…&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&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;bank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;account statement|opening balance|ifsc|salary slip|…&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/i&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;work&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;offer letter|internship|resume|appointment letter|…&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// …&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemma still decides anything the keywords don't recognise. After this change, all nine landed in the right folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 2: don't show a small model the old name, or any real-looking example
&lt;/h3&gt;

&lt;p&gt;My first title prompt included the filename, and Gemma handed &lt;code&gt;scan0003&lt;/code&gt; and &lt;code&gt;Document&lt;/code&gt; straight back. Then I removed the filename and added an example title, "Sunrise Telecom September Bill", and the &lt;em&gt;prescription&lt;/em&gt; came back as "Sunrise Telecom Patient Care". A 1B model will happily copy whatever concrete text you put in front of it.&lt;/p&gt;

&lt;p&gt;The fix was to give it &lt;strong&gt;only the document's contents&lt;/strong&gt;, plus a &lt;em&gt;shape&lt;/em&gt; rather than an example, chosen for the folder it's going to:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SHAPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;bills&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&amp;lt;company&amp;gt; &amp;lt;month&amp;gt; bill" or "&amp;lt;shop&amp;gt; order receipt"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;travel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&amp;lt;airline&amp;gt; &amp;lt;from&amp;gt; to &amp;lt;to&amp;gt; ticket" or "&amp;lt;city&amp;gt; hotel booking"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;work&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&amp;lt;company&amp;gt; offer letter" or "&amp;lt;name&amp;gt; resume"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// …&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's how &lt;code&gt;e-ticket_8F3K2.pdf&lt;/code&gt; became &lt;strong&gt;"Pune to Bengaluru flight ticket"&lt;/strong&gt; instead of "Airline November Bill airline", which is what it produced while every document got the same bill-shaped hint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 3: a tool that moves your files has to be boring about safety
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It plans first and moves nothing.&lt;/strong&gt; The plan is written to disk, and &lt;code&gt;apply&lt;/code&gt; does exactly that plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It only moves files, inside the one folder.&lt;/strong&gt; Nothing is deleted or overwritten, and a name clash gets " (2)".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It refuses to escape.&lt;/strong&gt; I edited a plan by hand to move a file to &lt;code&gt;../../escaped.txt&lt;/code&gt;, and &lt;code&gt;apply&lt;/code&gt; refused: &lt;code&gt;Refusing to touch ../../escaped.txt: outside …&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Half-finished downloads are left alone&lt;/strong&gt; (&lt;code&gt;.crdownload&lt;/code&gt;, &lt;code&gt;.part&lt;/code&gt;, anything modified in the last 2 minutes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;undo&lt;/code&gt; restores everything&lt;/strong&gt; and removes only the folders it created, and only if they're empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Open Matters Here
&lt;/h2&gt;

&lt;p&gt;Your Downloads folder is the most private folder on your computer: bank statements, ID scans, offer letters, prescriptions, tickets with your PNR on them. &lt;strong&gt;A tool like this only makes sense if nothing leaves your machine.&lt;/strong&gt; A hosted model would mean uploading all of it to sort it.&lt;/p&gt;

&lt;p&gt;With an open-weight model, that isn't a trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It runs locally.&lt;/strong&gt; Gemma 3 1B runs through &lt;a href="https://huggingface.co/docs/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt; and onnxruntime-node, on a normal laptop CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline.&lt;/strong&gt; After a one-time ~860 MB download, it never touches the network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's free, forever.&lt;/strong&gt; No API key and no per-file cost, so running it on 2,000 files costs the same as on 20.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is swappable.&lt;/strong&gt; Set &lt;code&gt;SLOTH_MODEL&lt;/code&gt; to try a bigger or newer ONNX model.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A watch mode&lt;/strong&gt;, so new downloads get filed as they arrive. Maximum sloth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images by content&lt;/strong&gt; with a small open vision model: receipts, whiteboards and memes are all &lt;code&gt;.jpg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hindi and Marathi documents&lt;/strong&gt;, which need a bigger multilingual model.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 3 1B is the only model in the project. It runs locally and does both the folder decisions the keywords can't make and every new file name.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with AI assistance (Claude Code) for scaffolding, testing and debugging. Every file in the demo is made up; no real documents were used.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Reply Desk: a review-reply assistant for small shops, where customers' words never leave the counter</title>
      <dc:creator>Yash Dharme</dc:creator>
      <pubDate>Thu, 15 Oct 2026 17:03:30 +0000</pubDate>
      <link>https://dev.to/yash_dharme/reply-desk-a-review-reply-assistant-for-small-shops-where-customers-words-never-leave-the-counter-4h4f</link>
      <guid>https://dev.to/yash_dharme/reply-desk-a-review-reply-assistant-for-small-shops-where-customers-words-never-leave-the-counter-4h4f</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;Reply Desk&lt;/strong&gt; is a review-reply assistant for small businesses: the chai stall, the salon, the neighbourhood clinic, the coaching centre, the home baker.&lt;/p&gt;

&lt;p&gt;These businesses live and die on Google and Zomato reviews. Replying to every review helps, and nobody behind the counter has time to do it. Worse, the reviews that actually matter ("found a hair in my food", "the staff was rude") get buried under twenty "nice chai!" ones.&lt;/p&gt;

&lt;p&gt;Reply Desk does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It puts urgent reviews first.&lt;/strong&gt; Food-safety, health, money and staff-behaviour complaints are flagged &lt;em&gt;"Today"&lt;/em&gt; and moved to the top, the moment you paste them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It reads the mood&lt;/strong&gt; of each review: positive, mixed or negative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It drafts a reply in the owner's voice,&lt;/strong&gt; signed with their name. Unhappy customers are pointed to the owner's own contact, like a WhatsApp number, instead of an argument in public.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You read it, tweak it, copy it, post it. If the mood is wrong, tap the right one and it redrafts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All of this runs on an open-weight model, Gemma 3 1B, inside the browser tab.&lt;/strong&gt; No account, no API key, no server, no upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who would pay for it
&lt;/h2&gt;

&lt;p&gt;A café owner who would never pay for "AI". They &lt;em&gt;would&lt;/em&gt; pay ₹199–299 a month for "every review answered in two minutes, and you'll never miss a complaint again". The same goes for a small agency that manages listings for ten local shops.&lt;/p&gt;

&lt;p&gt;The pricing only works because of the open model, which is a later section.&lt;/p&gt;

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

&lt;p&gt;It's a static web app with no backend, so you run it on your own machine:&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/Yash11778/reply-desk
&lt;span class="nb"&gt;cd &lt;/span&gt;reply-desk
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open the local URL, click &lt;strong&gt;"Fill in an example"&lt;/strong&gt;, then &lt;strong&gt;"Draft replies"&lt;/strong&gt;. With WebGPU it's quick. Without it, it falls back to the CPU and takes roughly 10–20 seconds a reply, but it works. The first run downloads the model once (~0.8–1 GB), and after that it's cached.&lt;/p&gt;

&lt;p&gt;This is real, unedited output from a test run on a laptop &lt;strong&gt;with no GPU at all&lt;/strong&gt; (CPU only, inside the browser), for a made-up café called "Chai Point Corner" run by "Priya":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Review:&lt;/strong&gt; &lt;em&gt;Found a hair in my poha. Disgusting. Want my money back.&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Flag:&lt;/strong&gt; Today: Something found in the food · &lt;strong&gt;Mood:&lt;/strong&gt; negative&lt;br&gt;
&lt;strong&gt;Reply:&lt;/strong&gt; I sincerely apologize for the unpleasant experience you had with our poha. I'm truly sorry that your experience was so disappointing. Please contact me directly at 98765 43210 so we can discuss this further and make things right. I value your feedback and want to ensure you're satisfied.&lt;br&gt;
Priya&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review:&lt;/strong&gt; &lt;em&gt;Waited 25 minutes for two sandwiches and the guy at the counter was rude when I asked.&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Flag:&lt;/strong&gt; Today: Staff behaviour · &lt;strong&gt;Mood:&lt;/strong&gt; negative&lt;br&gt;
&lt;strong&gt;Reply:&lt;/strong&gt; Thank you for your feedback. I sincerely apologize for the delay and the experience you had with our staff. I want to assure you that I'll investigate this matter immediately. Please contact me directly at 98765 43210 so we can discuss this further.&lt;br&gt;
Priya&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review:&lt;/strong&gt; &lt;em&gt;Nice ambience, friendly owner aunty. Sandwich was soggy though.&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Mood:&lt;/strong&gt; mixed&lt;br&gt;
&lt;strong&gt;Reply:&lt;/strong&gt; I'm sorry to hear you had a disappointing experience. Thank you for your feedback. I'll certainly take your comments about the sandwich and will investigate further to understand what went wrong.&lt;br&gt;
Priya&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They aren't Shakespeare, and they don't need to be. They're polite, specific, signed, and they never invent a discount or a fake phone number. That last part took work (see below). The third one is also a fair example of why the owner reads before posting: it skipped thanking her for the "friendly owner aunty" bit, and a human would add that back in five seconds.&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/Yash11778" rel="noopener noreferrer"&gt;
        Yash11778
      &lt;/a&gt; / &lt;a href="https://github.com/Yash11778/reply-desk" rel="noopener noreferrer"&gt;
        reply-desk
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Draft replies to customer reviews with an open-weigh model (Gemma) that runs in your browser. Nothing isuploaded.
    &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;Reply Desk&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Paste your Google or Zomato reviews. An open-weight model running &lt;strong&gt;in your browser tab&lt;/strong&gt;
sorts them, flags the ones you must deal with today, and drafts a reply to each one in
your own voice. Your customers' words never leave your computer.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Who it's for&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Small businesses that live on reviews: cafés, salons, clinics, coaching centres, home
bakers. Replying to every review is good for them and nobody has time for it. The ones
that matter most, like "found a hair in my food" or "staff was rude", get buried under
"nice chai!".&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set up once:&lt;/strong&gt; business name, what it is, your name, and where unhappy customers
should reach you. It's saved in your browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paste reviews,&lt;/strong&gt; with a blank line between each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Urgent first:&lt;/strong&gt; a plain keyword check flags food-safety, health, money…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Yash11778/reply-desk" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. TypeScript and Vite, no framework, about 9 kB of app code plus the model runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;paste reviews ──► split on blank lines
              ──► keyword check: urgent?  (instant, no model)
              ──► complaint words?  no ──► positive (no model)
                                    yes ─► Gemma: mixed or negative?
              ──► Gemma: draft a reply with the prompt for that mood
              ──► clean-up: strip chatter, placeholders, double sign-offs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model runs in a Web Worker through &lt;a href="https://huggingface.co/docs/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt;, using &lt;a href="https://huggingface.co/onnx-community/gemma-3-1b-it-ONNX" rel="noopener noreferrer"&gt;&lt;code&gt;onnx-community/gemma-3-1b-it-ONNX&lt;/code&gt;&lt;/a&gt;. That's 4-bit weights on WebGPU, and 8-bit on CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 1: give a 1B model one job, not three
&lt;/h3&gt;

&lt;p&gt;My first prompt asked Gemma to classify the mood, flag urgency &lt;em&gt;and&lt;/em&gt; write the reply, in a fixed format. It ignored the format and wrote things like &lt;code&gt;**First line: POSITIVE**&lt;/code&gt;. It also wrote this, for a real-looking complaint:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please contact Priya directly at [Priya's Phone Number or Email]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two changes fixed it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One prompt per job.&lt;/strong&gt; Classification is its own short prompt, and the reply is another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One prompt per mood.&lt;/strong&gt; "If the review is bad, apologise" made it apologise to &lt;em&gt;happy&lt;/em&gt; customers. So the code picks the mood first, and each mood gets its own one-line instruction:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TASK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Mood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Business&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;positive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thank them for their review.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mixed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Thank them for the good part, and say plainly that you'll look at the thing they didn't like.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;negative&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Apologise once, without excuses, and ask them to contact you on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; so you can make it right.`&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 owner's real contact detail is passed in, and the prompt says &lt;em&gt;"Do not invent phone numbers, emails, discounts or promises."&lt;/em&gt; A small clean-up pass then strips anything in &lt;code&gt;[square brackets]&lt;/code&gt;, "Okay, here's a reply:" preambles, markdown, and a doubled sign-off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 2: don't use the model where a word list is better
&lt;/h3&gt;

&lt;p&gt;Missing a food-safety complaint is the one mistake that really matters, so urgency is &lt;strong&gt;a plain keyword list&lt;/strong&gt;, not the model: &lt;em&gt;hair, cockroach, sick, food poisoning, stale, refund, rude&lt;/em&gt;, and so on. It's predictable and instant, and it lets the urgent cards appear at the top before the model has even loaded.&lt;/p&gt;

&lt;p&gt;The same goes for clearly happy reviews. The CPU (8-bit) build liked to hedge and call &lt;em&gt;"Best masala chai in the area!!"&lt;/em&gt; "mixed". Now a review with no complaint words at all (&lt;em&gt;but, though, cold, slow, rude, not…&lt;/em&gt;) is positive without asking the model. Gemma only decides the hard case: mixed or negative.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 3: "runs in the browser" has fine print
&lt;/h3&gt;

&lt;p&gt;Getting the no-GPU path working took three fixes, all found by running it rather than reading docs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The 4-bit build crashed on CPU:&lt;/strong&gt; &lt;code&gt;Could not find an implementation for GatherBlockQuantized&lt;/code&gt;. onnxruntime-web's WASM backend doesn't have that kernel, so the CPU path loads the 8-bit build instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 8-bit build then 404'd&lt;/strong&gt; on &lt;code&gt;model_int8.onnx_data&lt;/code&gt;. The model repo's config says its weights live in an external data file, which is true for the other builds but not for int8, which is one self-contained file. Passing &lt;code&gt;use_external_data_format: { 'model_int8.onnx': 0, … }&lt;/code&gt; fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It ran on one CPU core.&lt;/strong&gt; WASM threads need &lt;code&gt;SharedArrayBuffer&lt;/code&gt;, which needs the page to be cross-origin isolated. Two headers in &lt;code&gt;vite.config.ts&lt;/code&gt; (&lt;code&gt;Cross-Origin-Opener-Policy: same-origin&lt;/code&gt;, &lt;code&gt;Cross-Origin-Embedder-Policy: credentialless&lt;/code&gt;) let it use every core. &lt;code&gt;credentialless&lt;/code&gt; matters here, because it keeps the Hugging Face CDN download working without that server sending extra headers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Open Matters Here
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's what makes the business work.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is the product.&lt;/strong&gt; Reviews carry customers' names, phone numbers, and complaints about named staff. "Your customers' words never leave this computer" is a promise I can actually keep, because there's no server to leave to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero cost per reply.&lt;/strong&gt; A hosted model charges per token, forever. Here the shop's own laptop does the work, so a ₹199/month price has no API bill eating it. A hosted model would also force me to meter usage, and "unlimited replies" is a much easier sell to a shop owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in.&lt;/strong&gt; No API key to leak, no provider changing prices or deprecating the model under me. If a better small open model comes out, swapping it is one line (&lt;code&gt;MODEL&lt;/code&gt; in &lt;code&gt;worker.ts&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It still works offline&lt;/strong&gt; once the model is cached, for example in a shop with patchy internet.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Hindi, Marathi and Hinglish replies, since many reviews here are written that way. That needs a bigger or fine-tuned model.&lt;/li&gt;
&lt;li&gt;Per-business tone examples: paste three replies you've written, and it matches them.&lt;/li&gt;
&lt;li&gt;A weekly summary: "4 people mentioned slow service this week".&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 3 1B is the only model in the app. It runs on-device through Transformers.js and does both the mood reading and every reply.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with AI assistance (Claude Code) for scaffolding, testing and debugging. All sample reviews are invented for the demo.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Side Quest: an AI that sends you on a walk, and never learns where you are</title>
      <dc:creator>Yash Dharme</dc:creator>
      <pubDate>Thu, 08 Oct 2026 16:57:56 +0000</pubDate>
      <link>https://dev.to/yash_dharme/side-quest-an-ai-that-sends-you-on-a-walk-and-never-learns-where-you-are-21fo</link>
      <guid>https://dev.to/yash_dharme/side-quest-an-ai-that-sends-you-on-a-walk-and-never-learns-where-you-are-21fo</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;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Side Quest&lt;/strong&gt; turns the streets around you into a short scavenger hunt.&lt;/p&gt;

&lt;p&gt;You tell it how long you have (15 to 60 minutes) and what kind of clues you like (playful, poetic, detective, or for kids). It finds a few interesting places within walking distance (a temple, a mural, an old bookshop, a pond) and plans a loop that fits your time. Then &lt;strong&gt;Gemma, running inside your browser, writes a riddle for each stop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then you put the phone away.&lt;/p&gt;

&lt;p&gt;When you think you've found the place, you check in. The app shows you one small thing to notice before you move on: &lt;em&gt;count the bells&lt;/em&gt;, &lt;em&gt;find the oldest crack in the wall&lt;/em&gt;, &lt;em&gt;listen for one sound that repeats&lt;/em&gt;. At the end, Gemma writes a three-line postcard about the walk. It is saved only on your device.&lt;/p&gt;

&lt;p&gt;Most "AI + outdoors" apps keep you looking at the screen while you're outside. I wanted the opposite: &lt;strong&gt;the screen is on for about ten seconds per stop&lt;/strong&gt;, and the rest of the time you're looking at the street.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://hacktoberfest-26.vercel.app" rel="noopener noreferrer"&gt;https://hacktoberfest-26.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try it from your desk:&lt;/strong&gt; &lt;a href="https://hacktoberfest-26.vercel.app/?at=18.5195,73.8553&amp;amp;sim" rel="noopener noreferrer"&gt;https://hacktoberfest-26.vercel.app/?at=18.5195,73.8553&amp;amp;sim&lt;/a&gt; starts you at Shaniwar Wada in Pune and adds a &lt;em&gt;Teleport&lt;/em&gt; button, so you can play a whole quest without walking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need a browser with WebGPU for the model: Chrome on Android 12+, or Chrome/Edge on Windows or macOS. The first run downloads Gemma once (~0.7 GB). Without WebGPU, the walk still works with simple built-in clues.&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/Yash11778" rel="noopener noreferrer"&gt;
        Yash11778
      &lt;/a&gt; / &lt;a href="https://github.com/Yash11778/Hacktoberfest-26" rel="noopener noreferrer"&gt;
        Hacktoberfest-26
      &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;Side Quest&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A short walking scavenger hunt around wherever you're standing. The clues are written
by &lt;strong&gt;Gemma, running entirely in your browser&lt;/strong&gt; (WebGPU via &lt;a href="https://github.com/mlc-ai/web-llm" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt;)
Your location never leaves the phone.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge, Week 1: Touch Grass&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Pick how long you have (15–60 min) and a clue style (playful, poetic, detective, for kids).&lt;/li&gt;
&lt;li&gt;The app finds interesting places nearby from &lt;strong&gt;OpenStreetMap&lt;/strong&gt; and &lt;strong&gt;Wikipedia&lt;/strong&gt;:
parks, temples, murals, old trees, bookshops, historic buildings.&lt;/li&gt;
&lt;li&gt;A small planner picks a loop of 2–4 varied stops that fits your time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma writes a riddle for each stop&lt;/strong&gt; plus one small thing to notice when you get there.&lt;/li&gt;
&lt;li&gt;Put the phone away. Check in when you think you've found it (it buzzes if the screen is on).&lt;/li&gt;
&lt;li&gt;At the end, Gemma writes you a three-line postcard of the walk, saved only on your device.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why&lt;/h2&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/Yash11778/Hacktoberfest-26" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. About 1,000 lines of TypeScript, no framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your phone ──► snap location to a ~1 km grid square
           ──► OpenStreetMap + Wikipedia: "what's interesting in this square?"
           ──► planner: pick 2–4 varied stops that fit your time
           ──► Gemma (WebGPU, in a Web Worker): write a riddle per stop
           ──► walk · check in · notice something · next clue
           ──► Gemma: write a postcard of the walk → saved on the device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. Finding places, with two free and keyless sources.&lt;/strong&gt; OpenStreetMap (via the public Overpass API) gives the small stuff: parks, temples, murals, named bakeries, old trees. Wikipedia's geosearch API gives named landmarks &lt;em&gt;with a line of description&lt;/em&gt;, which turns out to be great riddle material. Near Shaniwar Wada in Pune, one query returned 188 candidate places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Planning the loop, deliberately without AI.&lt;/strong&gt; A small randomised search tries 400 greedy loops and keeps the one with the most stops, the most &lt;em&gt;variety&lt;/em&gt; (it heavily penalises a second temple in a row), and a total time closest to what you asked for. Walking speed is 70 m/min and streets get a 1.3× detour factor. A planner is the right tool here: it's deterministic and checkable, and a 1B model is bad at geometry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Writing the clues, which is where the model earns its place.&lt;/strong&gt; Each stop's OpenStreetMap tags and Wikipedia summary go to Gemma with one job: hint at the place without naming it, and suggest one thing to notice with your senses.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You write clues for a real-world walking scavenger hunt.

The walker must find this place: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;place&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;.

Write:
1. A riddle of at most two short sentences that hints at the place without saying its name.
   Use what someone would see, hear or smell there.
2. One small thing to notice once they arrive, using their senses. One sentence.

Tone: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;VOICE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;vibe&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;. No emoji. No hashtags.

Reply in exactly this format and nothing else:
CLUE: &amp;lt;riddle&amp;gt;
NOTICE: &amp;lt;thing to notice&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the model's answer can't be parsed, or it gives the name away, a template clue takes over and the screen says so honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Matters Here
&lt;/h2&gt;

&lt;p&gt;This is the part I care about most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hosted model would need your live location for every clue.&lt;/strong&gt; Here nothing about where you're standing ever reaches an AI provider, because the AI provider is your own GPU. Gemma runs in the browser through &lt;a href="https://github.com/mlc-ai/web-llm" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt; on WebGPU, inside a Web Worker.&lt;/p&gt;

&lt;p&gt;The only network requests are the map lookups, and even those never carry your exact position. The query box is snapped outward to a 0.01° grid (about 1.1 km) before it leaves the phone:&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="cm"&gt;/**
 * A bounding box snapped outward to a 0.01° grid (~1.1 km). This is the only
 * thing about your location that ever leaves the phone: the map server learns
 * which grid squares you asked about, never the point you're standing on.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;snappedBbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;radiusM&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everyone standing anywhere in the same square sends the identical request.&lt;/p&gt;

&lt;p&gt;The rest follows from using open pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; There are no API keys and no accounts. It's a static site plus one tiny cached relay function on Vercel's free tier. With a closed model API I'd be paying per riddle, or asking you for a key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline after the first run.&lt;/strong&gt; The model weights are cached by the browser, and map results are cached per area for a week, so a second walk in the same neighbourhood works with no signal at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is swappable.&lt;/strong&gt; You can pick Gemma 3 1B (~0.7 GB, sized for phones) or Gemma 2 2B (~1.4 GB, better clues on a laptop). Swapping is one line in &lt;code&gt;MODELS&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Went Wrong (and What I Changed)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The public Overpass server turns away browsers.&lt;/strong&gt; It worked from my dev machine, then failed on the deployed site: &lt;code&gt;overpass-api.de&lt;/code&gt; answers anonymous browser requests with &lt;code&gt;406 Not Acceptable&lt;/code&gt; and no CORS header, so the browser reports a CORS error that hides the real reason. The server wants clients to identify themselves, and a browser isn't allowed to set &lt;code&gt;User-Agent&lt;/code&gt;. The fix is a ~50-line Vercel function, &lt;code&gt;api/places.ts&lt;/code&gt;. It accepts &lt;em&gt;only&lt;/em&gt; a grid-snapped box (anything off-grid or over ~9 km is rejected), sends the fixed query with an identifying &lt;code&gt;User-Agent&lt;/code&gt;, falls back across four mirrors, and sets &lt;code&gt;s-maxage=604800&lt;/code&gt; so the CDN answers repeat boxes for a week without touching OpenStreetMap at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Even when it identified itself, Overpass was often overloaded&lt;/strong&gt;, with &lt;code&gt;504 Gateway Timeout&lt;/code&gt; more often than not during testing. So OpenStreetMap and Wikipedia run &lt;strong&gt;in parallel&lt;/strong&gt;, the relay starts its mirrors a few seconds apart and takes the first complete answer, and either source alone is enough to plan a walk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overpass sometimes "succeeds" with nothing.&lt;/strong&gt; Under load it can give up mid-query and still answer &lt;code&gt;200 OK&lt;/code&gt; with an empty list and a remark saying it timed out. The app read that as "nothing here". Those answers are now treated as failures, and an empty area is only cached for an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wikipedia was silently dropping most results.&lt;/strong&gt; The geosearch generator returned 47 pages, but only 10 had coordinates attached. &lt;code&gt;prop=coordinates&lt;/code&gt; has its &lt;em&gt;own&lt;/em&gt; limit (&lt;code&gt;colimit&lt;/code&gt;) that defaults to 10. Adding &lt;code&gt;colimit=max&lt;/code&gt; took the area from 1 usable landmark to dozens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;'gpu' in navigator&lt;/code&gt; is not enough.&lt;/strong&gt; A browser can expose WebGPU and still have no usable adapter. Chrome on Linux does exactly that by default. The check now actually calls &lt;code&gt;requestAdapter()&lt;/code&gt;, and if the model can't start, the walk still happens with built-in clues instead of an error screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laptops don't really know where they are.&lt;/strong&gt; Desktop browsers locate by Wi-Fi or IP and can be kilometres out. When a lookup comes back empty, the app now says how coarse your location is and offers a demo walk instead of a dead end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browsers pause GPS when the screen is off.&lt;/strong&gt; I can only buzz you on arrival while the screen is on. Otherwise you tap &lt;em&gt;I think I'm here&lt;/em&gt;. Honestly, that turned out to be part of the fun.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Fine-tune a small Gemma on good riddles, since the 1B model's clues vary in quality.&lt;/li&gt;
&lt;li&gt;A "share this quest" link, so friends can walk the same route without either of you sending your location anywhere.&lt;/li&gt;
&lt;li&gt;Hindi and Marathi clues.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma is the only AI in the app. It runs entirely on-device via WebGPU and writes every clue and postcard.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Map data © OpenStreetMap contributors (ODbL). Wikipedia text CC BY-SA. Built with AI assistance (Claude Code) for the scaffolding and debugging.&lt;/em&gt;&lt;/p&gt;

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