Vote system: federation stub + contested-review generalization - #7
Merged
Conversation
The rebrand made the nav link and the modal's section heading share the exact same text, so an unscoped page.getByText() now matches both (a Playwright strict-mode violation - CI caught 2-4 matching elements). Scoped both assertions to the detailed-view modal specifically. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Generalizes the existing printing-tag weighted-consensus mechanism (CardPrintingTag, PRINTING_TAG_* settings) into a shared core (vote_consensus.resolve_weighted_consensus) used by two new sibling systems: CardArtistVote (who illustrated a card) and CardTagVote (descriptor tags, multi-valued per card via update_or_create rather than delete-then-create). Also fixes a durability gap found while designing this: a scheduled re-scan could silently revert a resolved tag-vote correction, since bulk_sync_objects wrote Card.tags purely from fresh filename extraction - now merges resolved consensus in first. New endpoints (submitArtistVote/artistConsensus/artistCandidates, submitTagVote/tagConsensus) and a frontend attributeVoting panel wired into both the printing-tag queue and the card detail modal, shown once a card's printing consensus is unresolved. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…emantic-card-attributes # Conflicts: # frontend/tests/PrintingTagQueue.spec.ts
Groundwork for a future cross-instance verdict-exchange feature (docs/federation-v1.md, spec only - no export/import code yet): VoteSource gains FEDERATED, AbstractWeightedVote gains a nullable peer field, and a new VOTE_FEDERATED_WEIGHT setting is wired into vote_consensus's weighting. Also de-duplicates the three consensus modules' identical _SOURCE_WEIGHTS dict into one shared constant (so a new source can't be forgotten in one of them), and renames VoteTuple.is_ai to is_human_backed - the caller now decides per vote rather than this being derived from source == AI, since a federated vote's human-backed-ness will depend on what the exporting peer reports. Behavior is unchanged for every source that exists today (confirmed by the full existing test suite passing unmodified). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Backend half of the review-tooling sprint: extracts printing-tag voting's "contested" detection into a shared, behavior-preserving vote_consensus.contested_queryset, used by new get_contested_artist_card_ids/get_contested_tag_pairs alongside the unchanged get_contested_card_ids. Persists this as real state instead of a cheap proxy where it matters: ArtistVoteStatus gains CONTESTED, and Card.tag_vote_statuses (a new JSONField) tracks resolved/ contested/unresolved per tag, written by the existing resolve_and_persist_tag_votes. New POST 2/voteQueue/ endpoint (a sibling of 2/printingTagQueue/, which is untouched) generalizes the review queue across kind= printing|artist|tag - printing keeps its exact existing candidate set/ordering; artist mirrors it; tag mode serves (card, tag) pairs filtered on the persisted status (not raw vote existence, so a resolved pair can't resurface from unrelated later votes), ordered by ascending net polarity weight with a card_id round-robin interleave so review doesn't get stuck repeating one card. Also rewrites Card.serialise()'s artist-fallback chain to capture which rung actually supplied the value as it's found, rather than inferring it after the fact by checking which other fields are empty - adds canonicalArtistIsFromVoteOnly/canonicalArtistSource to the Card schema, needed by the "wrong?" affordance landing next. Admin gets ContestedArtistFilter/ContestedTagFilter (mirroring the existing printing one) and peer visibility on both vote models. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Frontend half of the review-tooling sprint. printingQueue.tsx gains a Printings/Artists/Tags tab switcher - printing mode keeps its exact existing PrintingTagQueue component and behavior unchanged; artist and tag modes share a new GenericVoteQueue shell driven by 2/voteQueue/, since only those two need the new endpoint's response shape. Artist mode reuses ArtistVotePicker directly; tag mode gets a new focused QueueTagQuestion (one (card, tag) pair, apply/not applicable/skip) rather than reusing TagVotePicker's full per-card chip grid, which is a different unit of interaction. ArtistVotePicker also gains the deferred "wrong?" affordance: when a card's artist is already confidently known independent of any vote, it renders as pre-filled text with a small link to reveal the full picker, using the canonicalArtistIsFromVoteOnly field Card.serialise() now provides. Made the two new Card fields optional in the schema (the backend always fills them; only frontend object literals in tests/mocks would otherwise need updating for no real benefit). Also fixes an unrelated pre-existing bug found while stabilizing the Playwright suite for this change: New.spec.ts was missing an await on its page load, a race that usually wins but explains this test's occasional documented flakiness under load. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reconciles the vote-consensus/federation-stub line of work (branched off semantic-card-attributes) with master's independent starburst/Pokemon styling, decklist-import scraping, Keyrune printing-filter flattening, and expansion_hint persistence fix. Migrations don't fork (master tops out at 0052, this branch's 0053-0055 apply cleanly on top of the same ancestor). Manual conflict resolution: - PrintingTagPicker.tsx: combined master's new `cardName` prop (DOM data attributes) with this branch's `onConsensusChange` prop (wrong? affordance precedence) - independent additions to the same prop interface. - CardDetailedViewModal.tsx: same combination at the call site. - pages/printingQueue.tsx: re-applied this branch's Printings/Artists/Tags tab switcher on top of master's rewritten full-bleed jagged-starburst layout (StarburstContent wrapper), replacing the older CSS conic-gradient version this branch had been built against. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Blank line after headings, per prettier's markdown formatting - caught by the pre-commit hook during verification of the merge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WilfordGrimley
added a commit
that referenced
this pull request
Jul 13, 2026
Read-only forensic pass over the printing/artist/tag weighted-vote system's full commit history (Stage 1 through the federation stub and contested-review generalization merged in PR #7), for whoever eventually cuts an upstream extraction branch per CLAUDE.md's cherry-pick workflow. Ordered commit list (21 SHAs, oldest first) with clean-cherry-pick vs. entangled classification per commit - notably PrintingTagQueue.tsx and printingQueue.tsx, which interleave real vote-queue logic with five rounds of fork-only starburst/Pokemon theming across ~10 commits and should not be cherry-picked commit-by-commit at all. Also covers: fork flavor to strip (the "Who's That Planeswalker?" rebrand, gamified copy - no hard branding violations found), semantic dependencies (the printing-candidate DOM data attributes and Stage 3 tag taxonomy this code currently assumes exist), and confirmation that upstream/master hasn't moved past the fork point as of this writing (re-check before acting). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two-part vote-system sprint, reconciled with master's independent work (starburst/Pokémon styling, decklist-import scraping, Keyrune printing-filter flattening, expansion_hint fix) via a merge commit - fast-forwardable onto
masterfrom here.Part 1 - federation-readiness schema stub:
VoteSource.FEDERATED, a nullablepeerfield onAbstractWeightedVote,VOTE_FEDERATED_WEIGHTsetting,docs/federation-v1.md(verdict-exchange spec). Inert today - no import path creates federated votes yet.Part 2 - contested-review generalization:
vote_consensus.contested_queryset()generalizes the existing printing-tag contested-detection logic across printing/artist/tag votes.ArtistVoteStatus.CONTESTEDand a newCard.tag_vote_statusesJSONField persist contested/resolved state so the review queue can filter cheaply. NewPOST 2/voteQueue/endpoint (kind=printing|artist|tag) generalizes2/printingTagQueue/without touching it. Frontend: the "Who's That Planeswalker?" page gains a Printings/Artists/Tags tab switcher, plus the deferred "wrong?" affordance onArtistVotePicker(collapses behind pre-filled text when the artist is already confidently known).Migrations
0054_cardartistvote_peer_cardprintingtag_peer_and_more.py- additive:peerfield +federatedsource choice on all three vote tables.0055_card_tag_vote_statuses_alter_card_artist_vote_status.py- additive:Card.tag_vote_statusesJSONField +contestedchoice onartist_vote_status.Neither touches existing columns/data.
makemigrations --checkclean.API shape
POST 2/voteQueue/- request{"kind": "printing"|"artist"|"tag", "page": int}, response{"hits": int, "pages": int, "items": [{"card": Card, "tagName": string|null}]}.Verification
client_secrets.json).makemigrations --checkclean against the reconciled tree.Test plan
/printingQueueCo-Authored-By: Claude Sonnet 5 noreply@anthropic.com