retro: Session 1 — greenfield to shipped posting ledger
parent
1276499df5
commit
ad5026b3eb
2 changed files with 194 additions and 0 deletions
189
Session1.md
Normal file
189
Session1.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
# 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 backups** — `scripts/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 ledger** — `applied` 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.
|
||||
5
SessionRetrospectives.md
Normal file
5
SessionRetrospectives.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Session Retrospectives
|
||||
|
||||
| # | Date | Summary |
|
||||
|---|---|---|
|
||||
| [1](Session1) | 2026-04-17 | Greenfield to shipped ledger: 10 PRs merged (scaffold, monthly view, backups, zero amount, groups + sinking funds, notes, lifecycle, UI redesign, posting ledger + 3 fixups). 117 tests. |
|
||||
Loading…
Reference in a new issue