Commit Graph
24 Commits
Author SHA1 Message Date
Lucas WintherandClaude Opus 5 4277c28e50 Let a reader switch off the chores the app invented
Today's dailies carries two different things: each game's standing chore —
"Commissions, resin", a fixed list nobody publishes — and any event with a
checklist. The first is the app guessing at a routine the reader never asked
for, and it was the only part of that strip with no way out.

So the switch removes exactly that. Events keep their checklists whatever
their source, including ones the reader added themselves and marked daily,
which was the requirement most at risk of being filtered away by a switch
aimed at something else.

Nothing is discarded. The ticks live under `dailies:<game>` and nothing here
reads or writes them, so switching back on restores every logged day and
every streak — the same promise `detectDaily` already makes. Defaulted on,
because everyone has these today and a setting that silently removes
something on upgrade is worse than one nobody notices.

Gating where the chore is built rather than where it is drawn means the
counts follow for free: "N still waiting on you today" is derived from the
items, and a game left with nothing contributes no group at all, so the
strip's own empty guard drops it rather than leaving a heading with no rows.

Named for what it removes. A switch called "dailies" would read as broken
while the strip stayed on screen showing the reader's own events.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 05:34:08 +02:00
Lucas WintherandClaude Opus 5 3a3b7d9c2f Give a reader their own events back
An event of theirs that had ended was on no surface at all. Every list and
the board drop a row once its end has passed, and settings only counted the
events a game held rather than naming them — so a one-off became unreachable
the day it finished: impossible to edit, and impossible to delete out of a
store nothing else can see. Repeating events escaped only because their
occurrences roll forward.

So settings names them now, under the game they were filed against, each row
opening the same detail sheet a row on the front page does. Nothing about
how they are managed changes; what was missing was the way back to them.

Two things the index has to get right or it leaves the same hole it closes.
The lists hold occurrences, never rules, so a repeating rule's own id opens
nothing — nearestOccurrence bridges that, and answers for a finished series
too by falling back to the first occurrence, since a rule whose `until` has
passed would otherwise be exactly as stuck. And an event filed under a game
we track has no row of theirs to nest under; the form allows that, so those
get their own heading rather than trailing the list and reading as though
they belonged to whichever game came last.

Each row says why it is not on the front page — ended and when, or its
cadence — because a list of bare titles leaves you guessing which of two
entries is the dead one.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 05:14:47 +02:00
Lucas WintherandClaude Opus 5 8671a309b0 Say that custom still allows an unknown end
The cadence table claimed custom wants "a start, an end, and how it
repeats", while the one-off row above it took care to say the end may be
unknown. Custom offers the same checkbox, so the table understated it — the
same staleness docs/PRD.md was corrected for, in a row written at the same
time and missed.

Found by the re-review.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 05:04:39 +02:00
Lucas WintherandClaude Opus 5 4251717615 Say what the app actually does now
Four things in here had gone false.

The games table listed six of nineteen and gave per-game event counts, which
change on every refresh — a number in a README nobody updates is a number
that lies, so the counts are gone and the app's own header keeps that score.
Arknights was described as awaiting a source it has had for a while.

Your own games and events had no entry at all despite being shipped, and
neither did the cadence that came with it. Both are now described in the
terms the form uses, including why a preset has no end date to give and why
an occurrence that states no end can still count down.

And the status section claimed the app keeps itself up to date. game8.co
answers the Actions runner with a bot-management 202, so nine of the twenty
sources only move when somebody refreshes them by hand. That is recorded in
docs/SOURCES.md and was the most misleading sentence in the file.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 05:04:39 +02:00
Lucas WintherandClaude Opus 5 02dd7b3ed4 feat(github): give readers two issue forms and link to them
Nothing a reader marks, types or ticks ever leaves their browser, so there is no
account and no server-side record to look anything up in after a report arrives.
Whatever the form captured is all there will ever be — which is the argument for
structured forms over a blank box, and for the bug form carrying the footer's
"event data last refreshed" line as a prefill. A stale calendar and a genuinely
wrong parse are indistinguishable from outside, eight sources cannot be fetched
from CI at all, and that line is the only thing that separates the two cases.

The feature form states the three constraints up front, because "sync across my
devices" and "have a model read the page" both have settled answers and a reader
deserves them before typing rather than after. It also points source requests at
the assessed-and-declined table: the loudest feedback on the first release was
that the game list is too short, so that is the request that arrives most, and a
wiki that forbids automated access or only archives finished events cannot be
used no matter how often it is asked for.

Blank issues stay enabled. A form that turns someone away loses the report, not
the noise.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-17 23:19:47 +02:00
Lucas WintherandClaude Opus 5 8d4a28b254 docs(license): split the data carve-out into NOTICE
The carve-out was 180 words appended to LICENSE, which pushes the file below
the similarity threshold licence detectors match against — a repository whose
licence reads "other" is less inviting to a forker than one that reads MIT,
and the carve-out was never part of MIT's text anyway.

LICENSE is verbatim MIT again, plus three lines binding NOTICE into the terms
so the carve-out travels with any copy rather than being optional reading.
NOTICE carries the substance. README points at both.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-17 22:33:29 +02:00
Lucas WintherandClaude Opus 5 50fb7b8efb docs(readme): the licence section names MIT and its limit
The section still read "not yet chosen" after the LICENSE file landed, which
is the one wrong answer worse than no answer for someone deciding whether
they may fork. Says MIT, and says in the same breath that the schedules are
not MIT's to give.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-17 22:31:02 +02:00
Lucas WintherandClaude Opus 5 025605aa09 docs: move the working agreement to AGENTS.md
The guidance in here is not Claude-specific — it is what this project is,
what it refuses to do, and the rules that are invisible from the code. Any
agent working here needs it, and AGENTS.md is the name they all look for.

CLAUDE.md stays as a pointer so Claude Code still finds it, and holds no
guidance of its own; two copies would drift and the wrong one would be read.
Every cross-reference in the source comments, docs, workflow and skills now
names AGENTS.md, and the image ignores both.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-17 22:16:40 +02:00
Lucas WintherandClaude Opus 5 a4ab5aa36c feat(sw): offer a reload when a newer version is ready
The shell is served cache-first, which is what makes the app work on a
train and also what makes a deploy invisible: a reader with the tab open
— the reader this app is built for — keeps running the bundle they first
loaded, so a new game or a corrected date reaches their device and sits
there with nothing saying why the page looks unchanged. An old app shown
as current is the same failure as old events shown as current.

So the worker now installs quietly and waits instead of calling
skipWaiting(), the page notices it waiting and says so, and the reader's
tap sends the skip-waiting message and reloads on controllerchange. The
app never reloads itself: someone may be mid-way through typing in one of
their own events, and the notice says what a reload costs (their place on
the page) and what it does not (marks and notes live in localStorage).

Detection is derived rather than remembered. build:static grew into a
script that stamps sw.js with a hash of the built shell, because the
browser only offers a worker whose bytes differ, and the predecessor —
a hand-bumped CACHE_VERSION — had already been forgotten once. The feed
is deliberately not part of that hash: it changes twice a day, needs no
reload, and announcing it would teach readers to dismiss the notice
unread. The cache name stays put for the same reason a per-build one
would be wrong — it holds the feed an offline reader is reading.

A first install is not an update and stays silent.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-17 18:55:08 +02:00
Lucas WintherandClaude Opus 5 6fc3c0b256 docs(readme): link the live Pages site
The app is deployed and reachable; the README only explained how to run
it locally.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-16 20:57:26 +02:00
Lucas WintherandClaude Opus 5 41f0330e02 feat: let the reader switch off daily detection
Guessing from a source's wording is right most of the time, and someone
who finds it wrong often enough should be able to stop it rather than
dismiss the same false positive every patch.

Off, only events the reader marked themselves get a checklist. It
silences the guess rather than deleting anything: overrides, ticks and
streaks all survive, so switching it back on restores exactly what was
there. With it off, marking an event stores an explicit yes, since there
is no longer a detection for an override to agree with.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 22:26:16 +02:00
Lucas WintherandClaude Opus 5 7f384eb9db feat: let the reader mark an event as repeating daily
Dailiness was read off the source's wording alone, which is wrong in both
directions: a grind that resets every day but whose page never prints
"daily" got no checklist, and a banner whose blurb mentions "daily login
rewards" got one nobody could dismiss.

The reader's answer now wins. The control sits exactly where the
checklist goes — the one place the answer visibly matters — so marking an
event and ticking today off are the same gesture in the same place.

An override is stored only when it disagrees with detection. Recording
agreement would freeze today's guess into the reader's own data, so a
later parser improvement could never reach that event.

Marked events also join today's dailies at the top of the page, beside
the per-game chores: at 23:50 a login campaign and a commission run are
the same job, and ticking one should not mean opening a sheet to find its
checklist.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 22:14:37 +02:00
Lucas WintherandClaude Opus 5 02863ed008 docs: cover the refresh pipeline, sanitisation and dailies
The status sections claimed the feed was generated from fixtures and the
scheduler unbuilt, which stopped being true. Also documents the
sanitisation stage and the two new key spaces — `dailies:<game>` and
game-day keys — beside the existing warning about event IDs, since they
carry the same "no server-side recovery" property.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 21:19:08 +02:00
Lucas WintherandClaude Opus 5 2b9338a8b0 docs: cover progress, effort and the completions migration
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 01:49:53 +02:00
Lucas WintherandClaude Opus 5 3bab95acd5 fix: copy serve.ts into the Docker build stage
The image build ran the tests without serve.ts present, so the server the
tests spawn never started and the hook stalled until it timed out. The
runtime stage would have failed on the same missing file immediately after.

The test now bails the moment the process exits and reports its stderr, so
this shows up as "serve.ts exited with 1 before listening" in 53ms rather
than an unexplained hook timeout after five seconds.

serve.ts is also in tsconfig's include now — it was outside it, so a type
error in the file that serves the app would only have surfaced at runtime.
Adding it immediately caught one in the test.

Verified by replaying the build stage against exactly the copied file set.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 01:35:25 +02:00
Lucas WintherandClaude Opus 5 fd1111017f fix: let the Pages job enable Pages itself
configure-pages fails with a 404 when Pages has never been switched on for
the repository. It can enable it given the pages: write permission the job
already holds, which beats requiring a trip through settings before CI can
go green.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 01:30:23 +02:00
Lucas WintherandClaude Opus 5 692b5a83d5 docs: bring every markdown file up to date with the code
The docs had drifted in ways that would mislead: DATA-MODEL documented a
localStorage shape the code stopped using (completedAt, no ignored store),
INGESTION claimed three Game8 templates when five are known, ARCHITECTURE
still listed the whole client and time.ts as unbuilt, and the review-quarantine
skill described a pipeline that does not exist yet without saying so.

Adds the parser roster and the six date formats as tables, documents the
subpath/base-href and offline behaviour, and records the new product surface
(first-run picker, ignore, offline, credit) as PRD features.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 01:27:56 +02:00
Lucas WintherandClaude Opus 5 b86794a62d feat: add GitHub Actions CI and support hosting under a subpath
Mirrors the GitLab pipeline — typecheck, tests, feed sanity — and adds a
Pages deploy.

Pages serves from /<repo>/, and the app used absolute asset paths, so that
job would have shipped a site that 404s on everything. Assets now resolve
against a <base href> the build substitutes, the feed URL resolves against
document.baseURI so deep links work too, and the service worker derives its
paths from its own registration scope. Root-hosted builds are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 01:22:22 +02:00
Lucas WintherandClaude Opus 5 517066dc65 feat: add Docker image, static server and GitLab CI
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]>
2026-08-15 01:20:42 +02:00
Lucas WintherandClaude Opus 5 dd596b5a43 feat: add wiki.gg as a second Endfield source
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]>
2026-08-15 01:17:17 +02:00
Lucas WintherandClaude Opus 5 ad45b52c84 feat: add Arknights: Endfield, and stop passing off requirements as summaries
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]>
2026-08-15 01:07:39 +02:00
Lucas WintherandClaude Opus 5 395d8b8ca8 docs: cover the five sources and offline support in the README
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-15 00:53:11 +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 Winther 308f288e6a Initial commit 2026-08-14 23:27:06 +02:00