Commit Graph
10 Commits
Author SHA1 Message Date
Eric WagonerandClaude Fable 5 9283a3e980 Paging clicks only visible controls: First/Prev are mobile-only
Munchkin Big Box and Tang Garden hung 30s each on a "First Page" anchor
that exists but is invisible: BGG renders every paging control twice,
and the First/Prev pair lives only in the mobile set
(<li class="visible-xs-inline">). A desktop viewport can never click it.

Paging now selects the first VISIBLE match, and returning to page 1
closes and reopens the sub-view (which always opens on page 1) instead
of reaching for a control that isn't there. A test proves no hidden
control is ever clicked — the fake picker raises if one is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-05 22:41:42 -04:00
Eric WagonerandClaude Fable 5 ba55863cef Version picker: real pagination, and matching that declines to guess
Five games uploaded; two landed version-less. Neither was the picker's
fault: paging is an AngularJS <ul class="pagination"> of anchors, not
buttons named "next", so the old guess found no control and quit after
page one — and BGG's API version names carry printing qualifiers the
picker omits ("English edition 2018-2" vs "(English edition) (2018)").

_select_version now scans the WHOLE list (verified selectors: rows are
<li>s with a thumbnail; a[title="Next Page"] advances; the parent <li>
disables at the end), collects every candidate, then decides: one exact
match wins; failing that, one match after stripping a trailing year
qualifier wins and says so; several matches are refused outright rather
than guessed, and the reason reaches upload_log.csv. Both call sites
carry the reason through.

docs/bgg-upload-flow.md records what the live site actually does —
including that every login-gate selector the doc called "verified" was
wrong, while the "unverified" dialog structure was right.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-05 22:36:48 -04:00
Eric WagonerandClaude Fable 5 6ae2667c58 First real upload lands: three browser-flow bugs, found by doing it
A Gentle Rain is on BGG (collid 148198034, version 701315 — the
English Bloom edition matched from a shelf photo), verified by
re-fetching the collection. The failures on the way, all in the
login gate the docs called "verified" and none in the version-picker
code they called "unverified":

1. BGG's Sign In is an <a class="btn"> with NO href, so it has no
   implicit link role: get_by_role("link", name="Sign In") matched
   zero elements in EVERY state, and "no Sign In link" was read as
   "already signed in". Every run browsed anonymously.
2. The header hydrates after domcontentloaded, so for a moment
   neither control exists — a check resting on one absence guesses.
   _signed_out() now polls until the page proves one state or the
   other (Sign In vs Sign Out) and raises after 30s rather than
   assume; login is verified by the transition, and the session file
   saves only after that proof.
3. get_by_label("Own") also matched "Prev. Owned" — strict-mode
   violation; the Own checkbox is now matched exactly.

Also: Playwright's multi-line call logs no longer break upload_log.csv
into ragged rows (errors flatten to one line). Three unit tests cover
the hydration window, positive detection, and the undeterminable case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-05 22:22:11 -04:00
Eric WagonerandClaude Fable 5 08b741671d Re-audit round 4: 5 blind reviewers over the new surface — 24 fixes, +28 tests
The findings clustered exactly where prediction said: the unreviewed web
layer. The big ones: decisions made while an extract/resolve job runs
are now refused with a 409 (the job's end-of-run rewrite from a
start-of-run snapshot would silently revert them); a cross-origin guard
blocks preflight-free mutations from hostile webpages (bodyless run
triggers, cross-site photo form posts); the JobRunner sets terminal
status in a finally catching BaseException (a greenlet death could
wedge every future run behind 409s) and writes tracebacks into the
visible job log; and a boot token lets clients accept the revision
reset after a server restart instead of freezing forever.

Even the thrice-audited core yielded one HIGH: an unvetoed bare
typo-read sibling of a confident row duplicated its add when the game
wasn't in the collection — diff now treats it as satisfied. Second-copy
adds carry a flag through to_add.csv and the upload log so verify
honestly reports them unverifiable instead of OK. Also: merged_into
chains collapse transitively; diff/enrich treat a BGG queue timeout
like a missing token; enrich prunes orphaned games.json keys; the
wizard shell-quotes .env values and creates the file 0600 from the
first byte; fsio stats the tmp inode before replace and uses unique tmp
names; an explicit missing --config errors; storage state is
owner-only; extract re-extracts corrupt caches, aborts on 3 identical
failures, and exits nonzero when nothing succeeded; torn JSON artifacts
degrade with in-browser warnings instead of 500ing every page; photo
uploads are atomic with cache-invalidation ordered first; the pipeline
page computes `running` before the buttons that depend on it; the
photo dropzone alerts on network failure; and lost-contact banners
clear on recovery everywhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:13:34 -04:00
Eric WagonerandClaude Fable 5 10f65d8aba Credibility pass: comments state constraints, not development history
A skeptical-cloner review flagged the patterns that read as AI-iteration
residue: test comments and section headers narrating the review process
that produced them, "legacy format" framing in a days-old repo, shadow
re-imports appended without reading file headers, one genuine machine
leftover (FIXTURE_CACHE = FIXTURE_CACHE), and a few register slips.
Every history-narrating comment is rewritten as the timeless invariant
it was guarding, test sections are grouped by behavior, function-local
imports are hoisted, and the README loses its one marketing clause and
heaviest dash runs. No behavior changes; 176 tests unchanged and green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 15:08:22 -04:00
Eric WagonerandClaude Fable 5 92aaa91a49 Re-audit round 3: 5 blind reviewers, 15 fixes, +18 tests — converging
Round 3's two HIGHs: _fill_version resolved versions with the LAST
same-title entry's cues (photo-aware lookup existed since round 1 but
this caller never used it), and the round-2 diff rework let an earlier
row's disagreement consume the exact-version copy a later row matched.
Diff claims now settle strongest-first across all rows (exact matches,
then versionless upgrades, then disagreement/second-copy), unvetoed
bare duplicates stay owned per spec, and updates are withheld with a
manual-fix note whenever any copy of the game already carries a version
(the row edit targets by name and could hit the wrong copy).

Also: entry-to-row pairing matches by photo overlap before position
(titles.json order churn from reshoot filenames could swap editions);
BGGQueueTimeout defers a title like a missing token; DismissStore
writes atomically, mutates memory only after the write, and
quarantines a torn file instead of bricking the server; version-picker
page-limit exhaustion stays retryable; verify's copy-count shortfall
reports once per game (the old guard was dead code); the upload log
header is created atomically; transient version-lookup failures record
a retryable version_error, not terminal version_unknown; extract
isolates per-photo failures and salvages JSON followed by prose; a
state revision counter stops stale poll responses reverting decisions;
plus the shared-predicate/fsio/docstring consolidation and CLI wiring,
live-diff, verify-wiring, and search-guard tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 14:55:04 -04:00
Eric WagonerandClaude Fable 5 65d4cdd5ec Re-audit round 2: 5 blind reviewers, 17 fixes, +12 tests
The re-run confirmed round 1 held and then caught second-order bugs in
its own fixes plus two long-standing ones everyone missed. TUI decisions
after a mid-session reload were counted but never written (rows are now
re-adopted into the fresh list on every save, preferring undecided slots
on duplicate keys); row_ix was computed by equality so duplicate rows
shared an ordinal (identity now, merges included, veto sends it); upload
job keys collided for two same-version copies (completions are counted
per key, so --limit or an interrupt can no longer strand the second
copy); diff consumes collids on exact-version matches (a vetoed
same-version second copy was silently swallowed) and splits mismatches:
report-only disagreement while an unclaimed copy exists, second-copy add
only when every copy is claimed.

Also: XML responses are validated and written atomically before caching
(a torn or truncated 200 body can never poison a re-run), JSON artifacts
write atomically, thing/search parsers refuse missing ids like the
collection parser, empty game names are refused by the upload queue, a
never-rendering version picker fails retryably instead of terminally,
the systemic-failure abort compares exception types, blocked same-title
entries defer as a group so positional pairing can't misalign,
truncation heads pick the earliest separator, diff messages tell the
truth when a token exists without a username, and the shared-constant
sweep now actually covers every module (statuses, search types, marker
names, client_for, ports). pydantic declared as a direct dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 14:34:44 -04:00
Eric WagonerandClaude Fable 5 38e20f2c30 Audit: 5-reviewer sweep — 19 fixes across every stage, +24 tests
Correctness: review vetoes persist via a dedupe_veto column (resolve
re-runs no longer overturn humans); diff emits second copies whose
confident version matches no owned copy (spec: pairs own only on both
ids) and fetches the live collection with refresh; resolve pairs
titles.json entries to rows by title so a reshoot photo updates
provenance instead of duplicating rows; version lookups survive empty
/thing results; publisher tie-break now honors the mixed
base/expansion veto and refuses multi-candidate picks; empty-normalized
(non-Latin) titles never count as exact.

Upload: LoginError aborts a run instead of logging N bogus failures
(and 3 identical consecutive failures abort as systemic); Cloudflare
interstitials are detected; added-without-version gets its own logged
status that verify understands; same-game updates run one per pass so
the name-targeted row edit can't overwrite a fresh version; absent
diff outputs fail loudly; pagination clicks are paced.

Web review: a lock serializes freshen/decide (threadpool race dropped
decisions); failed saves roll memory back and always alert the browser
(non-JSON 500s included); session warnings reach the page instead of a
StringIO; state-load failures and dead servers show banners instead of
a blank page; duplicate (title, photos) rows are addressable by
ordinal.

Consistency: shared CONFIDENT_VERSION_STATUSES, client_for(),
Config paths for every artifact, one review-port constant, named
matching thresholds, strict collection-id parsing, error-doc responses
never cached, unknown config keys warn, extract reports dropped vision
entries, fixture generators share escaping + marker text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 14:10:57 -04:00
Eric WagonerandClaude Fable 5 8e3cdc145f Docs for strangers: real quick start, own-shelves guide, clone-safe stub guard
README rewritten for a fresh clone: working status, quick start with
env-var table and per-stage commands, photo-taking guidance, a
bring-your-own-shelves section (this repo carries the author's live
data), tokenless workflows, and a dev section. New committed
data/STUB_DATA.marker closes a real gap: the cache marker is
gitignored, so a fresh clone had stub-derived to_add.csv with no
guard — upload now refuses on either marker. Username docs: BGG_USERNAME
in .env is the one place it's needed; config.toml is a fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 13:33:52 -04:00
Eric WagonerandClaude Fable 5 3ca7e7f650 Stage 5 upload: Playwright-driven adds and version updates
Queue from to_add/to_update minus upload_log.csv (append-per-attempt,
so runs resume); per-game failure isolation with 2-4s pacing;
--dry-run/--verify/--retry-failed/--limit; stub-fixture marker blocks
real runs, dry-run warns. Headed browser by default: live recon showed
Cloudflare Turnstile hard-blocks headless, and BGG never reaches
networkidle. Login selectors verified anonymously; version-picker
pagination and the collection-row update flow remain unverified until
real data exists. Client collection fetches gain a refresh passthrough
so --verify sees the live collection, not cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 19:03:25 -04:00