← all tickets

49 — Cards page: stop special-casing identities in Tests/Manual columns

Status: fixed

What to fix: The cards page shows "N/A" in the Tests and Manual columns for every identity, even though all 7 implemented sg identities have real card_<code>_* Rust tests and has_tests: true in game/cards_impl/sg.json. The manifest is honest; the page hard-codes the lie in static/cards.js:94-97:


const isIdentity = c.type === 'identity';
const impl = isIdentity && !c.implemented ? null : c.implemented;
const tests = isIdentity ? null : c.has_tests;
const manual = isIdentity ? null : c.tested_manually;

This is a leftover from before identities had ability hooks.

Fix (agreed):

Definition of done: on the cards page, sg identities show Tests: Yes / Manual: No matching the manifest; vanilla cards show N/A; unimplemented identities show Implemented: No.

Comments

Fixed in 73ba1ea ("fix: report real identity test status on the cards page"), which landed the code before this ticket file was committed. Verified against the running app's payload rather than the diff alone:

Both suites green at baseline: cargo test --workspace 241 passed, pytest -q 199 passed.

Left alone deliberately, out of this ticket's scope — two older identity special cases survive in static/cards.js and are not part of the columns this ticket covers:

Both date to the original cards page (314dc7b), same vintage as the column lie, and both are a product call about what the filter and the coverage counters should mean now that identities are implementable. Worth a follow-up ticket.

Review follow-up — a 3-model panel read 73ba1ea and found the fix real but under-covered and undeliverable. All four findings addressed:

Suites after the follow-up: cargo test --workspace 241 passed (untouched), pytest -q 213 passed.

Review round 2 — the panel re-read c80a0c1, confirmed all four fixes above hold, and found five gaps in the machinery guarding them. All five addressed (one round-2 finding, that status_columns is missing from /api/cards/sg|pool|startup, was rejected: no consumer — the page fetches /api/cards exclusively).

Suites after round 2: cargo test 241 passed (untouched), pytest -q 218 passed.

Review panel — findings surviving the 3-round cap (2026-08-03)

Three review rounds (glm-5.2, gpt-5.6-terra, gpt-5.6-luna) over commits 73ba1ea, f0d97e2, c80a0c1, e266f68. Rounds 1 and 2 produced nine findings, all fixed (see c80a0c1 and e266f68); the panel confirmed each fix on re-inspection. Round 3 produced nine more, all P3 test-hygiene issues in the guard machinery rather than in shipped behaviour, and the round cap stopped the work here. They are recorded for a future hardening pass — none of them affects what a user sees today. (Update 2026-08-04: the nine findings are filed as ticket 66, 66-cards-page-guard-machinery-hardening.md, ready-for-agent; the applyFilters()/buildSummary() product call recorded above is filed as ticket 67, 67-identities-in-filter-and-summary-product-call.md, needs-triage.)

1. _game_fn was never consolidated (glm) — tests/test_static_pages.py:661 still uses re.search(rf"function {name}\(.*?\n}}"), the column-0 closing-brace regex the round-1 fix replaced everywhere else. _js_fn is a drop-in replacement. 2. Naive // stripper survives at tests/test_static_pages.py:469 (glm) — re.sub(r"//[^\n]*", "", _editor_fn("makeDeckRow")), the exact pattern round 2 replaced in _js_code. 3. Identity invariant is pinned only over hardcoded sets (glm, terra) — tests/test_api.py:244-272 checks the 7 sg codes and 2 vanilla codes; the one implemented vp identity, 36017 Hiram "0mission" Svensson, falls through every bucket, and no assertion pins the endpoint's raw manifest passthrough for the complete identity set (test_every_card_..._carries_its_status_cells recomputes both sides from the same dict, so it is a tautology). 4. VANILLA_IDENTITIES = ["30076", "30077"] (glm) — the same snapshot anti-pattern round 2 removed from the unimplemented count; adding a third vanilla card turns the suite red for correct behaviour. 5. Unimplemented identities forced to has_tests: false (terra) — tests/test_api.py:268-269 rejects a truthful manifest that gains card_35001_* tests before implemented flips to true. 6. Browser cell order is unpinned (terra) — appending .reverse() to statusCells()'s map renders the columns backwards under the right headers and every current assertion still passes. 7. Source scanners are not regex-literal aware (terra) — a literal such as /[//]/ or /[}]/ in a scanned function misleads _matching()/_js_code(). 8. _js_fn function discovery can select a commented-out example (terra) — the re.search that locates the function is not syntax-aware, so // function statusCells(c) { ... } placed above the real one wins. 9. _LOCAL_ASSET only versions double-quoted, query-free, flat paths (terra, luna) — src='/static/cards.js', src="/static/cards.js?module=1" or static/js/cards.js would silently ship unversioned, recreating the stale-JS failure this ticket fixed.

Rejected by the orchestrator, recorded so they are not re-litigated: manifest schema validation for malformed values (luna — manifests are generated and covered by test_card_manifest_honesty.py); status_columns missing from /api/cards/sg|pool|startup (glm — no consumer, the page fetches /api/cards exclusively); content-hash/in-place-deploy race (luna — deployment practice, not a code defect); and the applyFilters()/buildSummary() identity exclusions (glm), which remain the out-of-scope follow-up recorded above.