Due Dates & Cash Flow Calendar
Add due-date awareness to bills and loans so the app can show when money leaves each account — not just how much. Culminates in a calendar view that plots paycheck deposits alongside obligation withdrawals, giving users a time-aware picture of their cash flow.
Problem Statement
1. Bills and loans have no "when"
Bills and loans track how much and how often, but not which day of the cycle the payment is due. A user with $1,200/month in bills from a checking account knows they need ~$553.85 per paycheck to cover it, but they don't know whether all $1,200 is due on the 1st, spread across the month, or back-loaded on the 28th. This matters: if most bills hit the account before the second paycheck of the month arrives, the account can go negative even though the monthly math is fine.
2. No temporal view of cash flow
The app's existing views are all amount-centric (per-paycheck, monthly average, annual). There is no view that plots events on a timeline. Users who want to answer "will my checking account be positive on the 15th?" have to do that math outside the app.
3. The data model is almost ready
Bill already has an orphaned dueDay?: number field (defined in src/types/obligations.ts but never exposed in the UI). Loan has startDate and paymentFrequency but no payment day. The plumbing is partially there — it just needs to be surfaced and extended.
Solution Overview
Three phases, each independently useful:
- Due-date fields on bills and loans — surface
dueDay in the bill form and add paymentDueDay to the loan form. Optional field; everything continues to work without it. No file format migration needed for bills (field already exists). Loans need one new optional field.
- Cash flow projection engine — a pure utility that takes bill/loan due dates, paycheck dates, and account assignments, then produces a chronological ledger of deposits (paychecks) and withdrawals (bills/loans) for any date range. This is the analytical core.
- Calendar tab view — a new
calendar tab in the dashboard that renders the cash flow projection as an interactive monthly calendar. Shows paycheck deposits in green, bill/loan withdrawals in red, running account balances, and highlights days where an account would go negative.
What this plan does NOT do
- No automatic bill-pay scheduling or reminders/notifications. This is a planning tool, not a payment tool.
- No recurring-event engine beyond what
payCalendar.ts already provides. Bill due dates are a simple day-of-month (or day-of-cycle for non-monthly frequencies); we don't need a full calendar recurrence engine.
- No changes to the stable allocation math from Phase 4 of v0.5.0. The per-paycheck allocation is still the recommended approach; the calendar view just shows the user whether that approach holds up temporally.
- No multi-month or yearly calendar panorama in Phase 3. The first version shows one month at a time (consistent with how
PeriodSelector already works in calendar-accurate mode).
Design constraints
dueDay is optional everywhere. Users who don't set due dates still get all existing behavior unchanged. Calendar view degrades gracefully: bills without a dueDay are shown as "unscheduled" in a separate section.
- No file format migration.
dueDay already exists on Bill. Loan gains one new optional field — older plan files simply lack it and default to undefined.
- The cash flow engine is a pure utility (
src/utils/cashFlowProjection.ts) with no React dependencies. It can be tested exhaustively in isolation.
Phase 1: Due-Date Fields on Bills and Loans
Parent status: [ ] Planned
1a. Surface dueDay in the Bill form
1b. Add paymentDueDay to Loan
1c. Tests
Done definition:
Phase 2: Cash Flow Projection Engine
Parent status: [ ] Planned
2a. Core utility
2b. Tests
Done definition:
Phase 3: Calendar Tab View
Parent status: [ ] Planned
3a. Tab registration
3b. Calendar view component
3c. Integration with existing features
3d. Tests
Done definition:
Effort Estimate & Risk Analysis
Scope summary
| Phase |
New files |
Modified files |
Estimated complexity |
| 1 — Due-date fields |
0–1 (ordinal util) |
4–5 (types, BillsManager, LoansManager, BudgetContext, search) |
Low–Medium |
| 2 — Cash flow engine |
2 (cashFlowProjection.ts, cashFlow types) |
0 |
Medium |
| 3 — Calendar view |
3+ (CalendarView component, CSS, index) |
3–4 (tabManagement, PlanDashboard, tab registration) |
High |
Risk areas
- Non-monthly bill frequencies and due dates: Weekly and bi-weekly bills don't have a natural "day of month." Phase 1 simply hides the due-day field for those frequencies. Phase 2's
generateBillDueDates only generates events for monthly+ bills. This is an intentional scope cut — the vast majority of household bills are monthly.
- Calendar view complexity: The calendar grid + day detail panel + account filtering + negative balance highlighting is the largest single UI deliverable. It should be built incrementally — grid first, then events, then detail panel, then warnings.
- Performance:
projectCashFlow scans one month at a time for display but could be asked for multi-month ranges. Keep it lazy (compute only the displayed month). Memoize with useMemo keyed on [selectedMonth, bills, loans, paySettings, accounts].
- Interaction with calendar-accurate mode: The calendar view always uses real dates (it is the calendar). The existing
calendarAccurate toggle on other views is independent. No conflict, but documentation should clarify the relationship.
- Backward compatibility:
dueDay on Bill already exists and is optional. paymentDueDay on Loan is a new optional field. No migration needed. Older plan files load fine with both fields as undefined.
Phase ordering
Phases are sequential and each is independently shippable:
- Phase 1 can ship alone — users see due dates on their bills/loans even without the calendar view.
- Phase 2 can ship with Phase 1 — the engine runs in tests and powers the buffer recommendations, even if the calendar UI isn't built yet.
- Phase 3 requires Phases 1 and 2 — it's the visualization layer on top.
Final Exit Checklist
Due Dates & Cash Flow Calendar
Problem Statement
1. Bills and loans have no "when"
Bills and loans track how much and how often, but not which day of the cycle the payment is due. A user with $1,200/month in bills from a checking account knows they need ~$553.85 per paycheck to cover it, but they don't know whether all $1,200 is due on the 1st, spread across the month, or back-loaded on the 28th. This matters: if most bills hit the account before the second paycheck of the month arrives, the account can go negative even though the monthly math is fine.
2. No temporal view of cash flow
The app's existing views are all amount-centric (per-paycheck, monthly average, annual). There is no view that plots events on a timeline. Users who want to answer "will my checking account be positive on the 15th?" have to do that math outside the app.
3. The data model is almost ready
Billalready has an orphaneddueDay?: numberfield (defined insrc/types/obligations.tsbut never exposed in the UI).LoanhasstartDateandpaymentFrequencybut no payment day. The plumbing is partially there — it just needs to be surfaced and extended.Solution Overview
Three phases, each independently useful:
dueDayin the bill form and addpaymentDueDayto the loan form. Optional field; everything continues to work without it. No file format migration needed for bills (field already exists). Loans need one new optional field.calendartab in the dashboard that renders the cash flow projection as an interactive monthly calendar. Shows paycheck deposits in green, bill/loan withdrawals in red, running account balances, and highlights days where an account would go negative.What this plan does NOT do
payCalendar.tsalready provides. Bill due dates are a simple day-of-month (or day-of-cycle for non-monthly frequencies); we don't need a full calendar recurrence engine.PeriodSelectoralready works in calendar-accurate mode).Design constraints
dueDayis optional everywhere. Users who don't set due dates still get all existing behavior unchanged. Calendar view degrades gracefully: bills without adueDayare shown as "unscheduled" in a separate section.dueDayalready exists onBill.Loangains one new optional field — older plan files simply lack it and default to undefined.src/utils/cashFlowProjection.ts) with no React dependencies. It can be tested exhaustively in isolation.Phase 1: Due-Date Fields on Bills and Loans
Parent status:
[ ] Planned1a. Surface
dueDayin the Bill formBillsManager.tsx.weekly,bi-weekly), the field label should adapt: "Day of cycle due" or be hidden if it doesn't make sense for the frequency (weekly/bi-weekly have no fixed calendar day). Only show formonthly,quarterly,semi-annual,yearlyfrequencies.payCalendar.ts). Use 1–31 with clamping for consistency with existing patterns.dueDaythroughaddBill/updateBillinBudgetContext.dueDayon bill cards in BillsManager when set.dueDayto bill search module (src/utils/searchModules/) if one exists, so bills can be searched by due date.1b. Add
paymentDueDayto LoanpaymentDueDay?: numberto theLoaninterface insrc/types/obligations.ts.dueDayon Bill: day of month (1–31), optional.LoansManager.tsx.paymentDueDayprominently on loan cards when set.paymentDueDaythroughaddLoan/updateLoaninBudgetContext.1c. Tests
paymentDueDayon loans load without error (backward compat).dueDaystill render identically to current behavior.Done definition:
Phase 2: Cash Flow Projection Engine
Parent status:
[ ] Planned2a. Core utility
Create
src/utils/cashFlowProjection.tswith:generateBillDueDates(bill, rangeStart, rangeEnd): CashFlowEvent[]dueDay,amount,frequency, andaccountId, return all due-date events in the range.dueDay(clamped to end of month).dueDayreturn an empty array (they are "unscheduled").generateLoanPaymentDates(loan, rangeStart, rangeEnd): CashFlowEvent[]paymentDueDayandmonthlyPayment(or per-frequency amount frompaymentBreakdown).generatePaycheckDeposits(paySettings, accounts, rangeStart, rangeEnd): CashFlowEvent[]generatePaycheckDates()frompayCalendar.ts.accountAllocation.ts).projectCashFlow(events, startingBalances?): CashFlowLedgerstartingBalancesis an optionalRecord<accountId, number>for initial account balances (can default to 0).{ date, events[], balances: Record<accountId, number> }.findNegativeBalanceDays(ledger): NegativeBalanceWarning[]Define types in
src/types/cashFlow.ts:2b. Tests
cashFlowProjection.test.ts:dueDay: 15, range = one month → exactly one event on the 15th.dueDay: 1, range = 6 months → exactly 2 events.dueDay→ returns empty array.dueDay: 31in February → clamped to 28 (or 29 in leap year).payCalendar.ts.Done definition:
projectCashFlowproduces correct running balances for a known bi-weekly paycheck + monthly bill scenario that can be verified by hand.Phase 3: Calendar Tab View
Parent status:
[ ] Planned3a. Tab registration
'calendar'toTabIdunion insrc/utils/tabManagement.ts.'calendar'toVALID_TABSinPlanDashboard.tsx.CalendarDaysfrom lucide), label "Calendar", and default position (after Savings, or at end).3b. Calendar view component
Create
src/components/tabViews/CalendarView/CalendarView.tsx:PeriodSelectorpattern from Phase 3 of v0.5.0 (month/year nav with prev/next arrows and "Today" button).dueDay/paymentDueDayin a separate "Unscheduled Obligations" list so the user knows these aren't plotted.Create
src/components/tabViews/CalendarView/CalendarView.csswith component-scoped styles.Create
src/components/tabViews/CalendarView/index.tsbarrel export.3c. Integration with existing features
calendarAccurateandpaychecksInPeriodstate from PlanDashboard into CalendarView (so paychecks appear even without calendar-accurate mode — the calendar always uses real dates).firstPaycheckDateto function. If missing, show an empty state directing the user to Pay Settings (same pattern as Phase 3 of v0.5.0).calculateStableAllocationfromaccountAllocation.tsto determine per-paycheck deposit amounts per account.onNavigateToBillspattern). Same for loans.3d. Tests
firstPaycheckDateis missing.dueDay.Done definition:
Effort Estimate & Risk Analysis
Scope summary
Risk areas
generateBillDueDatesonly generates events for monthly+ bills. This is an intentional scope cut — the vast majority of household bills are monthly.projectCashFlowscans one month at a time for display but could be asked for multi-month ranges. Keep it lazy (compute only the displayed month). Memoize withuseMemokeyed on[selectedMonth, bills, loans, paySettings, accounts].calendarAccuratetoggle on other views is independent. No conflict, but documentation should clarify the relationship.dueDayon Bill already exists and is optional.paymentDueDayon Loan is a new optional field. No migration needed. Older plan files load fine with both fields asundefined.Phase ordering
Phases are sequential and each is independently shippable:
Final Exit Checklist
[x] Doneor explicitly deferred.