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>
Sections stack vertically under a fixed-width column. Each
section renders a table with two data columns (name, amount)
plus a delete action. The Primary Debt Target now sits between
Debt Minimums and Food and Essentials and uses a dashed rule to
distinguish it from sections that accept freeform entries.
Refs #1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Service-level and route-level coverage. Route tests share an
in-memory SQLite engine across threads via StaticPool and
override the get_session dependency.
Refs #1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Index view renders one card per section plus the Primary Debt
Target card. Adding or deleting a Debt Minimums entry returns the
section partial plus an out-of-band swap for the target card so
the target dropdown stays in sync without a reload.
Refs #1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entry stores one row per section entry (name, amount, timestamps).
DebtTarget is a singleton table (CHECK id = 1) with a nullable
foreign key to Entry using ON DELETE SET NULL so deleting the
referenced Debt Minimums row clears the pointer.
Refs #1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>