Adds the brainstorm-phase design for inline name/amount/notes edit on
the budget template page. Layout variant A (swap in place), notes folded
into edit mode, no schema change, forward-facing only.
Also adds .superpowers/ to .gitignore so the brainstorm companion's
working dir does not end up tracked.
Every section used to end with a wide horizontal add form (name
input, amount input, button, notes input spanning the row). It took
up more horizontal real estate than the entries themselves. Now the
form is wrapped in a <details class="add-entry"> whose summary is a
small tracked-caps link ("+ add fixed amount bills"). Click to
reveal the same form below; HTMX submission still resets and hides
on success.
Same treatment on the budget page sections and the month page
sections.
Refs #19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The notes row was display:none when empty and revealed on entry-row
hover. Moving the cursor down to click the input left the entry row
and immediately hid the notes again, a classic hover-gap. Fix by
always rendering the row with a subtle 0.55 opacity when empty and
bumping it to 1.0 on its own hover or focus. Now the input is
always reachable without a hover dance.
Refs #19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Widen the applied column from 5.5rem to 9rem so "\$134.32 · 7 txns"
fits on one line. Add white-space: nowrap to the cell and its
children as belt-and-braces. Mobile breakpoint gets 7rem with the
count text shrunk, still single-line.
Refs #19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New test_postings.py walks service and route layers: add sums into
applied, negatives are allowed, update and delete round-trip, entry
deletion cascades postings, order is desc by date, update_month_entry
rejects the removed applied kwarg. Route tests assert HTTP behaviour,
invalid-date rejection, closed-month lock, tone flip after a posting,
and the "N txns" count badge renders.
Existing tests that previously set applied via update_month_entry or
the entries route now use add_posting or POST to /postings. Format
assertions updated to match the new thousands-separator number
rendering and the replaced entry-notes-row markup.
Refs #19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each month entry becomes a <details> block. The summary is the same
dense row (name, planned, applied, delete) plus a leading caret and
an applied cell that shows the transaction count ("$412.33 · 7 txns")
when postings exist. Expansion adds no horizontal space.
Expanded body holds: the entry's notes input, a transactions table
with date / description / payee / amount / delete per posting, and
an inline add-transaction form (date, description, payee, amount,
submit). Every field is HTMX-wired so editing any cell triggers the
section partial re-render with fresh derived totals.
Closed month: name / planned / notes / posting fields all collapse
to read-only spans, delete buttons and add forms are omitted. The
existing editable flag controls the branching.
Refs #19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
add_posting / update_posting / delete_posting all go through
ensure_editable(month), so closed months reject posting mutations
with the same lifecycle guard as every other mutation. Negative
amounts are allowed for refunds / corrections. Dates are parsed as
ISO (YYYY-MM-DD) but not constrained to the month for now.
update_month_entry loses the applied keyword; the route no longer
accepts an applied form field. applied is derived only from now
on. Three new routes wire the ledger:
POST /month/{ym}/entries/{entry_id}/postings
POST /month/{ym}/postings/{posting_id}
DELETE /month/{ym}/postings/{posting_id}
Each returns the updated section partial plus OOB swaps for the
zero widget and all four group totals, same pattern the existing
mutations use.
Refs #19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Posting is a child of MonthEntry with occurred_on, amount, optional
description and payee. Cascade delete so removing an entry wipes its
ledger. Ordered on load by occurred_on DESC for readable UIs.
MonthEntry.applied becomes a @property summing posting amounts. The
stored applied column is dropped in the same migration.
The migration walks existing month_entry rows: for every non-zero
applied value, it inserts one opening-balance posting on the month's
activated_at (or created_at) date with description "opening balance"
and amount equal to the existing applied. Empty applied values get
no opening posting. Closed months go through the same path; their
totals stay intact via that single seeded row.
Downgrade is symmetric: re-adds the column and populates from
SUM(postings).
Refs #19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three iterations captured under docs/mockups/ with shared logo
assets:
* month-editorial-ledger.html — first pass, loose spacing, Fraunces
serif throughout, no logo.
* month-editorial-ledger-dense.html — tightened spacing, logo
integrated as masthead and behind the zero widget.
* month-condensed-sans.html — the shipped direction. Logo in the
zero hero, Barlow Condensed everywhere, masthead dropped.
These give a paper trail for why the app looks the way it does and
a sandbox for future typographic experiments without touching the
live CSS.
Refs #17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The condensed-sans redesign unifies budget and month zero widgets
into a single class; the pair variant is distinguished by column
count, not class. Assertion now checks that the month page still
renders both Applied and Planned labels alongside the zero widget
id.
Refs #17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pretty_year_month('2026-04') -> 'April 2026' for display. The
templates fall back to the raw year_month slug when the helper
returns nothing. Used by month.html and month_create.html to give
the nav a broadsheet feel; the URL routes still use the YYYY-MM
slug everywhere.
Refs #17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rebuild index.html, month.html, month_create.html, and every partial
against the new stylesheet:
* Drop the old Quartermaster / Household budget header block.
* Zero hero carries the logo on the left column with flanking
Applied / Planned on the month page, and a Budget / Planning-for
split on the budget page. Colophon tagline at the foot of every
page.
* month_nav switches to a single horizontal line: prev / title /
next / state badge / lifecycle button, with the month picker
wrapping below when present.
* Entry rows render through the new table.entries grid (name,
planned, applied, actions) with the notes row tucked underneath
and hidden when empty via :has(input:placeholder-shown).
* Primary Debt Target card uses the target-section styling with the
burgundy left bar.
* Back to Budget / Back to Configuration link justified right under
each month nav.
Refs #17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap the old minimal stylesheet for a comprehensive condensed-sans
ledger system:
* Barlow Condensed everywhere (300-800 + italic), via Google Fonts;
Barlow proportional as a secondary pair. No more system-ui stack.
* Warm cream paper background with layered radial gradients for
depth. Ink is warm near-black; accent is the logo burgundy
#732629 sampled directly from the shield.
* Tabular lining figures for every numeric column.
* <details> groups with a hairline chevron rotating on [open].
* Dense entry rows with a 2px progress bar riding the row's bottom
border. State colours by deviation: sage at-plan, ochre under,
accent over, indigo new-in-month.
* Primary Debt Target card with a burgundy left bar and margin
glyph so it reads as a hint pointing inward.
* State badges inline as tracked-caps labels with bullet separators
(planning indigo, active sage, closed muted italic).
* Favicon wired to the shield mark.
Refs #17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
logo.png at the repo root is a working 4.8MB source; optimised
variants used by the running app live in src/quartermaster/static/brand/
(full mark, mark-wide without wordmark, shield-wide for favicon).
Background checkerboard was masked out of all three so they sit on
any page colour. Gitignore the root source and WSL Zone.Identifier
sidecars.
Refs #17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Service tests walk Planning -> Active -> Closed -> Active and
confirm rejects on out-of-order transitions. Close rejects when
applied zero is nonzero; succeeds when balanced; reopens cleanly.
Route tests confirm each endpoint's status codes, HX-Redirect
headers, and that the page renders the right badge and button per
state. Closed months reject every mutation with 400 and their
rendered HTML carries disabled inputs without add forms or delete
buttons.
Refs #15
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
POST /month/{ym}/activate, /close, /reopen each return 204 with
HX-Redirect so the page re-renders in the new state. All existing
mutation routes now go through _require_editable_month, which 400s
on closed months.
Month nav grows a state badge and a context-appropriate lifecycle
button. Close is rendered with a disabled attribute and tooltip
when applied zero != 0. On closed months, name / planned / applied
/ notes inputs carry the disabled attribute; delete buttons, add
forms, and the target form are omitted entirely.
Refs #15
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
activate_month moves Planning to Active and stamps activated_at.
close_month moves Active to Closed only when applied zero equals
exactly $0.00; otherwise raises MonthLifecycleError with a message
naming the current balance. reopen_month moves Closed back to
Active and nulls closed_at. ensure_editable is the guard mutation
routes call before any write. No automatic sweep: filling the
target row is the user's job via editing applied amounts.
Refs #15
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
state defaults to 'planning' (server default plus SQLAlchemy default).
activated_at and closed_at are nullable timestamps that record when
the month crossed each boundary. Alembic batch_alter_table handles
the SQLite rewrite. MonthState is a Python string enum mapped to a
non-native VARCHAR(16).
Refs #15
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Service tests hit create, update, missing-id, blank-collapses-to-null,
and the snapshot copying behaviour. Route tests hit both pages'
create flows, the budget notes endpoint, and the month update route.
A dedicated assertion confirms that changing notes on a month entry
does not flip the deviation state: notes are free-form annotation,
not a signal of plan drift.
Refs #13
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each entry row gains a secondary notes row with an inline-editable
text input. Budget entries post to a new /entries/{id}/notes
endpoint; month entries reuse the existing update route. Add forms
gain an optional "notes (optional)" input that spans the form row.
Notes render muted with a dashed underline on hover to signal
editability without cluttering the layout. Changing notes on a
month row does not flip the deviation state since the financial
values are unchanged.
Refs #13
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
add_entry and add_month_entry accept an optional notes keyword. A
new set_entry_notes function updates a single budget entry's notes.
update_month_entry gains a notes parameter guarded by a sentinel so
callers can distinguish "do not touch notes" from "clear to NULL".
create_month copies entry.notes into each freshly snapshotted
month_entry. Blank / whitespace notes normalise to NULL.
Refs #13
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Free-text annotation up to 1024 chars. Nullable so existing rows need
no backfill; an empty or whitespace-only input will be normalised to
NULL in the service layer. Alembic batch_alter_table handles the
SQLite table rebuild automatically.
Refs #13
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every section maps to a group. Group order and defaults match the
spec. Budget and month subtotal calculations check out across
seeded entries. Pages render the expected details ids, income is
open by default, committed is closed. Mutations return OOB group
total spans. Sinking Funds section is visible on both pages.
Refs #11
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Budget and month pages now wrap sections in native <details> blocks
with summary rows showing the group name and subtotal. Income and
Flexible default open, Committed and Savings default closed so the
day-to-day editing targets are visible and the set-and-forget
commitments collapse out of the way. Primary Debt Target renders
inside the Committed group after Debt Minimums.
Every mutation appends a group-totals partial with OOB spans for
all four group subtotals so the header stays in sync without a
reload regardless of which section changed.
Refs #11
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
budget_group_views composes SectionViews into grouped dataclasses
with a combined total and the default open flag. month_group_views
does the same with planned and applied totals. Group order, labels,
and section-to-group mapping all come from the groups module.
Refs #11
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Section enum gains sinking_fund with label "Sinking Funds". A new
groups module maps each section to one of Income, Committed, Savings,
Flexible and records the default open state per group. The existing
section column is a plain VARCHAR(32) with no CHECK, so no schema
migration is needed to accept the new value.
Refs #11
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs/wiki/ is a checkout of quartermaster.wiki.git, not part of the
main repo. Ignoring it keeps git status clean and prevents accidental
cross-repo commits.
Refs #9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Service tests hit empty, positive, negative, and exactly-zero cases,
verify debt_target is excluded from the calculation, and confirm
month_zero responds to applied updates. Route tests assert the zero
widget appears OOB on every mutation with the expected tone class.
Refs #7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single large number on /, Planned + Applied pair on /month/YYYY-MM,
both colour-coded (green at zero, amber positive, red negative).
Every mutation route (add, delete, month entry update) appends the
zero widget to its response with hx-swap-oob so totals stay in sync
without a full page reload. An _append_oob helper replaces the ad-hoc
string concatenation the target card was already using.
Refs #7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
budget_zero sums income minus every non-income section on the budget
config. month_zero returns both Planned and Applied versions over a
month's entries. zero_tone classifies a value as zero / positive /
negative so templates can pick a colour.
Refs #7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Service tests assert that create_month produces origin fields matching
the budget, that edits flip deviation_state to edited, that added rows
are new_in_month, and that a budget entry deleted after snapshot leaves
the month entry unchanged. Route tests exercise the create flow,
applied updates, name edits producing the modified tag, per-month
target isolation, and the malformed-year-month 404.
Refs #3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Non-existent months return a page with a single "Create this month"
button; create POSTs return HX-Redirect to the newly-created month.
Each entry row carries three inline HTMX-wired inputs (name, planned,
applied) that trigger on change, posting only the field that changed.
Edits swap the section partial so totals and deviation tags update
together. Deleting a debt minimum in a month also re-renders the
target card via OOB swap. The budget page grows a This-month link and
a month picker; each month page has prev / next / picker / back-to-
config controls.
Refs #3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
create_month copies every budget entry into month_entry with
origin_name/origin_planned retained, resolves the budget's debt target
through source_entry_id to the corresponding MonthEntry, and is
idempotent. deviation_state classifies each row as unchanged, edited,
or new_in_month. Year-month handling (validation, shift across year
boundaries) lives here so the route layer stays thin.
Refs #3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A month is a snapshot of the budget. MonthEntry holds the copied planned
amount plus applied and origin_name/origin_planned so the UI can mark
edited rows. source_entry_id links back to the budget but is nullable
with ON DELETE SET NULL, so deleting a budget row after snapshot leaves
the month intact. MonthDebtTarget is one row per month via CASCADE from
month.
Refs #3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Asserts soft exit when the source DB is missing, a successful backup
round-trips sqlite rows, a non-file sqlite URL is rejected, the reason
slug defaults to "manual", and a messy reason is sanitised to a safe
filename fragment.
Refs #5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md states the durable rule: run scripts/backup-db.sh before
any schema change, data migration, or destructive DB operation. The
rule deliberately excludes routine app writes. README summarises
backup location, override env var, and restore procedure.
Refs #5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs at env module load so the backup fires ahead of offline and
online migration paths, as well as alembic current / revision. A
failing backup does not stop the migration: this is defense in depth,
not a hard prerequisite, and the common failure case is "DB does not
exist yet" on a fresh checkout.
Refs #5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Uses sqlite3.Connection.backup for an online, WAL-safe copy. Resolves
the DB path from QUARTERMASTER_DB_URL or ./quartermaster.db, places
the snapshot in <db-dir>/backups (override with QUARTERMASTER_BACKUP_DIR),
timestamps the filename, and tags it with an optional reason slug.
Absent DB file is a soft exit so the script is safe to call from hooks.
Refs #5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>