Commit graph

71 commits

Author SHA1 Message Date
3b4b34a84c Merge pull request 'Add MCP proposal document' (#24) from feat/23-mcp-proposal-doc into main
Reviewed-on: #24
2026-04-19 11:27:01 -06:00
archeious
e94b2ef202 Add MCP proposal document
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>
2026-04-17 21:04:55 -06:00
4a51e64112 Merge pull request 'Edit name/amount on budget template entries (#21)' (#22) from feat/21-edit-template-entries into main 2026-04-17 19:28:55 -06:00
archeious
73825bc305 chore: drop dead --ratio attribute, tidy service.py whitespace (#21) 2026-04-17 19:19:51 -06:00
archeious
c2afacfe6e test: end-to-end template-edit isolation across months (#21) 2026-04-17 19:14:14 -06:00
archeious
fe419fe802 refactor(service): remove set_entry_notes, superseded by update_entry (#21) 2026-04-17 19:12:43 -06:00
archeious
1c525f0202 refactor: remove POST /entries/{id}/notes, superseded by save route (#21) 2026-04-17 19:11:01 -06:00
archeious
a814ec6e01 feat(routes): GET /sections/{section} for edit-mode cancel (#21) 2026-04-17 19:08:38 -06:00
archeious
c331211afd feat(routes): POST /entries/{id} saves edits with OOB totals (#21) 2026-04-17 19:05:09 -06:00
archeious
7de8f918fb feat(routes): add GET /entries/{id}/edit for edit-mode toggle (#21) 2026-04-17 19:01:52 -06:00
archeious
c96d3f5d2f fix(ui): restore target card layout, tighten edit-mode UX (#21) 2026-04-17 18:59:04 -06:00
archeious
af276f0eec feat(ui): rewrite budget section row for inline edit mode (#21) 2026-04-17 18:50:46 -06:00
archeious
6f98618b51 feat(service): add update_entry for template rows (#21) 2026-04-17 18:45:12 -06:00
archeious
ab5b88a52b docs: implementation plan for editing template entries (#21)
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.
2026-04-17 18:42:09 -06:00
archeious
aa7ebaa234 docs: spec for editing budget template entries (#21)
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.
2026-04-17 18:37:10 -06:00
archeious
c6126852a2 chore: update CLAUDE.md for session 1 2026-04-17 17:58:20 -06:00
19cac8f08b Backing transaction ledger: Postings replace the applied field (#20) 2026-04-17 17:54:15 -06:00
archeious
1384fae5e4 fix(ui): condense add-entry to a compact disclosure trigger
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>
2026-04-17 17:46:39 -06:00
archeious
2f571b236e fix(notes): make empty notes rows clickable on the budget page
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>
2026-04-17 17:42:35 -06:00
archeious
84d77b804c fix(ledger): give the applied cell room for the transaction count
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>
2026-04-17 17:40:02 -06:00
archeious
e80a3508b6 test: cover posting CRUD and update existing tests to use the ledger
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>
2026-04-17 17:34:53 -06:00
archeious
cca05fe9fc feat(ledger): expandable entry rows with transactions table and add form
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>
2026-04-17 17:34:48 -06:00
archeious
52bc52ec7f feat(ledger): service CRUD for postings and three new routes
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>
2026-04-17 17:34:41 -06:00
archeious
517578f4f3 feat(db): add Posting model, derive MonthEntry.applied, seed opening balances
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>
2026-04-17 17:34:34 -06:00
52e3217aee UI redesign: condensed-sans ledger style with logo in the zero hero (#18) 2026-04-17 17:04:54 -06:00
archeious
761336c71d docs: keep design mockups alongside the shipped UI
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>
2026-04-17 16:58:22 -06:00
archeious
3fa5887d3f test: update selector expecting the retired zero-widget-pair class
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>
2026-04-17 16:58:18 -06:00
archeious
eb689cd9a1 feat(ui): pretty_year_month helper, render month names in titles
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>
2026-04-17 16:58:14 -06:00
archeious
59ccb4db3c feat(ui): page templates for the new layout, logo in the zero hero
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>
2026-04-17 16:58:11 -06:00
archeious
368a4d0741 feat(ui): condensed-sans ledger CSS and Barlow Condensed typography
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>
2026-04-17 16:58:03 -06:00
archeious
4ba4a1ba18 chore(brand): add cleaned logo assets and gitignore source files
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>
2026-04-17 16:57:51 -06:00
961ea46669 Month lifecycle: Planning, Active, Closed with reconciliation gate (#16) 2026-04-17 13:04:59 -06:00
archeious
8fec2fdff7 test: cover lifecycle transitions, balance gate, and edit-locking
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>
2026-04-17 13:04:06 -06:00
archeious
1df3c1c218 feat(lifecycle): transition routes, state badge, and edit-locking UI
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>
2026-04-17 13:04:03 -06:00
archeious
fa9a397d83 feat(lifecycle): activate, close, reopen transitions with validation
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>
2026-04-17 13:03:57 -06:00
archeious
eb7e47bcbe feat(db): add MonthState enum and lifecycle columns
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>
2026-04-17 13:03:53 -06:00
d040b7b66c Notes field per entry (#14) 2026-04-17 12:54:24 -06:00
archeious
28d097cfbf test: cover notes add, update, clear, snapshot copy, and no-deviation
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>
2026-04-17 12:51:36 -06:00
archeious
0f2f549d85 feat(notes): render and wire notes inputs on budget and month pages
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>
2026-04-17 12:51:31 -06:00
archeious
034a8d65f5 feat(notes): service-layer support for notes on entry and month_entry
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>
2026-04-17 12:51:25 -06:00
archeious
4d40843e24 feat(db): add nullable notes column to entry and month_entry
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>
2026-04-17 12:51:22 -06:00
1eecfc3ae8 Section groups with collapsible headers + Sinking Funds (#12) 2026-04-17 12:46:04 -06:00
archeious
0ba7a19972 test: cover group mapping, subtotals, default state, and OOB swaps
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>
2026-04-17 12:44:21 -06:00
archeious
4d9b64d760 feat(groups): render collapsible group details and OOB-swap subtotals
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>
2026-04-17 12:44:18 -06:00
archeious
0c533d62ed feat(groups): group views with subtotals for budget and month
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>
2026-04-17 12:44:12 -06:00
archeious
032c35c75e feat(groups): add sinking_fund section and define four-group layout
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>
2026-04-17 12:44:09 -06:00
496f44cf8c Gitignore docs/wiki/ (#10) 2026-04-17 12:16:26 -06:00
archeious
671a7405cb chore: gitignore the local wiki checkout
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>
2026-04-17 12:14:52 -06:00
85efed8f2c Zero Amount header at the top of budget and month pages (#8) 2026-04-17 12:08:49 -06:00
archeious
ce7e0f2b3f test: cover zero-amount math and OOB rendering
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>
2026-04-17 12:06:45 -06:00