Skip to content

Anchor distortion windows at construction (fix cross-midnight + sparse-read bugs)#5

Merged
boatmeme merged 1 commit into
mainfrom
fix-cross-midnight
Jun 28, 2026
Merged

Anchor distortion windows at construction (fix cross-midnight + sparse-read bugs)#5
boatmeme merged 1 commit into
mainfrom
fix-cross-midnight

Conversation

@boatmeme

Copy link
Copy Markdown
Owner

What & why

Distortion windows were hardwired to re-resolve to "today" on every read
(TimeWindowClockTime.forTodayInMillis), while Clock._offset accumulates on a
single continuous timeline. Across a calendar boundary these two models conflict, which
produced two latent correctness bugs — one already pinned in the suite as a known
limitation
with a regression target:

  • Cross-midnight drop-out — an overnight window (e.g. 23:00→03:00) stopped
    distorting after midnight, because its start re-resolved to tonight's future
    occurrence. This is precisely the README's bedside-clock use case.
  • Sparse-read gap — read before a window → idle through it (even for days) → read
    after, and only the read-time "today" instance was integrated; the whole excursion was
    silently skipped.

The fix

Pin each window once, at Clock construction, to a single fixed [start, end)
occurrence (one-shot semantics) rather than re-resolving on every read:

  • Clock captures a _runAnchor at construction and resolves every window against it.
  • TimeWindow.resolveAt(anchorMs) returns the occurrence containing the anchor, else the
    next upcoming one — with prior-day look-back so a midnight-wrapping window's current
    occurrence (which may have started yesterday) is found. Legacy today-resolving getters
    are kept for backward compatibility.
  • ClockTime.forDateInMillis resolves a wall-clock time on an arbitrary date.
  • Adds a recurrence: 'none' | 'daily' seam ('none' implemented), establishing the
    anchor abstraction that the deferred multi-day / stopwatch / countdown modes build on.

Tests

  • The two previously-pinned "known limitation" cases now assert the fixed behavior
    (distortion continues across midnight).
  • New regression coverage: cross-midnight continuity, and sparse reads integrating a
    fully-elapsed window exactly once.
  • Full pre-existing suite passes unchanged — 48/48 green, tsc --noEmit and eslint
    clean.

Notes

  • New user-observable behavior: a window now pins to its occurrence as of when the
    Clock is constructed
    — build the clock at/before the window you intend to bend.
    Documented in a new README "How windows are scheduled" section.
  • Version bumped to 0.2.0 (minor — behavior change on a 0.x line); also corrects
    pre-existing package-lock.json version drift (0.0.10.2.0).

Refs #4

🤖 Generated with Claude Code

…and sparse-read bugs

Distortion windows previously re-resolved to "today" on every read
(TimeWindow -> ClockTime.forTodayInMillis), while Clock._offset
accumulated on a single continuous timeline. These two models conflicted
across a calendar boundary and produced two latent correctness bugs:

- Cross-midnight drop-out: an overnight window (e.g. 23:00->03:00) stopped
  distorting after midnight because its start re-resolved to tonight's
  (future) occurrence -- exactly the README's bedside use case.
- Sparse-read gap: a window read before, idled through, then read after
  (even days later) integrated only the read-time "today" instance and
  silently skipped the excursion entirely.

Resolve both by pinning each window once, at Clock construction, to a
single fixed [start, end) occurrence (one-shot semantics):

- Clock captures a _runAnchor at construction and resolves every window
  against it instead of re-resolving on each read.
- TimeWindow.resolveAt(anchorMs) picks the occurrence containing the
  anchor, else the next upcoming one; for midnight-wrapping windows the
  containing occurrence may have begun the prior calendar day. The legacy
  today-resolving getters are retained for backward compatibility.
- ClockTime.forDateInMillis resolves a wall-clock time on an arbitrary
  date so neighbouring-day occurrences can be searched.
- Adds a recurrence: 'none' | 'daily' seam on TimeWindow ('none'
  implemented) for the deferred multi-day/stopwatch/countdown work (#4).

The previously-pinned "known limitation" tests now assert the fixed
behavior, plus new cross-midnight and sparse-read regression tests. Full
existing suite passes unchanged. Bumps to 0.2.0 (user-observable behavior
change) and corrects pre-existing package-lock version drift.

Refs #4

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@boatmeme boatmeme merged commit 15635b7 into main Jun 28, 2026
4 checks passed
@boatmeme boatmeme deleted the fix-cross-midnight branch June 28, 2026 03:31
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.

Generalize the distortion window anchor (unlocks multi-day, stopwatch, and countdown modes)

1 participant