DEV Community

Dhamith Kumara
Dhamith Kumara Subscriber

Posted on

GrassMate: The AI That Tells You to Stop Using AI

Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass Submission 🌿

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass

Close your laptop. Go outside. Come back happier.

What I Built

GrassMate is a small web app with one job: get you away from it as fast as possible.

You tell it three things: how much time you have, what the weather is like, and where you are (park, beach, forest, village or city). A Gemma 3 4B model running on your own laptop turns that into a playful outdoor mission, shown as a collectible Adventure Card:

🌿 Leaf Hunter
Difficulty: Easy        Time: 30 min

Today's mission
β€’ Find three different leaf shapes.
β€’ Listen for five different sounds.
β€’ Walk one street you have never taken.
β€’ Sit quietly for five minutes.

Reward: More curiosity.
Enter fullscreen mode Exit fullscreen mode

Don't want to choose? Hit 🎲 Surprise Adventure and let Gemma invent everything.

Then comes the part I care about most. When you press Start Adventure, GrassMate doesn't show a timer or a map. It shows this:

🌱
Adventure begins now.
Close this laptop.
Put your phone in your pocket.
We'll be here when you return.

When you come back, Gemma has one reflection question waiting ("What surprised you most during today's walk?"). Your answer goes into an Adventure Journal, and a streak and a few simple badges (🌱 First Adventure, 🌿 Explorer, 🌳 Nature Lover, πŸ‚ Seven-Day Streak) bring you back tomorrow.

The screen is the shortest part of the experience: about 30 seconds of tapping, then however long you spend outside.

It's for anyone who spends the day at a laptop and knows they should go outside, but has no idea what to do with the next 30 minutes. "Go for a walk" is boring. "Collect five sounds you usually tune out" is a game.

Demo

  • Start Adventure
    Start Adventure

  • Adventure Card
    Adventure Card

  • I'm leaving
    I'm leaving

  • I'm back
    I'm back

  • Save to Adventure Journal
    Save to Adventure Journal

  • Adventure Journal with badges
    Adventure Journal with badges

  • Settings
    Settings

Code

🌱 GrassMate

AI that tells you to stop using AI.

Create an outdoor adventure in seconds. Runs completely on your computer.

Close your laptop. Go outside. Come back happier.

GrassMate turns three quick choices (how much time you have, the weather and where you are) into a playful outdoor mission, written by Gemma 3 4B running locally through Ollama. You get a collectible Adventure Card, a calm nudge to close the laptop, and a reflection question when you come back, saved in your Adventure Journal.

Your data never leaves your machine: no account, no cloud API, no tracking.

Built for the Hacktoberfest 2026 Open-Source AI Challenge.

Features

  • Generate Adventure: pick 15 to 60 minutes, the weather and your environment (park, beach, forest, village or city).
  • 🎲 Surprise Adventure: one tap, and Gemma invents everything.
  • Adventure Card: a named mission with difficulty, time, steps and a reward. It fills in…

How I Built It

Stack: Next.js (App Router, TypeScript), Ollama, Gemma 3 4B (gemma3:4b), plain CSS, and the browser's localStorage. There's no database, no account, no cloud API.

Browser β†’ Next.js /api/mission β†’ Ollama β†’ Gemma 3 4B β†’ Adventure JSON β†’ Adventure Card β†’ Journal (localStorage)
Enter fullscreen mode Exit fullscreen mode

Structured output instead of parsing prose

I didn't want to parse free text from a 4B model, so every mission is a JSON object enforced by Ollama's structured outputs. The schema goes straight into the format field:

export const MISSION_SCHEMA = {
  type: "object",
  properties: {
    emoji: { type: "string" },
    title: { type: "string" },
    description: { type: "string" },
    difficulty: { type: "string", enum: ["easy", "medium", "active"] },
    duration: { type: "integer" },
    steps: { type: "array", items: { type: "string" }, minItems: 3, maxItems: 5 },
    reward: { type: "string" },
    encouragement: { type: "string" },
    reflection: { type: "string" },
  },
  required: ["emoji", "title", "description", "difficulty", "duration",
             "steps", "reward", "encouragement", "reflection"],
} as const;
Enter fullscreen mode Exit fullscreen mode

The server still validates every reply: it trims long text, caps duration at the time you picked, and rejects anything with fewer than three steps. In my testing every reply from Gemma passed. If one doesn't, or if Ollama isn't running at all, GrassMate quietly picks from 30 hand-written missions in the same format. The app never breaks, it just says "AI offline" in small print. (This post on clean JSON extraction with Ollama is a good intro to the same idea in Python.)

The real challenge: my laptop

GrassMate runs on my everyday laptop: an Intel Core i5-10210U (4 cores, 15 W) with 16 GB of RAM and a 2 GB NVIDIA MX130. My first working version took 76 seconds per mission, and the cold start was worse. Here's what I measured and changed:

Change Effect
Measured the baseline Gemma writes about 3 tokens/s; a mission was about 220 tokens
num_gpu: 0 (CPU only) +25% faster, model load 75 s β†’ 20 s, RAM 4.8 GB β†’ 2.9 GB
8 threads instead of 4 4Γ— slower (hyperthreads hurt; leave it to Ollama)
Shorter prompt and shorter fields Output about 220 β†’ 110-160 tokens, prompt reading 18 s β†’ 11 s
Warm-up when the app opens The model loads while you're still picking options
Stream the card The title appears after about 15-20 s instead of a 60 s spinner

The GPU result surprised me. This benchmark shows a 4 GB laptop GPU beating a 12-core CPU by 4.3Γ—. My 2 GB card could only hold a sliver of the model, though, and splitting it was slower than plain CPU. Measure on your own machine. (For another take on old hardware, see Old PC vs New AI.)

I also learned that Ollama only reused its prompt cache for exactly identical prompts. My plan to pre-read the system prompt during warm-up did nothing, so cutting prompt tokens was the fix that actually helped.

Making a 50-second wait feel short

At about 4 tokens per second, a full mission still takes 45-60 seconds on this machine. Instead of hiding that behind a spinner, the API route streams Gemma's reply as newline-delimited JSON, and the browser fills in the Adventure Card field by field as it arrives. A tiny parser pulls out only the fields that are already complete:

// Only trust the number once something follows it, so "2" of "25" isn't shown.
const duration = /"duration"\s*:\s*(\d+)\s*[,}\s]/.exec(text);
if (duration) preview.duration = Number(duration[1]);
Enter fullscreen mode Exit fullscreen mode

Watching the emoji, then the title, then each step appear turns the wait into part of the experience. If you leave or press Try another, the request to Ollama is cancelled, so the laptop doesn't keep working on a card nobody will read.

Why Does Open Innovation Matter?

GrassMate only makes sense with an open, local model:

  • Privacy: a journal of where you went and how you felt is personal. With Gemma running locally, nothing leaves your machine. The only network call is to Ollama on localhost, and the journal lives in your browser.
  • It works offline: no API key, no signal needed. Even with Ollama off, the built-in missions keep working.
  • It costs nothing to run: no per-request bill for an app whose whole goal is to be used less.
  • I could tune it for my hardware: with a closed API I couldn't choose how the model loads, how many threads it uses or whether the GPU is used. Here, one environment variable (OLLAMA_NUM_GPU=0) made it 25% faster and cut memory almost in half.
  • It's swappable: the model is one setting (OLLAMA_MODEL), so anyone can try a smaller or bigger open model.

There's also a nice irony: an AI that runs on your laptop, whose best outcome is that you close the laptop.

My Agent Session

I built GrassMate with Claude Code, from the first PRD, through two rounds of feedback that cut the scope (goodbye, screen-time timer), to the performance investigation above. Here's the full session:

GrassMate: from PRD to a local-AI MVP on a low-end laptop
You

Create a README.md with the title "GrassMate".

Agent

I created README.md in the project root. It contains only the heading # GrassMate, since the directory was empty and there was nothing to describe yet. If you tell me what the project does, I can add a description, setup steps and usage.

You

Can you check whether the DevRelay MCP server is available and list the tools it provides?

Agent

Yes, the DevRelay MCP server (devrelay-gateway) is registered in this session, and its tool schemas load. I haven't called any of its tools, so I haven't checked that the server responds or that you're signed in. Several tools need a connected MLH account (connect_mlh_account).

It provides 70 tools, grouped here by area:

DEV articles & content
- create_article, update_article, unpublish_article
- get_articles, get_latest_articles, get_my_articles, get_article_by_path, get_article_content
- get_organization_articles, get_reading_list
- search_dev_to_semantic

Comments, tags, pages
- get_comments, get_comment_by_id
- list_tags, get_tag_by_id, get_followed_tags
- get_pages, get_page_by_id

Users & organizations
- get_authenticated_user, get_user_by_id, get_user_by_username, get_user_contributions, get_followers
- get_organization, get_organization_users

DEV challenges & events
- get_challenges, get_challenge_details
- get_events, get_event_by_id

MLH account & events
- connect_mlh_account, disconnect_mlh_account, mlh_connection_status, get_my_mlh_profile
- search_mlh_events, get_mlh_event, list_my_mlh_events
- register_for_mlh_event, check_in_to_mlh_event

MLH projects & submissions
- create_project, update_project, list_my_projects
- submit_project_to_event
- enter_challenge, withdraw_challenge_entry, reactivate_challenge_entry

Sponsor offers & skills
- list_event_offers, claim_promo_code, list_my_promo_code_redemptions
- list_event_agent_skills

Agent session transcripts
- list_agent_sessions, get_agent_session, get_agent_session_raw_url
- presign_agent_session_upload, submit_agent_session

Concepts & knowledge base
- create_concept, update_concept, delete_concept
- list_concepts, search_concepts, get_concept_details, get_concept_articles
- trigger_concept_lookback
- get_knowledge_document

Other
- execute_dev_api_request (a direct DEV API call)
- send_feedback

The DevRelay skills (devrelay-navigator, devrelay-publishing, devrelay-challenges and others) are also available and use these tools. To check that the server actually responds, I can run get_authenticated_user or mlh_connection_status. Both only read your account status.

You

I am building a project for Hacktoberfest 2026.

Before we start coding, please check my MLH connection status and verify that my agent session can be submitted later.

Do not write any code yet.

Agent

Your MLH account is connected, and your agent session should be submittable later. I haven't tested an actual upload, because that would create a real session on DEV.

MLH connection
- You're connected as Dhamith Kumara, and DevRelay reports no missing permissions.
- The granted permissions include dev:write:all, which covers uploading sessions to DEV. They also include the MLH project, event-participation and promo-code permissions.

Agent session submission
- I could list the agent sessions on your DEV account, so access works. You have none saved yet.
- When you want to save this session, the steps are presign_agent_session_upload and then submit_agent_session.

I haven't written any code or made any changes. When you're ready, tell me what the project is, and I can also look up the Hacktoberfest 2026 rules and eligibility in the DevRelay knowledge base.

You

I want to build a project called GrassMate for the Hacktoberfest 2026 Open-Source AI Challenge.

Before writing any code, let's create a Product Requirements Document (PRD).

The application should encourage people to spend less time looking at screens and more time outdoors.

The AI must run locally using Ollama with the Gemma 3 4B model.

The project should be lightweight because my laptop has limited performance.

Do not write any code.

Please create a detailed PRD with the following sections:

  • Project Overview
  • Problem Statement
  • Goals
  • Non-Goals
  • Target Users
  • User Stories
  • Functional Requirements
  • Non-Functional Requirements
  • Technical Stack
  • AI Integration
  • Application Flow
  • Future Improvements
  • Success Criteria

Keep the scope small enough to finish within one week.

Agent

Outline's up. Starting on the Project Overview.

Overview's in. Now the problem statement and goals.

Next: non-goals, target users and user stories.

Now the functional and non-functional requirements.

Requirements are in. Moving to non-functional requirements and the technical stack.

Now the AI integration section. I'm also correcting a streaming detail in NFR-3 to match it.

Next: future improvements and success criteria with the one-week plan.

Last section: the application flow, as a diagram.

Let me check how the diagram renders.

The GrassMate PRD is ready, with all 13 sections; edit it inline or comment and I'll revise.

You

Things I would change
1. ❌ Remove the screen-session timer
This is my biggest recommendation.
Your app is becoming two different products:
- Screen time tracker
- Outdoor AI assistant
The timer is a lot of work:
- Notifications
- Background timers
- Browser permissions
- Pause/reset logic
- Settings
But it is not the reason judges will remember your project.
Instead, imagine this:
GrassMate

↓

Tell me:

β€’ I have 30 minutes

β€’ Sunny

β€’ City

↓

Generate Adventure

↓

Go Outside

Simple.
2. Replace "Outdoor Log"
Instead of
Outdoor Log

I would rename it
Adventure Journal

Much more memorable.
Example:
October 6

🌿 City Observation Walk

30 minutes

I noticed three birds
and a quiet street
I never walked before.

That's much nicer than
30 minutes logged

  1. Give every mission a personality Instead of Walk outside.

Generate names like:
🌿 Leaf Hunter

β˜€ Morning Explorer

πŸ“· Color Hunt

πŸ‚ Quiet Corner

🐦 Five Sounds

🌳 Tiny Adventure

🌸 Hidden Nature

🚢 Slow Walk

This makes screenshots much more attractive.
4. Make the AI response structured
I wouldn't ask Gemma to return plain text.
I'd require JSON like:
{
"title": "",
"description": "",
"difficulty": "",
"duration": 30,
"steps": [],
"encouragement": ""
}

It will make the UI much easier to build.
5. Add one killer feature
Instead of:
Generate Ideas

Have
🎲 Surprise Adventure

One click.
Gemma invents something fun.
People love randomness.
6. Better slogan
Instead of
Outdoor ideas.

I would use
AI that tells you to stop using AI.

or
Close your laptop.

Go outside.

Come back happier.

That line alone is memorable.
I would simplify the MVP
Instead of eleven functional requirements, I'd focus on just these five:
Feature Keep?
Generate AI outdoor mission βœ…
Adventure Journal βœ…
Daily streak βœ…
Offline fallback βœ…
Settings βœ…

Everything else can wait.
One more idea
At the end of every mission, instead of simply saying:
Mission completed.

Have Gemma say something like:
"You spent 30 minutes outdoors today. Nice work! Tomorrow, try noticing something you walked past today but didn't really see."

That gives the app a friendly personality while still keeping the AI usage lightweight.
My overall score
- Challenge fit: 10/10
- Scope: 9/10
- Originality: 8/10
- Technical feasibility on your laptop: 10/10
- Winning potential after these refinements: 9.5/10

Agent

Your feedback cuts the scope a lot, so I'm reworking the PRD section by section. Starting with the overview and slogan.

Fixing the slogan line, then the problem statement and goals.

Now non-goals, target users and user stories.

Now the functional requirements, cut down to the features you chose.

Updating wording in the non-functional requirements, and checking the stack table so I can remove the notifications row.

AI integration now uses your mission schema. Next: future improvements and success criteria.

Last piece: redrawing the application flow for the new loop.

Removing the one label that overlaps a box.

Now the text around the diagram.

Checking for leftover references to the old timer and log.

I've reworked the GrassMate PRD around your feedback. The app is now just the adventure loop:

  • Timer removed: the screen-session timer, break reminders and notifications are now a non-goal and are listed under Future Improvements.
  • Seven requirements, down from eleven: your five are now "Must" items, plus two small "Should" items, the completion message and Settings. Surprise Adventure is listed as its own "Must", next to the mission generator, as the standout feature.
  • Adventure Journal: this replaces the outdoor log. Each entry has the date, the mission's emoji and name, the minutes, and a reflection.
  • Mission schema: the AI section now uses your JSON fields, with an example "Five Sounds" mission. I added one field, emoji, so the app can show the mission icons separately from the title.
  • Completion message: it combines "You spent N minutes outdoors today. Nice work!" with the mission's encouragement line, which includes a tip for tomorrow. That means no second AI call.
  • Slogan and tagline: both now open the Project Overview.
  • Diagram, success criteria and one-week plan: all three are redrawn or rewritten for the new loop.

I made two calls of my own:
- The streak counts days with at least one journal entry, so there's no daily-goal setting.
- Settings covers default time and surroundings, an AI on/off switch, and a button to delete all journal data.

The comment asking whether a browser-based Flask app suits you is still open in the doc.

You

βœ… What I love
1. The core idea is much stronger
The project now has a clear purpose:
Generate a personalized outdoor micro-adventure with local Gemma, then encourage the user to leave the screen.

That's exactly what the judges are asking for. Pasted text
2. Adventure Journal
Changing "Outdoor Log" to "Adventure Journal" was a great decision.
It makes the app feel emotional instead of technical. Pasted text
3. Mission Titles
Examples like
- 🌿 Leaf Hunter
- πŸ‚ Quiet Corner
- πŸ“· Color Hunt
make the app memorable. Those titles will also look great in screenshots. Pasted text
4. JSON output
Excellent.
Much easier than parsing plain text.
5. Offline-first
This is one of your biggest advantages.
Many Hacktoberfest submissions will use cloud APIs.
Your story is:
My AI works without sending your data to someone else's server.

That's powerful. Pasted text
πŸ”₯ One thing I would add
I think the app needs one emotional moment.
Imagine the user clicks:
Start Adventure

Instead of immediately showing a timer, show this:
🌱

Adventure begins now.

Close this laptop.

Put your phone in your pocket.

We'll be here when you return.

See you soon.

This screen lasts 3–5 seconds.
Then:
[I'm leaving]

That moment reinforces the app's purpose.
⭐ Add an "Adventure Card"
Every mission should look like a collectible card.
Example:
────────────────────────

🌿 Leaf Hunter

Difficulty
Easy

Time
30 min

Today's mission

β€’ Find three different leaf shapes

β€’ Listen to five sounds

β€’ Walk one unfamiliar street

β€’ Sit quietly for five minutes

Reward

More curiosity.

────────────────────────

That will look fantastic in screenshots.
πŸ… Add a badge system
No AI required.
Just simple milestones.
🌱 First Adventure

🌿 Explorer

🌳 Nature Lover

πŸ‚ Seven-Day Streak

Very little code.
Big visual improvement.
πŸ“· Demo strategy
This is important.
Don't just record your screen.
Instead:
Scene 1
Generate mission.
Scene 2
Close laptop.
Scene 3
Take your phone outside.
Scene 4
Show a tree.
Scene 5
Return.
Scene 6
Adventure Journal updated.
That tells a story.
README
I would add one architecture diagram.
Browser

↓

Next.js

↓

Ollama

↓

Gemma 3 4B

↓

Adventure JSON

↓

Mission Card

Very easy.
Very professional.
One thing I would remove
I would not save GPS coordinates.
Not needed.
Adds complexity.
Doesn't help judging.
Instead simply ask
Environment

β—‹ Park

β—‹ Beach

β—‹ Forest

β—‹ Village

β—‹ City

Much easier.
Don't overcomplicate settings
Keep only:
Dark Mode

Mission Duration

Theme

Nothing else.
Logo
I'd keep it extremely simple.
🌱

GrassMate

Green.
Rounded.
Friendly.
The hero section
I think this is better than anything in the PRD:
GrassMate

AI that tells you
to stop using AI.

Create an outdoor adventure
in seconds.

Runs completely on your computer.

[Start Adventure]

One last feature (my favorite)
At the end of every mission, have Gemma generate one reflection question.
Example:
Today's Reflection

What surprised you most
during today's walk?

or
Did you notice something
you usually ignore?

Store the answer in the Adventure Journal.
This creates a much more personal experience without adding much complexity.
Final verdict
I think you've reached the point where planning is done.
Don't spend more time refining the PRD.
From here, I'd switch to implementation. Since you're recording your work with DevRelay, your development process will already include:
- Planning
- Requirements
- Design decisions
- Implementation
- Testing
That's exactly the kind of end-to-end workflow judges like to see.
My suggested implementation order
1. Homepage (hero section + mission form)
2. Ollama API integration
3. JSON mission generation
4. Mission Card UI
5. "Go Outside" screen
6. Adventure Journal (saved in localStorage)
7. Streaks and badges
8. Polish, screenshots, and DEV article
At this point, I would stop planning and start building.

Agent

Updating the PRD first. Starting with the overview, hero and requirements.

Stack switched. Now the AI section: calls through the Next.js route, plus reward and reflection fields in the schema.

Now updating the flow diagram to match the new screens.

Checking for leftover references to the old stack.

PRD is updated. Now building, starting with the local toolchain.

Ollama 0.35 is running with gemma3:4b already pulled, and Node 24 is installed. I'll set up Next.js by hand rather than with create-next-app, to keep dependencies minimal.

TypeScript 7 is the native build and doesn't ship the JavaScript API that Next.js uses for type-checking, so I'm pinning TypeScript 6.

Now the app itself. First the config files.

Now the shared types and options.

Next, the mission schema and validation.

Now the prompt builder and Ollama client.

Now the 30 built-in offline missions.

Now streak and badge logic.

Now browser storage and the API routes.

Now the UI, beginning with the layout and main page that holds the app's state.

Now the screen components, starting with Home and the Adventure Card.

Now the Go Outside screen, Reflection, Journal and Settings.

Now the stylesheet: green, rounded and friendly, with light and dark modes.

Now the tests.

All 23 tests pass. I'll rename the Vitest config to .mts to clear that warning, then type-check and do a production build.

The build and type-check both pass. Next I'll start the production server and test real Gemma calls.

Port 3000 is already taken by a different app on your machine (a "SplitMate AI" dev server), so GrassMate couldn't start there. I'll leave that server alone and run GrassMate on port 3100 instead.

The fallback works, but Gemma's replies are being rejected. Checking the server log for the reason:

Every call is timing out, and a direct call to Ollama returned nothing within 90 seconds. Let me see what Ollama is doing.

Loading the model from cold took 132 seconds. Now it's warm, so let me time a real mission request.

Ollama is idle now. I'll rename the probe to .mts so it can use top-level await.

The CPU is an Intel i5-10210U, a 4-core, low-power laptop chip. There's also an NVIDIA MX130 GPU with 2 GB, too small to hold the model. Baseline is 3.0 tokens per second. Trying pure CPU next:

Pure CPU is faster: 3.7 tokens per second, and the model loads in 24 seconds instead of 75. Now thread counts:

Now the stream protocol and a parser that reads the partial JSON as it arrives:

If the user leaves or taps "Try another", the Ollama request should be cancelled so the laptop doesn't keep generating an unused mission. I'll pass the browser request's abort signal through.

Those are my own edits. I'll also make closing the stream safe after a disconnect.

Now the client side: read the stream, show the card filling in, and cancel when the user navigates away.

Now the Adventure Card renders from the live preview while Gemma is writing.

Type-check passes. Adding tests for the stream preview parser:

All 29 tests pass. Rebuilding, then restarting only GrassMate's server on port 3100 and timing the full flow: health check, warm-up, then a streamed mission.

The model now loads pure-CPU in about 18 seconds, using 2.9 GB of RAM (it was 4.8 GB). Timing streamed missions, including when the title first appears:

Surprise Adventure's first token arrived in 1.7 seconds, but custom missions took about 17 seconds. So Ollama reused its prompt cache for one and not the other. Investigating:

Ollama isn't reusing its prompt cache here, so each request re-reads all 200 prompt tokens, which takes 17 to 20 seconds. Checking whether the JSON format setting is what breaks the cache:

The cache only hits when the prompt is exactly the same, so the warm-up can't pre-read the system prompt. The fix that actually helps is a much shorter prompt. Rewriting it:

No browser is installed, so I can't drive the UI or take screenshots here. I'll check the server-rendered page and run the mission stream end to end.

Bad input is rejected with a 400. To test the offline fallback, I'll start a second instance on port 3101 pointed at a closed port, as if Ollama were down.

The offline fallback returns a saved mission in 32 ms. Now the README with the architecture diagram, plus an MIT licence.

Before wrapping up, let me reread page.tsx, since I edited it with a script.

One gap: if the stream ends without a final "done" event, for example because the connection drops, the card screen would go blank. Fixing that:

Rebuilding so the running server has the latest code, then updating the PRD's performance targets to the measured numbers.

The PRD is updated and the first version of GrassMate is built. It's running on your laptop at http://localhost:3100. Port 3000 was already taken by another app (SplitMate AI), so I left that alone. All 29 tests and the type-check pass, and the production build works. I haven't seen the UI in a browser, because there's no browser in this environment, so please click through the full loop once yourself.

PRD changes (GrassMate PRD): it now covers the hero section and logo, the five environments, the Go Outside screen, the Adventure Card, badges and the reflection question. Settings are cut to the three you listed. The stack is now Next.js with localStorage, and the PRD includes the architecture line, your implementation order and the demo-video storyboard. I replied in the doc comment about Flask.

What's built: the hero and mission form, Surprise Adventure, Adventure Card, Go Outside screen, reflection with the Adventure Journal, streak and badges, 30 built-in missions for when Ollama is off, settings, the README with the architecture diagram, and an MIT licence. Gemma returns strict JSON via Ollama's schema setting. Two checks with real data:
- I ran six full missions against the real model, and all of them returned valid JSON.
- With Ollama unreachable, the app falls back to a saved mission in 32 ms.

Your laptop is slower than the PRD assumed. It has an i5-10210U processor, and Gemma writes only about 4 tokens per second on it. The original 20-second target isn't reachable, so I made changes to reduce the wait:
- Live card: the card now fills in while Gemma writes. The title appears after about 15–20 seconds, and the full mission takes about 45–60 seconds.
- Shorter prompt: this roughly halved both the prompt-reading time and the length of Gemma's reply.
- CPU only: your 2 GB graphics card was slowing things down. Running Gemma on the processor alone, via OLLAMA_NUM_GPU=0 in .env.local, made writing about 25% faster and loading about 3 times faster (about 20 seconds instead of 75). It also uses 2.9 GB of RAM instead of 4.8 GB.
- Warm-up: Gemma starts loading as soon as the app opens.

I updated the PRD targets and the README with these measured numbers.

Decisions you might want to change:
- I read the "Theme" setting as the style of mission (nature, mindful, creative, active), not a colour theme.
- I pinned TypeScript to version 6, because version 7 breaks Next.js's type-checking.
- I ran git init but haven't committed anything. Say the word and I'll make the first commit.

You

okay can you guide me to test this please

Agent

GrassMate and Ollama are both running, so you can start testing now.

1. Open the app

In your Windows browser, go to http://localhost:3100. WSL forwards localhost automatically.

Under the Start Adventure button you should see a small green dot and "Gemma 3 4B is ready on this computer". Opening the page also starts loading Gemma in the background, so wait about 20 seconds before your first mission.

2. The main loop (about 2 minutes)

  1. Pick 30 min, Sunny and Park, then click Generate Adventure.
  2. Watch the card fill in. The footer counts seconds. Expect the title after about 15–20 seconds and the full card by about 60. Start Adventure stays disabled until the card is finished.
  3. Click Start Adventure to open the Go Outside screen. The sprout grows, the "Close this laptop…" lines appear, and I'm leaving shows up after about 4 seconds.
  4. Click I'm leaving, then I'm back.
  5. On the reflection screen, check three things:
    • "You spent N minutes outdoors today", where N is how long you were away. You can edit it.
    • Gemma's encouragement line.
    • Gemma's reflection question. Type an answer.
  6. Click Save to Adventure Journal. You should see a yellow "New badge: 🌱 First Adventure" message, your entry, a 1-day streak, and four badges with one earned.

3. Other things to check

Test How Expect
Surprise Home, then 🎲 Surprise Adventure Gemma picks the time and theme
Try another On a finished card, click Try another A new card fills in with a different name
Cancel Click Cancel while the card is being written Back to home; Gemma stops working, so the laptop goes quiet
Settings Settings: Dark, 15 min, Mindful The page turns dark right away; the home form defaults to 15 min; the next mission feels calmer
Persistence Refresh the page The journal, streak and dark mode are still there
Delete Journal, then Delete on an entry It asks first, then removes the entry
Phone layout Press F12, open the device toolbar, choose a phone No sideways scrolling; the chips wrap onto new lines

4. Offline fallback

I'd test this without stopping Ollama. Run a second copy of the app that points at a port where nothing is listening:

! cd ~/projects/grassmate && OLLAMA_HOST=http://[REDACTED]:9 npx next start -p 3101

Then open http://localhost:3101. The status should say "AI offline", missions should appear instantly, and the card footer should say "Saved mission Β· AI offline". Press Ctrl+C to stop it when you're done.

5. Badges without waiting a week (optional)

In the browser console (F12, Console tab) on localhost:3100, paste this to add 7 days of entries:

const days=[...Array(7)].map((_,i)=>{const d=new Date();d.setDate(d.getDate()-i-1);return d.toLocaleDateString('en-CA')});
const j=JSON.parse(localStorage.getItem('grassmate.journal.v1')||'[]');
localStorage.setItem('grassmate.journal.v1',JSON.stringify([...j,...days.map((d,i)=>({id:'t'+i,date:d,createdAt:d+'T10:00:00Z',emoji:'🌿',title:'Test Walk '+i,minutes:20,question:'Test?',answer:''}))]));
location.reload();

You should then get the Explorer and Seven-Day Streak badges. To clear all GrassMate data afterwards, run localStorage.clear(); location.reload().

If something looks wrong

  • The card takes over 2 minutes, or "AI offline" appears unexpectedly: the model was probably still loading. Wait 20 seconds and try again.
  • To see the server log, run this in the terminal: ! tail -20 <scratchpad>

Tell me what you see, especially anything that looks off in the layout. I couldn't view the UI from here, so your eyes are the real visual check.

You

this cool i think everything is working perfeclty

Agent

Good to hear everything works.

Nothing has been committed yet. Next steps, in the order I'd do them:

  1. Commit the MVP. I'll make the first commit on a branch. Your .env.local file is ignored by git, so it won't be included.
  2. Push to GitHub. Judges need a public repository. I can create it with gh if you're logged in.
  3. Save this session to DevRelay. You mentioned submitting your agent session later. This session already covers planning, the PRD, design decisions, the performance investigation, implementation and testing. I'll only save it when you say so; nothing gets posted automatically.
  4. Screenshots and demo video. Follow the storyboard in the PRD. Dark mode and the Adventure Card make good shots.
  5. DEV article. The story of making a 4B model usable on a 4-core laptop CPU (about 4 tokens per second, the live-streaming card, and why CPU-only was faster) would make a strong post. I can draft it for you to review.

Want me to start with the commit?

You

yes commit this

Agent

The staged files look right: no .env.local, node_modules or build output. Committing:

I committed the MVP as 51d40d6 on a new branch, mvp. All 33 project files are in it, and your .env.local stays out of git.

Your repo has no master commits yet, because I branched before committing. When you're ready, you can either push mvp and merge it on GitHub, or tell me to make it master directly.

I haven't pushed anything. Next up, I can create a public GitHub repository and push to it, or save this session to DevRelay.

You

create repo and publish and save the session to DevRelay

Agent

The GitHub CLI (gh) isn't installed, so I'll come back to the repo. Starting with the DevRelay session.

The remaining hits are your local username inside file listings and paths, plus the scrubber's own regex. I'll mask the username as well:

Prize Categories

  • Best Use of Gemma: Gemma 3 4B is the core of the app. It runs fully locally through Ollama, returns schema-enforced JSON, streams into the UI, and is tuned to run on a 4-core laptop CPU.

Thanks for reading. Now close this tab and go outside. 🌱

Top comments (0)