The server is a placeholder for the one in docs/ARCHITECTURE.md — it serves
public/ and a health endpoint, nothing more. Reads are confined to public/ by
resolving the path and checking it stays inside the root; string-matching
".." is not enough, since encodings and URL normalisation both change what
the string looks like and only the resolved path says which file would open.
The image runs typecheck and tests during build, ships no source or
toolchain, and runs unprivileged.
CI's feed job fails if the event count collapses. A source that quietly stops
yielding events is what a parser-only pipeline is most prone to, and nothing
else would surface it. Everything is offline, so a red pipeline always means
the code changed rather than a wiki being down.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
First non-Game8 parser, and the first source that states region-scoped ends:
wiki.gg emits ISO timestamps with one timer per server region, so Endfield
events now carry exact times and separate Asia / Americas-Europe ends. That
gap is up to 13 hours, which is what regionEnds was built for.
Two sources for one game also exercised merge for real. It caught both
overlaps — including "Bedazzling Dawnstar" against Game8's "Bedazzling
Dawnstar Sign-In" — and flagged that the two disagree on the end date by 70
hours rather than averaging them into a date neither states.
Matching an appended qualifier needed a prefix test, not a subset test: a
subset would also fuse "Gold Clash" with "Gold Rush Clash Royale".
Also fixes build-feed picking fixtures by game rather than by source, which
handed the wiki.gg page to the Game8 parser once Endfield had two.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Endfield was previously written off as undatable. That was wrong: a first
pass only inspected its Duration rows (all "Permanently Available") and its
year-less version grid, and missed an "Event | Schedule & Summary" table
whose cells read "Period: 08/09/26 - 08/30/26 During the event...". Two real
events, with a year.
Adds MM/DD/YY range parsing for that shape. Month-first ordering is not
assumed lightly — Endfield's own version grid reads 01/22, 04/17, 07/16 for
versions 1.0, 1.2 and 1.4, chronological only if the month comes first.
Also stops presenting unlock conditions as descriptions. Several templates
put "Reach Union Level 8" where a blurb would go; filling the summary slot
with text that never says what the event is is worse than leaving it empty.
Wuthering Waves correctly drops to zero summaries as a result.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Three more Game8 templates. ZZZ spans each event name across two rows with a
rowspan, so a flat cell reader sees [title, "Start", date] then ["End",
date]; losing that pairing would silently halve the calendar.
Section matching also had to widen — these pages head their sections
"Featured Events", "List of Events and Schedule" and "Ongoing Events".
Verified independently of the parser: HSR's year-less summary table lists
six ongoing events and all six are captured via their detail rows, so
nothing is silently dropped.
28 events added across the three games.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Honkai: Star Rail and Wuthering Waves state a year on each side of a range
("Aug. 14, 2026 - Aug. 24, 2026"), which the year-on-the-end parser cannot
read.
Both also publish genuinely open-ended events — "Jul. 24, 2026 - End of
4.6", "July 10, 2026 - Permanent". Those keep their real start and report no
end, rather than being dropped or given a guessed one.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Time is this product's subject, so the vocabulary lives in one module:
remaining, progress through a window, and urgency. Urgency is derived from
absolute time left, deliberately independent of proportion — a 90-day event
with three hours left is as urgent as a 3-day one.
An unannounced end is never urgent and has no progress: "we don't know" and
"loads of time" are different facts, and conflating them is what this
product exists to avoid.
Games carry a hue for identity only. Urgency is a separate axis, so one
glance answers both "whose event is this?" and "how long have I got?".
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Collapses the same event seen by two sources, matching on ID or on title
similarity plus start-date proximity. Proximity is the real guard against
false positives, since a rerun reuses its name months later.
Agreement from an independent source raises confidence; the same row seen
twice in one document does not. Disagreement on an end date is recorded as
a conflict for the review gate rather than averaged — splitting the
difference would publish a date neither source asserts.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Parsers are keyed by site template, not game: one Game8 parser serves
every Game8 page, and adapters bind a URL and game to a parser. Adding a
source for a known site is one SOURCES entry.
Game8 uses at least three templates. This handles label/value detail
tables (Genshin, 9 events) and column tables (NTE, 13 events). Its
image-grid schedule has no year and no end date, so Arknights: Endfield
is not supportable from that source and gets no adapter.
canParse is a structural tripwire: a site redesign fails the run loudly
instead of publishing an empty calendar that reads as "no events".
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Every date function returns null rather than inferring a missing year,
month or end date. A source that does not state an end yields
endsAt: null, never a plausible guess — a confidently wrong end date is
the failure this product exists to prevent.
Handles the three formats seen on Game8: "August 12, 2026",
"August 12 - September 21, 2026" (year applies to both ends, rolling the
start back when the range crosses New Year), and a slash datetime range.
Abbreviated months parse too, so a source switching to "Apr. 29" does not
silently drop events.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>