<?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: Nitsuj</title>
    <description>The latest articles on DEV Community by Nitsuj (@nitsuj).</description>
    <link>https://dev.to/nitsuj</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%2F3929911%2Fc45fcf27-1e29-4234-a374-4226cbb85497.jpg</url>
      <title>DEV Community: Nitsuj</title>
      <link>https://dev.to/nitsuj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nitsuj"/>
    <language>en</language>
    <item>
      <title>SlothDigest</title>
      <dc:creator>Nitsuj</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:53:31 +0000</pubDate>
      <link>https://dev.to/nitsuj/slothdigest-3d9e</link>
      <guid>https://dev.to/nitsuj/slothdigest-3d9e</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;SlothDigest is a ~200-line Python tool that does your morning reading for you. Overnight (via a single cron line), it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scans your folders for new files since the last run&lt;/li&gt;
&lt;li&gt;Pulls new headlines from your RSS/Atom feeds&lt;/li&gt;
&lt;li&gt;Hands the pile to a local open-weight model and asks for a two-line brief&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You wake up, run &lt;code&gt;cat digest.log&lt;/code&gt;, and know what happened. No feeds to scroll, no inbox triage — sloths move at their own pace, and so does this.&lt;/p&gt;

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

&lt;p&gt;No deploy needed — it's a CLI that runs on your own machine. Here's the real session output from a test run against a mock downloads folder plus two live feeds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First run:&lt;/strong&gt; collects 4 new files + 20 new headlines, produces the brief above&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immediate rerun (dedup):&lt;/strong&gt; &lt;code&gt;Nothing new since the last run. The sloth rests.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second line matters: it remembers what it has already reported, so a rerun never nags you with stale items. &lt;code&gt;--dry-run&lt;/code&gt; mode prints the raw material without calling the model, so you can see exactly what it would read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 slothdigest.py run

============================================================
SlothDigest — 2026-10-21 07:32 UTC
============================================================
Here's your lazy-person's morning brief:

1. New files were downloaded overnight, including a budget tracker and a
   hacktoberfest t-shirt idea, but nothing that seems critical.
2. Top 5 things to look at:
  - What nine job and property sites were advertising in mid-October 2026
  - Windows Robocopy Command: Mirror a Folder Without Deleting the Wrong Files
  - Anthropic tells investors quarterly revenue up nearly 50% to $16.8bn ahead of IPO
  - Chirp Pong
  - eMudhra Intermediate CA Compromise
3. Sloth verdict: Ignore the hacktoberfest design and just grab a coffee.

Saved to digest.log. Now go back to bed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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/hey-nitsuj" rel="noopener noreferrer"&gt;
        hey-nitsuj
      &lt;/a&gt; / &lt;a href="https://github.com/hey-nitsuj/SlothDigest" rel="noopener noreferrer"&gt;
        SlothDigest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A small Python tool that does your morning reading for you. Overnight (via a single cron line)
    &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;🦥 SlothDigest&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;An overnight digest that does your reading while you sleep. It watches folders
for new files and RSS/Atom feeds for new headlines, then asks a &lt;strong&gt;local
open-weight model&lt;/strong&gt; (Gemma, via &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;) to compress
everything into a blunt two-line morning brief with a "Sloth verdict" on what
to ignore.&lt;/p&gt;
&lt;p&gt;No cloud. No API keys. No UI to test. ~200 lines of standard-library Python.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this exists (the lazy payoff)&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Every morning the average developer re-scrolls the same feeds, re-checks the
same downloads folder, and re-decides what matters. SlothDigest does that pass
overnight, on your own machine, so you wake up to a decision-ready brief
instead of a pile. On a normal weekday it saves 10–15 minutes of skimming —
and it never sends a byte of your file names or reading list to anyone, because
the model runs locally.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Requirements&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Python 3.8+ (standard library only — nothing to…&lt;/p&gt;
&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/hey-nitsuj/SlothDigest" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;The open-weight model is the core of the project, not a decoration. The pipeline is deliberately boring everywhere else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local inference via &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/strong&gt; — the model is &lt;code&gt;llama3.2:3b&lt;/code&gt; (open weights, runs 100% on my CPU). Gemma was my first pick, but on a CPU-only laptop the 6.6B model took ~50s per brief; the 3B model answers in ~10s. The model tag is one line in &lt;code&gt;config.json&lt;/code&gt;, so anyone can swap in Gemma, Qwen, or anything else they've pulled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One &lt;code&gt;POST /api/chat&lt;/code&gt; call&lt;/strong&gt; with all the collected material, a fixed instruction to return a two-sentence summary, at most 5 bullets, and a "Sloth verdict". Generation is capped at 300 tokens — on CPU-only hardware an uncapped local model can grind for minutes, which looks exactly like a hang.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else is Python standard library&lt;/strong&gt;: &lt;code&gt;urllib&lt;/code&gt; for feed fetching and the Ollama call, &lt;code&gt;xml.etree&lt;/code&gt; for RSS 2.0/Atom parsing, a JSON state file for dedup. Nothing to install, nothing to configure beyond a config file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture, such as it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;folders ──┐
          ├──&amp;gt; digest material ──&amp;gt; local model (Ollama) ──&amp;gt; 2-line brief + bullets
feeds ────┘         ▲                                            │
             .slothdigest_state.json (dedup)                    └──&amp;gt; digest.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Three things this build gets &lt;em&gt;because&lt;/em&gt; the model is open and local:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by architecture.&lt;/strong&gt; The digest's raw material includes file names from my downloads folder — invoices, resumes. A closed API means shipping that list to a third party every night. Here it never leaves the machine; the model runs on my CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost that scales with laziness, not usage.&lt;/strong&gt; The whole thing runs daily forever for $0. There is no per-request meter watching me be lazy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swap-ability.&lt;/strong&gt; When Gemma was too slow on my hardware, the fix was changing a string in a config file — no vendor migration, no API version to chase. That's what open weights buy you that a closed endpoint can't: the right to run the model on the hardware you actually have.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Measured:&lt;/strong&gt; ~10–15 minutes a day of feed-scrolling and folder-checking, replaced by one line of terminal output over coffee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One cron line does the work:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  30 5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/slothdigest &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 slothdigest.py run &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm asleep at 5:30 AM. The sloth is not. That's the entire user experience: the tool runs, and you read two sentences.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero maintenance surface:&lt;/strong&gt; no server, no database, no UI to break. Dedup state is one JSON file.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Quotely</title>
      <dc:creator>Nitsuj</dc:creator>
      <pubDate>Sun, 18 Oct 2026 12:16:51 +0000</pubDate>
      <link>https://dev.to/nitsuj/quotely-8g0</link>
      <guid>https://dev.to/nitsuj/quotely-8g0</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;Built for Hacktoberfest Week 2 (Side Hustle). A small CLI that uses a local open-weight model via Ollama to generate freelance quotes.&lt;/p&gt;

&lt;p&gt;Takes a few inputs (client, description, hours, rate, timeline, currency) and outputs a simple Markdown quote. Runs entirely locally with Ollama, so client info stays on the machine.&lt;/p&gt;

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

&lt;p&gt;Example Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# QUOTE&lt;/span&gt;

&lt;span class="gs"&gt;**Quote #:**&lt;/span&gt; Q-20261018
&lt;span class="gs"&gt;**Date:**&lt;/span&gt; 2026-10-18
&lt;span class="gs"&gt;**To:**&lt;/span&gt; Acme Corp
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="gu"&gt;## Project Description&lt;/span&gt;

Build a landing page with contact form
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="gu"&gt;## Scope of Work&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Design and development of responsive landing page
&lt;span class="p"&gt;-&lt;/span&gt; Implementation of contact form with validation
&lt;span class="p"&gt;-&lt;/span&gt; Mobile-friendly layout and cross-browser testing
&lt;span class="p"&gt;-&lt;/span&gt; Deployment-ready deliverables
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="gu"&gt;## Pricing&lt;/span&gt;

| Item | Hours | Rate (USD) | Amount (USD) |
|------|-------|------------|--------------|
| Landing page development... | 8 | 75.00 | 600.00 |

&lt;span class="gs"&gt;**Total:**&lt;/span&gt; USD 600.00

&lt;span class="gu"&gt;## Terms &amp;amp; Conditions&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Payment:**&lt;/span&gt; 50% deposit required to begin work. Remaining balance due upon completion.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Validity:**&lt;/span&gt; This quote is valid for 30 days from the date issued.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Revisions:**&lt;/span&gt; 2 minor revisions included. Additional scope billed at quoted rate.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Timeline:**&lt;/span&gt; As discussed

Thank you for your consideration!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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/hey-nitsuj" rel="noopener noreferrer"&gt;
        hey-nitsuj
      &lt;/a&gt; / &lt;a href="https://github.com/hey-nitsuj/qoutely" rel="noopener noreferrer"&gt;
        qoutely
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A small CLI that uses a local open-weight model via Ollama to generate freelance quotes.
    &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;Quotely - Local AI Quote Generator for Freelancers&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Quotely is a simple CLI tool that generates professional freelance quotes using a local open-weight model via &lt;a href="https://ollama.com/" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;. All client data stays on your machine - no third-party APIs, no data leakage, and zero ongoing costs.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🎯 Hacktoberfest Open-Source AI Challenge: Week 2&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;This project was built for the &lt;strong&gt;Hacktoberfest Week 2: Side Hustle&lt;/strong&gt; challenge. The theme is to build something with open-weight models or open-source AI that someone would actually pay for.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Why open innovation matters&lt;/h3&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy-first&lt;/strong&gt;: Client project details, hours, and rates never leave your laptop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero costs&lt;/strong&gt;: Once you have a model running locally, there are no API charges - critical for maintaining profit margins on a side hustle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No vendor lock-in&lt;/strong&gt;: Swap between any open-weight model (Llama, Qwen, Gemma, etc.) with a simple flag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline capable&lt;/strong&gt;: Works without internet connectivity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent &amp;amp; auditable&lt;/strong&gt;…&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/hey-nitsuj/qoutely" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;Python CLI (&lt;code&gt;quotely/cli.py&lt;/code&gt;, &lt;code&gt;quotely/generator.py&lt;/code&gt;, &lt;code&gt;quotely/formatter.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Uses Ollama's &lt;code&gt;/api/generate&lt;/code&gt; with &lt;code&gt;think: false&lt;/code&gt; for reasoning models&lt;/li&gt;
&lt;li&gt;Defaults to &lt;code&gt;qwen3.5:4b&lt;/code&gt;; any local model should work&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
ollama pull qwen3.5:4b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 quotely.py &lt;span class="nt"&gt;--client&lt;/span&gt; &lt;span class="s2"&gt;"Client"&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Work"&lt;/span&gt; &lt;span class="nt"&gt;--hours&lt;/span&gt; 4 &lt;span class="nt"&gt;--rate&lt;/span&gt; 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Running locally keeps costs at zero and avoids sending client details to third-party APIs. It also avoids being tied to a specific vendor's pricing.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Touch Grass Bingo: an offline AI walk card that never leaves your browser</title>
      <dc:creator>Nitsuj</dc:creator>
      <pubDate>Wed, 07 Oct 2026 14:03:47 +0000</pubDate>
      <link>https://dev.to/nitsuj/touch-grass-bingo-an-offline-ai-walk-card-that-never-leaves-your-browser-9kj</link>
      <guid>https://dev.to/nitsuj/touch-grass-bingo-an-offline-ai-walk-card-that-never-leaves-your-browser-9kj</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;Every "AI feature" in a web app today is a round trip: collect input, send it to a hosted&lt;br&gt;
model, stream tokens back. For a game whose whole point is &lt;strong&gt;getting you off the screen&lt;/strong&gt;, a&lt;br&gt;
server would have been both a contradiction and a liability — someone else would know where&lt;br&gt;
you walked.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;Touch Grass Bingo&lt;/strong&gt; runs Gemma 3 1B &lt;em&gt;inside the browser&lt;/em&gt;, via&lt;br&gt;
&lt;a href="https://github.com/mlc-ai/web-llm" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt; and WebGPU. Pick a habitat, a time of day, and an&lt;br&gt;
optional focus ("birds, street art, dogs"). The model writes a 5×5 bingo card of things you&lt;br&gt;
can actually spot in the next 15 minutes. You tap squares on your walk — with zero signal —&lt;br&gt;
and afterwards the same model writes a short field recap of your outing.&lt;/p&gt;

&lt;p&gt;No accounts, no API keys, no analytics, no server. After the first visit it works in&lt;br&gt;
airplane mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it asks for, and what you get
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    subgraph Browser["Browser (static files, any CDN)"]
        UI["UI shell&amp;lt;br/&amp;gt;13 KB app JS"]
        SW["Service Worker&amp;lt;br/&amp;gt;precaches app + engine"]
        subgraph Worker["Web Worker"]
            H["WebWorkerMLCEngineHandler"]
            E["MLCEngine / TVM"]
        end
        Cache["Browser weight cache"]
        Store["localStorage&amp;lt;br/&amp;gt;current card"]
    end
    HF[("HuggingFace CDN&amp;lt;br/&amp;gt;gemma3-1b-it-q4f16_1-MLC&amp;lt;br/&amp;gt;~570 MB, once")]

    UI -- "dynamic import()" --&amp;gt; E
    UI --- Store
    UI --- SW
    SW -. precache .- E
    E -- "one-time fetch" --&amp;gt; HF
    E --- Cache
    H -- "chat.completions.create()" --&amp;gt; E
    E -- "24 squares / recap" --&amp;gt; UI&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Generation runs in a &lt;strong&gt;Web Worker&lt;/strong&gt; so the UI never janks while the model prefills, and the&lt;br&gt;
6 MB engine chunk is &lt;strong&gt;lazy-loaded on first Generate&lt;/strong&gt; — the initial page is 13.8 KB of app&lt;br&gt;
code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open innovation is the whole project, not a footnote
&lt;/h2&gt;

&lt;p&gt;The challenge asks why the open piece matters. For this app it &lt;em&gt;is&lt;/em&gt; the app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It runs on a phone in the backcountry with no internet.&lt;/strong&gt; Weights are cached after the
first load; generation, checking, and recaps are all local. A closed API can't promise
that, and I'd have nothing to demo at mile three of a trail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your location data stays off a server you don't control.&lt;/strong&gt; The app never sees where you
are. There is no backend to leak.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; Static hosting, no inference bill, no rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is swappable in one line.&lt;/strong&gt; WebLLM ships Llama, Qwen, Phi and more in its
prebuilt config — change &lt;code&gt;MODEL_ID&lt;/code&gt; in &lt;code&gt;src/engine.ts&lt;/code&gt; and the card's personality changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The interesting engineering problem: 1B models are flaky
&lt;/h2&gt;

&lt;p&gt;The fun part wasn't calling an API, it was making a small model reliable enough for a game&lt;br&gt;
that must always produce exactly 24 squares. Defenses, in order:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;th&gt;Defense&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JSON wrapped in prose or markdown fences&lt;/td&gt;
&lt;td&gt;bracket-hunting parser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model answers with a numbered list instead of JSON&lt;/td&gt;
&lt;td&gt;line-based fallback parser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON arrays with trailing commas (Gemma's signature)&lt;/td&gt;
&lt;td&gt;salvage quoted strings when strict &lt;code&gt;JSON.parse&lt;/code&gt; fails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeated squares across attempts&lt;/td&gt;
&lt;td&gt;case-insensitive dedupe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single-word lazy squares (&lt;code&gt;dog&lt;/code&gt;, &lt;code&gt;leaf&lt;/code&gt;, &lt;code&gt;flash&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;bare-noun gate drops them; card tops up from the pad pool, or regenerates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recap falls into a repetition loop (&lt;code&gt;a *another* dog…&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;repetition_penalty&lt;/code&gt; + degenerate-output detector + colder retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fewer than 24 items returned&lt;/td&gt;
&lt;td&gt;top-up from a themed pad pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Genuinely bad response&lt;/td&gt;
&lt;td&gt;retry with a stricter prompt, then a built-in preset deck&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No WebGPU / failed worker&lt;/td&gt;
&lt;td&gt;preset decks + an honest status chip, never a dead end&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every path terminates in a playable card. The parser and defense tests live in &lt;code&gt;tests/extract.test.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A detail worth stealing:&lt;/strong&gt; before picking the model I checked WebLLM's prebuilt config for&lt;br&gt;
&lt;code&gt;required_features&lt;/code&gt;. Plenty of popular &lt;code&gt;q4f16_1&lt;/code&gt; builds declare&lt;br&gt;
&lt;code&gt;required_features: ["shader-f16"]&lt;/code&gt; and throw &lt;code&gt;ShaderF16SupportError&lt;/code&gt; on GPUs without that&lt;br&gt;
feature. &lt;code&gt;gemma3-1b-it-q4f16_1-MLC&lt;/code&gt; (711 MB VRAM, &lt;code&gt;low_resource_required: true&lt;/code&gt;) does not —&lt;br&gt;
which makes it the right default for a game anyone should be able to open. Another landmine:&lt;br&gt;
Gemma 3's config ships both &lt;code&gt;context_window_size&lt;/code&gt; and &lt;code&gt;sliding_window_size&lt;/code&gt; positive, which&lt;br&gt;
WebLLM refuses (&lt;code&gt;WindowSizeConfigurationError&lt;/code&gt;) — unless you disable one at load time via&lt;br&gt;
&lt;code&gt;chatOpts: { sliding_window_size: -1 }&lt;/code&gt;. Feature-detecting &lt;code&gt;navigator.gpu&lt;/code&gt; up front and&lt;br&gt;
falling back to preset decks keeps the promise: the app never dead-ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting it running
&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/hey-nitsuj/Touch-Grass-Bingo
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev        &lt;span class="c"&gt;# local dev&lt;/span&gt;
npm run &lt;span class="nb"&gt;test&lt;/span&gt;       &lt;span class="c"&gt;# parser tests&lt;/span&gt;
npm run build      &lt;span class="c"&gt;# typecheck + production build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebGPU needs a secure context — &lt;code&gt;https://&lt;/code&gt; or &lt;code&gt;localhost&lt;/code&gt;, nowhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaming.&lt;/strong&gt; Card generation currently resolves as one blob; streaming the 24 squares in
as they decode would make the wait feel shorter. WebLLM supports it; I chose simplicity
first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A service worker engine host.&lt;/strong&gt; WebLLM can host the engine in a service worker so the
model survives reloads without a worker respawn. Worth it for a v2.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it, then go outside
&lt;/h2&gt;

&lt;p&gt;The point of the game is that the screen is the shortest part of the experience. Generate a&lt;br&gt;
card, put the phone in your pocket, walk, and come back for the recap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play it live: &lt;a href="https://hey-nitsuj.github.io/Touch-Grass-Bingo/" rel="noopener noreferrer"&gt;https://hey-nitsuj.github.io/Touch-Grass-Bingo/&lt;/a&gt;&lt;/strong&gt; — or read the source at&lt;br&gt;
&lt;a href="https://github.com/hey-nitsuj/Touch-Grass-Bingo" rel="noopener noreferrer"&gt;github.com/hey-nitsuj/Touch-Grass-Bingo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Built with WebLLM + Gemma 3 1B (open weights), Vite, and no backend at all. MIT licensed.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>webgpu</category>
      <category>devchallenge</category>
    </item>
  </channel>
</rss>
