Skip to content

Implement pay-periods phases 1–4: types, engine, months.ts integratio…#78

Merged
code-with-jov merged 12 commits intoopsx_pp_13_99from
claude/apply-pay-periods-phase-4-o7tge
Mar 8, 2026
Merged

Implement pay-periods phases 1–4: types, engine, months.ts integratio…#78
code-with-jov merged 12 commits intoopsx_pp_13_99from
claude/apply-pay-periods-phase-4-o7tge

Conversation

@code-with-jov
Copy link
Owner

…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

claude and others added 8 commits March 1, 2026 05:38
…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>
variant="button"
buttonVariant="bare"
onPress={() => onSelect(currentMonth)}
aria-label={t('Go to current period')}
Copy link
Owner Author

@code-with-jov code-with-jov Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aria-labels are worded for period even though they are shared with month

@code-with-jov code-with-jov merged commit 8ccb92a into opsx_pp_13_99 Mar 8, 2026
@code-with-jov code-with-jov deleted the claude/apply-pay-periods-phase-4-o7tge branch March 8, 2026 18:09
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.

2 participants