Add Off Hours configuration for the Weekly Pace widget#4
Conversation
|
Took the code for a spin — the wall-clock Heads up: this branch is on v2.4.1, and main jumped to v2.4.2 a couple weeks back — TypeScript 6, Maintainer edits is on, so happy to rebase onto main and take it from there. Or if you'd rather do the rebase yourself, no problem — flag it when ready. |
OffHoursConfig in Settings (enabled/startMinutes/endMinutes, defaults 22:00->07:00 disabled) lets users declare a recurring daily window where they won't use Claude. Weekly Pace now subtracts off-hours ms from both numerator and denominator when computing expected %, so the delta holds steady through sleep instead of silently decaying as wall-clock advances. dayOfWeek label still tracks raw wall-clock (decoupled in computePace) so D4/7 continues to mean calendar Wednesday, regardless of off-hours.
New OffHoursMenu screen with enable toggle, start/end HH:MM text inputs, reset-to-defaults, and a live preview of the off-window duration and resulting active hours per week. Wired into MainMenu between Global Overrides and Install, plus App screen routing.
- src/utils/__tests__/off-hours.test.ts (39 tests): offMsInRange overlap math for same-day, wrap-midnight, multi-day, and edge-case windows, plus the sleep-stability property (expected % does NOT drift across the off window) and the asymmetric ratio at the wall-clock midpoint. - src/widgets/__tests__/WeeklyPace.test.ts: 5 integration tests that verify the off-hours path in render() — delta stability across sleep, baseline drift when disabled, day label always from wall-clock, and graceful fallback when weeklyResetAt is missing. - src/tui/components/__tests__/OffHoursMenu.test.ts: helper tests for describeOffWindow and formatActiveHoursPerWeek. Drive-by: CurrentWorkingDir.test.ts now sets the required offHours field on its manual Settings fixture. OffHoursMenu inlines settings.offHours instead of a needless null-fallback helper (the type guarantees it).
- Deduplicate DEFAULT_OFF_HOURS. Single source of truth is now
OffHoursConfigSchema.parse({}) exported from types/Settings.ts;
both the schema's top-level .default(...) and the TUI "reset"
action consume it. Tweaking any default now requires one edit.
- Document the DST limitation in offMsInRange's JSDoc: on spring-
forward, setHours(2,...) on the nonexistent hour is normalized to
03:00, undercounting that day's off-period by 1h. The default
22:00 → 07:00 window is unaffected. Worst-case skew is <1% of a
~105h denominator once or twice a year — well below the ±5%
"On Pace" band, so not worth correcting today.
- Size the iteration cap proportionally to the range instead of
hardcoding 10 — removes a hidden 7-day assumption if a future
caller passes a longer range.
- Flip the null-safety order in computeAdjustedExpectedPercent so
TS narrowing leads, with a comment explaining why both checks
are present.
- Document the nowMs reconstruction in WeeklyPace.render: it's
derived from window.elapsedMs (already clamped) rather than a
second Date.now() call. Warn future refactors not to repurpose
it as the real wall-clock instant.
Auto-generated by eslint --fix. No behavior changes: - Add trailing newlines to four off-hours files - Reorder imports in OffHoursMenu.tsx and off-hours.test.ts to match the repo's @stylistic/import-x/order rule
The rebase onto v2.4.2 introduced upstream menu-structure tests that didn't account for the offHours entry. Add 'offHours' to all expected menu-value arrays and bump selection indices by 1.
5155ccc to
396ee45
Compare
|
Updated the PR to v2.4.2 @pcvelz |
|
Merged on main as c6c62b2 — going out as v2.4.3. DST note plays out exactly as commented, the raw-drift baseline test catches what it should. Off Hours is off by default; Thanks for the rebase + the test suite. Will tag you in the release notes. |
1 similar comment
|
Merged on main as c6c62b2 — going out as v2.4.3. DST note plays out exactly as commented, the raw-drift baseline test catches what it should. Off Hours is off by default; Thanks for the rebase + the test suite. Will tag you in the release notes. |
Adds an optional "off hours" window (e.g.
22:00 → 07:00) that Weekly Pace subtracts from both sides of its expected-% calc. Practical effect: while you're asleep, expected-% stops climbing, so delta doesn't drift negative overnight.Only the delta adjusts —
dayOfWeekstill tracks wall-clock, soD4/7is still calendar Wednesday.OffHoursConfiginSettings:Stored as minutes-since-local-midnight. Off by default. Existing configs parse unchanged (schema default applies on load).
offMsInRange(startMs, endMs, startMin, endMin)insrc/utils/off-hours.tssums off-hours ms that overlap an arbitrary range. Handles same-day and wrap-midnight windows.computeAdjustedExpectedPercentsubtracts that from numerator and denominator.The widget pulls
nowMsfromwindowStartMs + window.elapsedMs(already clamped bybuildUsageWindow) rather than a secondDate.now()— noted inline so nobody "fixes" it later.New Off Hours menu:
(e)toggle,(s)/(n)edit start/end,(r)reset. HH:MM input with validation. Shows computed active-hours/week as a sanity check.off-hours.test.ts— 39 tests, covers overlap math, wrap-midnight, multi-day ranges, the "stable during sleep" property, plus a paired baseline that asserts raw does drift so a regression in either direction breaks.WeeklyPace.test.ts— 5 integration tests throughrender().OffHoursMenu.test.ts— helper tests.All 109 off-hours tests pass;
bun run lintandbun run buildclean.setHours(2, ...)on the nonexistent local 02:00 normalizes to 03:00, so that day undercounts by 1h. Documented inoffMsInRange. The default 22:00→07:00 window is unaffected. <1% of a ~105h denominator, once a year — not worth correcting.start === endmeans disabled.