Implement pay-periods phases 1–4: types, engine, months.ts integratio…#78
Merged
code-with-jov merged 12 commits intoopsx_pp_13_99from Mar 8, 2026
Merged
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>
code-with-jov
commented
Mar 8, 2026
| variant="button" | ||
| buttonVariant="bare" | ||
| onPress={() => onSelect(currentMonth)} | ||
| aria-label={t('Go to current period')} |
Owner
Author
There was a problem hiding this comment.
These aria-labels are worded for period even though they are shared with month
…rovider config type
Add OpenSpec proposal for pay-period-label-improvements
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.
…n, and tests
payPeriodsEnabledFeatureFlag,PayPeriodConfigtype, andshowPayPeriods/payPeriodFrequency/payPeriodStartDateto SyncedPrefspay-periods.tswithisPayPeriod,generatePayPeriods(memoized, ≤87-period assertion),getPayPeriodFromDate(year-boundary aware),nextPayPeriod,prevPayPeriod,addPayPeriods,generatePayPeriodRange, andgetPayPeriodLabelmonths.ts_parse,bounds,prevMonth,nextMonth,addMonths,monthFromDate,currentMonth,rangeInclusive,nameForMonth,isBefore, andisAfterwith optionalPayPeriodConfigparameter; pay period IDs dispatch to period-aware logic; calendar callers unchangedpay-periods.test.tscovering all engine functions and months.ts integration; all 546 existing tests continue to passhttps://claude.ai/code/session_015cHkMuws4XaokUtBqa46bv