Follow-up to #285. Three corrections that rode in with the Japanese locale.
1. The ja key-parity check failed the build whenever en.js had a key ja.js
lacked. i18n.js lookup() is already
`registry[lang]?.[key] ?? fallback[key] ?? key`, so an untranslated string
renders in English and nothing is broken by a gap - the only effect was
that adding any English string blocked CI until a Japanese translation
existed. It also singled out one locale; es/fr/de/pt/hi/it were never held
to it, and hi.js is a deliberate skeleton whose own header explains that
every key falls back to English on purpose.
Replaced with two checks over EVERY locale: a locale may not define a key
that English does not (dead weight after a rename, and fixable by whoever
touched the file, whatever language they speak), and coverage is printed
rather than gated. Help tips still have to exist everywhere - that test is
unchanged and still fails.
Current coverage: ja 100%, es 65.5%, fr/de/pt 63.7%, it 59.5%, hi 0%.
2. Applying the strict half to all locales immediately found
add_display.smart_tv_note living in fr, pt, it and de but not in en.js and
referenced by no view - a string dropped from English that left four
translations behind. Removed.
3. The new timezone test restored process.env.TZ by assigning the saved value
back. When TZ was not set to begin with - which is the case in CI - that
assigns undefined, which writes the STRING "undefined"; Node cannot parse
it and silently falls back to UTC for the rest of the process. Every test
after it in that file is date arithmetic. It now deletes the key when it
was previously unset.
4. package-lock.json removed from .gitignore. server/package-lock.json is
tracked, so the rule was inert, but it would silently prevent a future
lockfile and works against the SBOM and reproducible-install setup added
in #282.
Claude-Session: https://claude.ai/code/session_014kfhrUPit5MCqxeTQyqr56
Co-authored-by: Dan Walters <dan.walters@bytetinker.net>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The calendar is the operator's only view of what is scheduled, and it disagreed with the engine in
both directions for the two most-used repeat presets.
The expansion stepped by the recurrence unit from the schedule's original start:
- WEEKLY advanced a whole week at a time, so dayOfWeek never changed and a
FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR rule could only ever match its start day. Created on a Monday
it drew one event a week; created on a Saturday it drew nothing at all.
- The walk began at the original start under a 366-iteration cap, so a schedule begun more than a
year ago never reached the current week and drew nothing.
The engine evaluates day-of-week directly, so those schedules were running Mon-Fri the whole time.
Screens switched content the calendar said was not scheduled.
The expansion now walks the visible range day by day and applies the same rule the engine does, so
the drawing follows what actually happens. Cost is bounded by the window being displayed rather than
by how long ago the schedule was created, and the loop re-anchors the time of day on each step so a
DST boundary does not drift the instances.
Overlap is left to resolve as it already does: a shorter, higher-priority schedule takes over while
it is active and the recurring one resumes underneath when it ends. Nothing here changes what fires
— only what is shown — so this cannot alter live screens.
8 tests: five events for a Mon-Fri rule whichever day it was created on, a two-year-old daily
schedule drawing again, WEEKLY-without-byDay still meaning the start's weekday, INTERVAL honoured,
recurrence_end stopping the drawing, one-offs unaffected, and durations preserved.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL