Plan covers 8 TDD tasks landing the /healthz endpoint and structured
JSON logs behind the platform contract (#25).
Also: spec updated to use JSON (not YAML) as the log-config source —
avoids a pyyaml runtime dep and removes drift between the dict tests
apply and the file uvicorn reads.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Part of the platform-contract intake (#25). Covers both pieces of work
that must land before first deploy to home-ctr-onyx.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Design proposal for a thin MCP server adapter over the existing
service layer so Claude Code and other MCP clients can consume
Quartermaster state and execute mutations without opening the
browser.
Document only. Covers:
- Goal and non-goals (no staging queue, no auth, no bank-feed
ingestion).
- Architecture: in-process Python module (quartermaster-mcp
script) sharing QUARTERMASTER_DB_URL with the web app; stdio
transport; QUARTERMASTER_MCP_MODE env var gates read/write.
- Tool surface aligned with the current model: get_budget,
list_months, get_month, get_month_entry, list_postings,
get_zero_amount (read); add/update/remove entry, create /
activate / close / reopen month, add / update / delete
transaction, set debt target (write).
- Resources: quartermaster://budget, quartermaster://month/YYYY-MM.
- Prompts: monthly_review, quick_log, close_prep.
- Typed errors with stable codes.
- Three-phase rollout: read-only -> transaction writes -> entry
and lifecycle writes.
- Four open questions parked at the bottom.
Refs #23
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ten-task TDD plan: service.update_entry, three new routes, template
rewrite, CSS, test migration for the removed notes route, end-to-end
isolation test, manual UI verification, and PR opening.
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>