Files
python-tdd/src/static_src/scss/_wallet-tokens.scss

176 lines
4.5 KiB
SCSS
Raw Normal View History

.token {
position: relative;
display: inline-block;
cursor: help;
color: rgba(var(--terUser), 1);
.token-tooltip {
position: absolute;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
}
&:hover .token-tooltip {
display: block; // legacy fallback; .tt is JS-portal-only (no CSS hover)
}
}
.token--empty {
cursor: help;
> i { opacity: 0.4; }
}
// Aperture foundation lives universally in _base.scss; nothing
// wallet-specific to override.
.wallet-page {
position: relative;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.wallet-tokens {
display: flex;
flex-direction: column;
overflow: visible;
.token-row {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
overflow: visible;
}
.token {
font-size: 1.5rem;
}
.token:hover .token-tooltip { display: none; }
}
#id_payment_methods {
overflow-y: auto;
}
@media (max-width: 768px) {
.token .token-tooltip {
width: 13rem;
max-width: 90vw;
left: 0;
transform: none;
}
.wallet-tokens .token-tooltip {
left: 50%;
transform: translateX(-50%);
}
}
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
// ── Wallet Shop applet ───────────────────────────────────────────────────────
// Mimics `.wallet-tokens` (horizontal row of tooltipped icons) but each tile
// carries an admin-defined catalog item + an optional `.shop-badge` (eg "×5"
// for the bundle) + a BUY-ITEM microbutton hosted in the tooltip portal.
// JS wiring lives in `apps/dashboard/static/apps/dashboard/wallet-shop.js`.
.wallet-shop {
display: flex;
flex-direction: column;
overflow: visible;
.shop-grid {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
gap: 1rem;
overflow: visible;
}
}
.shop-tile {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: rgba(var(--terUser), 1);
cursor: help;
}
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
// ×N quantity badge — top-right corner, --quaUser glyph on --quiUser bg.
// User-tweaked 2026-05-22: shrunk from 2rem → 1.5rem + nudged further up
// + right so most of the underlying tile icon stays visible.
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
.shop-badge {
position: absolute;
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
top: -0.8rem;
right: -1.2rem;
width: 1.5rem;
height: 1.5rem;
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
border-radius: 50%;
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
background: rgba(var(--secUser), 1);
color: rgba(var(--priUser), 1);
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
display: flex;
align-items: center;
justify-content: center;
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
font-size: 0.75rem;
font-weight: 900;
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
pointer-events: none;
}
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
// `.tt-micro` — sibling of `.tt` on each `.shop-tile`. Holds the BUY-ITEM
// btn (or × + 'Already owned' when capped). wallet.js's tooltip handler
// clones this into `#id_mini_tooltip_portal` on hover so the btn appears
// as a small floating bubble adjacent to the main tooltip card —
// mirroring Game Kit's Equipped/Unequipped/In-Use microtooltip pattern.
// Hidden in the source DOM; only the portal clone is visible.
.tt-micro {
display: none;
}
// Wallet-side mini portal — pinned to the bottom-right of the main
// portal by wallet.js (mirrors gameboard.js's gameKit positioning).
// Mostly mirrors gameboard's mini at `_gameboard.scss:140` but allows
// the BUY-ITEM btn label to wrap onto multiple lines (gameboard's
// mini holds short status text like "In-Use: X" which wants nowrap;
// our buy btn is round + needs the label to break onto 2 lines).
#id_mini_tooltip_portal {
position: fixed;
z-index: 9999;
width: fit-content;
text-align: center;
padding: 0.5rem 0.75rem;
display: none;
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
flex-direction: column;
align-items: center;
gap: 0.25rem;
.tt-buy-btn {
padding: 0.25rem 0.75rem;
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
white-space: normal;
word-break: normal;
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
}
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
// `.tt-already-owned` text — match Game Kit's "Equipped" / "In-Use: X"
// microtext styling (--secUser at full alpha, slightly bigger than
// 0.75rem) so the wallet shop's "Already owned" pill reads as the
// same widget as the gameboard's status pills.
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
.tt-already-owned {
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
font-size: 0.85rem;
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
margin: 0;
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
font-style: italic;
color: rgba(var(--secUser), 1);
white-space: nowrap;
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
}
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 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:21:10 -04:00
&.active { display: flex; }
feat: wallet Shop applet — tile grid + BUY-ITEM microbutton + Stripe.js wiring — Chunk 4 of [[project-wallet-shop-expansion]]. The shop applet (slug `wallet-shop`, seeded in Chunk 2) now renders the catalog as a horizontal grid of `.shop-tile` icons: `tithe-1` ($1, fa-piggy-bank), `tithe-5` ($4, fa-piggy-bank w. `×5` badge), `band-1` ($20, fa-ring). Each tile hosts a hover-portaled tooltip carrying name + description + price + a `.tt-microbutton-portal` w. a `.btn-primary` BUY ITEM button — clicking opens `#id_guard_portal` w. "Buy {name} for ${price}?" prompt; confirming triggers Stripe.js confirmCardPayment then POSTs to /shop/confirm + reloads. Items where the user's owned-count has hit `max_owned` (eg. BAND, owned=1, cap=1) render w. `.btn-disabled` + × glyph + "Already owned" microtooltip text — visible-but-unbuyable per the locked decision. View context — `wallet` view + `toggle_wallet_applets` view both pass `shop_items` (decorated w. per-user `.available` via the new `_shop_items_for(user)` helper) + `default_payment_method_id` + `stripe_publishable_key`. SCSS — `.wallet-shop` (flex column wrapping `.shop-grid` flex row), `.shop-tile` (inline-flex tooltip target), `.shop-badge` (2rem circle, --quaUser glyph on --quiUser bg, top-right corner per spec), `.tt-microbutton-portal` (column-flex, BUY btn + 'Already owned' caption styling). JS in `wallet-shop.js` exposes a singleton `WalletShop` module (matching the project's `Brief` / `SeaDeal` / `StageCard` module pattern) w. a tested `initWalletShop()` method — uses event delegation on the shop root (so portal-relocated buy btns still hit the handler) + a DOM-keyed `data-shop-wired` flag (not a module-level boolean) so per-test fixture rebuilds re-wire cleanly. Wired into `wallet.html` after `wallet.js`. **TDD** — 5 Jasmine specs in `WalletShopSpec.js`: T1 click-on-enabled-BUY opens guard w. correct prompt; T2 click-on-disabled-BUY no-op; T3 onConfirm POSTs `shop_item_slug` to `/shop/buy`; T4 init idempotent (calling twice doesn't double-wire); T5 missing-root no-throw. **2 Jasmine traps caught**: (a) `spyOn(window, 'fetch')` collides if another spec already spied on fetch — switched to save+restore via per-test `_origFetch` capture; (b) T3 async pollution — sync assertion passed, `afterEach` restored `window.Stripe=undefined`, then `_doBuy`'s async continuation hit `Stripe(pubKey)` and threw "Unhandled promise rejection". Fixed by T3-local fetch mock returning a never-resolving promise so the chain pauses at the first await. **3 new FTs** in `test_dash_wallet.py`: tiles + icons + ×5 badge + tooltip prose; BUY click opens guard portal + NVM dismisses; BAND-already-owned shows disabled BUY w. 'Already owned' microtext (reads via `textContent` since `.tt` is `display: none`). FT trap caught: `TransactionTestCase` wipes both migration-seeded Applets + ShopItems → setUp must re-seed both manually (mirrors `test_shop_views.py`'s `_seed_starting_items` pattern). 1208 IT/UT + 9 wallet FTs + 5 Jasmine specs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:15:05 -04:00
}