<?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: Iqbal Taufiqurrochman</title>
    <description>The latest articles on DEV Community by Iqbal Taufiqurrochman (@iiqbalt).</description>
    <link>https://dev.to/iiqbalt</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%2F960754%2F7620ca66-f17a-4f26-a05f-ef3cf7d6d330.png</url>
      <title>DEV Community: Iqbal Taufiqurrochman</title>
      <link>https://dev.to/iiqbalt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iiqbalt"/>
    <language>en</language>
    <item>
      <title>Sloth Handover: three lines for standup from an open-weight model</title>
      <dc:creator>Iqbal Taufiqurrochman</dc:creator>
      <pubDate>Thu, 22 Oct 2026 21:39:17 +0000</pubDate>
      <link>https://dev.to/iiqbalt/sloth-handover-three-lines-for-standup-from-an-open-weight-model-o61</link>
      <guid>https://dev.to/iiqbalt/sloth-handover-three-lines-for-standup-from-an-open-weight-model-o61</guid>
      <description>&lt;p&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;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Sloth Handover&lt;/strong&gt; turns a messy &lt;code&gt;git log&lt;/code&gt; into the three lines you actually say at standup.&lt;/p&gt;

&lt;p&gt;Paste your commits, or press &lt;strong&gt;Use the sample repo&lt;/strong&gt;, and an open-weight model returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Done&lt;/strong&gt; — what the week actually finished&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next&lt;/strong&gt; — what the log implies is still open&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocked&lt;/strong&gt; — or "Nothing blocked", when the log says so&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minutes saved&lt;/strong&gt; — a deliberately rough estimate, clamped between 5 and 45&lt;/li&gt;
&lt;/ul&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%2Fzku6g1ytdtuyk812w9ja.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%2Fzku6g1ytdtuyk812w9ja.png" alt="Sloth Handover with a real model reply" width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No account, no database, no stored history. The log goes to the model, the reply is checked against a schema, and nothing is written down. The empty state is one screen and one textarea:&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%2Fli5y9jnln2it9lpf3b0s.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%2Fli5y9jnln2it9lpf3b0s.png" alt="The empty state" width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Live: &lt;a href="https://sloth-handover.vercel.app" rel="noopener noreferrer"&gt;https://sloth-handover.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The sample button loads a 12-commit week from a fictional project, complete with a merge, a revert and a version bump, so the whole thing runs in one click.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://gitlab.com/iqbal_taufiqurrochman/sloth-handover" rel="noopener noreferrer"&gt;https://gitlab.com/iqbal_taufiqurrochman/sloth-handover&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;li&gt;Run it locally:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://gitlab.com/iqbal_taufiqurrochman/sloth-handover.git
&lt;span class="nb"&gt;cd &lt;/span&gt;sloth-handover
nvm use
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env   &lt;span class="c"&gt;# add OPENROUTER_API_KEY&lt;/span&gt;
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To run the same prompt with no API key and no network:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma3:27b
&lt;span class="nv"&gt;AI_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ollama npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To have the server read a repository directly instead of pasting:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GIT_REPO_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/repo npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;SvelteKit 3 with Svelte 5 runes, Tailwind 4, &lt;code&gt;adapter-vercel&lt;/code&gt;, Node 22. One page, one endpoint, no database.&lt;/p&gt;

&lt;p&gt;The AI layer is a small provider interface with two implementations: OpenRouter calling &lt;code&gt;google/gemma-3-27b-it&lt;/code&gt; (open weights), and Ollama running the same model family locally. One prompt, one schema, two transports; switching is one environment variable.&lt;/p&gt;

&lt;p&gt;The prompt is strict on purpose: three lines, past tense, start with a verb, no file paths, no invented work, and "Nothing blocked" when the log is quiet. The reply must parse as JSON and pass a shape check; &lt;code&gt;minutes_saved&lt;/code&gt; is clamped so the model cannot claim it saved four hours. The endpoint sanitises the log, caps it at 8,000 characters, rate-limits eight requests per IP per ten minutes in memory, and answers 400, 429 or 502 with a sentence instead of a stack trace.&lt;/p&gt;

&lt;p&gt;I tested the plumbing with a local OpenAI-compatible stub before spending a real model call: happy path, one-character log (400), malformed JSON (400), rate limit (429). Then the real call: 12 commits in, three accurate lines out, 3.4 seconds. It ignored the merge and the version bump, noticed the revert, and correctly said nothing was blocked.&lt;/p&gt;

&lt;p&gt;Two things that bit me along the way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vite loads &lt;code&gt;.env&lt;/code&gt; into &lt;code&gt;import.meta.env&lt;/code&gt;, not &lt;code&gt;process.env&lt;/code&gt;, while the server code reads &lt;code&gt;process.env&lt;/code&gt;. I load the file in &lt;code&gt;vite.config.js&lt;/code&gt;; on Vercel the platform supplies the variables and there is no file to load.&lt;/li&gt;
&lt;li&gt;The GitLab repository already had an initial commit, so the first push was rejected. Rebased onto &lt;code&gt;origin/main&lt;/code&gt;, kept the project README, pushed again.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The model is the point, not a black box behind a paywall. Gemma 3 27B has open weights, so the same prompt can run through a hosted API for a public demo or through Ollama on a laptop with the network unplugged. A reader can check the licence, swap the provider in one file, or self-host the whole thing for free. That is the difference between an AI feature and AI you can own.&lt;/p&gt;

&lt;p&gt;It changes the privacy story too. With Ollama, the commit log never leaves the machine. With the hosted demo, commit subjects do go to OpenRouter, which is why the app stores nothing and says so plainly. I would rather state that trade-off than pretend a thin wrapper is local-first.&lt;/p&gt;

&lt;p&gt;The code is MIT, and the prompt, the schema and the failure paths are all in the repository. If the model gets a line wrong, you can see exactly what it was asked and change it.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="OpenCode"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Sloth Handover: build and deploy notes&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Build and submit the Hacktoberfest Week 3 challenge. Submission is enough, no prizes. Use SvelteKit and Vercel, OpenRouter for AI.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Picked Sloth Handover: paste a git log, and an open-weight Gemma model writes Done / Next / Blocked plus an estimate of minutes saved. No database, no accounts, no stored logs.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;SvelteKit 3 scaffold, AI provider switch, prompt, schema, endpoint, page&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;SvelteKit 3 scaffold, AI provider switch, prompt, schema, endpoint, page&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;OpenRouter default, Ollama for fully local inference, JSON validated before render&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Verified with a local OpenAI-compatible stub first: the happy path returned JSON, a one-character log and malformed JSON both returned 400. svelte-check reported zero errors and the Vercel build succeeded.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;npm run check &amp;amp;&amp;amp; npm run build&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ npm run check &amp;amp;&amp;amp; npm run build&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;0 errors and 0 warnings; adapter-vercel build finished&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Then a real call on google/gemma-3-27b-it with 12 commits took 3.4 seconds. It ignored the merge and version bump, noticed the revert, and reported nothing blocking.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;POST /api/handover with a 12-commit sample log&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ POST /api/handover with a 12-commit sample log&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;three accurate lines, 15 minutes saved, 3.4s latency&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Pushed to GitLab after rebasing onto the repository's initial commit. A Vercel deployment exists, but the runtime still needs OPENROUTER_API_KEY set on the project before the live demo can answer.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git rebase origin/main &amp;amp;&amp;amp; git push origin main&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ git rebase origin/main &amp;amp;&amp;amp; git push origin main&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;two commits on gitlab.com/iqbal_taufiqurrochman/sloth-handover&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        5 of 6 messages
          · some content was hidden by the author
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



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

&lt;p&gt;Standup is a short meeting wrapped in a long act of remembering. Here it is one paste and 3.4 seconds, and the result is short enough to read out loud. The laziness is aimed at the chore, not the work: the model summarises what the log already proves, and the minutes-saved number stays an estimate rather than a metric.&lt;/p&gt;

&lt;p&gt;If it saves one round of "wait, what did I do yesterday?", it did its job.&lt;/p&gt;

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

&lt;p&gt;No partner technology is used, so this is entered for the overall prize only.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Jejak: a field dex where every bird you spot becomes a card you can trade</title>
      <dc:creator>Iqbal Taufiqurrochman</dc:creator>
      <pubDate>Thu, 15 Oct 2026 14:16:51 +0000</pubDate>
      <link>https://dev.to/iiqbalt/jejak-a-field-dex-where-every-bird-you-spot-becomes-a-card-you-can-trade-2j2h</link>
      <guid>https://dev.to/iiqbalt/jejak-a-field-dex-where-every-bird-you-spot-becomes-a-card-you-can-trade-2j2h</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;&lt;strong&gt;Jejak&lt;/strong&gt; is a field dex. You photograph something alive, an open-weight vision model identifies it against a forkable species glossary, and the result becomes a collectible card with a rarity and a live value. Cards can be shared to a feed and sold into requests on a market board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who would pay for it: a nature guide running birding walks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is a real trade here in Indonesia. A guide in Bali or Bogor takes guests out before dawn, and the guest goes home with a camera roll and a vague memory of "a blue bird". The guide's morning is worth more if the guest leaves with an artifact: &lt;em&gt;this&lt;/em&gt; was a Jalak Bali, &lt;em&gt;this&lt;/em&gt; was a Cekakak Sungai — here is your collection, come back for the endemics. That is differentiation a guide can charge for, on a tour that already costs real money.&lt;/p&gt;

&lt;p&gt;So the buyer is the operator — an individual guide, or a small eco-lodge running a few — paying a small monthly fee per guide. Which only works if the marginal cost of a catch is near zero. That constraint is the entire reason the model is open-weight, and I will come back to it.&lt;/p&gt;

&lt;p&gt;Two segments are worth naming honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The operator&lt;/strong&gt; pays, because a takeaway their guests actually keep is cheap differentiation for a tour they already sell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hobbyist&lt;/strong&gt; does not pay for the identification, but plays: because scarcity is measured from the database, the first person to catch a species holds the expensive copy and the tenth person holds a cheap one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The problem it solves:&lt;/strong&gt; identification apps stop at the answer. You get a name, you close the app, you never open it again. There is no record, no artifact, no reason to go out again. Jejak makes the answer the &lt;em&gt;beginning&lt;/em&gt; of the thing — the card is the record, and the collection is the reason to look up tomorrow.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live:&lt;/strong&gt; [LIVE URL]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sign in with:&lt;/strong&gt; &lt;code&gt;demo&lt;/code&gt; / &lt;code&gt;jejak-demo-2026&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The demo account is pre-seeded and the market board restocks itself, so you can catch, collect and sell without a second human being online. The best first move is &lt;strong&gt;Catch&lt;/strong&gt; — upload any photo of an animal and watch the whole loop run.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://gitlab.com/iqbal_taufiqurrochman/jejak" rel="noopener noreferrer"&gt;gitlab.com/iqbal_taufiqurrochman/jejak&lt;/a&gt; — MIT licensed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env        &lt;span class="c"&gt;# Neon pooled URL, session secret, OpenRouter key&lt;/span&gt;
npm run db:push &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; SvelteKit 3 (JavaScript + JSDoc), Tailwind 4, Postgres on Neon through &lt;code&gt;@neondatabase/serverless&lt;/code&gt;, deployed on Vercel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The open-source AI at the core:&lt;/strong&gt; the default backend is &lt;code&gt;google/gemma-3-27b-it&lt;/code&gt;, an open-weight Gemma vision model served through OpenRouter. One call per catch does three jobs at once — identify the animal, ground the answer against my glossary, and write a short bilingual field note — returned as JSON and parsed tolerantly. Set &lt;code&gt;AI_PROVIDER=ollama&lt;/code&gt; and the same code runs on a laptop with &lt;code&gt;gemma3:4b&lt;/code&gt; and no network at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grounding, not vibes.&lt;/strong&gt; The model does not free-associate a species name. &lt;code&gt;data/glossary.spesies.json&lt;/code&gt; is a plain, forkable, CC-BY list of around 65 Indonesian species, and the model has to pick from it. If it returns a scientific name that is not in the list, the catch is rejected. Anyone who knows more about Indonesian fauna than I do can fix the list in a pull request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The confidence gate, which is where the game lives.&lt;/strong&gt; Below 60% confidence the model is instructed to say so, the catch fails, and the keeper walks away with nothing — or pays 50 credits to re-analyse the same photo. Every game needs a way to lose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Value is a live number, not a label:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value = base(rarity) × (1 + 2 / (1 + keepers_who_hold_this_species))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scarcity is read from the database at catch time, so catching what nobody else has is worth more, and duplicates get cheap. Other keepers post requests on the market board — &lt;em&gt;"I want a Jalak Bali, I pay 520 credits"&lt;/em&gt; — and you sell your card into that request in one transaction. The first person who catches something ends up holding the scarce copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy as a feature rather than a promise:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What people usually do&lt;/th&gt;
&lt;th&gt;What Jejak does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upload the full photo&lt;/td&gt;
&lt;td&gt;Sends a 1024px copy to the model; stores only a 320px thumbnail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strip EXIF on the server&lt;/td&gt;
&lt;td&gt;Strips it on the device, before a byte is sent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trust the client&lt;/td&gt;
&lt;td&gt;The server &lt;strong&gt;refuses&lt;/strong&gt; any image that still carries an EXIF header&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep precise GPS&lt;/td&gt;
&lt;td&gt;Rounds to two decimals, about a kilometre&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log IP addresses&lt;/td&gt;
&lt;td&gt;Stores a truncated SHA-256 of the caller, never the address&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Full-resolution photos never reach the server. The GPS read happens locally with &lt;code&gt;exifr&lt;/code&gt; and the JPEG is re-encoded through a canvas, which destroys the whole EXIF block on the phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two serverless gotchas the community wrote about, handled in code.&lt;/strong&gt; Delehq's &lt;a href="https://dev.to/delehq/why-serverless-functions-keep-exhausting-your-postgres-connections-35gj"&gt;&lt;em&gt;Why Serverless Functions Keep Exhausting Your Postgres Connections&lt;/em&gt;&lt;/a&gt; is why this uses the Neon HTTP driver and never a raw TCP pool. Omer Hochman's &lt;a href="https://dev.to/omer_hochman/your-database-scales-to-zero-your-retry-loop-doesnt-know-that-373e"&gt;&lt;em&gt;Your database scales to zero. Your retry loop doesn't know that.&lt;/em&gt;&lt;/a&gt; is why only the database stage is retried, with exponential backoff — the model call is not retried blindly, because re-running a vision call you have already paid for is how you double your bill.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The business only works because the model is open.&lt;/strong&gt; A closed, metered identification API turns every guest photo into a cost line. A small monthly subscription per guide would get eaten by usage that &lt;em&gt;grows with the guide's success&lt;/em&gt; — the better a guide does, the more they pay, and the margin inverts. Open weights on a shared endpoint make the marginal catch close to free; self-hosting Gemma on a lodge's own laptop makes it literally free after hardware. That is the difference between a nice demo and something a guide in Ubud can actually afford.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rare species should not require a third party.&lt;/strong&gt; Indonesia has the wildlife problems you would expect from a country with desirable songbirds. The photos are of animals at known locations. A closed API means that data leaves the building by design — the request &lt;em&gt;is&lt;/em&gt; the data. Here the default is a hosted open model, but flipping one environment variable keeps every photo on your own machine, and the server-side EXIF rejection means the app refuses to be the weak link either way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A local list beats a global model on local names.&lt;/strong&gt; No frontier API knows what a &lt;em&gt;Cekakak Sungai&lt;/em&gt; is in the way a guide does. Grounding against a CC-BY glossary means fixing that is an edit to a JSON file, not a fine-tuning run — and the same file can be translated or replaced wholesale for another country's fauna.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And no lock-in as a policy.&lt;/strong&gt; &lt;code&gt;OPENROUTER_MODEL&lt;/code&gt; is a string. If a better open-weight vision model ships next month, that is a configuration change, not a migration.&lt;/p&gt;

&lt;p&gt;The honest trade-off: a closed frontier model would probably identify the hard cases — small brown birds in bad dawn light — more accurately than a 27B open-weight model does. What open bought me is the cost floor that makes the side hustle arithmetic work at all, the ability to keep guest photos on hardware the operator controls, and direct ownership of the glossary and the prompt for a niche where the honest answer from a general API is too often just "bird".&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;[AGENT SESSION EMBED — to be filled before publishing]&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;[FIRST USER — to be filled before publishing]&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — the default vision model is &lt;code&gt;google/gemma-3-27b-it&lt;/code&gt;, from Google's open-weight Gemma family. It is not decoration: Gemma does the identification, the grounding against the glossary, and the bilingual field note in a single call, and the whole app is built around that one call being cheap enough to give away in a demo.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>sveltekit</category>
      <category>opensource</category>
    </item>
    <item>
      <title>ChatTodo: A Chat Box That Stops My Friend Fatik From Double-Booking Himself</title>
      <dc:creator>Iqbal Taufiqurrochman</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:29:20 +0000</pubDate>
      <link>https://dev.to/iiqbalt/chattodo-a-chat-box-that-stops-my-friend-fatik-from-double-booking-himself-jjo</link>
      <guid>https://dev.to/iiqbalt/chattodo-a-chat-box-that-stops-my-friend-fatik-from-double-booking-himself-jjo</guid>
      <description>&lt;p&gt;&lt;em&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;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;ChatTodo&lt;/strong&gt; is a chat-first todo app for my friend &lt;strong&gt;Fatik&lt;/strong&gt;, who meets a lot of people and keeps making appointments — client meetings, workouts, catch-ups. His problem is simple: he forgets. And when he forgets, his appointments collide — a gym session he just promised lands on top of a meeting he already made.&lt;/p&gt;

&lt;p&gt;He's also not going to adopt a heavyweight task manager. He'd rather type a message than fight a due-date picker. So I didn't build him another app with forms. I built him &lt;strong&gt;a chat box&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;He types the way he talks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"besok jam 9 rapat Budi, jam setengah 10 olahraga di gym"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ChatTodo turns that into two dated todos, checks them against everything already on his plate, and warns him on the spot:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bentrok jadwal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Rapat sama Budi" (Sen, 5 Okt, 09.00) vs "Olahraga di gym" (Sen, 5 Okt, 09.30)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Under the chat there's a simple dashboard: how many are pending, which are overdue, what's coming in the next seven days — enough for Fatik to glance at in the morning and close the tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem it solves for Fatik:&lt;/strong&gt; he's the person who promises a friend a workout session and then realizes it clashes with a meeting he already agreed to. He told me he needs something to watch his appointments for him, but it has to be effortless — one box, one send button, no app to "keep up with".&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live:&lt;/strong&gt; [DEPLOY URL]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video demo:&lt;/strong&gt; [RECORDING LINK]&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://gitlab.com/iqbal_taufiqurrochman/chat-todo" rel="noopener noreferrer"&gt;gitlab.com/iqbal_taufiqurrochman/chat-todo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setup is three commands: &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;cp .env.example .env&lt;/code&gt; (plus your Neon connection string), &lt;code&gt;npm run db:push&lt;/code&gt;. The README walks through every environment variable.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; SvelteKit 3 + Svelte 5 (runes), Tailwind CSS 4, Neon Postgres via Drizzle ORM, deployed on Vercel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The open-source AI at its core:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open-weight model via OpenRouter&lt;/strong&gt; — the default is &lt;code&gt;google/gemma-3-27b-it&lt;/code&gt; (Gemma is Google's open-weight family). Every chat message goes through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI-compatible client layer&lt;/strong&gt; — a thin abstraction (&lt;code&gt;src/lib/ai/client.ts&lt;/code&gt;) where &lt;code&gt;baseURL&lt;/code&gt; and model are just two settings. Point it at OpenRouter's hosted open-weight model, or at a local &lt;strong&gt;Ollama&lt;/strong&gt; server, without touching a single line of application code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured extraction, not vibes&lt;/strong&gt; — each message runs in JSON mode against a &lt;strong&gt;zod schema&lt;/strong&gt; (&lt;code&gt;{ type: "create" | "list" | "complete" | "delete" | "chat", ... }&lt;/code&gt;). The model never writes to the database directly; it proposes a typed action, my server validates it, and only then does it execute. Bad JSON = rejected = safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How a message flows:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The message lands in &lt;code&gt;/api/chat&lt;/code&gt; and is persisted to the &lt;code&gt;messages&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;The last 10 messages + the current todo list are sent to the model (so "selesai yang rapat" finds the right todo)&lt;/li&gt;
&lt;li&gt;The model returns one structured action; zod validates it; the server executes it&lt;/li&gt;
&lt;li&gt;The confirmation streams back to the chat UI over SSE, and the dashboard updates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conflict detection is mine, not the model's.&lt;/strong&gt; When a &lt;code&gt;create&lt;/code&gt; action lands, the server compares each new due time against every pending todo and flags anything within 60 minutes as &lt;code&gt;Bentrok jadwal&lt;/code&gt;. The model proposes; the server double-checks — so a hallucinating model can't quietly hide Fatik's next double-booking. I tested it by pointing the app at a fake OpenAI-compatible server returning back-to-back appointments: new-vs-new, new-vs-existing, and exact-duplicate times all get caught, while unrelated dates stay quiet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything that isn't the model is boring on purpose:&lt;/strong&gt; a &lt;code&gt;todos&lt;/code&gt; table, a &lt;code&gt;messages&lt;/code&gt; table, and a &lt;code&gt;settings&lt;/code&gt; key-value table. No agent framework, no vector database — the schema fits on an index card, which is exactly how Fatik's mental model of his own appointments works.&lt;/p&gt;

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

&lt;p&gt;Three things made this project possible &lt;em&gt;because&lt;/em&gt; the core is open:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;His data never has to land on a server he doesn't control.&lt;/strong&gt; The whole app can run against Ollama on a laptop: set &lt;code&gt;ai.base_url&lt;/code&gt; to &lt;code&gt;http://localhost:11434/v1&lt;/code&gt; on the settings page, and the same app now speaks to a model that never touches the internet. A closed API can't offer that — the request &lt;em&gt;is&lt;/em&gt; the data, and it has to leave the building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I can swap the model as a configuration change, not a rewrite.&lt;/strong&gt; Because the open-weight model speaks the OpenAI-compatible dialect, changing &lt;code&gt;ai.base_url&lt;/code&gt; + &lt;code&gt;ai.model&lt;/code&gt; on the settings page (no redeploy) moves the app between OpenRouter's hosted Gemma and a local Ollama model. Try that with a closed provider's SDK — you're refactoring imports, not editing a text field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run for him.&lt;/strong&gt; An open-weight model on a free-tier local runtime means his "second brain" has no subscription that can quietly lapse and take his appointments with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The honest trade-off: a closed model would probably parse messier sentences on the first try. What open bought me was control over exactly the things that mattered for &lt;em&gt;this&lt;/em&gt; user — his privacy, his budget, and the freedom to tune the extraction prompt (&lt;code&gt;ai.extraction_prompt&lt;/code&gt;, editable from the settings page) until it understood how &lt;em&gt;he&lt;/em&gt; actually talks, without shipping a new build for every experiment.&lt;/p&gt;

&lt;p&gt;[HANDOVER: what Fatik said after you gave it to him — the challenge gives bonus points for actually handing it over.]&lt;/p&gt;

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

&lt;p&gt;Overall prize only — this project doesn't use any partner technology.&lt;/p&gt;

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