<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel><title>BLACK BOX — PayDown Build Log</title><link>https://blog.acarr.org/paydown/</link><atom:link href="https://blog.acarr.org/paydown/feed.xml" rel="self" type="application/rss+xml" /><description>Building PayDown in public: a BNPL payoff app built with Claude, from first prompt to TestFlight.</description><language>en-us</language><lastBuildDate>Thu, 30 Jul 2026 12:00:00 +0000</lastBuildDate><item><title>This blog is part of the plan</title><link>https://blog.acarr.org/posts/this-blog-is-part-of-the-plan/</link><guid isPermaLink="true">https://blog.acarr.org/posts/this-blog-is-part-of-the-plan/</guid><pubDate>Thu, 30 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>If a weekly public artifact is the rule, the artifact needs somewhere to live.</description><content:encoded><![CDATA[<p>If a weekly public artifact is the rule, the artifact needs somewhere to live. So this site got built in a day, the same way everything else in this project gets built — me steering, Claude building.</p>
<ul>
<li>Static pages on the same serverless hosting pattern as my other sites: pennies a month, nothing to maintain, nothing to get hacked at 2am.</li>
<li>I post from a simple form behind passwordless sign-in; Claude posts through a small CLI — fitting, since half this dev log is generated from the work itself.</li>
<li>One email box does two jobs: joins the app waitlist <em>and</em> registers you to comment.</li>
<li>Even the domain had a story. I almost bought paydownaffirm.com (trademark trouble waiting to happen) and paydownbnpl.com (try saying "b-n-p-l" out loud in a video) before landing on <strong>getpaydown.com</strong>.</li>
</ul>
<p>If you're reading this before the beta: <a href="/index.html#register">join the waitlist</a>. The debt-free date is the product. The weekly artifact is the promise. This is week one.</p>]]></content:encoded></item><item><title>Marketing, accounts, and keeping financial data safe</title><link>https://blog.acarr.org/posts/marketing-auth-and-security/</link><guid isPermaLink="true">https://blog.acarr.org/posts/marketing-auth-and-security/</guid><pubDate>Thu, 30 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>Before going further I wanted three things nailed down: the marketing plan, whether PayDown needs accounts like Rocket Money has, and what "secure" actually…</description><content:encoded><![CDATA[<p>Before going further I wanted three things nailed down: the marketing plan, whether PayDown needs accounts like Rocket Money has, and what "secure" actually means for stored financial data.</p>
<p><strong>Marketing, compressed:</strong> position as the BNPL-sprawl app, not another budget app — that fight is unwinnable against incumbent ad budgets. Build in public (this blog). Seed the beta in debt-free communities with honest walkthroughs. Launch on low-competition App Store keywords like "bnpl tracker." Grow with a free payoff calculator and share cards. Zero ad spend until organic numbers prove out.</p>
<p><strong>Accounts:</strong> yes, but look at <em>why</em> Rocket Money needs heavy account infrastructure — they hold your bank credentials. PayDown never does. Accounts here are opt-in, exist only for sync, use passwordless email links, and sit on database-enforced row-level security.</p>
<p><strong>Security, plainly:</strong> data lives on your phone by default, protected by device encryption. Cloud sync is opt-in, encrypted in transit and at rest, one user per row, enforced below the application layer. No bank logins, no Plaid, no transaction feeds. Worst-case breach exposes a list of self-reported balances — bad — not credentials to anyone's bank — catastrophic. Smallest possible attack surface, as a product decision. It's all on the <a href="/privacy.html">privacy page</a> in plain English.</p>]]></content:encoded></item><item><title>37 apps since 2008. Why no hit?</title><link>https://blog.acarr.org/posts/thirty-seven-apps-and-no-hit/</link><guid isPermaLink="true">https://blog.acarr.org/posts/thirty-seven-apps-and-no-hit/</guid><pubDate>Thu, 30 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>PayDown is the 37th mobile app I've built since 2008.</description><content:encoded><![CDATA[<p>PayDown is the 37th mobile app I've built since 2008. My first — a vacation countdown widget — sold about 2,000 copies and is still on the Play Store. None of the thirty-seven ever really took off, and mid-build I finally asked the question out loud: why can't I make an app that makes money?</p>
<p>The answer I got back from Claude was uncomfortable because it was true, so I'm publishing it where I can't un-see it:</p>
<p><strong>Building was never the problem.</strong> Thirty-seven shipped apps in eighteen years is an elite shipping record. But each one got months of engineering and roughly zero sustained distribution. That's not bad luck thirty-seven times; it's the same allocation decision thirty-seven times.</p>
<p>There's no guaranteed money on the App Store. What exists instead: a niche with real pain (BNPL sprawl qualifies), a product that solves it honestly (this one does), and then the part I've always skipped — <strong>showing up in public every single week</strong>. A post, a video, a build log, an answer in a community. Boring, repeatable, compounding.</p>
<p>So that's the standing rule now: from beta onward, every week ships one marketing artifact alongside the code. This post is one. This whole blog is the enforcement mechanism.</p>]]></content:encoded></item><item><title>Taking PayDown to the cloud (carefully)</title><link>https://blog.acarr.org/posts/taking-paydown-to-the-cloud/</link><guid isPermaLink="true">https://blog.acarr.org/posts/taking-paydown-to-the-cloud/</guid><pubDate>Wed, 29 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>I wanted my numbers visible from a website too — which meant deciding what "cloud" means for an app whose best privacy feature is not having your data.</description><content:encoded><![CDATA[<p>I wanted my numbers visible from a website too — which meant deciding what "cloud" means for an app whose best privacy feature is <em>not having your data</em>.</p>
<p>The architecture Claude and I settled on:</p>
<p><strong>Local-first stays the default.</strong> Data lives on the phone. The cloud is opt-in, for people who want a web dashboard or multi-device sync.</p>
<p>For those who opt in: a Postgres backend where every row is tagged with its owner and <strong>row-level security is enforced by the database itself</strong> — not by app code that might have a bug. Sign-in is a passwordless email link. Timestamps and tombstones make sync last-write-wins with no server brain to maintain.</p>
<p>The web dashboard reuses the <em>exact same</em> engine package as the phone — same math, same golden tests, running in the browser. Two clients, one truth.</p>
<p>And no bank logins anywhere in any of it. That's not a missing feature; it's the design.</p>]]></content:encoded></item><item><title>The totals didn’t match Affirm</title><link>https://blog.acarr.org/posts/the-totals-didnt-match-affirm/</link><guid isPermaLink="true">https://blog.acarr.org/posts/the-totals-didnt-match-affirm/</guid><pubDate>Wed, 29 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>PayDown told me I owed $9,530.85.</description><content:encoded><![CDATA[<p>PayDown told me I owed <strong>$9,530.85</strong>. The Affirm app said <strong>$9,153.92</strong>. For an app whose entire promise is one trustworthy number, a $377 disagreement is disqualifying.</p>
<p>I sent Claude both screenshots. The culprit: statements list an "Amount Financed" — the principal at <em>origination</em>. For a loan that's already been paid on, that number overstates today's payoff, and the import was trusting it anyway.</p>
<p>The fix draws a clean line:</p>
<ul>
<li>No payments made yet? Amount Financed is correct — use it.</li>
<li>Otherwise, compute today's payoff as the present value of the remaining payments at the plan's rate — which is exactly what the provider's own payoff quote is.</li>
</ul>
<p>Plus the usual one-time repair for anything imported before the fix. That was build 7. My totals now match Affirm to the dollar, and that's the standard going forward: if PayDown and the provider ever disagree, it's a bug, not a rounding story.</p>]]></content:encoded></item><item><title>Why was "extra per month" capped at $500?</title><link>https://blog.acarr.org/posts/why-is-extra-capped-at-500/</link><guid isPermaLink="true">https://blog.acarr.org/posts/why-is-extra-capped-at-500/</guid><pubDate>Wed, 29 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>Playing with the strategy screen, I hit the ceiling on the "extra per month" slider: $500.</description><content:encoded><![CDATA[<p>Playing with the strategy screen, I hit the ceiling on the "extra per month" slider: $500. Why $500? No reason. A slider needed a maximum and $500 got picked out of thin air — a developer default that silently became product policy.</p>
<p>Now the slider's range scales with your actual monthly obligations, and next to it is a plain dollar field that accepts any number at all. Sliders are for exploring; the text field is for deciding.</p>
<p>Small fix, but the lesson generalizes: every hardcoded limit in an app is a guess somebody made on a Tuesday. When a user asks "why is it limited?", the answer needs to be better than "the slider needed a max." If it isn't, the limit shouldn't exist.</p>]]></content:encoded></item><item><title>Payment history was missing</title><link>https://blog.acarr.org/posts/payment-history-was-missing/</link><guid isPermaLink="true">https://blog.acarr.org/posts/payment-history-was-missing/</guid><pubDate>Wed, 29 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>I imported a loan I'd been paying on for months, and the payments I'd already made simply didn't exist — no history, and no way to add them.</description><content:encoded><![CDATA[<p>I imported a loan I'd been paying on for months, and the payments I'd already made simply didn't exist — no history, and no way to add them.</p>
<p>That's the <em>normal</em> case, not the edge case. Nobody starts tracking a loan the day they open it. Builds 5 and 6 filled the gap:</p>
<ul>
<li>A <strong>history screen</strong> per account: paid-so-far on top, still-owed below.</li>
<li><strong>Log a past payment</strong> — and after I caught the first version getting this backwards, logging a payment now <em>reduces the balance</em> by default, with a toggle for the exceptions.</li>
<li><strong>Backfill</strong> for the other case: the balance was already correct, and I just wanted the record to show the installments I'd paid. History only, balance untouched.</li>
</ul>
<p>The difference between those last two matters more than it looks — one changes what you owe, the other changes what the record shows. Collapsing them was the first version's mistake.</p>]]></content:encoded></item><item><title>Swipe, notes, and the Lead Bank problem</title><link>https://blog.acarr.org/posts/swipe-notes-and-real-logos/</link><guid isPermaLink="true">https://blog.acarr.org/posts/swipe-notes-and-real-logos/</guid><pubDate>Wed, 29 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>Three more findings from daily use, all shipped in build 4.</description><content:encoded><![CDATA[<p>Three more findings from daily use, all shipped in build 4.</p>
<p>The interesting one is what I think of as the Lead Bank problem. One of my loans showed an "L" icon — for Lead Bank, the underwriter on the statement. Technically correct. But I didn't borrow from Lead Bank in any way I think about; I borrowed from <strong>Affirm</strong>. An app that's technically correct and intuitively wrong is still wrong.</p>
<p>So: a provider picker with the known BNPL brands and their colors, plus an underwriter map that auto-resolves statement names like Lead Bank to the brand you actually used.</p>
<p>The other two:</p>
<ul>
<li><strong>Swipe navigation</strong> between the main screens — reaching for the tab bar every time felt clunky on a phone.</li>
<li><strong>A note on every account.</strong> A loan called "Affirm #4823" means nothing; a loan called "necklace" is a thing I remember buying. Debt is personal; the labels should be too.</li>
</ul>]]></content:encoded></item><item><title>The 3,361% APR</title><link>https://blog.acarr.org/posts/the-import-was-all-messed-up/</link><guid isPermaLink="true">https://blog.acarr.org/posts/the-import-was-all-messed-up/</guid><pubDate>Tue, 28 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>I pasted a real statement into the import screen and got back an account with a 3,361% APR and a balance of $138.51 on a much larger loan.</description><content:encoded><![CDATA[<p>I pasted a real statement into the import screen and got back an account with a <strong>3,361% APR</strong> and a balance of $138.51 on a much larger loan. Garbage in every field.</p>
<p>I sent Claude screenshots of exactly what I pasted and exactly what came out, and the eval harness went to work reproducing it. Three separate bugs:</p>
<ul>
<li><strong>APR ×100.</strong> The model returned 33.61% as "3361". The fix is deterministic: any parsed rate over 100 gets normalized down, and the prompt now pins rates to a sane range. Never trust a language model with a decimal point.</li>
<li><strong>The balance trap.</strong> Statements are full of numbers that look like balances. The extraction now follows a formula rule for which one is real — and the app <em>computes</em> the balance from the installment terms rather than trusting what was scraped.</li>
<li><strong>Two-digit years.</strong> "6/11/26" parsed as 2023. A date-format rule fixed it.</li>
</ul>
<p>For data already imported wrong, Claude added one-time repair migrations that detect impossible values and fix them in place. All of it shipped as build 3, same day.</p>]]></content:encoded></item><item><title>Who do I owe?</title><link>https://blog.acarr.org/posts/who-do-i-owe/</link><guid isPermaLink="true">https://blog.acarr.org/posts/who-do-i-owe/</guid><pubDate>Tue, 28 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>First real session with the app on my phone surfaced two problems in ten minutes.</description><content:encoded><![CDATA[<p>First real session with the app on my phone surfaced two problems in ten minutes.</p>
<p>First: the schedule showed <em>when</em> money was due but not <em>who</em> it went to. Every row looked the same.</p>
<p>Second, and sneakier: the app said my total debt was $4,267 when the honest number was $3,907. It was summing my remaining payments — principal <em>plus</em> the interest baked into future installments — and calling that "debt."</p>
<p>Both fixed in build 2:</p>
<ul>
<li>Every payment row got a provider avatar — brand colors for known BNPL providers, a tinted initial for everything else, green for income.</li>
<li>The headline number became <strong>principal owed</strong>: what it would cost to be done today. Future interest belongs in the projection, not in the scary big number.</li>
</ul>
<p>That second fix became a rule the app still follows: <em>the balance is the payoff amount, not the sum of remaining payments.</em> Sounds obvious. Half the import bugs that came later were violations of exactly that rule.</p>]]></content:encoded></item><item><title>From empty repo to TestFlight</title><link>https://blog.acarr.org/posts/lets-get-it-in-testflight/</link><guid isPermaLink="true">https://blog.acarr.org/posts/lets-get-it-in-testflight/</guid><pubDate>Tue, 28 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>Two working days after the first commit, I told Claude to get it on my phone.</description><content:encoded><![CDATA[<p>Two working days after the first commit, I told Claude to get it on my phone. Apple made that... educational:</p>
<ul>
<li>Cloud signing rejected the App Store Connect API key with a cryptic "No Account for Team." The workaround was fully manual signing — certificate and provisioning profile created through Apple's REST API, archive and upload scripted with xcodebuild. No Xcode GUI, no interactive logins anywhere.</li>
<li>The first build uploaded successfully and then didn't show up for testing. The reason: the tester group had zero testers in it. One API call fixed what an hour of staring couldn't.</li>
<li>The newest Xcode broke a dependency with a Swift compiler error; a one-line patch, applied automatically on every install, un-broke it.</li>
</ul>
<p>What came out the other side is a fully scripted release pipeline: bump the build number, archive, upload, wait ten minutes, it's on my phone. Seven builds went through it in the first week — which is why every piece of feedback in the next few posts turned into a new build the same evening.</p>]]></content:encoded></item><item><title>Calendar math is hard (so the engine has golden tests)</title><link>https://blog.acarr.org/posts/calendar-math-is-hard/</link><guid isPermaLink="true">https://blog.acarr.org/posts/calendar-math-is-hard/</guid><pubDate>Mon, 27 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>The heart of PayDown is a pure TypeScript payoff engine with zero app dependencies — the same math runs on the phone and, later, on the web.</description><content:encoded><![CDATA[<p>The heart of PayDown is a pure TypeScript payoff engine with zero app dependencies — the same math runs on the phone and, later, on the web.</p>
<p>It works in whole days: no timezones, no DST, no clock. Dates are stored as integers. That sounds academic until you see the bugs it surfaced during the build:</p>
<ul>
<li><strong>The weekday off-by-one.</strong> Twice, "biweekly on Friday" landed on Thursday because of a wrong assumption about which day is day zero. Claude fixed it with tests pinned to calendar truth — dates a human can verify against a wall calendar.</li>
<li><strong>Month-end drift.</strong> A payment anchored to Jan 31 must hit Feb 28, then <em>Mar 31</em> — not slide to the 28th forever. Anchor-preserving month math handles it.</li>
</ul>
<p>Every projection — daily interest accrual, extra-payment cascades, freed-minimum rollovers — is pinned against an independent Python implementation Claude wrote separately. Dates must match exactly; interest within a dollar. If the two ever disagree, a test fails.</p>
<p>The rule that came out of this build: AI parses, deterministic code computes. Always.</p>]]></content:encoded></item><item><title>Testing the AI import on my own hardware</title><link>https://blog.acarr.org/posts/parse-with-my-local-model/</link><guid isPermaLink="true">https://blog.acarr.org/posts/parse-with-my-local-model/</guid><pubDate>Mon, 27 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>PayDown uses AI for exactly one job: reading pasted statement text and pulling out the plan details.</description><content:encoded><![CDATA[<p>PayDown uses AI for exactly one job: reading pasted statement text and pulling out the plan details. Every number is computed by a deterministic engine — the AI never does math.</p>
<p>When it came time to test the parsing, the default path was the Claude API. I pushed back: I run Ollama on a box at home, and I wanted the extraction tested against <em>my</em> model, on my hardware, where iterating costs nothing.</p>
<p>Claude built me a small eval harness: sample statements (Klarna emails, Affirm statements, virtual-card schedules) run through my local qwen2.5 model, with the structured output diffed against known-correct values.</p>
<p>That harness turned out to be the best investment of the whole project. Over the following days it caught six-plus real extraction bugs from my actual statements — for free, before any user would have hit them. A $0 local model you can hammer beats a paid API you're afraid to iterate on.</p>]]></content:encoded></item><item><title>What makes a debt app worth opening?</title><link>https://blog.acarr.org/posts/what-makes-a-debt-app-worth-opening/</link><guid isPermaLink="true">https://blog.acarr.org/posts/what-makes-a-debt-app-worth-opening/</guid><pubDate>Sun, 26 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>Before any code, I made myself answer a harder question: is BNPL tracking alone enough, or is that just a spreadsheet with an icon?</description><content:encoded><![CDATA[<p>Before any code, I made myself answer a harder question: is BNPL tracking alone enough, or is that just a spreadsheet with an icon?</p>
<p>It's a spreadsheet. What Claude and I landed on as the features that make it an <em>app people open</em>:</p>
<ul>
<li><strong>The debt-free date.</strong> One number that moves. That's the emotional core of the whole product.</li>
<li><strong>Safe-to-spend</strong> — after every scheduled payment clears, what's actually yours this pay period.</li>
<li><strong>Strategy comparison</strong> — avalanche vs. snowball, with interest saved shown in dollars, not lectures.</li>
<li><strong>Paste-to-import</strong> — nobody types eleven installment plans in by hand.</li>
<li><strong>Milestones</strong> — paying off a plan should feel like something.</li>
</ul>
<p>BNPL management is the wedge, the underserved pain nobody else centers. But the payoff <em>date</em> is the product.</p>]]></content:encoded></item><item><title>Naming it PayDown</title><link>https://blog.acarr.org/posts/naming-it-paydown/</link><guid isPermaLink="true">https://blog.acarr.org/posts/naming-it-paydown/</guid><pubDate>Sun, 26 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>Halfway through planning I realized the thing didn't have a name.</description><content:encoded><![CDATA[<p>Halfway through planning I realized the thing didn't have a name.</p>
<p>Claude pitched a shortlist and three survived: <strong>ZeroDue</strong>, <strong>Burndown</strong>, and <strong>PayDown</strong>.</p>
<p>I picked PayDown because it's literally what the app does. You pay down debt; the app shows the date you're done. Nothing to explain in an App Store listing.</p>
<p>Burndown didn't lose entirely — it became the brand mark. The icon is a burndown chart line heading to zero, which is exactly the chart the app draws for your balances.</p>]]></content:encoded></item><item><title>Why I’m building PayDown</title><link>https://blog.acarr.org/posts/the-prompt-that-started-paydown/</link><guid isPermaLink="true">https://blog.acarr.org/posts/the-prompt-that-started-paydown/</guid><pubDate>Sun, 26 Jul 2026 12:00:00 +0000</pubDate><category>paydown</category><description>Klarna in one app, Affirm in another, a card minimum in a third, and no single place that answers the only question that matters: when am I done?</description><content:encoded><![CDATA[<p>Klarna in one app, Affirm in another, a card minimum in a third, and no single place that answers the only question that matters: <strong>when am I done?</strong></p>
<p>I've been writing software since 1996 and shipping mobile apps since 2008, and this is the first one I'm building primarily for myself. I sat down with Claude and laid out what I wanted: every payment — monthly, bi-weekly, weekly, BNPL installments — on one schedule, a debt-free date, and suggestions for moving that date up. Personal tool first; if it earns the right to be a business, there's a monetization plan waiting.</p>
<p>Day one produced the unglamorous foundation: a private repo, GitHub Issues split into five milestones (tracker, payoff engine, AI import, beta, monetization), and planning docs for the product, the math, and the marketing.</p>
<p>This blog is the build log for all of it.</p>]]></content:encoded></item></channel></rss>
