- model: DeckVariant.free_in_shop flag (0015 schema); data migration 0016
seeds RWS + Minchiate Fiorentine True (Earthman stays False — it's auto-
granted at signup, not shopped)
- view: _free_decks_for decorates the free-in-shop catalog w. a per-user
.owned flag; shop_claim_free POST endpoint adds the deck to unlocked_decks
(idempotent M2M add) — the free_in_shop filter is the guard that stops the
$0 endpoint unlocking paid/auto-granted decks (404 otherwise). free_decks
wired into both the wallet view + toggle_wallet_applets HX context
- url: wallet/shop/claim (action, no trailing slash)
- template: free-deck tiles reuse the deck's own Game Kit tooltip prose
(name / card-count / description / stock-version line) + a $0 .tt-price
pinned top-right like paid tiles; .tt-micro carries .tt-free-btn (FREE
ITEM) or the same .tt-already-owned pill once owned; reuses
_deck_stack_icon.html
- js: wallet-shop.js _onFreeClick → _doClaimFree POSTs deck_slug → reload
(server-rendered owned pill, same posture as the BUY reload). No guard
portal — free = one-click. Rides the SAME delegated roots as BUY +
idempotent wiring
- css: FREE ITEM wraps to 2 lines like BUY ITEM (extend the mini-portal
.tt-buy-btn white-space:normal rule to .tt-free-btn); shop deck tiles get
the Game Kit fan-out on hover/active by adding .shop-tile-deck to the
.deck-stack-icon splay trigger list — DRY, no transform duplication
- tests: 8 ITs (shop_claim_free behaviors + free_decks context owned flag);
FT claims RWS → 'Already owned' swap → id_kit_tarot_deck appears in Game
Kit; 3 Jasmine specs F1-F3 (claim POST / no-guard / idempotent wiring);
679 dashboard+epic green, no regressions
- trap: hover-hidden microtooltip btn → .text is '' under Selenium; read
get_attribute('textContent') instead [[feedback-selenium-opacity-zero]]
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
24 lines
1.2 KiB
Python
24 lines
1.2 KiB
Python
from django.urls import path
|
|
from . import views
|
|
|
|
urlpatterns = [
|
|
# Post/Line CRUD has moved to apps.billboard.urls (`billboard:` namespace).
|
|
path('set_palette', views.set_palette, name='set_palette'),
|
|
path('set_profile', views.set_profile, name='set_profile'),
|
|
path('toggle_applets', views.toggle_applets, name="toggle_applets"),
|
|
path('wallet/', views.wallet, name='wallet'),
|
|
path('wallet/toggle-applets', views.toggle_wallet_applets, name='toggle_wallet_applets'),
|
|
path('wallet/setup-intent', views.setup_intent, name='setup_intent'),
|
|
path('wallet/save-payment-method', views.save_payment_method, name='save_payment_method'),
|
|
path('wallet/shop/buy', views.shop_buy, name='shop_buy'),
|
|
path('wallet/shop/confirm', views.shop_confirm, name='shop_confirm'),
|
|
path('wallet/shop/claim', views.shop_claim_free, name='shop_claim_free'),
|
|
path('kit-bag/', views.kit_bag, name='kit_bag'),
|
|
path('sky/', views.sky_view, name='sky'),
|
|
path('sky/preview', views.sky_preview, name='sky_preview'),
|
|
path('sky/save', views.sky_save, name='sky_save'),
|
|
path('sky/delete', views.sky_delete, name='sky_delete'),
|
|
path('sky/data', views.sky_data, name='sky_data'),
|
|
path('set-pronouns', views.set_pronouns, name='set_pronouns'),
|
|
]
|