Skip to content

Vote system: federation stub + contested-review generalization - #7

Merged
WilfordGrimley merged 8 commits into
masterfrom
vote-review-and-federation-stub
Jul 13, 2026
Merged

Vote system: federation stub + contested-review generalization#7
WilfordGrimley merged 8 commits into
masterfrom
vote-review-and-federation-stub

Conversation

@WilfordGrimley

@WilfordGrimley WilfordGrimley commented Jul 13, 2026

Copy link
Copy Markdown

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 master from here.

Part 1 - federation-readiness schema stub: VoteSource.FEDERATED, a nullable peer field on AbstractWeightedVote, VOTE_FEDERATED_WEIGHT setting, 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.CONTESTED and a new Card.tag_vote_statuses JSONField persist contested/resolved state so the review queue can filter cheaply. New POST 2/voteQueue/ endpoint (kind=printing|artist|tag) generalizes 2/printingTagQueue/ without touching it. Frontend: the "Who's That Planeswalker?" page gains a Printings/Artists/Tags tab switcher, plus the deferred "wrong?" affordance on ArtistVotePicker (collapses behind pre-filled text when the artist is already confidently known).

Migrations

  • 0054_cardartistvote_peer_cardprintingtag_peer_and_more.py - additive: peer field + federated source choice on all three vote tables.
  • 0055_card_tag_vote_statuses_alter_card_artist_vote_status.py - additive: Card.tag_vote_statuses JSONField + contested choice on artist_vote_status.

Neither touches existing columns/data. makemigrations --check clean.

API shape

POST 2/voteQueue/ - request {"kind": "printing"|"artist"|"tag", "page": int}, response {"hits": int, "pages": int, "items": [{"card": Card, "tagName": string|null}]}.

Verification

  • Backend: 418 passed, only the 4 documented secret-dependent failures (2 Moxfield, 2 missing client_secrets.json).
  • mypy/black/isort/ruff/prettier/eslint clean via the isolated pre-commit environment.
  • Full Playwright suite: 150/150 substantive tests passing (1 failure during local verification was a self-inflicted side effect of running the dev server on a non-default port to dodge a stale server from an unrelated worktree - confirmed pre-existing and unrelated by diffing the failing mock handler against this branch's changes).
  • Production build succeeds.
  • makemigrations --check clean against the reconciled tree.

Test plan

  • Review the two migrations above
  • Spot-check the Printings/Artists/Tags tab switcher on /printingQueue
  • Merge (fast-forward, no conflicts)

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

WilfordGrimley and others added 8 commits July 12, 2026 12:58
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
WilfordGrimley merged commit 87db3dd into master Jul 13, 2026
7 of 8 checks passed
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>
@WilfordGrimley
WilfordGrimley deleted the vote-review-and-federation-stub branch July 14, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant