1 Session1
archeious edited this page 2026-04-17 17:57:46 -06:00

Session 1 Notes — 2026-04-17

What We Set Out to Do

Greenfield: build a zero-based household budget tracker in Python + SQLite. Start with a minimal scaffold, then iterate based on what the app needed to actually be useful for zero-based budgeting.

What Actually Happened

One long session, ten PRs merged, one name picked early (quartermaster, fits the marchwarden / harbormind role-based naming convention in the user's other repos). The arc:

  1. #1 scaffold — FastAPI + Jinja + HTMX + SQLAlchemy + Alembic + uv. Budget config page with seven sections, Primary Debt Target as a singleton pointer.
  2. #3 monthly view — snapshot-from-config, deviation tags (unchanged / edited / new_in_month), per-month debt target. Applied column stored directly on month_entry.
  3. #5 backupsscripts/backup-db.sh using sqlite3.Connection.backup, alembic env.py hook, memorialised in a project-level CLAUDE.md. This was reactive: I had wiped his live DB several times mid-session as dev churn, and a single question from him ("What happened to the previous data?") surfaced the incident. The backup script is the fix that should have existed from the start.
  4. #7 zero-amount widget — hero at the top of each page, income minus all other sections, colour-tone by state.
  5. #9 wiki gitignore — small chore after the initial wiki clone.
  6. #11 groups + Sinking Funds — four display groups (Income, Committed, Savings, Flexible) with collapsible <details> headers. New sinking_fund section for emergency fund and other goal-funded categories. No schema migration needed (Section enum has no DB-level CHECK constraint).
  7. #13 notes field — optional free-text per entry, copied through the snapshot. Notes changes don't flip deviation state.
  8. #15 month lifecycle — three explicit states (Planning / Active / Closed) with a balance gate: close requires applied zero == $0.00. Reopen allowed. Nothing sweeps automatically — the Primary Debt Target is a hint, user manually moves applied to close.
  9. #17 UI redesign — Barlow Condensed everywhere, warm cream paper, burgundy accent sampled from the logo shield, logo anchors the zero hero. Three mockups iterated first (docs/mockups/): loose editorial (Fraunces), dense editorial, dense condensed-sans. The last one shipped.
  10. #19 posting ledgerapplied dropped as a column; each month_entry owns a ledger of Posting rows (date, amount, optional description and payee). Applied is now a derived @property. Migration seeds one opening-balance posting per existing non-zero applied. Schema term posting chosen over transaction or month_entry_transaction.

Plus three small UX fixes on the ledger branch before merge: the txn count was wrapping in a too-narrow cell; empty notes were hover-hidden and couldn't be clicked through the hover gap; the full-width add form was horizontally noisy, now collapsed behind a + add <section> disclosure.

Key Decisions & Reasoning

  • Snapshot over mirror for months. Month entries carry origin_name / origin_planned at creation, and their own editable name / planned afterwards. Deviation tags compare current vs origin. This keeps month pages self-contained when the budget changes later.
  • Primary Debt Target is a pointer, not a budgeted amount. Jeff runs avalanche (highest-interest first). Non-target debts get minimums; target gets minimum plus whatever's leftover. The "leftover" is not pre-allocated — it's whatever the user manually moves to target's applied at close time. This shaped the close gate: balance at $0 applied is the user's signal that they've done the allocation by hand.
  • No auto-sweep on close. I proposed automatic sweep of unassigned applied to the target. He rejected: "The month cannot close until it balances at 0. Target is just where it should go." Cleaner semantics; forces the user to own the allocation.
  • UI direction chosen after three mockups. Editorial ledger with Fraunces felt too magazine-y at the density of a real working ledger. Condensed-sans (Barlow Condensed) with the logo in the zero hero had the right workaday-but-distinct feel. Fraunces is preserved in mockup history for future explorations.
  • posting over transaction. Transaction is a SQL reserved word in some dialects and clashes with SQLAlchemy's own Session.transaction concept. posting is accounting-correct (a debit or credit posted to a ledger) and avoids every collision. UI-facing language still says "transactions"; the split is intentional.
  • Applied derived only. No column, no setter, no click-to-edit on applied. Users manage applied by adding / editing / deleting postings. Makes the ledger the single source of truth.
  • Opening-balance migration. Existing applied values become one posting per non-zero entry with description = "opening balance", dated to activated_at or created_at. Preserves every pre-ledger total cleanly; downgrade restores via SUM.

Surprises & Discoveries

  • I wiped the live DB four times. Twice during alembic autogen (regenerating schema), twice during smoke tests. He caught it with a single question. Saved a memory about never treating quartermaster.db as dev scratch. The backup script now exists partly because of this.
  • Adding sinking_fund to the Section enum required no DDL. The section VARCHAR(32) column has no CHECK constraint in the actual SQL schema, so new enum values are a pure Python change. Alembic's autogenerate produced an empty migration; I deleted it.
  • Classic hover-gap bug on empty notes. Hiding empty notes rows and revealing on :hover of the adjacent entry row means moving the cursor toward the notes row leaves the entry row and hides the notes again. Visible only in live use; tests wouldn't have caught it.
  • The posting table name discussion. I proposed month_entry_transaction; he pushed back ("a bit verbose"). Short negotiation landed on posting (my pick), which also fit the editorial accounting aesthetic.
  • The em-dash rule. I kept writing em-dashes in chat output despite his global CLAUDE.md rule. I caught and fixed a few cases in committed code (mockup templates) but I'm sure I still slipped in chat. Worth keeping on my radar.

Concerns & Open Threads

  • No browser verification. Every smoke test was curl. The inline-edit flows, HTMX partial swaps, and <details> expand/collapse interactions were verified only at the HTML structure level. The UX could have hover-gap bugs, focus-management issues, or layout problems that show up only on a real browser. Jeff caught one of these (empty notes) within minutes of first use; there are probably more.
  • Over-budget isn't visually distinct. The entry progress bar caps at 100% but no longer shows the overflow nub past the 100% line that the early mockups had. Mental note for a follow-up.
  • Budget-side inline edit for name / amount. Currently requires delete-and-recreate to change a budget entry's name or amount. Notes are editable. Parity would match the month page's inline edit. Deferred.
  • Posting dates are free-form. Nothing stops a posting dated May 3 from sitting on April's entry. Most users would expect the constraint; deferred as "constrain posting dates to the month" on the roadmap.
  • Closed-month archived tone. Today closed months just carry disabled on inputs and a muted state badge. A deeper archive treatment (desaturated palette, watermark) is on the roadmap.
  • Deviation semantics on posting-only edits. Notes don't flip deviation. Postings don't either. But if the user edits planned from $400 to $450, that does flip. Might be worth a gentle indicator when the plan drifts vs when applied drifts, though the current model is defensible.

Raw Thinking

  • The logo was a turning point for the UI direction. Before Jeff mentioned it, I was committing to Fraunces serif. Once I saw the shield-with-$0 mark I realised the app's identity is already communicated by the logo, and the type should complement rather than compete. The Barlow Condensed pairing (also condensed sans, echoes the logo's wordmark) feels inevitable in retrospect.
  • Jeff's pattern of "explain your thinking" is a forcing function. When he asked for honest thoughts on the redesign, I tried to flatter first. That triggered a course-correction in my own response. The "ask for self-assessment" move gets more honest work out of me than unsolicited reviews would.
  • Decision minimalism compounds. Answers like "1) snapshot 2) one applied number 3) explicit 4) yeah 5) yep" compress a 10-minute back-and-forth into a 10-second exchange. The trade is that I have to be extremely clear about which defaults I'd pick if he said nothing — because often he does say nothing, and my defaults ship.
  • Posting is a better name than transactions. Retrospectively, the editorial tone of the rest of the app (colophons, italic captions, the wordmark setting) benefits from "posting" as a word of art. "Transactions" would have been neutral and forgettable.
  • Ten PRs in one sitting is a lot. Jeff's profile noted sustained multi-PR sessions as routine. This one pushed that envelope — and the density was maintained without him ever asking for a slow-down. Validates the profile's "capacity exceeds prior models" note.

What's Next

Priority order for the next session:

  1. Browser eyeball pass. Run the full app in a real browser and hunt for UX issues that curl + pytest miss. Expect: hover states, focus management, layout overflow on mobile, empty-state edge cases.
  2. Constrain posting dates to the month (or at least warn on out-of-range). Small scoped change.
  3. Budget-side inline edit for name / amount. Matches month page capability; removes the delete-and-recreate friction.
  4. Over-budget visual nub. Restore the overflow indicator from the early mockups on entry rows where applied > planned.
  5. Closed-month archived treatment — follow-up on #15.