Commit Graph
321 Commits
Author SHA1 Message Date
Lucas WintherandClaude Opus 5 90be312942 feat: parse both-year ranges and open-ended events
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]>
2026-08-15 00:47:50 +02:00
Lucas WintherandClaude Opus 5 107179acfa feat: ask which games you play on first run
A calendar full of games you don't play is worse than an empty one — it
buries the thing you came for.

Nothing is preselected; the button stays disabled until a game is picked,
which is clearer than guessing and hoping the reader notices. The choice is
stored as hiddenGames, the inverse, so a game added later shows up by
default rather than staying invisible.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:39:42 +02:00
Lucas WintherandClaude Opus 5 e0ba53f369 feat: show an excerpt and make rows respond to hover and press
The whole row is now one click target, which gives a generous tap area and
a single unambiguous hover region.

Hover transitions are fast and eased out (110-160ms) so a row responds on
the way in and settles on the way out — a slow symmetric fade is what makes
an interface feel laggy even at full framerate. The meter is the element
that visibly wakes up, since it is the signature. Press feedback lands on
touch as well as mouse, and every hover rule sits behind `hover: hover` so a
tap never leaves a row stuck looking hovered.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:39:42 +02:00
Lucas WintherandClaude Opus 5 6fffd283a5 feat: extract event descriptions from Game8 detail tables
Column tables already carried a description column, but label/value detail
tables did not — Genshin events had no blurb at all. The page follows each
detail table with a sentence of prose, so take that, skipping the
call-to-action paragraphs, which are navigation rather than description.

Summary coverage goes from 13/22 events to 22/22.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:39:42 +02:00
Lucas WintherandClaude Opus 5 62f673161b docs: rewrite the README for the working app
Covers what's built versus specified, how to run it, the parser/adapter/
merge layering, and why Arknights: Endfield has no source.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:30:40 +02:00
Lucas WintherandClaude Opus 5 b1d18eaf59 feat: add the Event Clock interface
Opens on the single event closest to expiring, at a size nothing else
competes with — the reader arrives with one question. Below it, live events
ordered by what ends soonest, then a quiet timeline view with one lane per
game.

Countdowns use tabular figures so ticking never reflows the row.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:28:45 +02:00
Lucas WintherandClaude Opus 5 ec8cc97c1a feat: add the depletion meter
A discrete tick-strip rather than a smooth progress bar, borrowed from the
stamina meters these games all use — ticks read as a finite resource visibly
draining, which is the right metaphor for a limited-time event.

An event with no announced end gets a hatched strip, never a full meter.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:28:45 +02:00
Lucas WintherandClaude Opus 5 ac165f0df7 feat: add local-only completion and preference state
Completions and prefs live in localStorage and never reach the server —
there is no account and nothing to log into. Reads never throw, so a corrupt
value costs a preference rather than the whole screen.

Import merges and never removes: a completion present on either side stays
completed, because nothing else holds a copy to restore from.

The feed client refuses a schemaVersion it does not know rather than
guessing at unfamiliar fields.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:28:45 +02:00
Lucas WintherandClaude Opus 5 49d52e3587 chore: add React, Tailwind and the client build
Tailwind v4 @theme carries the two colour axes as tokens: a deep blue-black
ground, per-game hues for identity, and a heat ramp for urgency that is
monotonic in salience (dim, blue, amber, red).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:28:17 +02:00
Lucas WintherandClaude Opus 5 5964f4b2fa feat: build a static event feed from fixtures
Emits exactly the shape GET /api/events.json will serve, so the client can
be developed against real parsed data before the server and database land.
Reads fixtures only, never the network.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:28:17 +02:00
Lucas WintherandClaude Opus 5 45ad5b39da feat: add shared time, games and feed contracts
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]>
2026-08-15 00:28:17 +02:00
Lucas WintherandClaude Opus 5 3b2feadf5d docs: rewrite CLAUDE.md for the current codebase
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:11:08 +02:00
Lucas WintherandClaude Opus 5 e9f69ea8dc chore: update agents and skills for the parser-only pipeline
Removes the extraction-evaluator agent and reframes adapter-author around
reusing an existing site parser before writing a new one.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:11:08 +02:00
Lucas WintherandClaude Opus 5 3ea7286f56 docs: drop the LLM extraction layer, document multi-source ingestion
Event data is parsed deterministically; there is no model call, API key or
per-run cost anywhere in the pipeline. A source that cannot be parsed
deterministically gets no adapter rather than an inference fallback.

Documents the parser/adapter/merge split, records that Game8 uses three
page templates, and adds Neverness to Everness.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:11:08 +02:00
Lucas WintherandClaude Opus 5 493fc9f22a feat: merge events across multiple sources per game
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]>
2026-08-15 00:11:08 +02:00
Lucas WintherandClaude Opus 5 489ce92ef5 feat: add Game8 parser with Genshin and NTE adapters
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]>
2026-08-15 00:10:53 +02:00
Lucas WintherandClaude Opus 5 caefe6b0d7 feat: add HTML table reader and date parsing
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]>
2026-08-15 00:10:53 +02:00
Lucas WintherandClaude Opus 5 e8293c7e8c feat: add GachaEvent schema and stable event IDs
Schema-level invariants enforce that endsAt is null exactly when
endPrecision is "unknown", and that regionEnds is populated exactly when
regionScoped is set, so an event object cannot be constructed in a state
the UI would misrender.

Event IDs double as localStorage keys on the client, so slugify() and
eventId() carry a warning: changing them orphans every user's completion
marks with no server-side recovery.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:10:35 +02:00
Lucas WintherandClaude Opus 5 3a1c604284 chore: add Bun + TypeScript project scaffolding
Bun is the runtime, bundler, test runner and SQLite driver, so zod is the
only dependency. Strict TS with noUncheckedIndexedAccess.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:10:35 +02:00
Lucas Winther 5db608563f Initial agent auxilliary files. 2026-08-14 23:45:35 +02:00
Lucas Winther 308f288e6a Initial commit 2026-08-14 23:27:06 +02:00