Queue redesign: unified question feed + attribute chips - #21
Conversation
…etraction, confidence New GET 2/questionFeed/ (cardpicker/question_feed.py) - unified single-question feed replacing the printing/artist/tag tab split, ranked union across four tiers (AI-suggested printing, contested, moderation, fresh unresolved), with per-voter own-vote exclusion at (card, tag) granularity. Supporting pieces: CardTagVote retraction via a polarity=0 sentinel (was apply/not_applicable only), a weighted net-polarity scalar on TagConsensusEntry for chip confidence fill, PrintingCandidate extended with borderColor/isShowcase/isExtendedArt/isEtched, and the new attribute-chip tag taxonomy (cardpicker/attribute_tags.py + seed_attribute_tags command). Design writeup: journal/2026-07-14-queue-question-feed-design.md (gitignored, local only). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016i9S7LQsCL3FGaih3ZTRBJ
Replaces the printing/artist/tag/moderation tab switcher (PrintingTagQueue, GenericVoteQueue, ModerationQueue, PrintingConfirmStrip - all deleted) with QuestionFeed.tsx, driven by 2/questionFeed/. Sticky starburst/reveal/ candidate-grid mechanics extracted verbatim into cardPanel.tsx and reused directly. New attribute-chip panel (tri-state, confidence-fill, exclusion groups) with live candidate filtering and no-match gating. Selecting a candidate auto-casts positive tag votes for its own standalone attributes, making PrintingConfirmStrip redundant. Route renamed printingQueue -> whatsthat (matches the page's own "What's That Card?" branding; every other route is a single word). Fixed a latent CardPanel z-index: -1 bug this stage's chips exposed - its negative z-index was never contained to its own column, silently making its whole subtree unclickable at the hit-testing layer the moment it hosted real interactive content for the first time. Fixed via a locally-contained stacking context (position: relative + explicit z-index on the wrapping Col). Full design writeup: docs/features/printing-tags.md's new Stage 7 section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016i9S7LQsCL3FGaih3ZTRBJ
Uncontained negative z-index on a sticky element silently makes its whole subtree unclickable once it hosts interactive content; a call-count-based MSW mock breaks under React 18 Strict Mode's dev-time double-invoke. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016i9S7LQsCL3FGaih3ZTRBJ
…chip ring Card panel + candidate grid columns swapped back (candidates left, starburst anchored right) and intro text right-aligned to match. The starburst is now contained to the card's own box (moved inside AttributeChipPanel's card slot, sized off CardArea instead of the whole ring+chips panel) rather than bleeding across the full page and covering the heading/candidate grid. AttributeChipPanel restructured to a CSS grid ring (standalone chips top, the two exclusion groups left/right, card dead center) instead of stacking chips above the card image. Also: tier 4 now prioritizes a card with one AI vote + one agreeing human vote (one vote from crossing PRINTING_TAG_MIN_VOTES=2) over a totally fresh zero-vote card - a small, concrete step toward "closest to resolving first" without building a full scoring system. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016i9S7LQsCL3FGaih3ZTRBJ
Layout rework per review feedback
Full suites re-verified after these changes: backend 574 passed (same 4 known pre-existing baseline failures), frontend Jest all green, full Playwright suite 164 passed, 0 failed. Screenshots sent directly to the reviewer (desktop + 375px mobile). Still open from the review thread, not yet actioned (flagged for a decision, not silently built or dropped):
🤖 Generated with Claude Code |
Current composition was hand-tuned via iterative screenshot review, not built against a real design system. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016i9S7LQsCL3FGaih3ZTRBJ
|
Marking ready for review — all requested layout fixes are in (starburst anchored/contained, card centered in the chip ring, right-aligned text) and the follow-up questions from review are answered in the earlier comment. Backend tests CI (red X): confirmed this is the pre-existing baseline, not a regression — Ready to merge whenever you want to sequence it in. |
Real-device breakage (labels/buttons banding across the card art, large blank reserved regions) persisted after PR #55 because that fix only addressed Level 1 - Level 2, the funnel's dominant default screen, kept the identical sticky-plus-negative-z-index CardPanel unchanged. CardPanel is now position: static below the md breakpoint (768px), sticky only at md+, where desktop's side-by-side layout genuinely benefits from it and never exhibited the bug. Second, independent mechanism found in the same pass: AttributeChipPanel's ChipRing (PR #21-era, reachable via Level 2's opt-in "Filter by attribute" disclosure) had no responsive behavior - its flanking left/right chip columns were always auto-sized to their own content while the card's own column was the only flexible one, squeezing the card narrower at mobile widths. Below sm (576px) the ring now collapses to a single vertical stack instead of forming a ring. Decision rule applied throughout: the funnel's document-flow design wins wherever it conflicts with chip-ring-era positioning on mobile; chip-ring visuals (starburst, ring arrangement) survive only where they stay contained inside the card's own layout box. Claude-Session: https://claude.ai/code/session_014dB7mGPQyoML2Ri5ZrprdK Co-authored-by: Claude <noreply@anthropic.com>
Summary
Replaces the printing/artist/tag/moderation tab switcher with a single
unified question feed (
GET 2/questionFeed/), one question at a time,typed per a 4-tier ranked union (AI-suggested printing → contested →
moderation → fresh unresolved). Adds tri-state attribute chips with
confidence-fill and exclusion groups, live candidate filtering, no-match
gating, and auto-tag-on-selection. Full design writeup in
docs/features/printing-tags.md's new Stage 7 section.Companion piece, explicitly out of scope here: backend derivation of
canonical attributes from resolved printings (flagged, not attempted).
ML/scoring schedulers beyond the ranked union, federation, and
voteQueue/consumers are also untouched, per the original brief.
Deliverables (held for review)
Layout
Starburst/card panel LEFT, candidate grid RIGHT on desktop — flipped from
the original brief's candidates-left/card-right per direct follow-up
instruction. Mobile stacks card+chips first, grid second, in plain DOM
order (no CSS
ordertrick).Screenshots attached to this PR conversation (desktop 1280px, mobile 375px,
plus the
tagandmoderationquestion types).Chip taxonomy proposal + data grounding
Queried live production data before proposing anything (see Stage 7 for
the full census). 11 chips total:
Etched — the three
frame_effectsvalues common enough (849–4165occurrences) to read as a distinct visual treatment;
legendary/invertedhad higher raw counts but were excluded as a judgment call(card-type marker / one narrow product line, not a printing-variant
signal) — flagged as a call worth a second look, not asserted as
data-forced.
omitted (small, ambiguous with Showcase overlap).
Scryfall's four raw frame years into three, since finer distinctions are
hard to reliably eyeball.
promo_typesexcluded entirely — mostly production/marketingprovenance, not something a viewer can identify by looking at a card
image.
Six of the eleven tags need seeding before this ships:
manage.py seed_attribute_tags(idempotent, same pattern asseed_sensitive_tags) — not yet run in production, part of this PR'sown deploy step.
questionFeed API shape
Own-vote exclusion at
(card, tag)granularity (not justcard) so avoter's own prior vote on one attribute doesn't hide a different,
still-open attribute on the same card.
Starvation risk, stated concretely: tier 1 alone is 28,112 cards (the
full AI deductive-backfill set). At current volume, a voter working only
this feed will not see a single contested or moderation item until all
28,112
confirm_suggestionquestions are exhausted. Implemented per spec("dumb ranked union" v1) anyway; an interleaved/weighted union is the
likely v2 fix and the first item on the follow-up list below.
Retraction-support finding
CardTagVoteonly ever supported apply/not-applicable — no delete path,so the chip's untouched-cycle-back had nothing to call. Minimal addition:
post_submit_tag_votenow acceptspolarity=0as a retract sentinel(never persisted — the two real
VotePolaritychoices are unchanged),deleting the existing row instead of upserting.
A bug this stage found, not introduced
CardPanel'sz-index: -1(unchanged since the originalPrintingTagQueue.tsx, needed so the starburst bleed doesn't cover thepage heading) was never actually contained to its own column. Harmless
as long as nothing inside
CardPanelneeded to be clicked — true untilthis stage's attribute chips. The escape made the whole panel unclickable
at the browser's hit-testing layer; fixed by giving the wrapping
Colitsown local stacking context (
position: relative+ an explicit non-autoz-index—position: relativealone doesn't establish one). Full storyin Stage 7.
Server follow-up checklist
manage.py seed_attribute_tags(six new chip tags 400 without it)direct query; whether it actually swamps tiers 2-4 in practice
needs live traffic, not just the raw candidate-set size
netPolarity's optimistic client update never visiblydiverges from the server's real value (not linear in vote count once
AI/admin weights are involved — can't fully check against MSW mocks)
real moderator/voter feedback
column than in the original layout — cosmetic polish, not a
functional bug (verified clickable/functional under Playwright)
specs all run at the default 800×600 desktop viewport; only a
static 375px screenshot was captured, not verified interaction
Test plan
cardpicker/tests/test_question_feed.py(18 tests: tierordering, own-vote exclusion incl. the (card,tag)-scoping regression
case, moderator gating),
test_tag_votes.py(retraction,get_tag_net_polarity),test_attribute_tags.py,test_printing_tags_views.py(extendedPrintingCandidatefields)network-dependent baseline failures)
attributeChips.test.ts,AttributeChipPanel.test.tsx(tri-state cycle, no-sibling-votes),QuestionFeed.test.tsx(no-match gating, auto-tag payloads)(
QuestionFeedConfirmSuggestion,QuestionFeedArtistAndTag,NoMatchReasonStriprewritten for the new gating flow,ModerationQueuerewritten for the unified feed) — full suite:164 passed, 0 failed
🤖 Generated with Claude Code
https://claude.ai/code/session_016i9S7LQsCL3FGaih3ZTRBJ