A.7-polish-3 stat-block title + arcana fields across 3 surfaces + spread-switch unlock after DEL — TDD. End-of-session 2026-05-25 PM. Two changes bundled (both user-requested as round-out work for tonight's final push):
1. Stat-block restructure per [[project-image-based-deck-face-rendering]]'s locked Q3 spec. User noticed during browser verify that image-mode card surfaces (Minchiate-equipped on my_sign main stage + My Sign applet + Sea Stage modal in my_sea) show only the EMANATION label in the stat-block — no title, no arcana type, no keywords (Minchiate keywords are empty per A.1 seed). For text-mode decks (Earthman, RWS) the title + arcana render ON the card; for image-mode the card is just an image so all textual metadata MUST move to the stat-block. Spec was originally written for image-mode only but user explicitly asked for it universally so non-image cards also get the info in the stat-block (visible duplicates w. card content — acceptable tradeoff per user). Implementation: added `<p class="stat-face-title">` + `<p class="stat-face-arcana">` to both upright + reversed `.stat-face` blocks in `my_sign.html` (line ~58) + `_sea_stage.html` (the modal). For `_applet-my-sign.html` (no SPIN btn, no reversed face), rendered server-side from `card.name` + `card.get_arcana_display` + the new `data-arcana-key="{{ card.arcana }}"` attr on the stat-block wrapper. New JS helper `StageCard.populateStatExtras(statBlock, card, opts)` in `stage-card.js` parallels the existing `populateKeywords` — fills `.stat-face-title` (card name minus any "Title, Qualifier" Earthman pattern stripping) + `.stat-face-arcana` (`_arcanaDisplay(card)` reused) + sets `data-arcana-key` on the stat-block parent for SCSS color-keying. Exported alongside populateKeywords; called from `my_sign.html` inline + `sea.js`'s `_populate` (the 2 dynamic stat-block sites). `sig-select.js` (room sig select) intentionally NOT updated — that's A.8 territory + its stat block markup differs. SCSS: extended `stat-block-shared` mixin in `_card-deck.scss` w. `.stat-face-title` (font-weight 700, color --quiUser default; `[data-arcana-key="MAJOR"]` selector flips to --terUser matching the contour-stroke arcana-color convention) + `.stat-face-arcana` (uppercase letter-spaced like `.stat-face-label`). Same rules duplicated in `_billboard.scss` `.my-sign-applet-stat-block` block (different sizing via `--applet-card-w` container query). `:empty` rule hides both title + arcana when JS hasn't populated yet (rest state — prevents zero-height paragraphs inflating the stat block). Also added user-spec'd underline to `.stat-face-label` (text-decoration: underline + 0.15em offset).
2. Spread-switch policy unlock after DEL. User-reported AUTO DRAW failure ("only works with default SOA spread, others give visual click feedback but no cards") + spread-switch failure ("won't persist with a partial draw either, only SAO will"). Investigated: root cause is `views.my_sea_lock` line 372 returning `409 spread_mismatch` whenever the POST's spread != the existing `MySeaDraw` row's spread. The row spread is committed at first-card moment and `active_draw_for` returns rows for 24h regardless of hand state (even empty post-DEL rows still hold the spread lock). Combobox switches visually but every subsequent POST 409s. Refined policy: spread is locked only during an ACTIVE non-empty draw. Once the user DELs (clears hand to []), the spread lock lifts — a POST w. a different spread UPDATES the existing row's spread + populates the new hand. The 24h quota window (created_at + paid_through_at) is preserved so the cooldown clock stays put. Sneaky-POST mitigation is still in effect for mid-non-empty-draw spread switches (those still 409). Server-side: 4-line change in `views.my_sea_lock` — `spread_changed = existing.spread != spread`; `if spread_changed and existing.hand: return 409` (preserves prior behavior for non-empty hands); `if spread_changed: existing.spread = spread; update_fields.append("spread")` in the update block. New IT `MySeaLockHandViewTest.test_lock_post_spread_switch_after_del_succeeds` exercises the full flow: first POST creates row w. spread=SAO + 1 card; DEL clears hand; second POST w. spread=waite-smith + different card → 200 + row.spread is now "waite-smith" + row.created_at unchanged. Existing `test_lock_post_spread_mismatch_within_quota_returns_409` test docstring updated to clarify the new policy ("for the duration of an ACTIVE non-empty draw"); the 409 assertion still holds for its specific scenario (mid-non-empty-draw switch).
Tests: 1 new IT green (lock-view spread-switch-after-DEL); 14/14 MySeaLockHandViewTest class green; 1307/1307 IT+UT total green (74s; +1 from 26cdf0d's 1306). Memory: `project_image_based_deck_face_rendering.md` has the detailed AUTO DRAW root-cause writeup; tomorrow's A.8 work is the only remaining image-rendering surface
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ var SeaDeal = (function () {
|
||||
uprightSel: '#id_sea_stat_upright',
|
||||
reversedSel: '#id_sea_stat_reversed',
|
||||
});
|
||||
StageCard.populateStatExtras(statBlock, card);
|
||||
_infoData = StageCard.buildInfoData(card);
|
||||
_infoIdx = 0;
|
||||
|
||||
|
||||
@@ -266,6 +266,40 @@ var StageCard = (function () {
|
||||
if (rl) rl.innerHTML = (reversedItems || []).map(function (k) { return '<li>' + k + '</li>'; }).join('');
|
||||
}
|
||||
|
||||
// Sprint A.7-polish-3 — fill the title + arcana fields per [[project-
|
||||
// image-based-deck-face-rendering]]'s locked Q3 spec: each stat face
|
||||
// gets a title (card name minus any comma+qualifier for Earthman-style
|
||||
// major arcana) + an arcana type label ("Major Arcana" / "Minor Arcana").
|
||||
// For text-mode decks the same info is on the card face too — duplicates,
|
||||
// not regressive. For image-mode decks the stat block is the only home
|
||||
// for textual metadata. Caller-side opt-out by passing opts.skipExtras=true.
|
||||
function populateStatExtras(statBlock, card, opts) {
|
||||
if (!statBlock) return;
|
||||
opts = opts || {};
|
||||
if (opts.skipExtras) return;
|
||||
// Strip any "Title, Qualifier" Earthman pattern → just "Title".
|
||||
var rawTitle = card.name_title || card.name || '';
|
||||
var title = rawTitle.split(',')[0].trim();
|
||||
var arcana = _arcanaDisplay(card);
|
||||
statBlock.querySelectorAll('.stat-face-title').forEach(function (el) {
|
||||
el.textContent = title;
|
||||
});
|
||||
statBlock.querySelectorAll('.stat-face-arcana').forEach(function (el) {
|
||||
el.textContent = arcana;
|
||||
});
|
||||
// Surface arcana on the stat-block parent so SCSS `[data-arcana-key]`
|
||||
// selectors can color-key the title (--quiUser for minor/middle,
|
||||
// --terUser for major) — mirrors the same hook used on the card.
|
||||
if (card.arcana_key) {
|
||||
statBlock.setAttribute('data-arcana-key', card.arcana_key);
|
||||
} else if (card.arcana) {
|
||||
// Fallback: card.arcana may be "MAJOR" (model code) or "Major
|
||||
// Arcana" (display) — uppercase + take first word for the key.
|
||||
statBlock.setAttribute('data-arcana-key',
|
||||
(card.arcana || '').toUpperCase().split(' ')[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Concatenate energies + operations into a single FYI nav array.
|
||||
function buildInfoData(card) {
|
||||
var data = (card.energies || []).map(function (e) {
|
||||
@@ -305,6 +339,7 @@ var StageCard = (function () {
|
||||
fromDataset: fromDataset,
|
||||
populateCard: populateCard,
|
||||
populateKeywords: populateKeywords,
|
||||
populateStatExtras: populateStatExtras,
|
||||
buildInfoData: buildInfoData,
|
||||
renderFyi: renderFyi,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user