Anchor distortion windows at construction (fix cross-midnight + sparse-read bugs)#5
Merged
Conversation
…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>
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.
What & why
Distortion windows were hardwired to re-resolve to "today" on every read
(
TimeWindow→ClockTime.forTodayInMillis), whileClock._offsetaccumulates on asingle 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:
23:00→03:00) stoppeddistorting after midnight, because its start re-resolved to tonight's future
occurrence. This is precisely the README's bedside-clock use case.
after, and only the read-time "today" instance was integrated; the whole excursion was
silently skipped.
The fix
Pin each window once, at
Clockconstruction, to a single fixed[start, end)occurrence (one-shot semantics) rather than re-resolving on every read:
Clockcaptures a_runAnchorat construction and resolves every window against it.TimeWindow.resolveAt(anchorMs)returns the occurrence containing the anchor, else thenext 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.forDateInMillisresolves a wall-clock time on an arbitrary date.recurrence: 'none' | 'daily'seam ('none'implemented), establishing theanchor abstraction that the deferred multi-day / stopwatch / countdown modes build on.
Tests
(distortion continues across midnight).
fully-elapsed window exactly once.
tsc --noEmitand eslintclean.
Notes
Clockis constructed — build the clock at/before the window you intend to bend.Documented in a new README "How windows are scheduled" section.
pre-existing
package-lock.jsonversion drift (0.0.1→0.2.0).Refs #4
🤖 Generated with Claude Code