<?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: Mochamad Faishal Amir</title>
    <description>The latest articles on DEV Community by Mochamad Faishal Amir (@mochfamir).</description>
    <link>https://dev.to/mochfamir</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%2F152303%2Fd13fb397-1c38-4436-8fde-4215e622a46f.jpeg</url>
      <title>DEV Community: Mochamad Faishal Amir</title>
      <link>https://dev.to/mochfamir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mochfamir"/>
    <language>en</language>
    <item>
      <title>Tangkwa Journal: A Soft AI Diary I Built for My Friend in ENT Training</title>
      <dc:creator>Mochamad Faishal Amir</dc:creator>
      <pubDate>Sat, 03 Oct 2026 06:46:02 +0000</pubDate>
      <link>https://dev.to/mochfamir/tangkwa-journal-a-soft-ai-diary-i-built-for-my-friend-in-ent-training-3aon</link>
      <guid>https://dev.to/mochfamir/tangkwa-journal-a-soft-ai-diary-i-built-for-my-friend-in-ent-training-3aon</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7p949qmynog8bekcxuoi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7p949qmynog8bekcxuoi.png" alt="Today screen" width="401" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmiysic79eidjofyqvcts.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmiysic79eidjofyqvcts.png" alt="Log meal / drink" width="401" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkozk0ny9m8w336akelej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkozk0ny9m8w336akelej.png" alt="Gemma reply" width="401" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;Tangkwa Journal&lt;/strong&gt; for my friend &lt;strong&gt;Tangkwa&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;She’s in a demanding &lt;strong&gt;ENT specialist training&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;So I made a &lt;strong&gt;cute, mobile-first PWA&lt;/strong&gt; where she can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a short &lt;strong&gt;diary&lt;/strong&gt; entry (optional mood chips: tired, okay, stressed, happy, overwhelmed)&lt;/li&gt;
&lt;li&gt;Log a &lt;strong&gt;meal or drink in plain language&lt;/strong&gt; — “a cup of sweet matcha”, “a slice of pizza”&lt;/li&gt;
&lt;li&gt;Get a warm reply from an open-weight companion that acts as a &lt;strong&gt;psychologist-style friend&lt;/strong&gt; and a &lt;strong&gt;gentle diet mentor&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The brain is &lt;strong&gt;&lt;code&gt;gemma2:2b&lt;/code&gt;&lt;/strong&gt; via &lt;strong&gt;Ollama&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;Accounts are per-person (username + password, httpOnly session cookie) so each journal stays private. Important: the app is explicit that this is a &lt;strong&gt;supportive friend, not a clinician&lt;/strong&gt;. There’s an in-app disclaimer. Crisis-looking language takes a fixed human-help path and skips model generation.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://tangkwa-journal.byfaishal.cv/" rel="noopener noreferrer"&gt;https://tangkwa-journal.byfaishal.cv/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Judges can &lt;strong&gt;Create an account&lt;/strong&gt; on the live site, or sign in with the seed account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;username: &lt;code&gt;tangkwa&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;password: &lt;code&gt;tangkwa-journal&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Local / judge repro:&lt;/strong&gt;&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/mochfamir/tangkwa-journal
&lt;span class="nb"&gt;cd &lt;/span&gt;tangkwa-journal
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;ollama pull gemma2:2b
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
pnpm dev:api   &lt;span class="c"&gt;# http://127.0.0.1:8787&lt;/span&gt;
pnpm dev:web   &lt;span class="c"&gt;# http://127.0.0.1:5173&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Health check (must see the open-weight model):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:8787/api/health
&lt;span class="c"&gt;# {"ok":true,"model":"gemma2:2b","ollama":"up","modelPresent":true}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Companion smoke test (register → cookie → create meal entry):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; /tmp/tj.jar &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8787/api/auth/register &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"username":"judge-demo","password":"judge-demo-pass","display_name":"Judge"}'&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; /tmp/tj.jar &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8787/api/entries &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"type":"meal","body":"a cup of sweet matcha","sweetness":"sweet"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or skip register and log in as the seed user (&lt;code&gt;tangkwa&lt;/code&gt; / &lt;code&gt;tangkwa-journal&lt;/code&gt;) against &lt;code&gt;/api/auth/login&lt;/code&gt; with the same cookie jar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/mochfamir/tangkwa-journal" rel="noopener noreferrer"&gt;https://github.com/mochfamir/tangkwa-journal&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built during the challenge window (commits from 2026-10-03, after the Oct 2 UTC start).&lt;/p&gt;

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

&lt;p&gt;I shipped in deliberate layers the same Saturday afternoon (WIB), because the challenge is short and Tangkwa’s problem is real.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Brain first — open-weight companion loop
&lt;/h3&gt;

&lt;p&gt;Before pastels, I wanted proof that Gemma could talk back.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small &lt;strong&gt;Hono&lt;/strong&gt; API (&lt;code&gt;apps/api&lt;/code&gt;) on port &lt;code&gt;8787&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; (&lt;code&gt;better-sqlite3&lt;/code&gt;) for diary + meal entries, scoped per user&lt;/li&gt;
&lt;li&gt;Versioned system prompt in &lt;a href="https://github.com/mochfamir/tangkwa-journal/blob/main/prompts/companion.md" rel="noopener noreferrer"&gt;&lt;code&gt;prompts/companion.md&lt;/code&gt;&lt;/a&gt; — friend first, coach second, ~100-word cap, no shame language, no fake calorie precision&lt;/li&gt;
&lt;li&gt;Ollama &lt;code&gt;POST /api/chat&lt;/code&gt; with &lt;code&gt;gemma2:2b&lt;/code&gt; and a tight &lt;code&gt;num_predict&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Only the last &lt;strong&gt;3 one-line summaries&lt;/strong&gt; as context (keeps a 2B model coherent; avoids dumping her whole journal into the prompt)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail closed:&lt;/strong&gt; if Ollama is down → HTTP &lt;code&gt;503&lt;/code&gt;, nothing half-written is persisted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Warm replies on my machine landed in about &lt;strong&gt;1–4 seconds&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;Sample meal reply from build-time testing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Matcha's a great pick, Tangkwa! Sometimes a little sweetness helps bridge the day's fatigue. How's that energy after rounds?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Then the shell she’d actually open
&lt;/h3&gt;

&lt;p&gt;UI only — &lt;strong&gt;no API contract changes&lt;/strong&gt;, so the open-weight path stayed honest.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vite + React + TypeScript&lt;/strong&gt; PWA&lt;/li&gt;
&lt;li&gt;Soft pastel system (blush / cream / mint) with &lt;strong&gt;Fraunces + Nunito&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Screens: login/register, Today (“Hi Tangkwa”), Write diary, Log drink/meal, History, Detail with AI reply, About/disclaimer&lt;/li&gt;
&lt;li&gt;“Gemma is thinking…” loading state&lt;/li&gt;
&lt;li&gt;PWA shell via &lt;code&gt;vite-plugin-pwa&lt;/code&gt; — offline shell is fine; &lt;code&gt;/api/*&lt;/code&gt; stays network-only so a dead model never becomes a fake journal reply in the UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture in one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Phone PWA  →  Hono API (+ session auth)  →  Ollama (gemma2:2b)
                 ↓
              SQLite journal (per user)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Why &lt;code&gt;2b&lt;/code&gt;, not a bigger Gemma?
&lt;/h3&gt;

&lt;p&gt;I already had a larger Gemma locally. I still chose &lt;strong&gt;&lt;code&gt;gemma2:2b&lt;/code&gt;&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;p&gt;This isn’t a todo list. It’s &lt;strong&gt;mood + eating notes&lt;/strong&gt; from someone I care about, written on exhausted hospital days.&lt;/p&gt;

&lt;p&gt;I didn’t want that intimacy routed through a closed commercial chatbot by default.&lt;/p&gt;

&lt;p&gt;Open-weight &lt;strong&gt;Gemma&lt;/strong&gt; + open-source &lt;strong&gt;Ollama&lt;/strong&gt; means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model is a &lt;strong&gt;setting&lt;/strong&gt;, not the product’s landlord — swap weights later without rewriting around a vendor&lt;/li&gt;
&lt;li&gt;Inference can stay on infrastructure I run (laptop now; VPS/Compose for her phone later)&lt;/li&gt;
&lt;li&gt;Judges (and Tangkwa) can see the companion voice in a file in the repo, not buried in a black-box prompt dashboard&lt;/li&gt;
&lt;li&gt;A small open model is enough for short, kind check-ins — which is what she actually has time for&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Closed APIs would have been faster to prototype. They would have been the wrong default for &lt;em&gt;this&lt;/em&gt; friend’s data.&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; — generation path is only &lt;code&gt;gemma2:2b&lt;/code&gt; via Ollama; no closed-LLM fallback in the companion loop. The PWA is a thin client; Gemma is the product’s brain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tangkwa&lt;/strong&gt; — and anyone who’s too busy for calorie apps but still deserves a soft place to check in.&lt;/p&gt;

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