Replace the local-vs-UTC test with a timestamp that actually straddles UTC
day boundary (00:30 local on 2 September = 22:30Z on the 1st). The original
test was vacuous because both readings agreed on the same calendar day.
Delete the rename-stability test which was a tautology — occurrenceId never
takes a title, so any pure function satisfied it. The real assertion belongs
in a later task where the whole rule is passed in and a rename can be
exercised.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
myevent:<token>#<YYYY-MM-DD>. The token says which recurring thing, the
local day says which time round, and marks, ignores, progress and daily
ticks all key off the whole string — so an occurrence carries its own
completion and its own streak rather than sharing the rule's.
'#' is outside [a-z0-9] and therefore outside CustomEventId, so an
occurrence cannot be written back into the store or survive an import.
That is the guardrail rather than a code path anybody has to remember, and
a test pins it in both directions.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The app can express a window closing and a day repeating; daily.ts counts
in days and stops there. This is the arithmetic under the rung between
them.
Local wall-clock rather than milliseconds, because a reader's own event is
local throughout — a weekly reset set for 09:00 stays at 09:00 across a DST
transition, where adding 7*DAY would move it an hour and drag every later
occurrence with it. Months clamp to the last valid day rather than letting
setMonth roll 31 February into 3 March, which is the same silent shift
readerInstant already refuses on the way in.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>