Commit Graph
17 Commits
Author SHA1 Message Date
Eric WagonerandClaude Fable 5 be8e19b418 Docs: the phone flow, in all three places it lives
README gains the home-screen tip and the camera-to-pipeline flow with
its shelf-<timestamp> naming; Help's Photos description now speaks
phone (and clarifies that only same-FILE-NAME re-uploads trigger the
reshoot replacement); CLAUDE.md documents the --lan architecture (key
file, QR pairing, guard exemptions, camera-name minting) and adds
data/.lan_key to the never-commit list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-03 17:18:04 -04:00
Eric WagonerandClaude Fable 5 44dd03d06a Pairing: scan a QR off the terminal once; the cookie lasts a year
The key already persisted across restarts, but the cookie was a
session cookie — Safari eventually drops those and the paste ritual
returned. The pairing cookie now lasts a year, and startup prints a QR
code of the pairing URL (qrcode dep, ASCII render) so a phone pairs by
pointing its camera at the terminal. Revoke every paired device by
deleting data/.lan_key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-03 16:50:36 -04:00
Eric WagonerandClaude Fable 5 d4e619611a Audit round 6 (mobile + --lan): access key replaces trust-by-allowlist
Five blind reviewers over 2936d21; 13 confirmed fixes. The design
change: the Host allowlist was CSRF armor being asked to do access
control. --lan now mints a per-run access key — the printed URLs carry
?k=..., the first visit sets a cookie — required on EVERY request,
reads included (shelf photos and pipeline state are private). That
closes DNS-rebinding read exfiltration (GETs were exempt from the old
guard), closes any-LAN-device mutations via a forged localhost Host,
and frees phones from allowlist accuracy — multi-interface machines,
DHCP renewals, and failed IP discovery no longer strand writes. A
foreign Origin is still refused even with the key.

Guard hardening: Host parsed via url.hostname (ports, IPv6 brackets,
case) instead of a manual split; refusals now echo one stderr line
(they were invisible at log_level=warning) and the LAN 403 names the
remedy; startup warns when no LAN IP could be determined instead of
printing hostname-only URLs as if verified.

Silent failures: the queue page no longer freezes blank forever when a
render throws (LAST was recorded before render; one malformed CSV cell
would blank all three ledgers and blame the network) — all three
change-detection pages record LAST only after a successful render, and
the queue null-guards source_photos.

Mobile: touch-size the review/ticket/merge buttons the finger-sized
rule lost to on specificity. Style: the meta-cell builder is one shared
metaLine() helper; the Help page no longer claims localhost-only;
dead -webkit prefix dropped; --lan help text in house style.

Tests: token gating (reads and writes, cookie handoff, foreign-Origin
refusal), the Origin-present + Host-with-port path every real browser
mutation takes (was fully uncovered), run_web_review's lan branch via
monkeypatched uvicorn, and a lan_hosts test that actually pins the
lowercase/non-empty/v4-only invariants the guard depends on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-03 16:12:01 -04:00
Eric WagonerandClaude Fable 5 2936d21f0a Mobile: every page earns its phone layout, and --lan serves the network
Verified page by page in a 390px viewport against live data. The rail
collapses to a brand line plus one horizontally scrollable nav row
(was: two wrapped rows). The Titles table becomes stacked line-cards —
title, chips, match, photos, finger-sized actions — with empty cells
dropped (the templates now emit them tight so :empty applies); the
editor stacks full-width inputs. Queue/library ledgers scroll inside
their card and photo lists wrap at commas instead of clipping. The
review done-card stats wrap, keyboard hints hide on touch widths,
reshoot stencils go horizontal, buttons get touch-sized padding.
Also: post-rename copy on the remove button.

bggpipe web --lan binds 0.0.0.0 and extends the mutation guard's host
allowlist with this machine's names and addresses (never a wildcard —
DNS-rebinding arrives under the attacker's hostname, which an allowlist
rejects), prints every reachable URL, and warns loudly that the app has
no login. Default stays localhost-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-03 16:02:08 -04:00
Eric WagonerandClaude Fable 5 c00b3a7ad8 README: a screenshot tour of all seven pages
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-03 15:20:32 -04:00
Eric WagonerandClaude Fable 5 f19b861251 The proofread checkpoint gets its place: nav order, badge, and a rename
Eric's observation: cleaning up raw reads happens BEFORE resolve, but
the app ordered Review ahead of Catalog and never said proofreading was
a step at all. The pipeline has two human checkpoints — proofread the
reads (after extract), decide the matches (after resolve) — and now the
app says so: the sidebar runs Pipeline, Photos, Titles, Review, Queue,
Library, Help in true workflow order; the Titles page (né Catalog — the
old name suggested a finished collection, which is the Library's job;
/catalog redirects) gets a badge counting unresolved shaky reads; an
edit marks its entry human-verified so the badge drains as you
proofread; the extract stage card nudges toward the proofread before
resolving; and the Help flow is rewritten as six stages + two
checkpoints with the loop called out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-03 00:19:59 -04:00
Eric WagonerandClaude Fable 5 25c1432a03 Docs catch-up + in-app Help page
README: the Catalog page description and a new "Fixing what the model
gets wrong" section cover the curation actions (edit/split/remove) and
their durability contract. Spec: amended-as-built notes — duplicate
same-edition copies are now supported via explicit splits, review is
both a TUI and the web app, and the data model lists the three curation
stores plus the dismissed-tickets file.

New Help page in the app (nav entry on every page): the six-stage flow,
what each page is for, the curation semantics with undo instructions, a
status-chip legend rendered with the real chips, keyboard shortcuts,
and where the data lives on disk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
2026-08-02 20:20:03 -04:00
Eric WagonerandClaude Fable 5 74fc4fe847 RPGs become local library citizens: identified, enriched, never uploaded
When the board-game search (and truncation heads) runs dry, resolve
falls back to type=rpgitem — the geekdo database is shared, so the same
API, token, cache, and classification machinery apply. Matched rpgitems
flow through review and enrich normally but diff routes them to a
local_only bucket, structurally outside to_add/to_update: their
collections live on RPGGeek, beyond this pipeline's write scope. The
library page gains an All/Board games/RPGs filter and an "RPG · local
only" badge; the catalog tags them too. Fixture generators write blanket
empty rpgitem stubs for every known query (the fallback fires for every
unmatched title) with real synthetic entries for Alice Is Missing.

Data: both Alice rows re-resolved from unmatched to auto rpgitem
matches. First diff since the audit reworks also lands their real-data
consequences: Dungeon! gains its TSR edition update on a versionless
copy the old claim ordering missed, to_add rows carry unioned reshoot
provenance, and the Herbaceous typo row's survivor is now the
correctly-spelled title.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 19:01:09 -04:00
Eric WagonerandClaude Fable 5 189c534315 Six-page app: sidebar shell with Juniper's portrait, whole-workflow IA
The two-page dashboard/review split becomes a proper information
architecture: Pipeline (stages + live activity), Photos (drag-and-drop,
gallery with per-photo extraction state, reshoot tickets — photo work
lives with photos), Review (decisions only, keyboard-first), Catalog
(the full title ledger with filtering), Queue (what upload will do and
everything it has done), and Library (the enriched collection browser,
with an honest empty state until real BGG data lands). Pages render
server-side from a shared shell — sidebar rail with the rainbow path
running its edge, live count badges on Photos/Review/Queue, and
Juniper's full portrait finally displayed, with her credit and a
standard third-party trademark attribution beneath it (one notice, not
per-mention symbols — the convention for referring to another party's
mark).

Shared client plumbing moves to static/app.js (escaping contract
documented at the innerHTML sink). New endpoints: /api/photos-list,
/api/queue, /api/library, plus a reshoot count in /api/pipeline.
Screenshot review caught two real bugs: photos-list crashed on
bare-array raw caches, and .DS_Store was listed as a shelf photo —
photo_names() now filters by suffix everywhere, including the /photos
allowlist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:34:17 -04:00
Eric WagonerandClaude Fable 5 6ecdd43ed2 bggpipe web: the whole pipeline as a local web app
A dashboard at / joins the review page (now at /review): drag-and-drop
photo upload (re-uploading a photo drops its raw cache so extract
re-reads it), per-stage status cards fed by /api/pipeline (counts and
key NAMES only — never values), and run buttons that execute stages
one-at-a-time in a background JobRunner with captured output streamed
to the page. The real upload sits behind a confirmation, defaults to
dry-run at the API layer, and stays disabled while stub data is
present. The CLI is unchanged and shares all state with the web UI.
python-multipart joins the deps for the upload endpoint; RunBody lives
at module scope because postponed annotations keep FastAPI from
resolving function-local models.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 17:08:00 -04:00
Eric WagonerandClaude Fable 5 bf9795235a bggpipe init: guided, idempotent first-run setup
One command replaces the clone-era checklist: creates photos/ and
data/, writes a default config.toml, prompts for the four credentials
with hidden input (appended to a 0600 .env, only the missing ones,
values never echoed), and offers the one-time Chromium download.
Re-runs report status and fill gaps; without a TTY it reports instead
of hanging. Groundwork for any future publishing path — PyPI or a
bundled app both need exactly this wizard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 16:56:28 -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 abf7181475 Username lives in .env only: drop bgg_username from config.toml
load_config no longer reads a bgg_username toml key — BGG_USERNAME in
the environment is its single home (public, but the account should have
exactly one). Docs, messages, and tests updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 13:36:22 -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 824719edc7 Mascot: Juniper's bggpipe piper joins the header, favicon, and README
Original art (a bagpiper whose bag is a board game box) lives in
assets/; web-sized derivatives ship in the package: a face-crop avatar
in the header and favicon, and the framed full-length piper on the
review done screen. Served via an allowlisted /static route. README
leads with the full portrait, credited to Juniper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 17:35:53 -04:00
Eric WagonerandClaude Fable 5 96a1e09956 Docs: BGG auth policy in spec/README, upload-flow recon notes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 13:05:15 -04:00
Eric WagonerandClaude Fable 5 6bd4222fc1 Initial commit: spec, Claude Code setup, and project docs
Design spec for the bggpipe shelf-to-BGG pipeline, CLAUDE.md and
bgg-api skill capturing BGG API constraints, ruff format-on-edit
hook, README, LICENSE, and .gitignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 12:11:14 -04:00