my sea preview: namespace cells .sea-prev-pos-* to de-collide from the live cross; harden spread dropdown + CI retry — TDD
- rename the spread-preview's `.sea-pos-*` cells to `.sea-prev-pos-*` (template + aliased Celtic-cross geometry under `.sea-cross--preview` in _gameboard.scss + the SeaDealSpec fixture) so a bare `.sea-pos-*` matches ONLY the live `.my-sea-cross`; fixes test_picker_renders_sao_default_position_subset (was 2≠1 — the preview duplicated every cross cell) - spread dropdown: cap `.sea-select-list` w. max-height + overflow-y:auto, & JS-click the option in the `_pick` FT helper, so the last option (escape-velocity) can't land below the un-scrollable picker-modal fold in landscape (ElementNotInteractable, seen in CI build 373) - _retry_failed.sh: anchor the FAIL/ERROR label sed to the FIRST paren group so parameterized subTest failures retry the real method label, not a bogus `position='…'` module (ModuleNotFoundError) [[project-my-sea-roadmap]] [[feedback-collectstatic-before-ft]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,10 +41,13 @@ if [ "$FIRST" -eq 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Parse failure labels. Match both FAIL: and ERROR: lines; the dotted
|
# Parse failure labels. Match both FAIL: and ERROR: lines; the dotted
|
||||||
# path lives inside the trailing parens. `sort -u` dedupes if a single
|
# path lives inside the FIRST parens. Parameterized subTest failures
|
||||||
# test produces multiple lines (rare but possible).
|
# append a SECOND `(param='...')` group, so anchor to the first group —
|
||||||
|
# a greedy `^.*\(` would wrongly grab the subtest param and feed
|
||||||
|
# `manage.py test` a bogus label (ModuleNotFoundError). `sort -u`
|
||||||
|
# dedupes the repeated method label the subtests share.
|
||||||
FAILED=$(grep -E '^(FAIL|ERROR): ' "$LOG" \
|
FAILED=$(grep -E '^(FAIL|ERROR): ' "$LOG" \
|
||||||
| sed -E 's/^.*\(([^)]+)\)[^()]*$/\1/' \
|
| sed -E 's/^[^(]*\(([^)]+)\).*/\1/' \
|
||||||
| sort -u \
|
| sort -u \
|
||||||
| tr '\n' ' ')
|
| tr '\n' ' ')
|
||||||
|
|
||||||
|
|||||||
@@ -623,7 +623,11 @@ class MySeaSpreadFormTest(FunctionalTest):
|
|||||||
By.CSS_SELECTOR,
|
By.CSS_SELECTOR,
|
||||||
f".sea-select-list [role='option'][data-value='{value}']",
|
f".sea-select-list [role='option'][data-value='{value}']",
|
||||||
)
|
)
|
||||||
opt.click()
|
# JS-click (matches the _open_spread_modal idiom): the dropdown
|
||||||
|
# opens downward in landscape inside the centered, un-scrollable
|
||||||
|
# picker modal, so the LAST option can land below the fold where a
|
||||||
|
# native .click()'s auto-scroll fails (ElementNotInteractable).
|
||||||
|
self.browser.execute_script("arguments[0].click()", opt)
|
||||||
|
|
||||||
for value, expected_visible in SPREAD_POSITIONS.items():
|
for value, expected_visible in SPREAD_POSITIONS.items():
|
||||||
with self.subTest(spread=value):
|
with self.subTest(spread=value):
|
||||||
|
|||||||
@@ -381,9 +381,10 @@ describe("SeaDeal", () => {
|
|||||||
// ── Spread-preview scoping ──────────────────────────────────────────────── //
|
// ── Spread-preview scoping ──────────────────────────────────────────────── //
|
||||||
// The spread modal carries a miniaturized `.sea-cross--preview` INSIDE the
|
// The spread modal carries a miniaturized `.sea-cross--preview` INSIDE the
|
||||||
// same `#id_sea_overlay`. SeaDeal must deal ONLY onto the real cross — the
|
// same `#id_sea_overlay`. SeaDeal must deal ONLY onto the real cross — the
|
||||||
// preview slots stay empty (user-spec 2026-06-07). Guards the trap where a
|
// preview slots stay empty (user-spec 2026-06-07). The shipped preview now
|
||||||
// bare `overlay.querySelector('.sea-pos-cover')` could grab the preview's
|
// namespaces its cells `.sea-prev-pos-*` so a bare `.sea-pos-*` never grabs
|
||||||
// slot. (Gameroom Sea Select felt rebuild.)
|
// it; SeaDeal also scopes via `.sea-cross:not(.sea-cross--preview)`, which
|
||||||
|
// this spec locks. (Gameroom Sea Select felt rebuild.)
|
||||||
describe("preview cross is never dealt to", () => {
|
describe("preview cross is never dealt to", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
makeFixture();
|
makeFixture();
|
||||||
@@ -391,7 +392,7 @@ describe("SeaDeal", () => {
|
|||||||
const preview = document.createElement("div");
|
const preview = document.createElement("div");
|
||||||
preview.className = "sea-cross sea-cross--preview";
|
preview.className = "sea-cross sea-cross--preview";
|
||||||
preview.innerHTML =
|
preview.innerHTML =
|
||||||
'<div class="sea-crucifix-cell sea-pos-cover">' +
|
'<div class="sea-crucifix-cell sea-prev-pos-cover">' +
|
||||||
'<div class="sea-card-slot sea-card-slot--empty"></div></div>';
|
'<div class="sea-card-slot sea-card-slot--empty"></div></div>';
|
||||||
overlay.appendChild(preview);
|
overlay.appendChild(preview);
|
||||||
SeaDeal._testInit(); // re-capture _cross with the preview present
|
SeaDeal._testInit(); // re-capture _cross with the preview present
|
||||||
@@ -402,7 +403,7 @@ describe("SeaDeal", () => {
|
|||||||
const realSlot = overlay.querySelector(
|
const realSlot = overlay.querySelector(
|
||||||
".sea-cross:not(.sea-cross--preview) .sea-pos-cover .sea-card-slot");
|
".sea-cross:not(.sea-cross--preview) .sea-pos-cover .sea-card-slot");
|
||||||
const previewSlot = overlay.querySelector(
|
const previewSlot = overlay.querySelector(
|
||||||
".sea-cross--preview .sea-pos-cover .sea-card-slot");
|
".sea-cross--preview .sea-prev-pos-cover .sea-card-slot");
|
||||||
expect(realSlot.classList.contains("sea-card-slot--filled")).toBe(true);
|
expect(realSlot.classList.contains("sea-card-slot--filled")).toBe(true);
|
||||||
expect(previewSlot.classList.contains("sea-card-slot--filled")).toBe(false);
|
expect(previewSlot.classList.contains("sea-card-slot--filled")).toBe(false);
|
||||||
expect(previewSlot.classList.contains("sea-card-slot--empty")).toBe(true);
|
expect(previewSlot.classList.contains("sea-card-slot--empty")).toBe(true);
|
||||||
@@ -411,7 +412,7 @@ describe("SeaDeal", () => {
|
|||||||
it("register() (AUTO DRAW path) also skips the preview slot", () => {
|
it("register() (AUTO DRAW path) also skips the preview slot", () => {
|
||||||
SeaDeal.register(CARD, ".sea-pos-cover", true);
|
SeaDeal.register(CARD, ".sea-pos-cover", true);
|
||||||
const previewSlot = overlay.querySelector(
|
const previewSlot = overlay.querySelector(
|
||||||
".sea-cross--preview .sea-pos-cover .sea-card-slot");
|
".sea-cross--preview .sea-prev-pos-cover .sea-card-slot");
|
||||||
expect(previewSlot.classList.contains("sea-card-slot--filled")).toBe(false);
|
expect(previewSlot.classList.contains("sea-card-slot--filled")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1972,6 +1972,13 @@ $_sea-card-glow: 0 0 0.5rem 0.5rem rgba(var(--ninUser), 0.3), 0 0 0.4rem rgba(0,
|
|||||||
border-radius: 0.3rem;
|
border-radius: 0.3rem;
|
||||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.4);
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
// Bound the dropdown height + give it its own scroll so a long list
|
||||||
|
// (or a low trigger position in landscape) can never run off the
|
||||||
|
// bottom of the un-scrollable picker modal — the last option stays
|
||||||
|
// reachable instead of landing below the fold.
|
||||||
|
max-height: 16rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
li[role="option"] {
|
li[role="option"] {
|
||||||
padding: 0.4rem 0.5rem;
|
padding: 0.4rem 0.5rem;
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
|
|||||||
@@ -870,19 +870,54 @@ body.page-gameboard {
|
|||||||
.sea-card-slot--crossing { width: 1.6rem; height: 2.6rem; }
|
.sea-card-slot--crossing { width: 1.6rem; height: 2.6rem; }
|
||||||
.sea-sig-card { transform: scale(0.85); }
|
.sea-sig-card { transform: scale(0.85); }
|
||||||
|
|
||||||
|
// The preview cells are namespaced `.sea-prev-pos-*` (renamed from
|
||||||
|
// `.sea-pos-*`) so a bare `.sea-pos-*` selector matches ONLY the live
|
||||||
|
// `.my-sea-cross` cells, not this preview. The live cross draws its
|
||||||
|
// Celtic-Cross geometry from bare `.sea-pos-*` rules in _card-deck.scss;
|
||||||
|
// the preview needs its own scoped copies of that geometry below.
|
||||||
|
.sea-prev-pos-crown { grid-area: crown; }
|
||||||
|
.sea-prev-pos-leave { grid-area: leave; }
|
||||||
|
.sea-prev-pos-core { grid-area: core; position: relative; }
|
||||||
|
.sea-prev-pos-loom { grid-area: loom; }
|
||||||
|
.sea-prev-pos-lay { grid-area: lay; }
|
||||||
|
|
||||||
|
// Cover + Cross absolutely overlaid on the center Sig card (mirrors
|
||||||
|
// _card-deck.scss's `.sea-pos-cover/.sea-pos-cross` overlay rules).
|
||||||
|
.sea-prev-pos-cover,
|
||||||
|
.sea-prev-pos-cross {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.sea-prev-pos-cover { z-index: 3; }
|
||||||
|
.sea-prev-pos-cross { z-index: 4; }
|
||||||
|
.sea-prev-pos-cross .sea-card-slot { transform: rotate(90deg); }
|
||||||
|
|
||||||
|
// Empty Cover/Cross slots stay transparent + dotted so the Sig card
|
||||||
|
// reads through (matches the live cross's empty-overlay styling).
|
||||||
|
.sea-prev-pos-cover .sea-card-slot--empty,
|
||||||
|
.sea-prev-pos-cross .sea-card-slot--empty {
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: rgba(var(--terUser), 0.25);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
// Per-spread position subsets — mirror the .my-sea-cross hide rules so the
|
// Per-spread position subsets — mirror the .my-sea-cross hide rules so the
|
||||||
// preview shape matches the live cross for the 3-card spreads.
|
// preview shape matches the live cross for the 3-card spreads.
|
||||||
&[data-spread="past-present-future"] {
|
&[data-spread="past-present-future"] {
|
||||||
.sea-pos-crown, .sea-pos-cross, .sea-pos-lay { display: none; }
|
.sea-prev-pos-crown, .sea-prev-pos-cross, .sea-prev-pos-lay { display: none; }
|
||||||
}
|
}
|
||||||
&[data-spread="situation-action-outcome"] {
|
&[data-spread="situation-action-outcome"] {
|
||||||
.sea-pos-leave, .sea-pos-loom, .sea-pos-cross { display: none; }
|
.sea-prev-pos-leave, .sea-prev-pos-loom, .sea-prev-pos-cross { display: none; }
|
||||||
}
|
}
|
||||||
&[data-spread="mind-body-spirit"] {
|
&[data-spread="mind-body-spirit"] {
|
||||||
.sea-pos-leave, .sea-pos-cover, .sea-pos-cross { display: none; }
|
.sea-prev-pos-leave, .sea-prev-pos-cover, .sea-prev-pos-cross { display: none; }
|
||||||
}
|
}
|
||||||
&[data-spread="desire-obstacle-solution"] {
|
&[data-spread="desire-obstacle-solution"] {
|
||||||
.sea-pos-leave, .sea-pos-cover, .sea-pos-lay { display: none; }
|
.sea-prev-pos-leave, .sea-prev-pos-cover, .sea-prev-pos-lay { display: none; }
|
||||||
}
|
}
|
||||||
// Celtic Cross variants (waite-smith / escape-velocity) — all 6 visible.
|
// Celtic Cross variants (waite-smith / escape-velocity) — all 6 visible.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,9 +381,10 @@ describe("SeaDeal", () => {
|
|||||||
// ── Spread-preview scoping ──────────────────────────────────────────────── //
|
// ── Spread-preview scoping ──────────────────────────────────────────────── //
|
||||||
// The spread modal carries a miniaturized `.sea-cross--preview` INSIDE the
|
// The spread modal carries a miniaturized `.sea-cross--preview` INSIDE the
|
||||||
// same `#id_sea_overlay`. SeaDeal must deal ONLY onto the real cross — the
|
// same `#id_sea_overlay`. SeaDeal must deal ONLY onto the real cross — the
|
||||||
// preview slots stay empty (user-spec 2026-06-07). Guards the trap where a
|
// preview slots stay empty (user-spec 2026-06-07). The shipped preview now
|
||||||
// bare `overlay.querySelector('.sea-pos-cover')` could grab the preview's
|
// namespaces its cells `.sea-prev-pos-*` so a bare `.sea-pos-*` never grabs
|
||||||
// slot. (Gameroom Sea Select felt rebuild.)
|
// it; SeaDeal also scopes via `.sea-cross:not(.sea-cross--preview)`, which
|
||||||
|
// this spec locks. (Gameroom Sea Select felt rebuild.)
|
||||||
describe("preview cross is never dealt to", () => {
|
describe("preview cross is never dealt to", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
makeFixture();
|
makeFixture();
|
||||||
@@ -391,7 +392,7 @@ describe("SeaDeal", () => {
|
|||||||
const preview = document.createElement("div");
|
const preview = document.createElement("div");
|
||||||
preview.className = "sea-cross sea-cross--preview";
|
preview.className = "sea-cross sea-cross--preview";
|
||||||
preview.innerHTML =
|
preview.innerHTML =
|
||||||
'<div class="sea-crucifix-cell sea-pos-cover">' +
|
'<div class="sea-crucifix-cell sea-prev-pos-cover">' +
|
||||||
'<div class="sea-card-slot sea-card-slot--empty"></div></div>';
|
'<div class="sea-card-slot sea-card-slot--empty"></div></div>';
|
||||||
overlay.appendChild(preview);
|
overlay.appendChild(preview);
|
||||||
SeaDeal._testInit(); // re-capture _cross with the preview present
|
SeaDeal._testInit(); // re-capture _cross with the preview present
|
||||||
@@ -402,7 +403,7 @@ describe("SeaDeal", () => {
|
|||||||
const realSlot = overlay.querySelector(
|
const realSlot = overlay.querySelector(
|
||||||
".sea-cross:not(.sea-cross--preview) .sea-pos-cover .sea-card-slot");
|
".sea-cross:not(.sea-cross--preview) .sea-pos-cover .sea-card-slot");
|
||||||
const previewSlot = overlay.querySelector(
|
const previewSlot = overlay.querySelector(
|
||||||
".sea-cross--preview .sea-pos-cover .sea-card-slot");
|
".sea-cross--preview .sea-prev-pos-cover .sea-card-slot");
|
||||||
expect(realSlot.classList.contains("sea-card-slot--filled")).toBe(true);
|
expect(realSlot.classList.contains("sea-card-slot--filled")).toBe(true);
|
||||||
expect(previewSlot.classList.contains("sea-card-slot--filled")).toBe(false);
|
expect(previewSlot.classList.contains("sea-card-slot--filled")).toBe(false);
|
||||||
expect(previewSlot.classList.contains("sea-card-slot--empty")).toBe(true);
|
expect(previewSlot.classList.contains("sea-card-slot--empty")).toBe(true);
|
||||||
@@ -411,7 +412,7 @@ describe("SeaDeal", () => {
|
|||||||
it("register() (AUTO DRAW path) also skips the preview slot", () => {
|
it("register() (AUTO DRAW path) also skips the preview slot", () => {
|
||||||
SeaDeal.register(CARD, ".sea-pos-cover", true);
|
SeaDeal.register(CARD, ".sea-pos-cover", true);
|
||||||
const previewSlot = overlay.querySelector(
|
const previewSlot = overlay.querySelector(
|
||||||
".sea-cross--preview .sea-pos-cover .sea-card-slot");
|
".sea-cross--preview .sea-prev-pos-cover .sea-card-slot");
|
||||||
expect(previewSlot.classList.contains("sea-card-slot--filled")).toBe(false);
|
expect(previewSlot.classList.contains("sea-card-slot--filled")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ NEVER receive dealt cards (SeaDeal scopes its slot queries to
|
|||||||
shape still reads. Shared by the gameroom Sea Select modal AND my_sea's spread
|
shape still reads. Shared by the gameroom Sea Select modal AND my_sea's spread
|
||||||
modal so the two locations are identical.
|
modal so the two locations are identical.
|
||||||
|
|
||||||
|
The cell classes are namespaced `.sea-prev-pos-*` (NOT `.sea-pos-*`) so a bare
|
||||||
|
`.sea-pos-*` selector matches ONLY the live `.my-sea-cross` cells, never this
|
||||||
|
preview. The preview's Celtic-Cross geometry is supplied by scoped copies under
|
||||||
|
`.sea-cross--preview` in _gameboard.scss.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
preview_spread — slug to seed `data-spread` (drives the per-spread show/hide).
|
preview_spread — slug to seed `data-spread` (drives the per-spread show/hide).
|
||||||
sig — the significator card for the center pip (corner_rank +
|
sig — the significator card for the center pip (corner_rank +
|
||||||
@@ -14,27 +19,27 @@ Args:
|
|||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
<div class="sea-cards-col sea-cards-col--preview">
|
<div class="sea-cards-col sea-cards-col--preview">
|
||||||
<div class="sea-cross sea-cross--preview" data-spread="{{ preview_spread }}">
|
<div class="sea-cross sea-cross--preview" data-spread="{{ preview_spread }}">
|
||||||
<div class="sea-crucifix-cell sea-pos-crown">
|
<div class="sea-crucifix-cell sea-prev-pos-crown">
|
||||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sea-crucifix-cell sea-pos-leave">
|
<div class="sea-crucifix-cell sea-prev-pos-leave">
|
||||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sea-crucifix-cell sea-pos-core">
|
<div class="sea-crucifix-cell sea-prev-pos-core">
|
||||||
<div class="sig-stage-card sea-sig-card" style="--sig-card-w: 2rem">
|
<div class="sig-stage-card sea-sig-card" style="--sig-card-w: 2rem">
|
||||||
{% if sig %}<span class="fan-corner-rank">{{ sig.corner_rank }}</span>{% if sig.suit_icon %}<i class="fa-solid {{ sig.suit_icon }}"></i>{% endif %}{% endif %}
|
{% if sig %}<span class="fan-corner-rank">{{ sig.corner_rank }}</span>{% if sig.suit_icon %}<i class="fa-solid {{ sig.suit_icon }}"></i>{% endif %}{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="sea-pos-cover">
|
<div class="sea-prev-pos-cover">
|
||||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sea-pos-cross">
|
<div class="sea-prev-pos-cross">
|
||||||
<div class="sea-card-slot sea-card-slot--empty sea-card-slot--crossing"></div>
|
<div class="sea-card-slot sea-card-slot--empty sea-card-slot--crossing"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sea-crucifix-cell sea-pos-loom">
|
<div class="sea-crucifix-cell sea-prev-pos-loom">
|
||||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sea-crucifix-cell sea-pos-lay">
|
<div class="sea-crucifix-cell sea-prev-pos-lay">
|
||||||
<div class="sea-card-slot sea-card-slot--empty"></div>
|
<div class="sea-card-slot sea-card-slot--empty"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user