[WIP] Enable budget planning by pay periods#7226
Draft
code-with-jov wants to merge 38 commits intoactualbudget:masterfrom
Draft
[WIP] Enable budget planning by pay periods#7226code-with-jov wants to merge 38 commits intoactualbudget:masterfrom
code-with-jov wants to merge 38 commits intoactualbudget:masterfrom
Conversation
…n, and tests - Phase 1: Add `payPeriodsEnabled` FeatureFlag, `PayPeriodConfig` type, and `showPayPeriods`/`payPeriodFrequency`/`payPeriodStartDate` to SyncedPrefs - Phase 2: Create `pay-periods.ts` with `isPayPeriod`, `generatePayPeriods` (memoized, ≤87-period assertion), `getPayPeriodFromDate` (year-boundary aware), `nextPayPeriod`, `prevPayPeriod`, `addPayPeriods`, `generatePayPeriodRange`, and `getPayPeriodLabel` - Phase 3: Extend `months.ts` `_parse`, `bounds`, `prevMonth`, `nextMonth`, `addMonths`, `monthFromDate`, `currentMonth`, `rangeInclusive`, `nameForMonth`, `isBefore`, and `isAfter` with optional `PayPeriodConfig` parameter; pay period IDs dispatch to period-aware logic; calendar callers unchanged - Phase 4: Add `pay-periods.test.ts` covering all engine functions and months.ts integration; all 546 existing tests continue to pass https://claude.ai/code/session_015cHkMuws4XaokUtBqa46bv
…gine integration Phase 5 — Server Preferences and Config Loading: - Add `loadPayPeriodConfig()` to `preferences/app.ts` — reads showPayPeriods/payPeriodFrequency/payPeriodStartDate from DB - Store config in `sheet.get().meta().payPeriodConfig` at budget-open time and reload it whenever a pay period pref is saved via `saveSyncedPrefs` - Pass config to `createAllBudgets(config)` in the budget-open flow Phase 6 — Server Budget Engine Integration: - `getBudgetRange`: period-aware when enabled — uses `addPayPeriods` for 3-period/12-period buffers and `generatePayPeriodRange` for the range - `createBudget`: passes config to `bounds` and `prevMonth`; also passes config into `envelopeBudget.createBudget` so the blank-sheet calculation uses `prevPayPeriod` instead of calendar `prevMonth` - `createAllBudgets`: passes config through to `getBudgetRange`/`createBudget` - `triggerBudgetChanges`: reads `payPeriodConfig` from sheet meta and passes it to `handleTransactionChange` (period-aware routing) and `envelopeBudget.handleCategoryChange` (config-aware category setup) - `envelope.ts`: thread config through `getBlankSheet`, `createBlankCategory`, `createBlankMonth`, `createBudget`, and `handleCategoryChange` - Integration tests: `createBudget` with period IDs creates correct sheet names and SQL date bounds; period 1 does not capture period 2 transactions All 554 tests pass. https://claude.ai/code/session_015cHkMuws4XaokUtBqa46bv
Phase 7 — Context and Hook: - Create PayPeriodContext/PayPeriodProvider and usePayPeriodConfig() hook in components/budget/PayPeriodContext.tsx - Thread PayPeriodConfig into budget/index.tsx by reading showPayPeriods, payPeriodFrequency, payPeriodStartDate prefs and payPeriodsEnabled flag; wrap budget tree in <PayPeriodProvider> - Add payPeriodsEnabled: false default in useFeatureFlag.ts Phase 8 — Budget Page Navigation: - budget/index.tsx: use monthUtils.currentMonth(config) for startMonth default; pass config to prewarmAllMonths; use addMonths(month, ±n, config) for prewarm direction heuristic - MonthsContext.tsx: use usePayPeriodConfig() in MonthsProvider so addMonths and rangeInclusive are period-aware - util.ts: prewarmAllMonths accepts optional PayPeriodConfig; replaces subMonths with addMonths(-1, config) - MonthPicker.tsx: all navigation (prev/next/today) and range calculations use config-aware month utils Phase 9 — Budget Column Display: - MonthPicker shows 'PP N' short labels via getPayPeriodLabel when a period ID is in the range; falls back to 'MMM' for calendar months - Envelope and tracking BudgetSummary: displayMonth uses getPayPeriodLabel (long format) for period IDs; prevMonthName uses short format - EnvelopeBudgetContext and TrackingBudgetContext: currentMonth uses monthUtils.currentMonth(config) for period-aware highlighting Phase 10 — Settings UI: - Create PayPeriodSettings component with enable toggle, frequency selector (weekly/biweekly/monthly), start date input, validation (start date and frequency required before enabling), and frequency-change warning - Add to settings/index.tsx gated on payPeriodsEnabled feature flag https://claude.ai/code/session_015cHkMuws4XaokUtBqa46bv
- Add e2e/pay-periods.test.ts with 11 tests covering all user-facing pay period scenarios from pay-period-ui spec (settings visibility, frequency selector, validation, enable/disable, navigation, labels) - Add data-testid="budget-month-header" to both BudgetSummary components and update headers to show PP N labels for pay period IDs - Add aria-labels to MonthPicker nav buttons for Playwright targeting: "Go to current period", "Previous period", "Next period" - Wrap PayPeriodSettings in <View data-testid="pay-period-settings"> for reliable test targeting; use Select component consistent with app - Add payPeriodsEnabled FeatureToggle in Experimental.tsx so the flag can be enabled via settings UI in tests https://claude.ai/code/session_015cHkMuws4XaokUtBqa46bv
When pay periods are enabled, createAllBudgets passes a calendar date (from the earliest transaction) as the start argument while currentMonth returns a pay period ID. The old condition required BOTH start AND end to be pay period IDs, so a calendar start would fall through to the calendar path which called monthUtils.addMonths on a pay period ID, triggering the '_parse requires PayPeriodConfig' error. Fix by normalising any calendar date/month to a pay period ID at the top of the pay-period branch, before the buffer arithmetic. https://claude.ai/code/session_015cHkMuws4XaokUtBqa46bv
Three classes of bugs discovered during smoke testing and fixed: 1. subMonths missing config param (months.ts) — unlike addMonths/prevMonth/ nextMonth, subMonths had no config? parameter, causing _parse to throw whenever a pay period ID was passed. Added config? and dispatches to addPayPeriods(month, -n, config) when isPayPeriod(month). 2. BudgetSummaries and DynamicBudgetTable not consuming PayPeriodConfig — both components called subMonths/addMonths/prevMonth/nextMonth with period IDs but without config. Added usePayPeriodConfig() to each and propagated config to all call sites (including the 0/←/→ hotkeys). 3. onShowActivity drill-through crash (Value.tsx RangeError) — clicking Spent passed a pay period ID as a month-equality filter, which subfieldFromFilter misidentified as a calendar month (both are length-7 strings), causing parseISO to produce Invalid Date. Now branches on isPayPeriod: pay period path emits gte/lte date-range conditions using bounds(month, config); calendar path is unchanged. OpenSpec updates: added subMonths to engine spec affected-functions list with scenarios; added two new requirements to UI spec (internal components must consume config, drill-through must use date-range filter); added tasks 3.5a, 9.4, 9.5, 9.6 to tasks.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Catch up to latest changes
🐞 Fix mobile transactions colors - fixes actualbudget#7042 (actualbudget#7047)
…rovider config type
Add OpenSpec proposal for pay-period-label-improvements
…ase-4-o7tge Implement pay-periods phases 1–4: types, engine, months.ts integratio…
…ate-range
- Change `getPayPeriodLabel` third parameter from `short: boolean` to
`format: 'picker' | 'summary'` with optional `locale?: Locale`
- Picker format produces `{monthLetter}{withinMonthCount}` (e.g. J1, F2)
using the locale-aware first char of the start month's MMM abbreviation
- Summary format produces `{startDate} - {endDate} (PP{n})` (e.g. Jan 4 - Jan 17 (PP1))
- Update `nameForMonth` in months.ts to forward locale and use new format strings
- Update both BudgetSummary components to use `format='summary'` with locale
- Update unit tests with new expected label formats and add additional coverage
- Update E2E tests to match new summary label regex
https://claude.ai/code/session_018hZh1bX4qEexFQAWDPb1xy
…facts Includes pay-period engine, UI components, budget integration, settings, openspec specs/design files, and opsx command updates that were already present but unstaged. https://claude.ai/code/session_018hZh1bX4qEexFQAWDPb1xy
Proposal, design, and tasks for bringing the mobile budget page (BudgetPage, CategoryPage, CategoryTransactions) in line with the desktop pay-period-aware implementation. https://claude.ai/code/session_01C6Li6m7S81FfFebW26gi35
- BudgetPage: read pay period prefs, build PayPeriodConfig, wrap in PayPeriodProvider; fix currentMonth, prev/next navigation, today button visibility, notes modal label to all pass/use config - MonthSelector: accept payPeriodConfig prop; use getPayPeriodLabel for period display; fix prev/next bounds via period start-date integer comparison against calendar bounds - CategoryPage: build PayPeriodConfig, wrap in PayPeriodProvider, fix header label for pay period IDs - CategoryTransactions: use usePayPeriodConfig() from context; switch getCategoryMonthFilter to date-range filter for pay period IDs https://claude.ai/code/session_01C6Li6m7S81FfFebW26gi35
Auto-fixed by oxlint during lint:fix run. https://claude.ai/code/session_01C6Li6m7S81FfFebW26gi35
Catch up to latest changes
Catch up to latest changes
Catch up to latest changes
…el-improvements-ZsC5e Claude/apply pay period label improvements zs c5e
✅ Deploy Preview for actualbudget ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
|
👋 Hello contributor! We would love to review your PR! Before we can do that, please make sure:
We do this to reduce the TOIL the core contributor team has to go through for each PR and to allow for speedy reviews and merges. For more information, please see our Contributing Guide. |
✅ Deploy Preview for actualbudget-storybook ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for actualbudget-website ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…dgeting-5l2kM Lint fixes
This comment has been minimized.
This comment has been minimized.
Catch up to latest changes
Contributor
|
VRT tests ❌ failed. View the test report. To update the VRT screenshots, comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Users are paid on schedules that don't align with calendar months — weekly, biweekly, or on specific dates — making month-based budgeting disconnected from their actual financial reality. Pay Periods brings the budget unit in line with the paycheck, so users can plan, track, and carry over funds within the period they actually have money to spend.
Related Links:
#5730
#5849
#975
#6412
Testing
packages/desktop-client/e2e/pay-periods.test.ts
packages/desktop-client/e2e/pay-periods.mobile.test.ts
Checklist
Bundle Stats
View detailed bundle stats
desktop-client
Total
Changeset
src/components/settings/PayPeriodSettings.tsxhome/runner/work/actual/actual/packages/loot-core/src/shared/pay-periods.tssrc/components/budget/PayPeriodContext.tsxsrc/components/mobile/budget/CategoryPage.tsxhome/runner/work/actual/actual/packages/loot-core/src/shared/months.tssrc/components/budget/index.tsxsrc/components/mobile/budget/CategoryTransactions.tsxsrc/components/mobile/budget/BudgetPage.tsxsrc/components/budget/MonthsContext.tsxsrc/components/budget/MonthPicker.tsxsrc/components/budget/tracking/TrackingBudgetContext.tsxsrc/components/budget/envelope/EnvelopeBudgetContext.tsxsrc/hooks/useFeatureFlag.tssrc/components/budget/DynamicBudgetTable.tsxsrc/components/budget/BudgetSummaries.tsxsrc/components/settings/index.tsxsrc/components/budget/envelope/budgetsummary/BudgetSummary.tsxsrc/components/budget/tracking/budgetsummary/BudgetSummary.tsxsrc/components/settings/Experimental.tsxsrc/components/budget/util.tsnode_modules/date-fns/differenceInDays.jsView detailed bundle breakdown
Added
No assets were added
Removed
No assets were removed
Bigger
Smaller
Unchanged
loot-core
Total
Changeset
home/runner/work/actual/actual/packages/loot-core/src/shared/pay-periods.tshome/runner/work/actual/actual/packages/loot-core/src/shared/months.tshome/runner/work/actual/actual/packages/loot-core/src/server/preferences/app.tshome/runner/work/actual/actual/packages/loot-core/src/server/budget/base.tshome/runner/work/actual/actual/packages/loot-core/src/server/budget/envelope.tshome/runner/work/actual/actual/packages/loot-core/src/server/budgetfiles/app.tshome/runner/work/actual/actual/packages/loot-core/src/server/budget/app.tshome/runner/work/actual/actual/packages/loot-core/src/server/encryption/app.tsView detailed bundle breakdown
Added
Removed
Bigger
No assets were bigger
Smaller
No assets were smaller
Unchanged
No assets were unchanged
api
Total
Changeset
home/runner/work/actual/actual/packages/loot-core/src/shared/pay-periods.tshome/runner/work/actual/actual/packages/loot-core/src/shared/months.tshome/runner/work/actual/actual/packages/loot-core/src/server/preferences/app.tshome/runner/work/actual/actual/packages/loot-core/src/server/budget/base.tshome/runner/work/actual/actual/packages/loot-core/src/server/budget/envelope.tshome/runner/work/actual/actual/packages/loot-core/src/server/budgetfiles/app.tshome/runner/work/actual/actual/packages/loot-core/src/server/budget/app.tshome/runner/work/actual/actual/packages/loot-core/src/server/encryption/app.tsView detailed bundle breakdown
Added
No assets were added
Removed
No assets were removed
Bigger
Smaller
No assets were smaller
Unchanged
cli
Total
View detailed bundle breakdown
Added
No assets were added
Removed
No assets were removed
Bigger
No assets were bigger
Smaller
No assets were smaller
Unchanged