<?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: Kuntal Gorai</title>
    <description>The latest articles on DEV Community by Kuntal Gorai (@kuntalgorai).</description>
    <link>https://dev.to/kuntalgorai</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%2F1073393%2F351134b7-c1a6-4fad-9df1-5e3fee215394.jpg</url>
      <title>DEV Community: Kuntal Gorai</title>
      <link>https://dev.to/kuntalgorai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kuntalgorai"/>
    <language>en</language>
    <item>
      <title>DOSE-touch-grass: an offline Gemma coach that hands you a stone and tells you to put your phone down</title>
      <dc:creator>Kuntal Gorai</dc:creator>
      <pubDate>Sun, 11 Oct 2026 17:23:00 +0000</pubDate>
      <link>https://dev.to/kuntalgorai/dose-touch-grass-an-offline-gemma-coach-that-hands-you-a-stone-and-tells-you-to-put-your-phone-down-24am</link>
      <guid>https://dev.to/kuntalgorai/dose-touch-grass-an-offline-gemma-coach-that-hands-you-a-stone-and-tells-you-to-put-your-phone-down-24am</guid>
      <description>&lt;p&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;/p&gt;

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

&lt;p&gt;We all know the four "happy chemicals": &lt;strong&gt;D&lt;/strong&gt;opamine, &lt;strong&gt;O&lt;/strong&gt;xytocin, &lt;strong&gt;S&lt;/strong&gt;erotonin and &lt;strong&gt;E&lt;/strong&gt;ndorphins. The phone has figured out how to hand out cheap dopamine on demand, so we keep coming back to it. &lt;strong&gt;DOSE&lt;/strong&gt; turns that around: you tell it which feeling you're short on, and it sends you &lt;em&gt;outside&lt;/em&gt; to get it the real way.&lt;/p&gt;

&lt;p&gt;How it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick a chemical.&lt;/strong&gt; Dopamine (achievement, novelty), Oxytocin (connection), Serotonin (calm, sunlight) or Endorphins (movement). There's also &lt;em&gt;Surprise me&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get a challenge.&lt;/strong&gt; Choose one from a library of 48 hand-written outdoor challenges, or ask the &lt;strong&gt;AI coach&lt;/strong&gt;. You tell it where you are, who you're with, how much energy you have and how many minutes you've got (it can also take the current weather), and &lt;strong&gt;Gemma 3, running locally in your browser&lt;/strong&gt;, writes a 15–45 minute challenge for that exact moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the phone away.&lt;/strong&gt; A timer runs with a chime and vibration at the end. DOSE also counts "screen peeks", meaning how often you opened the app during the challenge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflect.&lt;/strong&gt; You get a fun fact about the chemical, a short journal questionnaire, and an optional reflection that Gemma writes from your answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the ratio.&lt;/strong&gt; The journal tracks your &lt;strong&gt;grass vs. screen ratio&lt;/strong&gt; over time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Who it's for:&lt;/strong&gt; anyone who has caught themselves scrolling and thought &lt;em&gt;"I should really go outside"&lt;/em&gt; without knowing what to actually do once they got there. DOSE gives you one small, specific thing to do, and then it gets out of your way.&lt;/p&gt;

&lt;p&gt;The app is built so that the screen is the shortest part of the experience: about 30 seconds to get a challenge, then 15–45 minutes outside.&lt;/p&gt;

&lt;h3&gt;
  
  
  I took it outside 🌱
&lt;/h3&gt;

&lt;p&gt;To test it, I picked &lt;strong&gt;Serotonin&lt;/strong&gt;, chose a short session, and asked the coach for a challenge. Gemma came back with this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Go out onto the grass and pick up a stone. Hold it, notice every sensation it gives you: its weight, temperature, texture. Let your thoughts go and focus only on its structure and beauty for 15 minutes.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It sounded almost too simple. It turned out to be one of the most satisfying 15 minutes of my week.&lt;/p&gt;

&lt;p&gt;The stone was smooth on one side and rough on the other, with tiny ridges I'd never have noticed if I'd been looking at a screen. It's strange that an object with no life in it, which has been lying in the same spot for who knows how long, can pass some kind of energy on to your mind and body. Focusing on its texture and smoothness slowly crowded out everything else I'd been thinking about. When the chime went off, I felt calm and honestly a little joyful.&lt;/p&gt;

&lt;p&gt;Back inside, I filled in the journal questionnaire and asked Gemma for a reflection. It picked up on what I'd written and gave me useful feedback about why the challenge worked for me. What stayed with me most is that &lt;strong&gt;this whole loop ran on my own laptop, offline, with an open model.&lt;/strong&gt; None of my thoughts about the stone, my mood or my journal went to anyone's server.&lt;/p&gt;

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

&lt;p&gt;🔗 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://dose-touch-grass.vercel.app/" rel="noopener noreferrer"&gt;https://dose-touch-grass.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To try the AI coach&lt;/strong&gt; you need a WebGPU browser: desktop Chrome/Edge 113+ or Android Chrome 121+. The first time, it downloads the model (about 700 MB). After that it loads from the cache in a few seconds and works offline. In browsers without WebGPU, the full challenge library, timer and journal still work.&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/kuntal0901" rel="noopener noreferrer"&gt;
        kuntal0901
      &lt;/a&gt; / &lt;a href="https://github.com/kuntal0901/dose-touch-grass" rel="noopener noreferrer"&gt;
        dose-touch-grass
      &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;🌱 DOSE: Touch Grass&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;An on-device AI coach that gets you off your phone and outside.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pick the feel-good chemical you're craving (&lt;strong&gt;D&lt;/strong&gt;opamine, &lt;strong&gt;O&lt;/strong&gt;xytocin, &lt;strong&gt;S&lt;/strong&gt;erotonin or &lt;strong&gt;E&lt;/strong&gt;ndorphins), and DOSE gives you a short screen-free challenge to do outdoors. A timer runs while you're out, and a private journal keeps track of how it went.&lt;/p&gt;
&lt;p&gt;The coach is &lt;strong&gt;Gemma 3 1B running entirely in your browser&lt;/strong&gt; through WebGPU. There are no API keys, no server and no account, and your journal never leaves your device.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges" rel="nofollow"&gt;DEV Hacktoberfest Open-Source AI Challenge&lt;/a&gt;, Week 1: &lt;em&gt;Touch Grass&lt;/em&gt; · &lt;code&gt;#hf26challenge&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;em&gt;add your Vercel URL here&lt;/em&gt; · &lt;strong&gt;Write-up:&lt;/strong&gt; &lt;em&gt;add your DEV post here&lt;/em&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why open-source AI?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;An app that wants you to put your phone down shouldn't send your mood journal to a cloud API.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private:&lt;/strong&gt; inference runs locally, so your mood, energy and journal…&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/kuntal0901/dose-touch-grass" 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;&lt;strong&gt;The open-source AI stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 3 1B Instruct&lt;/a&gt;&lt;/strong&gt; (&lt;code&gt;gemma3-1b-it-q4f16_1-MLC&lt;/code&gt;), quantized to 4 bits, about 700 MB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/mlc-ai/web-llm" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt;&lt;/strong&gt; from MLC runs the model in the browser on &lt;strong&gt;WebGPU&lt;/strong&gt;, inside a &lt;strong&gt;Web Worker&lt;/strong&gt; so the UI never freezes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt;, Tailwind v4, shadcn/ui and zustand. There's &lt;strong&gt;no backend at all&lt;/strong&gt;: the journal lives in &lt;code&gt;localStorage&lt;/code&gt;, and a service worker caches the app shell for offline use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://open-meteo.com/" rel="noopener noreferrer"&gt;Open-Meteo&lt;/a&gt;&lt;/strong&gt; (also open source) adds optional weather context, with coordinates rounded before the request.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick chemical ─► CoachPanel ─► engine.ts ──postMessage──► llm.worker.ts
                                   ▲                         │ WebLLM + Gemma 3 1B
                                   │                         ▼ (WebGPU)
                    parse + validate (prompts.ts) ◄── streamed tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Making a 1B model behave in a browser
&lt;/h3&gt;

&lt;p&gt;Getting Gemma to &lt;em&gt;load&lt;/em&gt; was the easy part. Getting it to &lt;em&gt;behave&lt;/em&gt; was the real work. Some of what I learned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. One config flag made all the difference.&lt;/strong&gt; WebLLM's prebuilt Gemma 3 record sets a 4096-token context window, but the model uses a 512-token &lt;strong&gt;sliding window&lt;/strong&gt;, and WebLLM rejects having both. My first fix was to disable the sliding window. The model loaded, but it rambled endlessly, never emitted end-of-turn, and wrote several JSON objects in a row. The right fix is to keep the sliding window Gemma 3 was trained with and drop the fixed context:&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="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;context_window_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sliding_window_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attention_sink_size&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one change took the output from garbage to coherent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. JSON mode works against small models.&lt;/strong&gt; Under WebLLM's JSON-schema grammar, Gemma 1B padded whitespace until it hit &lt;code&gt;max_tokens&lt;/code&gt; and never closed the object. It also kept leaving out the &lt;code&gt;steps&lt;/code&gt; array. So I dropped JSON and asked for plain lines instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: ...
Description: ...
1. ...
2. ...
3. ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tolerant parser accepts &lt;code&gt;**Title:**&lt;/code&gt;, &lt;code&gt;Step 1:&lt;/code&gt;, inline &lt;code&gt;Steps: 1. … 2. …&lt;/code&gt; and other variations, and keeps a JSON salvage path as a fallback. As soon as step 3 is complete, generation is interrupted, so there's no rambling afterward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A validator rejects off-theme output.&lt;/strong&gt; Any challenge that mentions screens, phones or apps is rejected, and the coach retries automatically. A coach that tells you to "open a meditation app" would defeat the whole point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The UI must never spin forever.&lt;/strong&gt; On my Intel Iris Xe laptop I hit &lt;code&gt;DXGI_ERROR_DEVICE_HUNG&lt;/code&gt;. WebLLM then unloads the model, and any pending generation waits forever. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each attempt has a 90 s timeout, then &lt;code&gt;interruptGenerate()&lt;/code&gt;, then a hard reset that terminates the worker and reloads from the cache;&lt;/li&gt;
&lt;li&gt;the worker wraps &lt;code&gt;GPUAdapter.prototype.requestDevice&lt;/code&gt; to watch &lt;code&gt;device.lost&lt;/code&gt; and broadcasts it on a &lt;code&gt;BroadcastChannel&lt;/code&gt;, so the UI can show a clear error right away instead of hanging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Shader warm-up.&lt;/strong&gt; A 4-token generation followed by &lt;code&gt;resetChat()&lt;/code&gt; after load makes the first real request noticeably faster.&lt;/p&gt;

&lt;p&gt;On an integrated Iris Xe GPU this runs at about 5 tokens/s, so a full challenge takes 20–35 seconds. The live preview of the title as it streams in makes that wait feel short.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;The README has an honest roadmap. Gemma 1B sometimes writes a challenge that fits its chemical only loosely, occasionally names the wrong chemical in the text, and my length caps can cut a sentence off mid-thought. Next up: few-shot examples per chemical, trimming at sentence boundaries instead of character limits, a validator that catches the wrong chemical, and an opt-in "quality mode" with a bigger Gemma on capable GPUs.&lt;/p&gt;

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

&lt;p&gt;For this app, a closed API would have been the wrong tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is the product.&lt;/strong&gt; DOSE asks how you feel, where you are, who you're with, and has you write a journal about it. That's some of the most personal data there is. With an open-weight model running in the browser, &lt;strong&gt;none of it leaves your device&lt;/strong&gt;. There's no account, no server and no logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works where it's needed.&lt;/strong&gt; The point is to go outside, often to places with bad or no signal. Once Gemma is cached, the coach works &lt;strong&gt;completely offline&lt;/strong&gt;. A cloud API would fail at exactly the moment you need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; There are no per-token costs and no API keys to protect. I host static files on Vercel's free tier, and anyone can fork it and run their own copy for $0. A wellbeing tool shouldn't need a subscription to pay for someone else's GPUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could fix what was broken.&lt;/strong&gt; Every bug in the "making it behave" section above I could only fix because the whole stack is open. I read WebLLM's source to find the sliding-window conflict, changed the model's runtime config, and changed how decoding stops. With a closed API, "the model rambles" would have stayed a support ticket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is swappable.&lt;/strong&gt; It's a single constant. When a better small open model comes out, DOSE gets better without any change to its architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The irony isn't lost on me: an AI model running on your laptop GPU, whose only job is to get you away from the laptop. Open models make that possible without a cost, either to your wallet or to your privacy.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 3 1B is the core of the app: it writes every personalized challenge and every journal reflection, entirely on-device, through WebLLM and WebGPU.&lt;/li&gt;
&lt;/ul&gt;




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