From f59c1af89a3f6fa28303cec8698f7d4cd7fc886f Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Thu, 21 May 2026 12:40:08 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20`/gameboard/my-sea/`=20sig=20polarity=20?= =?UTF-8?q?now=20matches=20`/billboard/my-sign/`=20=E2=80=94=20two-bug=20s?= =?UTF-8?q?tack.=20**Bug=201=20(primary):**=20`my=5Fsea.html:10`=20had=20`?= =?UTF-8?q?{%=20if=20significator=5Freversed=20%}gravity{%=20else=20%}levi?= =?UTF-8?q?ty{%=20endif=20%}`=20=E2=80=94=20INVERTED=20from=20`my=5Fsign.h?= =?UTF-8?q?tml:22`'s=20`{%=20if=20current=5Fsignificator=5Freversed=20%}le?= =?UTF-8?q?vity{%=20else=20%}gravity{%=20endif=20%}`=20+=20its=20JS=20`=5F?= =?UTF-8?q?polarity()`=20(`revInput.value=20=3D=3D=3D=20'1'=20=3F=20'levit?= =?UTF-8?q?y'=20:=20'gravity'`).=20Same=20`User.significator=5Freversed`?= =?UTF-8?q?=20value=20produced=20opposite=20polarity=20styling=20across=20?= =?UTF-8?q?the=20two=20surfaces=20=E2=86=92=20a=20levity=20sig=20picked=20?= =?UTF-8?q?on=20my-sign=20rendered=20gravity-styled=20on=20my-sea=20(`--pr?= =?UTF-8?q?iUser`=20bg=20+=20`--secUser`=20text);=20a=20gravity=20sig=20re?= =?UTF-8?q?ndered=20levity-styled.=20User-reported=202026-05-21.=20**Bug?= =?UTF-8?q?=202=20(latent,=20masked=20by=20Bug=201):**=20`.sea-sig-card`?= =?UTF-8?q?=20hardcoded=20`.fan-corner-rank`=20+=20`i`=20to=20`color:=20rg?= =?UTF-8?q?ba(var(--secUser),=20=E2=80=A6)`=20=E2=80=94=20fine=20against?= =?UTF-8?q?=20gravity's=20`--priUser`=20bg,=20but=20against=20levity's=20`?= =?UTF-8?q?--secUser`=20bg=20(set=20by=20`.sig-stage-card`=20in=20the=20po?= =?UTF-8?q?larity=20rule=20at=20`=5Fcard-deck.scss:935-943`)=20the=20rank?= =?UTF-8?q?=20+=20suit-icon=20collided=20w.=20the=20bg=20and=20disappeared?= =?UTF-8?q?.=20Bug=201=20was=20hiding=20this:=20levity=20sigs=20were=20get?= =?UTF-8?q?ting=20rendered=20gravity-styled=20(visible),=20so=20the=20invi?= =?UTF-8?q?sibility=20only=20surfaced=20for=20gravity=20sigs=20(which=20go?= =?UTF-8?q?t=20levity-styled).=20Fixing=20Bug=201=20alone=20would've=20exp?= =?UTF-8?q?osed=20Bug=202=20for=20the=20previously-fine=20levity=20case=20?= =?UTF-8?q?=E2=86=92=20fix=20both=20in=20one=20shot.=20**Bug=202=20fix:**?= =?UTF-8?q?=20switch=20`.fan-corner-rank`=20+=20`i`=20to=20`color:=20curre?= =?UTF-8?q?ntColor`=20w.=20opacity=20preserved=20(0.85=20/=200.75);=20add?= =?UTF-8?q?=20explicit=20`color:=20rgba(var(--secUser),=201)`=20on=20the?= =?UTF-8?q?=20default=20`.sig-stage-card.sea-sig-card`=20rule=20so=20gravi?= =?UTF-8?q?ty=20inherits=20secUser;=20levity=20polarity=20rule=20already?= =?UTF-8?q?=20sets=20`.sig-stage-card=20{=20color:=20rgba(var(--priUser),?= =?UTF-8?q?=201)=20}`=20so=20it=20cascades=20down=20through=20currentColor?= =?UTF-8?q?.=20TDD=20=E2=80=94=20new=20`MySeaPolarityMatchesMySignTest`=20?= =?UTF-8?q?(2=20ITs)=20pins=20both=20pages=20to=20the=20same=20`User.signi?= =?UTF-8?q?ficator=5Freversed=20=E2=86=92=20data-polarity`=20mapping:=20un?= =?UTF-8?q?reversed=20=E2=86=92=20gravity=20on=20BOTH=20surfaces;=20revers?= =?UTF-8?q?ed=20=E2=86=92=20levity=20on=20BOTH.=201147=20IT/UT=20green.=20?= =?UTF-8?q?Visual=20verify=20deferred=20to=20user=20=E2=80=94=20the=20SCSS?= =?UTF-8?q?=20edge=20case=20wasn't=20reachable=20via=20Selenium=20(compute?= =?UTF-8?q?d-style-on---secUser=20would=20require=20palette=20resolution?= =?UTF-8?q?=20at=20runtime)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../gameboard/tests/integrated/test_views.py | 43 +++++++++++++++++++ src/static_src/scss/_card-deck.scss | 12 +++++- src/templates/apps/gameboard/my_sea.html | 2 +- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/apps/gameboard/tests/integrated/test_views.py b/src/apps/gameboard/tests/integrated/test_views.py index 6c7a476..aa65262 100644 --- a/src/apps/gameboard/tests/integrated/test_views.py +++ b/src/apps/gameboard/tests/integrated/test_views.py @@ -640,6 +640,49 @@ class MySeaPickerPhaseTemplateTest(TestCase): self.assertNotContains(response, "my-sea-picker") +class MySeaPolarityMatchesMySignTest(TestCase): + """Bug 2026-05-21 (user-reported): a levity sig chosen on /billboard/ + my-sign/ rendered as gravity-styled on /gameboard/my-sea/ (priUser bg + + secUser text) — and vice versa for gravity sigs (which then collided + w. the hardcoded --secUser corner-rank color in `.sea-sig-card`, + making the rank + suit-icon invisible against a --secUser bg). + + Root cause was a polarity inversion in `my_sea.html:10` — + `{% if significator_reversed %}gravity{% else %}levity{% endif %}` — + opposite of `my_sign.html:22` + its JS `_polarity()`. This test pins + the two surfaces to agree on the same `User.significator_reversed` + → `data-polarity` mapping, so any future drift gets caught.""" + + def setUp(self): + from apps.epic.models import personal_sig_cards + self.user = User.objects.create(email="polarity@test.io") + self.client.force_login(self.user) + target = personal_sig_cards(self.user)[0] + self.user.significator = target + self.user.save(update_fields=["significator"]) + + def test_unreversed_sig_renders_gravity_on_both_surfaces(self): + """`significator_reversed=False` (the on-creation default) renders + data-polarity="gravity" on BOTH my_sign + my_sea — the convention + established by my_sign's Sprint 4a picker + its `_polarity()` JS.""" + self.user.significator_reversed = False + self.user.save(update_fields=["significator_reversed"]) + sea = self.client.get(reverse("my_sea")).content.decode() + sign = self.client.get(reverse("billboard:my_sign")).content.decode() + self.assertIn('data-polarity="gravity"', sea) + self.assertIn('data-polarity="gravity"', sign) + + def test_reversed_sig_renders_levity_on_both_surfaces(self): + """`significator_reversed=True` (FLIP-toggled on my_sign) renders + data-polarity="levity" on BOTH surfaces.""" + self.user.significator_reversed = True + self.user.save(update_fields=["significator_reversed"]) + sea = self.client.get(reverse("my_sea")).content.decode() + sign = self.client.get(reverse("billboard:my_sign")).content.decode() + self.assertIn('data-polarity="levity"', sea) + self.assertIn('data-polarity="levity"', sign) + + class MySeaSpreadFormTemplateTest(TestCase): """Sprint 5 iter 3 — form col + SPREAD dropdown structure + default- spread context + cross's `data-spread-shape` attribute. Iter 3 spec diff --git a/src/static_src/scss/_card-deck.scss b/src/static_src/scss/_card-deck.scss index 4e1d215..1cb0fee 100644 --- a/src/static_src/scss/_card-deck.scss +++ b/src/static_src/scss/_card-deck.scss @@ -1364,13 +1364,20 @@ $sea-card-h: 6.5rem; position: relative; z-index: 2; + // Corner-rank + suit-icon track `color` so the polarity rules below + // (which set `.sig-stage-card { color: ... }` to --priUser for levity) + // flip the contrast w. the card's bg. The default (gravity, --priUser + // bg) inherits --secUser from the `.sig-stage-card.sea-sig-card` rule + // below; the levity polarity rule overrides `.sig-stage-card { color }` + // to --priUser, which propagates down through currentColor. .fan-corner-rank { font-size: 1.2rem; font-weight: 700; line-height: 1; - color: rgba(var(--secUser), 0.85); + color: currentColor; + opacity: 0.85; } - i { font-size: 1rem; color: rgba(var(--secUser), 0.75); } + i { font-size: 1rem; color: currentColor; opacity: 0.75; } } // .sig-stage-card is normally scoped inside .sig-stage — re-apply the card shell @@ -1384,6 +1391,7 @@ $sea-card-h: 6.5rem; border-radius: 0.5rem; background: rgba(var(--priUser), 1); border: 0.15rem solid rgba(var(--secUser), 0.6); + color: rgba(var(--secUser), 1); // default (gravity) text color; `[data-polarity="levity"] .sig-stage-card { color: --priUser }` overrides for levity. Corner-rank + suit-icon track currentColor. display: flex; flex-direction: column; position: relative; diff --git a/src/templates/apps/gameboard/my_sea.html b/src/templates/apps/gameboard/my_sea.html index 52dfeb6..ff6bda9 100644 --- a/src/templates/apps/gameboard/my_sea.html +++ b/src/templates/apps/gameboard/my_sea.html @@ -7,7 +7,7 @@ {% block content %}
+ data-polarity="{% if significator_reversed %}levity{% else %}gravity{% endif %}"> {% if not user_has_sig %} {# Sprint 4b sign-gate. The draw UX is gated behind a saved #} {# significator — render a Look!-formatted Brief-style line w. #}