Files
dd99364b78c289f97c19794a5b5977d38bffc71a
.sig-stage-card.sig-stage-card--image SCSS contract via a comma-list selector extension covering the parallel container classes (.my-sign-applet-card.my-sign-applet-card--image + .my-sea-slot.my-sea-slot--image) — single source of truth for the contour-stroke drop-shadow chain + tray-card silhouette black depth shadow + .is-flipped-to-back visibility toggle + the --img-stroke-color arcana-keyed CSS prop. Templates branch server-side on card.deck_variant.has_card_images: image-mode renders <img class="sig-stage-card-img" src="{{ card.image_url }}"> w. the marker class + data-arcana-key attr; text mode keeps the existing fan-card-corner + fan-card-face scaffold unchanged. SCSS import-order quirk: _card-deck.scss imports BEFORE both _billboard.scss (which nests .my-sign-applet-card inside .my-sign-applet-body for container queries) and _gameboard.scss (which nests .my-sea-slot--filled.--gravity/--levity inside #id_applet_my_sea w. specificity 1,2,0). The shared top-level image-mode rule at 0,2,0 loses on bg/border/padding to those nested base rules, so each app's stylesheet gets a parallel &.--image { background: transparent; border: 0; padding: 0 } override inside its own nest. The filter-chain rules on .sig-stage-card-img (descendant selector inside the shared rule) DO win since the apps don't restyle that class — only the outer container needs the parallel override. Sprint A.6 bonus: applet-level FLIP btn for non-polarized image-equipped decks (Minchiate today). Mirrors the my_sign.html main page A.5-polish-2 FLIP-to-back contract — .my-sign-applet-flip-btn nested inside the .--image card so absolute positioning anchors to the card bounds; inline <script> IIFE (gated inside the sig-present {% with card %} scope to keep card in lexical reach + prevent the JS selector string leaking into the no-sig DOM where assertNotContains "my-sign-applet-card" ITs catch it) attaches a click handler that runs the same rotateY 0→90→0 animation, toggles .is-flipped-to-back at the halfway point, and clears data-flipping at end; SCSS .my-sign-applet-card[data-flipping] .my-sign-applet-flip-btn { opacity: 0; pointer-events: none } hides the btn mid-spin. Critical scope bug caught + fixed during browser verify: initial draft had the script BLOCK + its {% if card.deck_variant.has_card_images %} gate placed AFTER the {% endwith %} closing tag — card was out of scope at the {% if %} evaluation, Django treats undefined vars as empty string, the gate evaluated falsy, and the script NEVER rendered (the FLIP btn rendered fine since it was inside the with block, but no JS handler → click did nothing but the CSS depress animation). Fix: move {% endwith %} to AFTER the script gate so card is still in scope. 7 new ITs total: 2 in BillboardAppletMySignTest (image-equipped Minchiate renders --image class + img + correct asset URL + lacks text scaffold; Earthman keeps the text scaffold + lacks --image); 3 in BillboardMySignViewTest (data-deck-polarized attr present; back-img element renders for non-polarized image deck; polarized deck omits it); 1 in GameboardViewTest (image-equipped Minchiate slot renders --image + img + lacks text scaffold); plus regression coverage on the no-sig empty-state assertion that originally caught the script-scope bug (assertNotContains validates the script doesn't leak in the no-sig case). Tests: 6 new ITs green; 1306/1306 IT+UT total green (72s; +6 from bdf6a25's 1303 — minus 3 dups since some ITs were counted across both A.6 + A.5-polish-2 runs). Visual verify by user 2026-05-25 PM: stage card image renders cleanly; FLIP cycles to back image + back via animation; FLIP btn hides during 500ms spin; placeholder dim styling correctly distinguishes no-deck state
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat: wallet Shop polish — microtooltip extraction, Shop-first ordering, DRY tooltip styling, writs rebalance, "no expiry" on all items. Visual-pass tweaks landing atop the 5-chunk Shop rollout (commits
8e476f5 → d28cf7b). **Microtooltip extraction**: .tt-microbutton-portal (Chunk 4's wrap-inside-.tt) replaced w. a sibling .tt-micro div on each .shop-tile. wallet.js's initWalletTooltips clones BOTH into separate portals on hover — .tt → #id_tooltip_portal (main card), .tt-micro → #id_mini_tooltip_portal (small italic pill at bottom-right of main, mirroring Game Kit's Equipped/Unequipped/In-Use mini portal). Hover persistence covers both portals + the source tile w. a 200ms grace timer cancelled by mouseenter on any of the 3 zones. Capped items (BAND-owned) render NO btn at all — just "Already owned" microtext (mirrors Game Kit's status-only "Equipped" pill rather than the disabled-× pattern that lived in Chunk 4). **Tooltip-pin on guard open**: WalletTooltips.pin() / .unpin() exposed on window; wallet-shop.js's BUY click calls pin() before showGuard() + both onConfirm / onDismiss callbacks call unpin() → the item tooltip stays visible behind the guard's "Buy {name} for ${price}?" prompt instead of orphaning. **Shop-first applet ordering**: new Applet.display_order field (default 100, lower = earlier; PK tie-break preserves legacy insertion-order for the existing 3 applets); seed migration sets wallet-shop.display_order=10 so Shop renders atop Balances/Tokens/Payment. applet_context() updated to .order_by("display_order", "pk"). New WalletAppletOrderTest (2 ITs) pins Shop-first DOM order + view-context list. **DRY tooltip styling**: shop tooltip now uses the same 4-slot .tt-title / .tt-description / .tt-shoptalk / .tt-expiry classes as the Tokens row. New ShopItem.shoptalk field for the italic flavor line (band-1 = "Unlimited free entry (BYOB)" split out of description; tithes blank). New ShopItem.tooltip_expiry() method returns "no expiry" — eternal-stock convention (all current items; seasonal listings could override later). **Writs rebalance**: locked 2026-05-22 — tithe-1 144→12 writs, tithe-5 750→60 writs. Description text updated in lockstep ("1 Tithe Token + 12 Writs" / "5 Tithe Tokens + 60 Writs"). **Badge tweak**: ×N badge shrunk 2rem → 1.5rem + nudged further off-tile (top: -0.7rem, right: -1rem) so most of the underlying icon stays visible. **SCSS**: .tt-micro hidden in source DOM (portal-only); #id_mini_tooltip_portal mostly mirrors gameboard's mini at _gameboard.scss:140 but allows BUY-btn label to wrap onto multiple lines (white-space: normal on .tt-buy-btn); .tt-already-owned styled w. --secUser italic at 0.85rem to match Game Kit pills. **Migrations** — 5 new: lyric/0010_repricing_tithe_writs (writs + description), lyric/0011_shopitem_shoptalk (schema), lyric/0012_seed_shop_shoptalk (band split), applets/0012_applet_display_order (schema), applets/0013_wallet_shop_display_order (Shop atop). All idempotent. **TDD** — 5 new ITs across test_shop_models.py (shoptalk default + per-item assertions, tooltip_expiry method, updated tithe writs values, WalletAppletOrderTest), 1 new FT (test_shop_buy_guard_portal_pins_item_tooltip — programmatically dispatches mouseenter/mouseleave to exercise the pin/unpin race), 3 new Jasmine specs (T6 pin-on-click, T7 unpin-on-confirm, T8 unpin-on-dismiss). Existing FT band-owned assertion switched to .tt-micro (no .tt-buy-btn present), Jasmine T2 rewritten to assert no btn renders. **3 traps caught** mid-build: (a) multi-line {# #} comment leaked into DOM again (cf [[feedback-django-comments-single-line-only]]) — pinned the trap; (b) spyOn(window, 'fetch') Jasmine double-spy collision (cf trapped previously); (c) async pollution where afterEach restores window.Stripe=undefined before _doBuy's continuation hits it — fixed by per-test never-resolving fetch mock. 1211 IT/UT + 9 wallet FTs green; Jasmine SpecRunner verified visually (FT hangs Selenium-side on spec count). Pipeline will sweep all FTs
+52 IT/UT to close IT/UT-only coverage gaps (93% → 96%) — full suite 983 tests in 47s ; UTs in epic/tests/unit/test_models.py —
TarotCardEmanationForTest (4) covers emanation_for(polarity) w. levity/gravity overrides + fallback to name_title for cards w.o a polarity split (cards 48-49 are the only polarity-split cards in the deck so this method is sparsely exercised by ITs); TarotCardReversalForTest (4) covers reversal_for(polarity) w. polarity-split + reversal_qualifier fallback + further fallthrough to emanation_for; TarotCardNameSplitTest (4) covers name_group/name_title colon-split parsing (prefix-w-colon / suffix / no-colon edge); TarotCardCautionsJsonTest (2) covers the cautions_json JSON serialiser ; UTs in epic/tests/unit/test_utils.py — PlanetHouseFallbackTest +1 happy-path test (degree=15 lands in house 1 w. sequential cusps) for the normal cusp-match branch alongside the existing pathological fallback test; TopCapacitorsTest (6) covers all top_capacitors() branches — empty dict / None / all-zero counts (the L56 max(counts.values()) <= 0 fallback that was uncovered) / single-winner / tie-clockwise-order / enriched dict {"count":N} input shape ; ITs in epic/tests/integrated/test_models.py — TarotDeckDrawTest extended w. 5 tests for remaining_count (happy + no-deck-variant fallback to 0) + draw() happy-path (returns n tuples of (TarotCard, bool) / appends to drawn_card_ids / never repeats cards across consecutive draws); existing ValueError + shuffle tests preserved ; ITs in epic/tests/integrated/test_views.py — SigEventRetractionTest (4 tests) covers the three data["retracted"] = True paths that the FT test_game_room_select_sig.py walks transitively but no IT pins directly: sig_unready retracts prior SIG_READY (L937), sig_ready retracts prior SIG_UNREADY (L907), sig_reserve action=release while ready retracts prior SIG_READY + records fresh SIG_UNREADY (L823); SigReserveInvalidCardIdTest (1) covers TarotCard.DoesNotExist → 400 (L840-841) ; SigSelectGravityContextTest (3) covers the user_polarity = 'gravity' branch (L322) + the gravity_sig_cards lookup (L357) — all existing SIG_SELECT context tests use the founder-as-PC-levity setup so these branches sat uncovered; logs in as gamers[5] (BC role) + asserts user_polarity + sig_cards match gravity_sig_cards() output ; SeaDeckViewTest (7) mirrors the test_game_room_select_sea.py FT but isolates the JSON contract — covers 403 when unseated, empty halves when seat has no deck_variant (L1255-1256 early-out), two-halves shape, ~even split, card_dict keys (id/name/arcana/corner_rank/suit_icon/name_group/name_title/reversed/qualifiers), reversed field is bool, claimed-significator exclusion via room.table_seats.exclude(significator__isnull=True) ; ITs in dashboard/tests/integrated/test_views.py — ProfileViewTest +2 (reserved-handle "adman" rejection — L116-117: username stays unchanged + redirect to /); KitBagViewTest (3) covers the kit_bag view's panel render w. TITHE-sort branch (L169-175) + login guard ; ITs in dashboard/tests/integrated/test_sky_views.py — SkyViewTest +2 (saved birth datetime renders in user's sky_birth_tz via astimezone L300-306 — 16:00 UTC → 12:00 EDT; invalid-tz string triggers ZoneInfoNotFoundError → swallowed pass → UTC fallback at 16:00) ; ITs in gameboard/tests/integrated/test_views.py — EquipTrinketViewTest +2 (POST equips trinket + returns 204 — L83-85; non-owner POST returns 404 via get_object_or_404); UnequipTrinketViewTest +2 (POST clears matching equipped_trinket — L107-110; POST of non-matching token is a 204 no-op, the implicit else branch) ; .coveragerc omit gains */reset_staging_db.py per user — mgmt cmd was the only 0%-stmt module that wasn't exercised by tests at all + we agreed it's deliberately untested staging-side code ; palette-monochrome-dark rebalance in rootvars.scss — --quiUser/--sixUser/--sepUser remapped to (secAg / quaAg / priPt) instead of (quaAg / terAg / secAg), shifting the secondary/subtle/deep-subtle anchors up the silver gradient so the palette reads more cleanly under the new sig-stage card colours from 3242873 ; uncovered remnants from earlier analysis intentionally left in place — consumers.py at 68% (channels-tag tests excluded; would need --tag=channels run), Carte Blanche slot navigation + sky_dice + tarot_deck preview view paths (the "bigger investments" tier from session triage; FT-covered + the IT setup is heavier than the immediate value), defensive except fallbacks that need contrived inputs to fire, and a handful of __str__s/pass branches not worth a test apiece — TDD