Skip to content

feat(web): Phase 4c — household & income editor - #199

Merged
chriskelly merged 29 commits into
mainfrom
feat/phase-4c-editor-income
Aug 21, 2026
Merged

chriskelly merged 29 commits into
mainfrom
feat/phase-4c-editor-income

Conversation

@chriskelly

Copy link
Copy Markdown
Owner

Summary

Phase 4c makes the income-side plan domains editable in the split-pane HTMX editor: household demographics + tax, jobs (with sabbaticals and a CalSTRS 2%-at-62 pension preset), Social Security (claim age + SSA XML earnings import), and manual income streams. Executed subagent-driven from docs/superpowers/plans/2026-06-12-phase-4c-editor-income.md (8 tasks, per-task spec+quality review, plus a whole-branch review).

  • Core: new PersonMaxAgeBoundary on the Boundary union + timeline resolution (birth_year + max_age_years, birth_month).
  • Household: demographic PATCH now merges into the full Plan (fixes a data-loss bug that dropped jobs/social_security/tax on save); adds explicit Single/MFJ filing status, residence state, SS/pension taxable fraction, and SS trust factor.
  • Shared plumbing: web/boundaries.py (flat-form ↔ Boundary parsing + indexed/nested-row collectors), a reusable _boundary.html boundary-control macro, and editor_lists.js (row add/remove/reindex + boundary toggling). "Now" stamps a CalendarMonthBoundary at save; "max age" persists symbolically.
  • Editors: jobs (per person, nested sabbaticals, CalSTRS preset with a Richer formula-pension editor (beyond CalSTRS preset) #197 tooltip), Social Security (claim age + XML upload that replaces earnings_record, read-only summary, 422 + partial re-render on bad XML), and manual income streams.
  • Form DTOs stay transport-only; all validation lives on core.* and surfaces as 422. HTML name attributes come from constants.

Test plan

  • make green on the final branch (ruff + format + pyright + full pytest, 434 tests)
  • Per-task TDD (red→green) with spec + code-quality review each task
  • Household PATCH preserves nested jobs/SS/tax; explicit filing status persisted
  • Boundary parsing/round-trip unit tests (all kinds; "now" stamps calendar; max-age symbolic)
  • Jobs persist/clear/CalSTRS-attach; absent-partner → 422
  • SS claim-age persist; valid XML replaces earnings; invalid XML → 422 with prior earnings preserved (seeded)
  • Manual income persist/clear
  • Home page renders all income sections
  • Whole-branch review fixes: nested-template reindex (correct parent index on cloned job rows); blank/invalid numeric fields → 422 (not 500)
  • Manual UI smoke test in the running app (add/remove jobs & sabbaticals across two persons; XML upload)

🤖 Generated with subagent-driven development

Made with Cursor

chriskelly and others added 29 commits July 19, 2026 06:34
Capture Phase 4c brainstorming decisions (CalSTRS pension preset,
SS XML upload, PersonMaxAgeBoundary terminals, section forms) and
point the rebuild index at the new spec. Gitignore personal SSA
statement downloads.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…x fields

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…or banner

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ect parent index

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ible while scrolled

Co-authored-by: Cursor <cursoragent@cursor.com>
Null starts were projection-equivalent to Now at timeline month 0; requiring
an explicit Boundary and defaulting new rows to Now removes the redundant
option while coercing legacy nulls on load.

Co-authored-by: Cursor <cursoragent@cursor.com>
No migration: recreate personal DB from blank template if needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Makes room for more presets later; default is None, CalSTRS 2% at 62 is
the first option.

Co-authored-by: Cursor <cursoragent@cursor.com>
Show whole-dollar currency in money fields, align checkbox labels with
their text, and keep year/month boundary inputs on one line.

Co-authored-by: Cursor <cursoragent@cursor.com>
Boundary calendar dates and household birth months now pick Jan–Dec
instead of bare numbers.

Co-authored-by: Cursor <cursoragent@cursor.com>
Label as Month of Birth with month and year controls on one row.

Co-authored-by: Cursor <cursoragent@cursor.com>
Offer California/New York from tax tables plus No income-tax state, with a
thumbs-up link to #200 for requesting additional states.

Co-authored-by: Cursor <cursoragent@cursor.com>
FastAPI treats empty Form strings as missing, so the prior empty-option
value never updated the plan and California tax kept applying.

Co-authored-by: Cursor <cursoragent@cursor.com>
Display 0–1 fractions as one-decimal percents in the editor, parse them back
on save, and label job raises as above inflation.

Co-authored-by: Cursor <cursoragent@cursor.com>
Surface month-0 total spending and the minimum along the lowest-percentile
path so the summary line is readable without reading the chart.

Co-authored-by: Cursor <cursoragent@cursor.com>
Require person-age years, drop dummy partner defaults, and share people
choices plus the plan_id fixture so the editor has fewer special cases.

Co-authored-by: Cursor <cursoragent@cursor.com>
A partner-referencing boundary crashed with an uncaught AttributeError when the
partner was removed, making the partner impossible to delete. A pension saved
against an open-ended job persisted fine but then raised on every subsequent
simulation. An unmodeled residence state was stored and then silently treated
as zero state tax, and omitted household percent fields quietly reset stored
values to their defaults.

Also caps the SSA statement upload, returns its error partial with a status
htmx will actually swap, stops "add row" from firing a save that cannot pass
validation, and replaces leaked decimal.InvalidOperation text with user-facing
messages.

Co-authored-by: Cursor <cursoragent@cursor.com>
… display

Confirm before partner removal, pension clear/overwrite, and row deletes.
Stop contiguous client reindex in favor of mint-max+1 sparse indices covered
by Python wire tests. Format percents with variable precision that strips
float junk without forcing one decimal place.

Co-authored-by: Cursor <cursoragent@cursor.com>
Embed custom pension tables and preserve echoed USD values so autosave
and delete-then-edit paths stop silently losing data; always confirm
partner removal and drop Max age from stream starts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve previous rows by USD display match when existing_index is stale,
remint indices after successful saves, and cover parser/echo error paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
@chriskelly
chriskelly merged commit 6489390 into main Aug 21, 2026
2 checks passed
@chriskelly
chriskelly deleted the feat/phase-4c-editor-income branch August 21, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant