Skip to content

Add Off Hours configuration for the Weekly Pace widget#4

Merged
pcvelz merged 6 commits into
pcvelz:mainfrom
BenIsLegit:feat/off-hours-configuration
May 13, 2026
Merged

Add Off Hours configuration for the Weekly Pace widget#4
pcvelz merged 6 commits into
pcvelz:mainfrom
BenIsLegit:feat/off-hours-configuration

Conversation

@BenIsLegit
Copy link
Copy Markdown

@BenIsLegit BenIsLegit commented Apr 23, 2026

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 — dayOfWeek still tracks wall-clock, so D4/7 is still calendar Wednesday.
OffHoursConfig in Settings:

{ enabled: false, startMinutes: 22 * 60, endMinutes: 7 * 60 }

Stored as minutes-since-local-midnight. Off by default. Existing configs parse unchanged (schema default applies on load).
offMsInRange(startMs, endMs, startMin, endMin) in src/utils/off-hours.ts sums off-hours ms that overlap an arbitrary range. Handles same-day and wrap-midnight windows. computeAdjustedExpectedPercent subtracts that from numerator and denominator.
The widget pulls nowMs from windowStartMs + window.elapsedMs (already clamped by buildUsageWindow) rather than a second Date.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 through render().
  • OffHoursMenu.test.ts — helper tests.
    All 109 off-hours tests pass; bun run lint and bun run build clean.
  • Spring-forward: setHours(2, ...) on the nonexistent local 02:00 normalizes to 03:00, so that day undercounts by 1h. Documented in offMsInRange. The default 22:00→07:00 window is unaffected. <1% of a ~105h denominator, once a year — not worth correcting.
  • No way to express a 24h off window; start === end means disabled.

@pcvelz
Copy link
Copy Markdown
Owner

pcvelz commented May 12, 2026

Took the code for a spin — the wall-clock dayOfWeek split and the paired regression test (asserting raw does drift) were the right calls.

Heads up: this branch is on v2.4.1, and main jumped to v2.4.2 a couple weeks back — TypeScript 6, tsconfig.json picked up "types": ["bun"], plus a few new imports landed in ccstatusline.ts. bun run lint fails on the branch as-is because of that, nothing to do with your changes.

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.

@pcvelz pcvelz assigned pcvelz and BenIsLegit and unassigned pcvelz May 12, 2026
Ben added 6 commits May 12, 2026 16:23
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.
@BenIsLegit BenIsLegit force-pushed the feat/off-hours-configuration branch from 5155ccc to 396ee45 Compare May 12, 2026 20:29
@BenIsLegit
Copy link
Copy Markdown
Author

Updated the PR to v2.4.2 @pcvelz

@pcvelz pcvelz merged commit c6c62b2 into pcvelz:main May 13, 2026
3 checks passed
@pcvelz
Copy link
Copy Markdown
Owner

pcvelz commented May 13, 2026

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; (e) toggles it on. Existing configs unchanged.

Thanks for the rebase + the test suite. Will tag you in the release notes.

1 similar comment
@pcvelz
Copy link
Copy Markdown
Owner

pcvelz commented May 13, 2026

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; (e) toggles it on. Existing configs unchanged.

Thanks for the rebase + the test suite. Will tag you in the release notes.

@BenIsLegit BenIsLegit deleted the feat/off-hours-configuration branch May 14, 2026 02:22
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