<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Development on NateBent.com</title>
    <link>https://natebent.com/categories/development/</link>
    <description>Recent content in Development on NateBent.com</description>
    <generator>Hugo</generator>
    <language>en</language>
    <managingEditor> (Nathan Bent)</managingEditor>
    <atom:link href="https://natebent.com/categories/development/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Zero to App with AI, Part 6: The App Taught Us What to Build</title>
      <link>https://natebent.com/posts/zero-to-app-with-ai-part-6-the-app-taught-us-what-to-build/</link>
      <pubDate>Sun, 19 Jul 2026 01:00:00 +0000</pubDate><author>Nathan Bent</author>
      <guid>https://natebent.com/posts/zero-to-app-with-ai-part-6-the-app-taught-us-what-to-build/</guid>
      <description>&lt;p&gt;Everything up to here was built from imagination: what I thought logging a workout should feel like. Then I trained with it for real, phone in hand, chalk on fingers, rest timer running, and the roadmap got humbled. This part is about the highest-signal planning input the project ever had: actual use.&lt;/p&gt;
&lt;h2 id=&#34;the-friction-list&#34;&gt;The friction list&lt;/h2&gt;
&lt;p&gt;After the first real gym sessions I dumped every annoyance into a list. Nine items, all of them things no amount of desk-testing had surfaced, because they only exist under gym conditions: tired, hurried, one-thumbed. The highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set grouping. A wall of individual sets is unreadable between exercises; sets want to cluster by exercise visually.&lt;/li&gt;
&lt;li&gt;Post-hoc editing. Fat-fingered 135 as 125? There was no way to fix a set after saving. At a desk you&amp;rsquo;d never notice. At the gym it&amp;rsquo;s constant.&lt;/li&gt;
&lt;li&gt;Smart defaults. The app made me re-enter the weight I just used. The previous set is almost always the best guess for the next.&lt;/li&gt;
&lt;li&gt;Sort order, collapsible sections, calmer check-ins. The start-of-workout screen had grown enthusiastic; under time pressure you want it quiet and fast.&lt;/li&gt;
&lt;li&gt;A Manage Plans page and plan archiving. Plans accumulate, and the picker shouldn&amp;rsquo;t scroll forever past experiments you&amp;rsquo;re done with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these is clever. That&amp;rsquo;s the point. They&amp;rsquo;re obvious in use and invisible in theory.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-tip&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Tip&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Write the friction list in the moment or right after. These observations evaporate within hours, and once they&amp;rsquo;re gone you&amp;rsquo;re back to designing from imagination.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;h2 id=&#34;steal-like-a-product-manager&#34;&gt;Steal like a product manager&lt;/h2&gt;
&lt;p&gt;Before turning the list into work, one session did something teams skip: surveyed the incumbents. Strong, Hevy, FitNotes, Jefit. What do apps with millions of gym-hours of accumulated feedback all agree on? The survey confirmed the friction list from the outside: auto-fill defaults from the previous set are table stakes everywhere, rest timers matter, set editing is universal.&lt;/p&gt;
&lt;p&gt;When your dogfooding complaints and the market&amp;rsquo;s converged solutions agree, you can build without second-guessing. Where they diverge is interesting too: you either have a differentiator or a blind spot, and it&amp;rsquo;s worth a minute deciding which.&lt;/p&gt;
&lt;h2 id=&#34;an-ideation-phase-with-no-code&#34;&gt;An ideation phase with no code&lt;/h2&gt;
&lt;p&gt;The friction list and the survey merged into a formal roadmap phase whose entire output was a document: a rated backlog, one line per idea, rated easy, medium, or hard, with just enough context to build from later, committed to the repo as &lt;code&gt;docs/FEATURE-IDEAS.md&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then the roadmap grew three implementation phases sorted by that rating: quick wins (the easy column, knocked out in one session), medium features, and the one genuinely hard interaction, supersets, which got a phase to itself rather than being wedged into a grab-bag.&lt;/p&gt;
&lt;p&gt;This sounds like bureaucracy for a hobby app. It&amp;rsquo;s the opposite. It&amp;rsquo;s what let an AI-paced project stay aimed. When building costs hours instead of weeks, the scarce resource is knowing what&amp;rsquo;s worth building. A rated backlog in the repo means every future session picks from a priority queue instead of improvising, and, per the docs-as-memory principle from &lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-1-write-the-docs-before-the-code/&#34;&gt;Part 1&lt;/a&gt;, it lives where the agent can actually see it: not in my head, not in chat history, not in a notes app.&lt;/p&gt;
&lt;h2 id=&#34;the-check-in-lesson-features-must-earn-their-place-in-the-flow&#34;&gt;The check-in lesson: features must earn their place in the flow&lt;/h2&gt;
&lt;p&gt;An earlier phase had added daily check-ins (sleep, mood) as cards when you start a workout, designed skippable from day one, on the principle that nothing stands between you and logging a set. Real use upgraded that principle from design guideline to law: even optional cards read as noise at 6 AM, which is why &amp;ldquo;calmer check-ins&amp;rdquo; made the friction list. The dopamine feature (&amp;ldquo;New PR!&amp;rdquo; celebrations) survived contact with reality. The earnest wellness features had to learn to whisper.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-insight&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M8 0 L9.6 6.4 L16 8 L9.6 9.6 L8 16 L6.4 9.6 L0 8 L6.4 6.4 Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Interesting!&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;The flow you optimize is the one you&amp;rsquo;re in when you&amp;rsquo;re weakest: tired, rushed, low-patience. Design reviews happen at desks by people at their sharpest. Your users, even when that&amp;rsquo;s just you, are neither.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;h2 id=&#34;what-id-tell-you&#34;&gt;What I&amp;rsquo;d tell you&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Ship to yourself early, then actually use it. The milestone &amp;ldquo;log a real workout from my phone&amp;rdquo; was in the roadmap from day zero, and everything after it was better-aimed than everything before it.&lt;/li&gt;
&lt;li&gt;Write the friction list immediately, in the moment or right after.&lt;/li&gt;
&lt;li&gt;Survey the incumbents once, deliberately. Where your complaints and their solutions agree, build with confidence. Where they diverge, you might have a differentiator, or a blind spot.&lt;/li&gt;
&lt;li&gt;Make ideation a phase with a committed artifact. A rated backlog in the repo beats ideas in your head, in your chat history, or in a notes app the agent can&amp;rsquo;t see.&lt;/li&gt;
&lt;li&gt;Rate by effort and let the easy column fund momentum. A session of quick wins after a heavy phase keeps the project fun, and fun is the actual fuel of side projects.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Part 7: the app grows up. Real multi-user auth, the migration that touched every table, and how &amp;ldquo;workout-tracker&amp;rdquo; became RepRepo.&lt;/p&gt;
&lt;h2 id=&#34;references-and-further-reading&#34;&gt;References and further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://code.claude.com/docs/en/sub-agents&#34;&gt;Create custom subagents&lt;/a&gt; and &lt;a href=&#34;https://code.claude.com/docs/en/memory&#34;&gt;How Claude remembers your project&lt;/a&gt;, Claude Code docs, if you&amp;rsquo;re wiring the ideation-phase pattern into your own repo: the backlog doc only works because the agent reads the repo.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>Zero to App with AI, Part 5: Scaling Me, Not the AI</title>
      <link>https://natebent.com/posts/zero-to-app-with-ai-part-5-scaling-me-not-the-ai/</link>
      <pubDate>Fri, 17 Jul 2026 01:00:00 +0000</pubDate><author>Nathan Bent</author>
      <guid>https://natebent.com/posts/zero-to-app-with-ai-part-5-scaling-me-not-the-ai/</guid>
      <description>&lt;p&gt;Around the middle of the project something flipped: the agent was no longer the bottleneck. I was. One session built a feature faster than I could specify the next one. &lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-2-keeping-it-cheap/&#34;&gt;Part 2&lt;/a&gt; warned that multiplying agents multiplies cost. This part is about the places where parallelism and delegation genuinely paid, and the structures that kept it from becoming chaos.&lt;/p&gt;
&lt;h2 id=&#34;parallel-phases-in-worktrees&#34;&gt;Parallel phases in worktrees&lt;/h2&gt;
&lt;p&gt;The roadmap had independent phases queued up. Daily check-ins and a settings/themes page touched almost disjoint code. So they ran as separate sessions, simultaneously, each in its own git worktree, launched from pre-written, self-contained prompts.&lt;/p&gt;
&lt;p&gt;If you haven&amp;rsquo;t used worktrees: &lt;code&gt;git worktree add ../feature-x&lt;/code&gt; gives you a second working directory checked out to its own branch, backed by the same repository. No second clone, no stash juggling, and each parallel session gets a filesystem of its own to make a mess in. It is the difference between two people working on two copies of a document and two people fighting over one keyboard.&lt;/p&gt;
&lt;p&gt;The mechanics that made this safe:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Independence first. Phases were picked for minimal overlap. The one predictable collision (both touching the main stylesheet) was flagged in advance as &amp;ldquo;expect a small merge conflict; resolve at merge time.&amp;rdquo; It was, and it was.&lt;/li&gt;
&lt;li&gt;Self-contained prompts. Each parallel session got the full brief: read these docs, build this phase, end with &lt;code&gt;/ship&lt;/code&gt;. No session depended on chat history from another.&lt;/li&gt;
&lt;li&gt;The repo as the only communication channel.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&#34;callout callout-insight&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M8 0 L9.6 6.4 L16 8 L9.6 9.6 L8 16 L6.4 9.6 L0 8 L6.4 6.4 Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Interesting!&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Parallel sessions coordinate through commits and docs, never through shared conversational state. Which is exactly how human teams that work, work. If two sessions need to talk to each other, the thing they need to say belongs in a doc anyway.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;h2 id=&#34;sub-agents-with-job-descriptions&#34;&gt;Sub-agents with job descriptions&lt;/h2&gt;
&lt;p&gt;Claude Code lets you define sub-agents as little markdown files: a description of when to use them, which tools they may touch, which model they run on, and a system prompt as the body. Over the project these accumulated into a small staff:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;code-reviewer: read-only tools (it literally cannot edit files, reviews report, they don&amp;rsquo;t meddle), pinned to the big model, briefed on this project&amp;rsquo;s specific bug habitat: PR-math edge cases, NULL weights, business logic leaking into the client.&lt;/li&gt;
&lt;li&gt;docs-keeper: pinned to the small cheap model, allowed to edit, briefed to be terse and never rewrite history in the decision log.&lt;/li&gt;
&lt;li&gt;And later, per-part specialists, which is where it gets interesting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The job-description framing is the useful part. A sub-agent file answers &amp;ldquo;when should you call me, what am I allowed to do, and what do I cost,&amp;rdquo; which is the same information you&amp;rsquo;d want about a contractor. A clever one-off prompt answers none of those and is gone tomorrow.&lt;/p&gt;
&lt;h2 id=&#34;sub-projects-a-plan-doc-plus-a-team&#34;&gt;Sub-projects: a plan doc plus a team&lt;/h2&gt;
&lt;p&gt;The best structure of the whole project emerged for a bigger feature (&amp;ldquo;exercise intelligence&amp;rdquo;: sectioned plans, a metadata-rich exercise database, a drag-and-drop builder, markdown import/export). Too big for a phase, it became a sub-project: one plan document splitting the work into parts A through D, and, this is the part I love, each part got its own scoped sub-agent defined in the repo: a schema-builder, an exercise-enricher, a UI-builder, a porter.&lt;/p&gt;
&lt;p&gt;Each part then ran as its own session with its own specialist, against a shared written plan. The plan doc did for the sub-project what CLAUDE.md does for the repo: carried the intent, so no session had to be told twice. Part A (schema plus migration) shipped with a boot-time, idempotent migration that correctly upgraded the live server&amp;rsquo;s real database, old plans got a default section, exercises re-pointed, which I verified against production data before Part B started.&lt;/p&gt;
&lt;h2 id=&#34;what-stayed-human&#34;&gt;What stayed human&lt;/h2&gt;
&lt;p&gt;With all this delegation, the striking thing is what couldn&amp;rsquo;t be delegated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sequencing decisions. Multi-user was deliberately ordered after the fun features. It touches every table, so doing it last let one migration sweep everything (Part 7). An agent proposed the reasoning; choosing it was mine.&lt;/li&gt;
&lt;li&gt;The test between phases. Every parallel stream still funneled through me using the app before the next phase started (&lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-3-the-phase-rhythm/&#34;&gt;Part 3&lt;/a&gt;&amp;rsquo;s rule).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&#34;callout callout-warning&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Warning&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Parallel building is safe. Parallel unreviewed shipping is how compound misunderstandings happen. The human reviewing at the merge seams is the thing that makes all of this safe to do at all; parallelize the work, never the judgment.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;Anything requiring trust. Sudo passwords, firewall decisions, what goes on the public internet. The agent scripts around these boundaries respectfully; it doesn&amp;rsquo;t cross them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I looked briefly at the always-on assistant frameworks (the OpenClaw genre) for &amp;ldquo;have the AI run the whole pipeline itself.&amp;rdquo; Wrong tool for this job: those shine as persistent personal assistants across chat apps. For repo work, the native primitives, slash commands, sub-agents, parallel sessions, and worktrees, turned out to be all the orchestration a one-person project needs, with no extra moving parts to maintain.&lt;/p&gt;
&lt;h2 id=&#34;what-id-tell-you&#34;&gt;What I&amp;rsquo;d tell you&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Parallelize phases, not opinions. Two sessions building disjoint features: great. Two sessions with overlapping judgment calls: merge hell with extra steps.&lt;/li&gt;
&lt;li&gt;Worktrees are the safety rail. Each parallel session gets its own checkout; the merge is explicit, reviewable, and yours.&lt;/li&gt;
&lt;li&gt;Write job descriptions, not prompts. A sub-agent file that says when to use me, what I may touch, and what model I run on is reusable forever. A clever one-off prompt is gone tomorrow.&lt;/li&gt;
&lt;li&gt;Big features want a plan doc and a team of specialists. The sub-project pattern (plan plus part-scoped agents plus one session per part) scales shockingly well.&lt;/li&gt;
&lt;li&gt;Keep yourself in the merge. Scale your attention, don&amp;rsquo;t remove it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-6-the-app-taught-us-what-to-build/&#34;&gt;Part 6&lt;/a&gt; changes register entirely: what happened when I actually took the app to the gym, and how nine annoyances between sets became the most productive planning document in the repo.&lt;/p&gt;
&lt;h2 id=&#34;references-and-further-reading&#34;&gt;References and further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://git-scm.com/docs/git-worktree&#34;&gt;git-worktree&lt;/a&gt;, git-scm.com. The full mechanics of linked worktrees.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://code.claude.com/docs/en/sub-agents&#34;&gt;Create custom subagents&lt;/a&gt;, Claude Code docs. The frontmatter format, tool allowlists, and delegation behavior.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://code.claude.com/docs/en/model-config&#34;&gt;Model configuration&lt;/a&gt;, Claude Code docs, for pinning models per sub-agent.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>Zero to App with AI, Part 4: Shipping Is Still Sysadmin</title>
      <link>https://natebent.com/posts/zero-to-app-with-ai-part-4-shipping-is-still-sysadmin/</link>
      <pubDate>Wed, 15 Jul 2026 01:00:00 +0000</pubDate><author>Nathan Bent</author>
      <guid>https://natebent.com/posts/zero-to-app-with-ai-part-4-shipping-is-still-sysadmin/</guid>
      <description>&lt;p&gt;By the end of &lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-3-the-phase-rhythm/&#34;&gt;Part 3&lt;/a&gt; the app worked great, on my desk. This is the part where it had to run somewhere else, and where &amp;ldquo;AI writes the code&amp;rdquo; met &amp;ldquo;computers are still computers.&amp;rdquo; Spoiler: the app-side work was almost eerily smooth, and every single problem lived in the last mile of Linux plumbing. As the guy whose day job is networks, I found this both humbling and deeply familiar.&lt;/p&gt;
&lt;h2 id=&#34;the-app-side-deliberately-boring&#34;&gt;The app side: deliberately boring&lt;/h2&gt;
&lt;p&gt;Auth started as the simplest thing that genuinely locks a door: one password in an environment variable, an HMAC-signed httpOnly cookie, zero new dependencies, about 70 readable lines. Unset the variable and auth is off, so local dev is unchanged. Change the password and every device is logged out, which is the feature, not a bug. The decision log records why not the alternatives: proxy-level basic auth couples the app to the proxy and looks awful on a phone, and JWT libraries are dependency weight with no benefit at one user. It later grew into real per-user auth with bcrypt (Part 7), and the upgrade path was clean precisely because v1 was small.&lt;/p&gt;
&lt;p&gt;Production shape: one process, one port. Express serves the API and the built client, with an SPA fallback. A &lt;code&gt;/api/export&lt;/code&gt; endpoint dumps all your data as JSON. Never build a personal-data app without an exit door.&lt;/p&gt;
&lt;p&gt;Backups use SQLite&amp;rsquo;s online backup API, stamped daily, pruned to fourteen, run by cron.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-caution&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.141.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Caution&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;A naive file copy of a live SQLite database can catch it mid-write, especially in WAL mode where recent transactions live in a separate -wal file next to the main one. The copy looks fine, restores fine, and is quietly missing or corrupting data. The online backup API exists specifically to take a consistent copy while the database is in use.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;p&gt;The full local smoke test, no cookie 401s, wrong password rejected, login sets the cookie, export works, static app serves, SPA fallback falls back, passed 8-for-8 before anything left my machine.&lt;/p&gt;
&lt;h2 id=&#34;the-vm-side-five-sharp-edges-in-one-afternoon&#34;&gt;The VM side: five sharp edges in one afternoon&lt;/h2&gt;
&lt;p&gt;Deploying to a fresh Ubuntu VM in the homelab produced, in order:&lt;/p&gt;
&lt;h3 id=&#34;the-invisible-byte&#34;&gt;The invisible byte&lt;/h3&gt;
&lt;p&gt;First deploy script, piped over SSH: &lt;code&gt;bash: line 1: #!/usr/bin/env: No such file or directory&lt;/code&gt;. The script had a UTF-8 BOM, three invisible bytes (&lt;code&gt;EF BB BF&lt;/code&gt;) that Windows tooling loves to prepend, and bash tried to execute &lt;code&gt;\xEF\xBB\xBF#!/usr/bin/env&lt;/code&gt; as a command. The shebang line was fine. The file just didn&amp;rsquo;t start with it.&lt;/p&gt;
&lt;h3 id=&#34;the-700-house&#34;&gt;The 700 house&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;useradd -m&lt;/code&gt; created the app user&amp;rsquo;s home directory with owner-only permissions, so the deploying user couldn&amp;rsquo;t &lt;code&gt;cd&lt;/code&gt; into the directory it had just created. One &lt;code&gt;chmod 755&lt;/code&gt; later, the whole install script ran clean.&lt;/p&gt;
&lt;h3 id=&#34;the-firewall-that-was-doing-its-job&#34;&gt;The firewall that was doing its job&lt;/h3&gt;
&lt;p&gt;App live on the VM, health check green on the VM, and total silence from my desk. UFW default-deny with only SSH allowed. This is the one I should have predicted before the agent did, given what it says on my resume. The fix wasn&amp;rsquo;t &amp;ldquo;open everything&amp;rdquo;; it was one rule allowing the app port from private-network addresses only.&lt;/p&gt;
&lt;h3 id=&#34;the-sudoers-ordering-trap&#34;&gt;The sudoers ordering trap&lt;/h3&gt;
&lt;p&gt;For unattended deploys I dropped a NOPASSWD rule into &lt;code&gt;/etc/sudoers.d/99-deploy&lt;/code&gt;, and sudo kept prompting anyway.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-insight&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M8 0 L9.6 6.4 L16 8 L9.6 9.6 L8 16 L6.4 9.6 L0 8 L6.4 6.4 Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Interesting!&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Sudoers drop-ins load alphabetically and the last matching rule wins. A NOPASSWD rule early in the alphabet is silently overridden by any later file that matches the same user. My &lt;code&gt;99-deploy&lt;/code&gt; lost to an existing file further down the list; renaming mine &lt;code&gt;zzz-&lt;/code&gt; fixed it.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;p&gt;Bonus discovery: this VM&amp;rsquo;s Ubuntu ships sudo-rs, the Rust rewrite of sudo that became the default in Ubuntu 25.10, and its error messages are just different enough from classic sudo to gaslight you mid-debug.&lt;/p&gt;
&lt;h3 id=&#34;powershells-crlf-ambush&#34;&gt;PowerShell&amp;rsquo;s CRLF ambush&lt;/h3&gt;
&lt;p&gt;The polished deploy pipeline piped the update script to the VM through PowerShell, which helpfully rejoins piped lines with Windows line endings. Bash met &lt;code&gt;$&#39;\r&#39;&lt;/code&gt; and quit.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-tip&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Tip&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Fix the instance, then make the class impossible: stop piping (scp the file, strip &lt;code&gt;\r&lt;/code&gt; on the far side) and add a &lt;code&gt;.gitattributes&lt;/code&gt; pinning &lt;code&gt;*.sh&lt;/code&gt; to LF forever, so a Windows checkout can never re-poison the scripts. Any time an OS boundary bites you, look for the one-line config that removes the whole category.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;p&gt;None of these are AI problems or AI solutions, but debugging them with an agent was genuinely faster: it read the error, named the BOM, knew the sudoers ordering rule, wrote the &lt;code&gt;.gitattributes&lt;/code&gt;. What it couldn&amp;rsquo;t do was hold my sudo password, by design. The privileged bootstrap steps stayed human. Then a deliberately narrow permanent rule (run commands as the app user plus restart this one service, nothing else) let everything after be unattended.&lt;/p&gt;
&lt;h2 id=&#34;ship-the-phase-ending-word&#34;&gt;/ship: the phase-ending word&lt;/h2&gt;
&lt;p&gt;All of it condensed into a slash command. Saying &lt;code&gt;/ship&lt;/code&gt; at the end of any session now runs the checklist: update the docs (roadmap ticks, decision log entries), commit and push in logical chunks, deploy committed HEAD to the test server, verify from the outside, report. The deploy script runs the test suite first and refuses to ship red. It archives committed HEAD only. Uncommitted work deliberately doesn&amp;rsquo;t deploy, which quietly enforces the commit discipline I kept flunking in &lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-3-the-phase-rhythm/&#34;&gt;Part 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The target is a parameter, so &amp;ldquo;add a second test server&amp;rdquo; is an argument, not a new script. And because it&amp;rsquo;s a checklist in a file rather than a habit in a head, every future session, mine or the agent&amp;rsquo;s, ends phases exactly the same way.&lt;/p&gt;
&lt;h2 id=&#34;what-id-tell-you&#34;&gt;What I&amp;rsquo;d tell you&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Make v1 auth small enough to read in one sitting. You can grow it later. You can&amp;rsquo;t easily shrink a framework.&lt;/li&gt;
&lt;li&gt;One process, one port, one SQLite file is a gloriously deployable shape for a personal app. Resist the urge to be web-scale.&lt;/li&gt;
&lt;li&gt;Expect the last mile to be sysadmin, and let the AI pair on it. It has read more man pages than you have.&lt;/li&gt;
&lt;li&gt;Keep privileged access narrow and boring. Broad temporary sudo for bootstrap, then a scoped permanent rule for the pipeline.&lt;/li&gt;
&lt;li&gt;Turn your deploy into one word. If shipping takes a checklist you must remember, you&amp;rsquo;ll skip steps. If it&amp;rsquo;s a command, you won&amp;rsquo;t.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-5-scaling-me-not-the-ai/&#34;&gt;Part 5&lt;/a&gt;: the multiplier. Running phases in parallel sessions, scoped sub-agents with their own job descriptions, and where &amp;ldquo;more agents&amp;rdquo; actually earned its cost.&lt;/p&gt;
&lt;h2 id=&#34;references-and-further-reading&#34;&gt;References and further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://sqlite.org/c3ref/backup.html&#34;&gt;Using the SQLite Online Backup API&lt;/a&gt;, sqlite.org. Why a live database wants this instead of &lt;code&gt;cp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://discourse.ubuntu.com/t/adopting-sudo-rs-by-default-in-ubuntu-25-10/60583&#34;&gt;Adopting sudo-rs by default in Ubuntu 25.10&lt;/a&gt;, Ubuntu Discourse announcement.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>Zero to App with AI, Part 3: The Phase Rhythm</title>
      <link>https://natebent.com/posts/zero-to-app-with-ai-part-3-the-phase-rhythm/</link>
      <pubDate>Mon, 13 Jul 2026 01:00:00 +0000</pubDate><author>Nathan Bent</author>
      <guid>https://natebent.com/posts/zero-to-app-with-ai-part-3-the-phase-rhythm/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-1-write-the-docs-before-the-code/&#34;&gt;Part 1&lt;/a&gt; and &lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-2-keeping-it-cheap/&#34;&gt;Part 2&lt;/a&gt; covered the setup: docs as memory, cost controls in config. This is the part where the app actually got built, a core logging loop, a plans system, and a PR detection engine, and where the working rhythm emerged that carried the whole project:&lt;/p&gt;
&lt;p&gt;One phase per session. Review at every phase boundary. The human tests between phases.&lt;/p&gt;
&lt;p&gt;That is the entire method. The rest of this post is what it looks like in practice and what it caught.&lt;/p&gt;
&lt;h2 id=&#34;the-loop&#34;&gt;The loop&lt;/h2&gt;
&lt;p&gt;Each phase ran the same way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Paste the kickoff prompt (pre-written at the end of the previous phase) into a fresh session.&lt;/li&gt;
&lt;li&gt;The agent builds, explaining non-obvious choices as it goes. That is a CLAUDE.md rule, because I am learning web dev here, not just extracting code from a machine.&lt;/li&gt;
&lt;li&gt;At the boundary, a review pass: read the diff, run everything, poke the edges.&lt;/li&gt;
&lt;li&gt;I use the result for real, note what is off, and feed that into the next phase.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The phases were deliberately small. Phase 2 was &amp;ldquo;pick exercise, enter weight and reps, save set, see today&amp;rsquo;s session.&amp;rdquo; Phase 3 added workout plans, so &amp;ldquo;start Push Day&amp;rdquo; pre-populates the session. Phase 4 was the dopamine feature: PR detection with a &amp;ldquo;New PR!&amp;rdquo; celebration, and a progress chart per exercise. Each one fit comfortably in a single session, which Part 2 explained is also where the money is.&lt;/p&gt;
&lt;h2 id=&#34;tests-first-but-only-where-bugs-hide&#34;&gt;Tests first, but only where bugs hide&lt;/h2&gt;
&lt;p&gt;We did not test everything. We tested the one module most likely to have subtle bugs, a rule written into CLAUDE.md on day zero: the PR math. Weight PRs, rep PRs at a given weight, estimated 1RM (the Epley formula, whose accuracy falls off as reps climb, so the app&amp;rsquo;s 12 rep cutoff is part of the spec, not an implementation detail), and session volume records.&lt;/p&gt;
&lt;p&gt;The tests were written against the rules in DATA-MODEL.md before the route existed, each one running against a fresh in-memory SQLite database. And they immediately forced real decisions the prose spec had glossed over. Does a bodyweight set (NULL weight) break a weight PR? No, it is its own &amp;ldquo;reps at bodyweight&amp;rdquo; bucket. Does the first set you ever log count as a PR? No, &amp;ldquo;broken&amp;rdquo; implies something existed to break. Does a heavy warm-up set the bar? Never. Warm-ups are excluded, and there is a test proving a 500 lb warm-up can&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Every one of those edge cases would have been a quiet production bug. Instead they are one-line test names.&lt;/p&gt;
&lt;h2 id=&#34;what-review-actually-caught&#34;&gt;What review actually caught&lt;/h2&gt;
&lt;p&gt;The end-of-phase reviews earned their keep with real finds, not style nits. Highlights, in ascending order of how much they would have annoyed me in production:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dormant features.&lt;/strong&gt; Review noticed the warm-up exclusion logic was beautifully tested and completely unreachable. The UI had no warm-up checkbox yet, so the flag was always false.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-insight&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M8 0 L9.6 6.4 L16 8 L9.6 9.6 L8 16 L6.4 9.6 L0 8 L6.4 6.4 Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Interesting!&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Nothing about &amp;ldquo;all tests green&amp;rdquo; tells you a feature can&amp;rsquo;t be triggered. Tests prove the logic is correct if reached. Only tracing the path from the UI proves it is reachable.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;The over-celebrating PR banner.&lt;/strong&gt; Session volume PRs originally fired on every set after your session total passed the old record, so on a good day, sets five through ten would each yell &amp;ldquo;New PR!&amp;rdquo; at you. Celebration inflation. The fix: only fire on the set that first crosses the record, with a regression test so it stays fixed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The evening-workout timezone bug.&lt;/strong&gt; The client resumed &amp;ldquo;today&amp;rsquo;s session&amp;rdquo; by comparing a UTC timestamp&amp;rsquo;s date prefix to the local calendar date. Which works fine, until you train after 7 PM in a UTC-minus timezone, when the UTC date rolls over and a page refresh mid-workout would silently create a duplicate session instead of resuming.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-caution&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.141.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Caution&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;This is the classic shape of a timezone bug: no error, no crash, just quietly wrong data, and it only reproduces in the evening, so it survives every daytime test you run. The fix (compare &lt;code&gt;Date&lt;/code&gt; objects, not string prefixes) landed with a comment explaining the trap for whoever touches that code next.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;h2 id=&#34;the-mistake-i-kept-making&#34;&gt;The mistake I kept making&lt;/h2&gt;
&lt;p&gt;Every phase, the same finding at review time: the work wasn&amp;rsquo;t committed. At one point two entire phases, around eight hundred lines of the best code in the project, existed only in the working tree, one bad &lt;code&gt;git checkout&lt;/code&gt; away from oblivion. This despite &amp;ldquo;small commits, one feature per commit&amp;rdquo; sitting right there in CLAUDE.md since day zero.&lt;/p&gt;
&lt;p&gt;The lesson generalizes. An agent optimizes for the thing you are asking about right now (the feature), and the standing rules (commit discipline) quietly lose priority. It is the same failure mode as a junior engineer who nails the change but skips the change ticket, and the fix is the same: not scolding, structure. Commit-and-push became part of an end-of-phase checklist that runs as a single command. Structure beats intention, for AIs exactly as for humans. That checklist became &lt;code&gt;/ship&lt;/code&gt;, which gets its own war story in Part 4.&lt;/p&gt;
&lt;h2 id=&#34;the-human-is-the-test-between-phases&#34;&gt;The human is the test between phases&lt;/h2&gt;
&lt;p&gt;The most valuable QA in the whole project cost nothing: I used the app. Logged sets from my phone. Noticed the exercise dropdown was miserable with 52 entries, which became a type-to-search picker. Noticed starting a workout felt bare, which became check-in cards (sleep, mood) that are deliberately skippable, because nothing is allowed to stand between you and logging a set.&lt;/p&gt;
&lt;p&gt;Later, real gym sessions generated a nine-item friction list that became an entire ideation phase (that is Part 6).&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-tip&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Tip&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;None of that feedback is producible by an agent, however good, because it comes from squinting at your phone between sets with chalk on your hands. Be the field test. Your friction list is the roadmap&amp;rsquo;s best input.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;h2 id=&#34;what-id-tell-you&#34;&gt;What I&amp;rsquo;d tell you&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Size phases to one session. If the kickoff prompt doesn&amp;rsquo;t fit in a paragraph, the phase is too big.&lt;/li&gt;
&lt;li&gt;Concentrate tests where the subtle bugs are, for us the PR math, and let curl checks cover the plumbing.&lt;/li&gt;
&lt;li&gt;Review at boundaries, not continuously. Batch review at the phase edge caught everything worth catching, cheaper.&lt;/li&gt;
&lt;li&gt;Make the standing rules structural. Anything you find yourself repeating at review time (commit! update docs!) should become a checklist the agent executes, not advice it remembers.&lt;/li&gt;
&lt;li&gt;Use the thing between phases. Real use finds what tests can&amp;rsquo;t.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Part 4 is the deploy story: auth in one file, a homelab VM, and the five sysadmin sharp edges that no amount of AI polish saved me from.&lt;/p&gt;
&lt;h2 id=&#34;references-and-further-reading&#34;&gt;References and further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://code.claude.com/docs/en/memory&#34;&gt;How Claude remembers your project&lt;/a&gt;, Claude Code docs, on CLAUDE.md rules and why standing instructions still need structural enforcement.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://opensiuc.lib.siu.edu/cgi/viewcontent.cgi?article=1744&amp;amp;context=gs_rp&#34;&gt;Validation of the Brzycki and Epley equations for 1RM prediction&lt;/a&gt;, if you want the actual research on where 1RM estimation formulas hold up and where they don&amp;rsquo;t.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>Zero to App with AI, Part 2: Keeping It Cheap</title>
      <link>https://natebent.com/posts/zero-to-app-with-ai-part-2-keeping-it-cheap/</link>
      <pubDate>Sat, 11 Jul 2026 01:00:00 +0000</pubDate><author>Nathan Bent</author>
      <guid>https://natebent.com/posts/zero-to-app-with-ai-part-2-keeping-it-cheap/</guid>
      <description>&lt;p&gt;The dirty secret of AI-assisted development is that the naive way to do it, one enormous chat session, the biggest model, all day, is expensive out of proportion to what you get. Before writing much code for &lt;a href=&#34;https://reprepo.app&#34;&gt;RepRepo&lt;/a&gt;, I set up cost controls. The interesting part is that almost none of them rely on me remembering to do anything, which turns out to be the whole trick.&lt;/p&gt;
&lt;p&gt;If you missed it, &lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-1-write-the-docs-before-the-code/&#34;&gt;Part 1&lt;/a&gt; covered the docs-as-memory setup this all builds on.&lt;/p&gt;
&lt;h2 id=&#34;the-routing-table&#34;&gt;The routing table&lt;/h2&gt;
&lt;p&gt;My &lt;code&gt;docs/AI-WORKFLOW.md&lt;/code&gt; starts with a table that says which model does which job:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Task&lt;/th&gt;
					&lt;th&gt;Model&lt;/th&gt;
					&lt;th&gt;Why&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Day-to-day coding, CRUD, UI, tests&lt;/td&gt;
					&lt;td&gt;Mid-tier (Sonnet)&lt;/td&gt;
					&lt;td&gt;Great at it, cheap&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Architecture, schema review, stuck bugs&lt;/td&gt;
					&lt;td&gt;Big model, one focused session&lt;/td&gt;
					&lt;td&gt;Deep reasoning is worth paying for &lt;em&gt;occasionally&lt;/em&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Doc updates, checkbox-ticking, bookkeeping&lt;/td&gt;
					&lt;td&gt;Small model (Haiku)&lt;/td&gt;
					&lt;td&gt;It&amp;rsquo;s bookkeeping&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Commit messages, fake seed data, &amp;ldquo;what is middleware?&amp;rdquo;&lt;/td&gt;
					&lt;td&gt;Local LLM (free)&lt;/td&gt;
					&lt;td&gt;Iteration costs nothing&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The rule of thumb that emerged: the big model is for decisions, the mid model is for code, the small model is for chores, and the free model is for anything where &amp;ldquo;okay&amp;rdquo; is good enough.&lt;/p&gt;
&lt;p&gt;If you have ever done QoS design, this will feel familiar. You are not trying to give everything the best treatment. You are trying to make sure the expensive queue only carries traffic that deserves it.&lt;/p&gt;
&lt;h2 id=&#34;config-beats-discipline&#34;&gt;Config beats discipline&lt;/h2&gt;
&lt;p&gt;A routing table you have to remember is a routing table you will ignore by Tuesday. So the routing went into configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sub-agents carry their own model. In Claude Code, a sub-agent is a markdown file with YAML frontmatter, and one of the frontmatter fields is &lt;code&gt;model&lt;/code&gt;. My code-reviewer agent is pinned to the big model in its config file, and my docs-keeper agent is pinned to the small one. When a review runs, the right model runs. Nobody chooses.&lt;/li&gt;
&lt;li&gt;Slash commands carry their own model too, via the same &lt;code&gt;model&lt;/code&gt; frontmatter field. &lt;code&gt;/wrap-up&lt;/code&gt;, the end-of-session command that ticks roadmap checkboxes and drafts decision log entries, runs on the small model, always.&lt;/li&gt;
&lt;li&gt;A git alias handles commit messages for free. &lt;code&gt;git aimsg&lt;/code&gt; pipes the staged diff through a code-tuned model on my home server&amp;rsquo;s Ollama instance and prints a conventional commit message. Costs nothing, runs on hardware I already own.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&#34;callout callout-quote&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M1.75 2.5A1.75 1.75 0 0 0 0 4.25v3.5C0 8.716.784 9.5 1.75 9.5H3c0 1.5-.5 2.5-2 3.5 2.5-.5 4.5-2 4.5-5.25v-3.5A1.75 1.75 0 0 0 3.75 2.5Zm8.5 0A1.75 1.75 0 0 0 8.5 4.25v3.5c0 .966.784 1.75 1.75 1.75h1.25c0 1.5-.5 2.5-2 3.5 2.5-.5 4.5-2 4.5-5.25v-3.5a1.75 1.75 0 0 0-1.75-1.75Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Quote&lt;/span&gt;&lt;span class=&#34;callout-cite&#34;&gt;docs/DECISIONS.md&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Cost control that relies on remembering to switch models fails; encoding it in config makes the cheap path the default path.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;p&gt;That entry from my own decision log says it better than I can in prose. The pattern shows up in networking all the time: policy that lives in muscle memory drifts, policy that lives in config holds.&lt;/p&gt;
&lt;h2 id=&#34;the-free-tier-is-better-than-you-think&#34;&gt;The free tier is better than you think&lt;/h2&gt;
&lt;p&gt;I run Ollama on a homelab box with a decent GPU, with a browser chat UI for me and a raw API for scripts. The models on it (a 14B code model, a couple of general models in the 14B to 24B range) are nowhere near frontier quality, and it does not matter, because I only send them work where mediocre is fine: commit messages, learning questions (&amp;ldquo;explain Express middleware like I&amp;rsquo;m new&amp;rdquo;), rubber-ducking, and generating fake seed data.&lt;/p&gt;
&lt;p&gt;The first time I tested the pipeline, the local model confidently explained that OLLAMA stands for &amp;ldquo;Online Learning and Language Acquisition for Mathematics and Arithmetic.&amp;rdquo; It does not stand for anything. That hallucination was clarifying rather than alarming: this is the tier where confident nonsense is acceptable, because everything it produces is either checked by me in two seconds (a commit message) or purely educational scaffolding I will verify against real docs anyway. Route accordingly.&lt;/p&gt;
&lt;h2 id=&#34;session-hygiene-the-biggest-lever-isnt-a-tool&#34;&gt;Session hygiene: the biggest lever isn&amp;rsquo;t a tool&lt;/h2&gt;
&lt;p&gt;Here is the thing that actually dominates cost: every message you send re-sends the whole conversation. The API is stateless, so your entire history rides along with each new message. Caching softens this (Claude Code caches your session automatically, so previously processed history is re-read at a steep discount instead of full price), but the history still gets re-read and billed on every turn, so a long meandering session still gets more expensive per message the longer it runs. The fix is structural, not heroic:&lt;/p&gt;
&lt;p&gt;One session, one task, then end it. Start a session, build the phase, run the wrap-up, close. The next session starts fresh, and because of the docs-as-memory setup from Part 1, it starts fresh with full context, for free. CLAUDE.md and the docs folder carry everything between sessions. Chat history carries nothing but cost.&lt;/p&gt;
&lt;p&gt;This is also why the phase structure matters financially, not just organizationally. A phase is sized to fit comfortably in one session. &amp;ldquo;Scaffold the client&amp;rdquo; is a session. &amp;ldquo;Also fix that bug and discuss deployment strategy&amp;rdquo; is a second one.&lt;/p&gt;
&lt;h2 id=&#34;the-counterintuitive-one-automation-isnt-cheaper&#34;&gt;The counterintuitive one: automation isn&amp;rsquo;t cheaper&lt;/h2&gt;
&lt;p&gt;When I first asked &amp;ldquo;can we automate this more?&amp;rdquo;, the honest answer I got, and later verified, was that most automation increases spend.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-caution&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.141.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Caution&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Multi-agent orchestration, agents controlling agents, parallel everything: these burn several times the tokens of a single focused session, because every agent carries its own copy of the context. The bill scales with the number of contexts, not the amount of work done.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;p&gt;The economical version of automation is boring: pinned models, one-keystroke commands for repetitive prompts, a local model for the free tier, and docs that eliminate re-explanation. Fancy orchestration has its place (Part 5 covers where parallel sessions genuinely earned their cost), but as a default it is a bill, not a feature.&lt;/p&gt;
&lt;h2 id=&#34;what-id-tell-you&#34;&gt;What I&amp;rsquo;d tell you&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Write the routing table down, then push it into config, per-agent models and per-command models, so the cheap path is the default path.&lt;/li&gt;
&lt;li&gt;Stand up a free tier. Even a modest local model absorbs a surprising amount of work once you are honest about which tasks tolerate &amp;ldquo;okay.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Treat sessions like transactions. Open, do one thing, commit, close. Long sessions are where money goes to die.&lt;/li&gt;
&lt;li&gt;Let the repo be the memory. Every fact in a doc is a fact you never pay to re-explain.&lt;/li&gt;
&lt;li&gt;Distrust automation that multiplies contexts. More agents does not mean more productivity per dollar. Usually the opposite.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next, &lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-3-the-phase-rhythm/&#34;&gt;Part 3&lt;/a&gt;: the actual build. The phase rhythm, what end-of-phase reviews caught (including a timezone bug that would have eaten my evening workouts), and the one mistake I kept making anyway.&lt;/p&gt;
&lt;h2 id=&#34;references-and-further-reading&#34;&gt;References and further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://code.claude.com/docs/en/model-config&#34;&gt;Model configuration&lt;/a&gt;, Claude Code docs. Covers the &lt;code&gt;model&lt;/code&gt; frontmatter field for sub-agents and commands.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://code.claude.com/docs/en/prompt-caching&#34;&gt;How Claude Code uses prompt caching&lt;/a&gt;, Claude Code docs. Why long sessions cost what they cost, and what invalidates the cache.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.claude.com/en/docs/build-with-claude/prompt-caching&#34;&gt;Prompt caching&lt;/a&gt;, Claude API docs, if you want the underlying mechanics.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>Zero to App with AI, Part 1: Write the Docs Before the Code</title>
      <link>https://natebent.com/posts/zero-to-app-with-ai-part-1-write-the-docs-before-the-code/</link>
      <pubDate>Thu, 09 Jul 2026 01:00:00 +0000</pubDate><author>Nathan Bent</author>
      <guid>https://natebent.com/posts/zero-to-app-with-ai-part-1-write-the-docs-before-the-code/</guid>
      <description>&lt;p&gt;This is a different kind of post for this blog. Less BGP, more JavaScript.&lt;/p&gt;
&lt;p&gt;I built and shipped a real app, &lt;a href=&#34;https://reprepo.app&#34;&gt;RepRepo&lt;/a&gt;, a workout tracker, essentially over one weekend, working with an AI coding agent (Claude Code, in my case, though most of this translates to any of them). Not a todo list demo either. It has a plans system, PR detection with unit tests, multi-user auth, security hardening, nightly backups, and tagged releases deployed by CI, and it is running on the public internet where I actually use it at the gym.&lt;/p&gt;
&lt;p&gt;I should be clear about my starting point, because it matters for how you read this series. I am a network engineer. I can script, I can read code, and I run a homelab full of self-hosted services, but I had never built a web application. The agent wrote nearly all of the code. My job was direction, review, testing, and the sysadmin work at the end. Whether that counts as &amp;ldquo;building an app&amp;rdquo; is a philosophical question I will leave to the comments section I do not have.&lt;/p&gt;
&lt;p&gt;This series is the honest write-up: what worked, what bit me, and what I would tell anyone starting the same experiment. Part 1 is about the counterintuitive first step, the one hour that made everything after it possible.&lt;/p&gt;
&lt;h2 id=&#34;the-empty-folder&#34;&gt;The empty folder&lt;/h2&gt;
&lt;p&gt;Day zero looked like this:&lt;/p&gt;
&lt;div class=&#34;code-wrap&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;workout-tracker/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── README.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── CLAUDE.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── docs/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── ROADMAP.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── DATA-MODEL.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── DECISIONS.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── AI-WORKFLOW.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── .claude/agents/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;No code. Not one line. Just markdown.&lt;/p&gt;
&lt;p&gt;That felt backwards. Every instinct says start with &lt;code&gt;npm create vite&lt;/code&gt; and get pixels on screen. But AI-assisted development changes the economics of documentation completely, for one reason.&lt;/p&gt;
&lt;blockquote class=&#34;callout callout-insight&#34;&gt;
    &lt;p class=&#34;callout-title&#34;&gt;
      &lt;span class=&#34;callout-icon&#34;&gt;&lt;svg viewBox=&#34;0 0 16 16&#34; aria-hidden=&#34;true&#34;&gt;&lt;path d=&#34;M8 0 L9.6 6.4 L16 8 L9.6 9.6 L8 16 L6.4 9.6 L0 8 L6.4 6.4 Z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;span class=&#34;callout-label&#34;&gt;Interesting!&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;The docs are the agent&amp;rsquo;s memory. An AI coding session starts from zero every time. Anything you don&amp;rsquo;t write down, you will re-explain every session, forever, at token prices. Write it down once and every future session starts already knowing the project.&lt;/p&gt;
  &lt;/blockquote&gt;&lt;p&gt;This is not a metaphor. A coding agent does not remember yesterday&amp;rsquo;s session, your preferences, or why you chose SQLite. Chat history dies when the session ends, and while it is alive it costs money to carry, because it rides along with every message you send. The repo, on the other hand, is free, permanent, and versioned. So the docs are not documentation in the usual sense, written after the fact for some hypothetical future reader. They are load-bearing infrastructure, consumed by the agent at the start of every single session.&lt;/p&gt;
&lt;p&gt;Once that clicked, spending the first hour on markdown stopped feeling like procrastination and started feeling like configuring the router before plugging in the clients.&lt;/p&gt;
&lt;h2 id=&#34;the-four-files-that-matter&#34;&gt;The four files that matter&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt; is the file Claude Code reads automatically at the start of every session (other tools have equivalents: rules files, instructions files, whatever your agent auto-loads). Mine is short and rule-dense: the stack, the architecture rules (&amp;ldquo;ALL business logic lives in the server, the client renders and calls /api&amp;rdquo;), workflow rules (&amp;ldquo;small commits, one feature per commit&amp;rdquo;), and cost rules (Part 2 is entirely about those). The guidance from the Claude Code docs is to treat it as the place you write down whatever you would otherwise re-explain, and to keep it concise, because shorter instruction files get followed more reliably.&lt;/p&gt;
&lt;p&gt;One sentence in mine paid for itself fifty times over: &amp;ldquo;The developer is learning, explain non-obvious choices briefly as you go, and prefer simple, readable code over clever code.&amp;rdquo; Every session, every explanation, calibrated to me, for free. If you are using a project like this to learn a stack, put a line like that in on day zero.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docs/ROADMAP.md&lt;/strong&gt; breaks the project into phases with checkboxes. Phase 1: foundation. Phase 2: core logging loop. Phase 3: plans. Phase 4: PRs and progress charts. Each phase is small enough for one focused session, and each ends with its checkboxes ticked. This file became the heartbeat of the whole project, which is Part 3&amp;rsquo;s story.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docs/DATA-MODEL.md&lt;/strong&gt; is the schema, written in SQL, before any code existed. Five tables, and, crucially, the business rules in prose: how PR detection should work, what the Epley 1RM formula is and where it stops being trustworthy, what a &amp;ldquo;workout&amp;rdquo; even is. Code can tell you what the app does. Only a doc can tell you what the app is supposed to do, which is exactly the distinction an agent needs when it writes tests.&lt;/p&gt;
&lt;p&gt;The file ended with a section called &amp;ldquo;Open questions (answer these before Phase 2)&amp;rdquo;: lbs or kg? Track RPE? How do bodyweight exercises work? I answered them inline like a form. That ten minute exercise surfaced decisions I would otherwise have made accidentally, mid-implementation, three phases deep, and at least one of them (bodyweight exercises) would have meant a schema migration if I had discovered it late.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docs/DECISIONS.md&lt;/strong&gt; is a decision log with a rigid little format, three lines per entry:&lt;/p&gt;
&lt;div class=&#34;code-wrap&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Decision: &amp;lt;what we chose&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Why: &amp;lt;the reason, one or two sentences&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Alternatives: &amp;lt;what lost, and why&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It started with two entries. It now has over forty, and it is the file I would rescue from a fire. When a fresh session asks &amp;ldquo;why is weight stored in lbs only?&amp;rdquo; the answer is in the repo, dated, with the alternatives that lost. Future me does not do archaeology, and the agent does not re-litigate settled questions. If you have ever kept a change log for a firewall, this is the same instinct pointed at software decisions.&lt;/p&gt;
&lt;h2 id=&#34;day-one-measured&#34;&gt;Day one, measured&lt;/h2&gt;
&lt;p&gt;With the docs in place, day one went: install Node, &lt;code&gt;git init&lt;/code&gt;, resolve the open data-model questions, then paste a kickoff prompt (pre-written at the bottom of the roadmap) that scaffolded the Express + SQLite server. Schema, a seed script with 52 exercises, and the first three API routes. The agent tested everything with curl before calling it done, including the failure cases: bad exercise IDs got clean 400s, not stack traces.&lt;/p&gt;
&lt;p&gt;By evening there was a working API with validated inputs. The next morning there was a React client. The docs took about an hour of that. Everything else in this series runs on the rails laid in that hour.&lt;/p&gt;
&lt;h2 id=&#34;what-id-tell-you&#34;&gt;What I&amp;rsquo;d tell you&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Write CLAUDE.md first, or whatever your tool&amp;rsquo;s auto-loaded context file is. Rules you write once are rules you never repeat.&lt;/li&gt;
&lt;li&gt;Put the schema and the business rules in a doc, not just in code. Code says what. The doc says why, and what counts as correct, which is exactly what an agent needs to write real tests.&lt;/li&gt;
&lt;li&gt;Keep a decision log with a format. Decision, why, alternatives considered. Three lines per entry. It compounds.&lt;/li&gt;
&lt;li&gt;Answer the open questions before building. Make the doc ask you questions. The ten minutes I spent answering &amp;ldquo;how should bodyweight exercises work?&amp;rdquo; saved a schema migration later.&lt;/li&gt;
&lt;li&gt;Pre-write the next kickoff prompt. Ending each phase by writing the prompt that starts the next one means zero cold-start cost.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The pattern under all five: move context out of your head and out of the chat history, into the repo. Chat history dies with the session and costs money to carry. The repo is free, permanent, and versioned.&lt;/p&gt;
&lt;p&gt;Next up, &lt;a href=&#34;https://natebent.com/posts/zero-to-app-with-ai-part-2-keeping-it-cheap/&#34;&gt;Part 2&lt;/a&gt;: how I kept the whole thing cheap. Model routing, running the boring work through a local LLM on the homelab, and why more automation usually means a bigger bill.&lt;/p&gt;
&lt;h2 id=&#34;references-and-further-reading&#34;&gt;References and further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://code.claude.com/docs/en/memory&#34;&gt;How Claude remembers your project&lt;/a&gt;, Claude Code docs. Covers CLAUDE.md loading behavior and what belongs in it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.claude.com/en/docs/claude-code/overview&#34;&gt;Claude Code overview&lt;/a&gt;, the official starting point if the tool is new to you.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
  </channel>
</rss>
