The library becomes browsable: detail pages, sorting, real filters

136 games with nothing to do but look at them. Now:

Every card links to /library/game/<key> — a detail page with the box
art, players (with best-at counts), playing time, weight, rank, rating,
ages, owner count, designers/artists/publishers, categories and
mechanics as chips, the description, YOUR edition (name, year,
publishers, languages), and the shelf photos the game was read from,
linking back to those photo pages. That provenance is the join only
this pipeline can make: games.json knows the game, matches.csv knows
which of your photos it came from.

The list gains sorting (name, year, BGG rank, weight, playing time —
with nulls always last, since an unranked game is not rank zero), a
"plays with N" filter that keeps games whose player range covers the
table, an Off-BGG kind filter, and a search that now covers designers,
mechanics, categories and edition names rather than titles alone.

/api/library drops the description field (a megabyte of dead weight
across 136 games); the detail endpoint serves the whole entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-05 23:38:35 -04:00
co-authored by Claude Fable 5
parent 2e1693c0be
commit f5e949bd62
6 changed files with 293 additions and 11 deletions
+45
View File
@@ -1243,3 +1243,48 @@ def test_same_title_lines_pair_rows_by_photos_not_csv_order(tmp_path):
}
assert lines[("a.jpg",)]["version_status"] == "version_unknown"
assert lines[("b.jpg",)]["version_status"] == "version_ambiguous"
def test_library_detail_serves_one_game_with_provenance(tmp_path):
cfg = make_cfg(tmp_path)
rows = read_matches(cfg.matches_path)
rows.append(
_row(
title_raw="Britannia",
match_status="auto",
bgg_id="240",
bgg_name="Britannia",
version_id="24621",
source_photos="shelf.jpg",
)
)
write_matches(cfg.matches_path, rows)
cfg.games_path.write_text(
json.dumps(
{
"240:24621": {
"bgg_id": 240,
"name": "Britannia",
"year": 1986,
"type": "boardgame",
"description": "A long description.",
"version": {"version_id": 24621, "name": "Avalon Hill second"},
}
}
)
)
web = TestClient(create_app(cfg, client=unauthorized_client(tmp_path)))
(listed,) = web.get("/api/library").json()
assert listed["key"] == "240:24621"
assert "description" not in listed # the list view stays light
detail = web.get("/api/library/240:24621").json()
assert detail["name"] == "Britannia"
assert detail["description"] == "A long description."
# provenance the pipeline knows and games.json doesn't: the shelf photo
assert detail["photos"] == ["shelf.jpg"]
assert web.get("/api/library/nope").status_code == 404
page = web.get("/library/game/240:24621")
assert page.status_code == 200 and 'href="/library"' in page.text