<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>React on hippotion</title><link>https://blog.hippotion.com/tags/react/</link><description>Recent content in React on hippotion</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 02 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.hippotion.com/tags/react/index.xml" rel="self" type="application/rss+xml"/><item><title>🫙 I Built a Tracker for My Kombucha. The Data Model Was the Hard Part.</title><link>https://blog.hippotion.com/posts/kombucha-tracker/</link><pubDate>Fri, 02 Jan 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/kombucha-tracker/</guid><description>Brewing kombucha looks simple until you try to model it: one batch splits into many flavored bottles, every jar generates a stream of pH and taste readings, and a SCOBY has a lineage. Here&amp;rsquo;s the little app I built to keep track — and why the schema, not the code, was the real work.</description><content:encoded><![CDATA[<h2 id="i-brew-kombucha">I brew kombucha</h2>
<p>If you haven&rsquo;t fallen down this hole: kombucha is sweet tea fermented by a SCOBY (a rubbery pancake of yeast and bacteria) into something tart and fizzy. It&rsquo;s a <em>living</em> hobby — the culture is alive, every batch is a little different, and the only way to get good is to pay attention and remember what you did.</p>
<p>I was not remembering what I did. Brew dates lived in my head, taste notes lived nowhere, and &ldquo;which jar was the ginger one again?&rdquo; was a genuine question I asked myself out loud, to a fridge.</p>
<p>So I built a tracker. It&rsquo;s called <strong>HipPotion</strong> — same family as everything else I run here. The brewing turned out to be the easy part. Modeling it was where it got interesting.</p>
<h2 id="why-a-simple-list-doesnt-fit">Why a simple list doesn&rsquo;t fit</h2>
<p>My first instinct was &ldquo;a batch is a row, log some notes.&rdquo; That falls apart fast, because kombucha isn&rsquo;t linear. It has two stages:</p>
<ul>
<li><strong>F1 (first ferment):</strong> the big jar of sweet tea + SCOBY, fermenting sour over a week or two. One vessel, one culture.</li>
<li><strong>F2 (second ferment):</strong> you split that sour base into bottles and flavor each one differently — ginger in this one, blackberry in that one, hibiscus in the next — then seal them to build carbonation.</li>
</ul>
<p>So <strong>one batch becomes many bottles, each with its own flavor, its own carbonation, its own outcome.</strong> A flat &ldquo;batch = row&rdquo; model can&rsquo;t express that. And on top of the branching, every jar and bottle produces a <em>stream</em> of observations over time: pH today, Brix tomorrow, &ldquo;tastes too sweet still&rdquo; the day after.</p>
<p>That&rsquo;s three different shapes at once — a lifecycle, a one-to-many split, and a time series — for what looks from the outside like &ldquo;I made some tea.&rdquo;</p>
<h2 id="the-model-i-landed-on">The model I landed on</h2>
<p>Six tables, each earning its place:</p>
<ul>
<li><strong><code>recipes</code></strong> — the templates. Tea blend, sugar ratio, target numbers. A batch points at one.</li>
<li><strong><code>batches</code></strong> — an actual F1 brew, with a lifecycle (<code>planned → active → conditioning → finished</code>) and a reference to its recipe.</li>
<li><strong><code>fermentation_log_entries</code></strong> — the time series. One row per observation per batch: pH, Brix, temperature, taste/smell notes, what I did. This is where the &ldquo;pay attention and remember&rdquo; lives.</li>
<li><strong><code>f2_variant_batches</code></strong> — the branch. Each is a flavored bottle split off a parent batch, tracked on its own.</li>
<li><strong><code>starter_log</code></strong> — SCOBY lineage. Cultures have parents; you grow new ones from old ones, and a sick culture ruins a batch, so the lineage matters.</li>
<li><strong><code>botanical_infusions</code></strong> — the flavoring ingredients, managed per recipe.</li>
</ul>
<p>The shape that took the longest to get right was the <strong>F1 → F2 split</strong>: a variant has to belong to its parent batch but live its own life. Once that relationship was clean, the whole thing clicked — the app finally matched how brewing <em>actually works</em> instead of how it&rsquo;s easy to store.</p>
<h2 id="the-stack-and-where-it-runs">The stack (and where it runs)</h2>
<p>Nothing exotic: React + Vite + TypeScript on the front (TanStack Query, shadcn/ui, Tailwind), a <a href="https://hono.dev">Hono</a> + Drizzle ORM API on the back, PostgreSQL underneath. Built with AI coding tools — I leaned on them hard for the React/shadcn front-end, less so for the schema, which I argued out by hand because it&rsquo;s the part that had to be <em>right</em>.</p>
<p>It runs on my k3s homelab like everything else: a Helm chart deploys the nginx frontend, the Hono API, and a Postgres StatefulSet, all reconciled by Argo CD from Git. Default-deny networking, secrets out of Git — the <a href="/posts/homelab-gitops/">usual platform defaults</a>. It&rsquo;s a hobby app, but it gets treated like a real one, because the platform doesn&rsquo;t know the difference and I don&rsquo;t want it to.</p>
<h2 id="it-became-an-api-for-something-else">It became an API for something else</h2>
<p>The unexpected payoff: because the data model was clean and the API was just a set of plain REST endpoints, it made a perfect target for an experiment. I later <a href="/posts/n8n-agent-cloud-vs-local/">pointed an AI agent at it from n8n</a> — &ldquo;what&rsquo;s fermenting right now?&rdquo;, &ldquo;log that this batch tastes tart&rdquo; — and the agent just called the same endpoints the UI does. A good schema is reusable in ways you don&rsquo;t plan for. The kombucha tracker quietly became a little knowledge base I can talk to.</p>
<h2 id="honest-notes">Honest notes</h2>
<p>This is a personal hobby app for an audience of one (me). It&rsquo;s AI-assisted, it has no tests, and the UI has rough edges. I&rsquo;m not pretending it&rsquo;s a product.</p>
<p>But the thing I keep coming back to: the hard, valuable part wasn&rsquo;t the framework or the deployment — it was sitting with a messy real-world process long enough to find the <em>shape</em> of it. The branching ferment, the time series, the lineage. Get the model honest and the rest is just typing. Get it wrong and no amount of nice UI saves you.</p>
<p>Also, the kombucha&rsquo;s been better since I started writing things down. Turns out the fridge wasn&rsquo;t a great database.</p>
]]></content:encoded></item></channel></rss>