This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend
What I Built
I built Tangkwa Journal for my friend Tangkwa.
She’s in a demanding ENT specialist training program. Her days are long, her schedule is messy, and when she’s rushed or stressed she often reaches for something sweet — a cup of sweet matcha, a quick comfort drink — without really tracking how often it happens. She doesn’t need another calorie spreadsheet. She needs something that feels like a friend.
So I made a cute, mobile-first PWA where she can:
- Write a short diary entry (optional mood chips: tired, okay, stressed, happy, overwhelmed)
- Log a meal or drink in plain language — “a cup of sweet matcha”, “a slice of pizza”
- Get a warm reply from an open-weight companion that acts as a psychologist-style friend and a gentle diet mentor
The brain is gemma2:2b via Ollama. The phone is a thin PWA. Inference stays on the server side on purpose — her notes stay on an open-weight stack I control, not a closed chatbot SaaS in the middle.
Accounts are per-person (username + password, httpOnly session cookie) so each journal stays private. Important: the app is explicit that this is a supportive friend, not a clinician. There’s an in-app disclaimer. Crisis-looking language takes a fixed human-help path and skips model generation.
Demo
Live demo: https://tangkwa-journal.byfaishal.cv/
Judges can Create an account on the live site, or sign in with the seed account:
- username:
tangkwa - password:
tangkwa-journal
Local / judge repro:
git clone https://github.com/mochfamir/tangkwa-journal
cd tangkwa-journal
pnpm install
ollama pull gemma2:2b
cp .env.example .env
pnpm dev:api # http://127.0.0.1:8787
pnpm dev:web # http://127.0.0.1:5173
Health check (must see the open-weight model):
curl -s http://127.0.0.1:8787/api/health
# {"ok":true,"model":"gemma2:2b","ollama":"up","modelPresent":true}
Companion smoke test (register → cookie → create meal entry):
curl -s -c /tmp/tj.jar -X POST http://127.0.0.1:8787/api/auth/register \
-H 'content-type: application/json' \
-d '{"username":"judge-demo","password":"judge-demo-pass","display_name":"Judge"}'
curl -s -b /tmp/tj.jar -X POST http://127.0.0.1:8787/api/entries \
-H 'content-type: application/json' \
-d '{"type":"meal","body":"a cup of sweet matcha","sweetness":"sweet"}'
Or skip register and log in as the seed user (tangkwa / tangkwa-journal) against /api/auth/login with the same cookie jar.
Code
https://github.com/mochfamir/tangkwa-journal
Built during the challenge window (commits from 2026-10-03, after the Oct 2 UTC start).
How I Built It
I shipped in deliberate layers the same Saturday afternoon (WIB), because the challenge is short and Tangkwa’s problem is real.
1. Brain first — open-weight companion loop
Before pastels, I wanted proof that Gemma could talk back.
- Small Hono API (
apps/api) on port8787 -
SQLite (
better-sqlite3) for diary + meal entries, scoped per user - Versioned system prompt in
prompts/companion.md— friend first, coach second, ~100-word cap, no shame language, no fake calorie precision - Ollama
POST /api/chatwithgemma2:2band a tightnum_predict - Only the last 3 one-line summaries as context (keeps a 2B model coherent; avoids dumping her whole journal into the prompt)
-
Fail closed: if Ollama is down → HTTP
503, nothing half-written is persisted
Warm replies on my machine landed in about 1–4 seconds. That was the moment it stopped being a scaffold and became something that could answer a tired ENT trainee who just logged a sweet matcha.
Sample meal reply from build-time testing:
Matcha's a great pick, Tangkwa! Sometimes a little sweetness helps bridge the day's fatigue. How's that energy after rounds?
2. Then the shell she’d actually open
UI only — no API contract changes, so the open-weight path stayed honest.
- Vite + React + TypeScript PWA
- Soft pastel system (blush / cream / mint) with Fraunces + Nunito
- Screens: login/register, Today (“Hi Tangkwa”), Write diary, Log drink/meal, History, Detail with AI reply, About/disclaimer
- “Gemma is thinking…” loading state
- PWA shell via
vite-plugin-pwa— offline shell is fine;/api/*stays network-only so a dead model never becomes a fake journal reply in the UI
Architecture in one line:
Phone PWA → Hono API (+ session auth) → Ollama (gemma2:2b)
↓
SQLite journal (per user)
3. Why 2b, not a bigger Gemma?
I already had a larger Gemma locally. I still chose gemma2:2b so a weekend deploy on modest hardware stays realistic, latency stays friendly on a phone wait screen, and the “live for a friend” path doesn’t depend on a GPU fairy tale. Quality is managed with a strict prompt and short outputs — not by hoping a bigger model forgives a vague product.
Privacy-minded journal builders keep saying the same thing: personal notes need a hard boundary on where inference runs and what gets remembered. That pushed me toward self-hosted open weights instead of a closed chat API as the product’s brain.
Why Does Open Innovation Matter?
This isn’t a todo list. It’s mood + eating notes from someone I care about, written on exhausted hospital days.
I didn’t want that intimacy routed through a closed commercial chatbot by default.
Open-weight Gemma + open-source Ollama means:
- The model is a setting, not the product’s landlord — swap weights later without rewriting around a vendor
- Inference can stay on infrastructure I run (laptop now; VPS/Compose for her phone later)
- Judges (and Tangkwa) can see the companion voice in a file in the repo, not buried in a black-box prompt dashboard
- A small open model is enough for short, kind check-ins — which is what she actually has time for
Closed APIs would have been faster to prototype. They would have been the wrong default for this friend’s data.
Prize Categories
-
Best Use of Gemma — generation path is only
gemma2:2bvia Ollama; no closed-LLM fallback in the companion loop. The PWA is a thin client; Gemma is the product’s brain.
Built for
Tangkwa — and anyone who’s too busy for calorie apps but still deserves a soft place to check in.



Top comments (0)