544ce978d5c2c90710f09c7a688b1b628eaaa2ae
781 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
544ce978d5 |
Sig Select theme FTs: skip 3 card-face qualifier asserts (moving to stat-block)
The Earthman deck now renders the sig stage in image-mode (has_card_images defaults True), so .fan-card-face — where .sig-qualifier-above/below live — is display:none and Selenium reads "". The Elevated/Enlightened/Graven qualifier belongs on the always-visible stat-block now (the card-face instance goes away once every deck gets images). Skipped pending the repoint + green-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0e4101ce95 |
FTs: unified Sig-stage felt/stat-block (single-browser) + live SCROLL refresh (channels)
End-to-end coverage for this session's two shipped features. SigStageUnifiedTest (FunctionalTest, no WS — both pass locally): - the sig stage renders INSIDE .room-hex-pane on green --duoUser felt (.has-sig-stage), the overlay is a descendant of the hex pane, the dark .sig-backdrop is gone, and the overlay bg is not a translucent-black wash; - OK'ing a card freezes the stage and reveals the DRY _stat_face.html — .stat-face-title + .stat-chip-rank populate (the old reduced block had neither; proves the populateStatExtras wiring). RoomScrollLiveRefreshTest (ChannelsFunctionalTest, @tag channels): - with the room open, a server-side record() of a new GameEvent grows the feed (#id_drama_scroll .drama-event 1 → 2) WITHOUT a reload, via the record() on_commit broadcast → RoomConsumer.scroll_update relay → room-scroll.js re-fetch+swap. Validated in the CI channels stage (needs a cross-process channel layer); the plumbing is already green via the consumer-relay + record-hook + scroll_status ITs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8ca3f79561 |
room SCROLL applet: live async refresh over WebSocket (not just on page reload)
The room's scroll-of-events feed only updated on refresh — a gamer watching the SCROLL view never saw a co-player's deposit / role pick / sig appear. Now every recorded GameEvent nudges all open room sockets to re-fetch the feed. - drama.models.record() broadcasts a `scroll_update` to the `room_<id>` group via transaction.on_commit — so the live re-fetch sees the committed row, and a rolled-back TestCase never fires it (zero overhead / channel-layer traffic for the plain IT suite). _broadcast_scroll_update is fully guarded: a missing/unreachable channel layer must NEVER break event recording (falls back to refresh-to-update). One central hook covers every event writer, current + future. - RoomConsumer gains a `scroll_update` relay handler (same one-liner shape as gate_update / turn_changed). - New `scroll_status` view + url (epic:scroll_status, room/<id>/scroll/status) renders JUST core/_partials/_scroll.html with the same events/viewer/scroll_position context as room_view's inline paint, so the swapped feed is identical. - room-scroll.js listens for `room:scroll_update`, fetches the partial, swaps #id_drama_scroll, then re-applies the saved Frame/Redact filter + restarts the buffer dots on the fresh nodes. URL comes from .room-page[data-scroll-status-url]. Refactored the dots + filter into re-runnable helpers; existing behavior (title reel IO, filter form, localStorage) preserved. TDD: - drama RecordBroadcast ITs: record() schedules the broadcast on commit (captureOnCommitCallbacks execute=True) and NOT before commit. - RoomConsumer relays scroll_update (InMemory layer, WebsocketCommunicator). - ScrollStatusViewTest: endpoint renders the feed section, reflects the latest events, is the bare partial (no navbar/aperture chrome). 544 drama+epic ITs green — the on_commit hook is inert under TestCase, so no existing event-writer test regressed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
71c00699a1 |
room Sig Select: unify with the my_sign card-stage apparatus (DRY stat-block, per-card stage image, --duoUser felt)
The in-room SIG_SELECT stage diverged from the polished GAME SIGN page:
a fixed dark-Gaussian modal over the hex, a stale label-only stat-block,
and no card imagery. This brings it in line with my_sign / my_sea.
A — Stat-block DRY: _sig_select_overlay.html now renders the shared
core/_partials/_stat_face.html (rank-chip + title + arcana + keywords)
instead of a reduced label-only copy; sig-select.js's updateStage() now
calls StageCard.populateStatExtras (the missing call that left those
fields blank). data-arcana-key added per card for title color-keying.
B — Per-card stage image: the stage card gains a .sig-stage-card-img
slot + data-image-url per thumbnail, so an image-equipped seat deck
(RWS / Minchiate) shows real card art on the preview. Thumbnails stay
glyph-only (rank + suit) at every deck — only the stage shows the image.
Keyed off each card's OWN deck_variant, so it auto-upgrades to mixed art
when the dubbodeck assembly lands. No backend change (cards already
carry a deck_variant via _room_deck_variant).
C — Felt-in-aperture: the stage renders INSIDE .room-hex-pane on edge-to-
edge green --duoUser felt (my_sea-style), replacing the hex content; the
old .sig-backdrop blur is gone. .sig-overlay absolute-fills the pane
(.room-hex-pane.has-sig-stage = positioning context); dismissing it
reveals the hex + waiting message behind. Scroll-down still reaches the
reelhouse carousel (untouched scroll pane).
Polishes:
- Image-mode bg escape: the levity 0,3,0 polarity rule
(.sig-overlay/.my-sign-page[data-polarity="levity"] .sig-stage-card)
hard-set a --secUser background that re-clothed image cards behind the
transparent PNG. Added the &.sig-stage-card--image { background:
transparent; border:0; overflow:visible } escape (parity w. the base +
my-sea rules). Latent my_sign bug too. Monodeck-era assumption.
- FLIP .btn-reveal: non-polarized image decks get a FLIP that turns the
preview to the deck card-back (my_sign parity) — back-img + reused
.my-sign-flip-btn (shared positioning/hide/counter-position rules
already cover .sig-stage-card) + a frozen-gated reveal scoped to
.sig-overlay + sig-select.js _flipToBack (500ms Y-rotate, midpoint
swap). SPIN now sets data-spinning so the btn hides mid-rotate.
- Reserved thumbs-up / hover cursors portal to a body-root fixed
container, so they hung over the reelhouse on scroll. sig-select.js now
toggles .cursors-hidden off the aperture scrollTop: instant hide the
moment the scroll leaves the hex, 0.5s opacity ease-in on the full
return. Tray intentionally kept.
TDD: SigSelectUnifiedStageTest (6 ITs) — DRY stat-face present, per-card
data-image-url + data-arcana-key, .sig-stage-card-img slot, image deck
non-empty face URL / text deck empty, has-sig-stage felt + overlay inside
the hex pane. 319 epic test_views ITs green; user-verified live on an RWS
room (no rect, FLIP works, thumb timing). Jasmine for the JS wiring +
the dubbodeck cross-deck assembly (per-seat segment cards, CARTE-solo
both-polarity case, per-card backs) are the tracked follow-on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
148fcac7af |
room GATE VIEW: keep all six position circles solid — never fade with role-assigned
The gatekeeper's GATE VIEW is the canonical "who holds which position" surface, but it reused _table_positions.html, which paints the role-assigned fade-out class (opacity:0 / scale .5) server-side from pos.role_assigned (slot <= assigned_count). So as gamers got seated during Role Select, the gate-view circles vanished one-by-one in lockstep with the table-hex — and by the time SCAN SIGS appeared (all six roles assigned) the gate showed an empty hex. The disappear-as- seated animation is meant as a TABLE-HEX-only cue. Fix: _table_positions.html now suppresses role-assigned when its new persist_circles flag is set; room_gate.html includes the partial with persist_circles=True. room.html passes no flag (→ falsy), so the table-hex keeps the fade animation untouched. No JS reads role-assigned in the gate view (role-select.js isn't loaded there), so the server-side guard is sufficient. TDD: PositionTooltipRenderTest.test_gate_view_circles_never_fade_when_roles_assigned — assigns all six roles, asserts the gate view keeps six .gate-slot circles with NO role-assigned. Verified live via Claudezilla on a SIG_SELECT setup_sig_session room. 47 render/gate ITs green (RoleSelectRenderingTest still asserts the table-hex DOES fade). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c4279c5515 |
room title reel: fix the landscape view-to-view + hex⇄views motion (vertical-rl axis)
- In landscape the wordmark is `writing-mode: vertical-rl` + `rotate(180deg)`. The rotation runs every reel child's translate in the rotated frame (inverting motion vs portrait), and the vertical-rl forces the flex MAIN axis onto the vertical, so the five view-word cells stack vertically. - Vertical reel (hex⇄views): negate the translateY so ROOM exits UP + the view reel rises from BELOW (was dropping ROOM below the rising view). - Lateral reel (view-to-view): the cells stack vertically, so the portrait `translateX` slid that stack sideways off the slot — only one word rendered (desktop showed PULSE, mobile ATLAS; `row`/`column`/`order`/`row-reverse` all collapse). Traverse with `translateY` instead: the words now slide up-down ALONG the rotated wordmark and each view lands its own word, consistently across desktop + mobile. (True left-right needs a track writing-mode override + reopens the rotate inversion — deferred; up-down is the working result.) - `overflow: hidden` on the view reel clips it to its one-slot box so the neighbouring cell can't bleed over ROOM (the lateral reel now shares the vertical axis with the hex⇄views reel). - Pure SCSS, verified visually across desktop + mobile landscape; portrait untouched (all rules inside the landscape media query). [[feedback-vertical-rl-flex-axis]] [[project-room-game-views-carousel]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fc49ca2c74 |
game post: New Post applet treatment — --duoUser felt, green-tinted title strip + input-pill .post-line rows
- The reelhouse POST card now takes the billboard New Post applet's look: a --duoUser green felt bg with the rotated room-name `> h2` given the SAME green-tinted strip — three translucent 0,0,0/0.125 layers over the felt, NO opaque base, so the felt shows through (matches #id_applet_new_post, which lands there via a %applet-box > h2 specificity quirk; we do it on purpose). - Each .post-line is restyled to LOOK LIKE the "Enter a post line" composer input below it (mirrors .form-control, _base.scss): a --priUser fill (0.8 alpha), a 0.1rem --secUser border at the same border-radius, full width, an up/down margin + content-driven (dynamic) height — so the thread reads as a stack of input-style pills on the felt. #id_post_table left/right padding zeroed so pills span the full card content width (= the composer row). - OK button wrapped in .applet-btn-panel (--priUser fill + faint --terUser border) so the green .btn-confirm reads against the felt, mirroring the New Post composer. - All scoped to .room-view--post — post.html (.post-page), the billboard New Post applet, and MY POSTS (.applet-list-entry) stay untouched (verified live: no .post-line bleed). - Verified: GameViewsCarouselTest.test_post_view_is_room_thread_with_working_composer + test_atlas_aggregates_provenance_and_posts green (composer still works with the OK-panel wrap); billboard + GAME POST visually confirmed via Claudezilla. [[feedback-scss-id-context-specificity-trap]] — %applet-box > h2's background-color inherits the applets-container ID via the @extend chain, out-specifying a plain #id_applet > h2 override, so an "opaque base" mask silently renders translucent (the felt bleeds through). New Post hit this by accident; GAME POST replicates it deliberately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c00cd151c4 |
room ATLAS: rebuild on the native-swipe (IO) path, not only goToView; drop the dead "atlas gathers" empty-state — TDD
- Bug (staging, touch only): a finger-swipe onto ATLAS landed blank ("The atlas gathers . . .") though SCROLL + POST had content; opening the ATLAS gear & clicking OK (no option change) made it appear. Desktop never reproduced.
- Cause: the ATLAS feed is a client-side merge of the live SCROLL + POST DOM. buildAtlasFeed ran in placeView (initial land) + goToView (icon-click / horizontal wheel) only. A native swipe is scroll-snap → it reaches the carousel solely through the IntersectionObserver, which called setActiveView('atlas') w. NO build. The gear-OK was the only other path that re-ran the merge.
- Fix: centralise the build in setActiveView — the single chokepoint placeView, goToView, AND the IO all share — so every activation path (incl. swipe) rebuilds; placeView/goToView no longer call buildAtlasFeed directly.
- Removed the empty-state (template + JS): ATLAS is never reached before SCROLL's game-creation "Welcome to <game>!" event, so rows is never bare; an empty render beats a stale placeholder lingering.
- Jasmine: new spec stubs window.IntersectionObserver, fires a synthetic atlas-intersect entry, asserts #id_room_atlas fills from the SCROLL/POST DOM w.o. any goToView or gear-OK (headless can't fire a real intersection — see the swipe-machine note). 473 specs green; the 3 ATLAS carousel FTs green (icon-click path + empty-state removal).
[[feedback-client-view-rebuild-on-io-swipe-path]] [[project-room-game-views-carousel]] [[feedback-headless-delayed-scroll-dropped]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
b243d512e4 |
post applet: unify header across post.html + reelhouse chat; seat-based recipients/access; fix async chip styling
Unifies the Post applet across post.html and the room game-views POST chat: - Extract _post_recipient.html (the @handle chip: post-recipient + post-attribution) + _post_header.html (title + 'shared between … & me' / 'just me' prose). post.html's owner branch + the reelhouse POST view both render via _post_header; the invitee branch stays bespoke but reuses the chip. - Async-chip styling bug (post.html): the bud-invite append built a bare <span class=post-recipient> with the raw display name, so the recipient rendered without the --quaUser key + the @ until a refresh. Now billboard:share_post returns recipient_chip_html (the server-rendered _post_recipient.html) and the bud panel splices THAT in — identical classes + @handle. Also fixed the 'just me'→'& me' flip to mutate only the leading text node so the self line's own .post-attribution span survives. - Reelhouse POST chat: gains the full .post-header — title hardcoded to 'Gamer Introduction' (dynamic template later) + recipients = the gamers OCCUPYING SEATS (room.table_seats, deduped, viewer excluded), NOT gate-slot/token depositors. And room_post ACCESS now requires a TableSeat, not a filled gate slot: a depositor who never took a seat can retract + leave, so they must not have R/W access to the private chat. Tests: header IT (seatmate listed, transient gate-slot-only depositor not — scoped to the recipients paragraph since the position strip carries every gate-slot handle elsewhere); room_post seat-access ITs (seated OK; non-seated + gate-slot-only → 403); share_post recipient_chip_html IT; carousel FT setUp now seats disco/amigo/bud (pal/dude/bro stay transient). All green: 255 ITs, 11 carousel FTs, 29 bud-btn/composer FTs. [[project-room-game-views-carousel]] [[feedback-at-handle-for-usernames]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
73644e226b |
game-views: per-view gear menus (SCROLL filter / ATLAS sources / disabled stubs); reelhouse POST placeholder DRY
Each reelhouse view now has its own gear. The single #id_room_menu swaps panes by the active view (room-views.js owns this now — removed from room-scroll.js, which keeps only the title reel): - hex → default NVM/DEL/BYE; SCROLL → the Frame/Redact log filter; ATLAS → a new source-checkbox pane (.room-menu-atlas / #id_atlas_source_form). - YARN/POST/PULSE → no menu yet: the gear goes .gear-disabled (opacity 0.6) and an active click is swallowed by a capture-phase listener that flashes a --priRd fa-ban (the burger inactive-flash cadence) instead of opening anything. ATLAS gear: a checkbox per other reelhouse view (Scroll/Post wired + checked; Yarn/Pulse disabled — struck label, an ✗ in a custom box matching the enabled ✓; starting-majuscule labels, capslock stays reel-only). OK persists to localStorage + re-runs buildAtlasFeed, which now gates each source on atlasSources() (scroll→provenance, post→post). Also: the reelhouse POST composer drops its bespoke 'Speak at the table' placeholder for the canonical 'Enter a post line' (same as the billboard New Post applet's _form.html + post.html). Verified: 11 carousel FTs (incl. the new per-view-gear FT) + 310 epic ITs (incl. the atlas-menu IT; room_gate shares _room_gear, unaffected) + the scroll-gear regression FTs + Jasmine, all green. [[project-room-game-views-carousel]] [[feedback-applet-menu-needs-extend]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9754f6a54c |
game-views ATLAS: mirror SCROLL log styling (strikethrough) + honour the redact filter
The merged ATLAS feed now reads its provenance rows the way SCROLL does: - Struck (retracted/redacted) logs carry the strikethrough — buildAtlasFeed captures the source .drama-event-body.struck state and renderAtlasRow re-applies it as .atlas-row-body.struck (styled to match .drama-event-body.struck). - A log hidden by the SCROLL Frame/Redact gear filter (display:none) is skipped in the ATLAS merge too — buildAtlasFeed checks getComputedStyle(ev).display, so unchecking Redact on SCROLL keeps those rows out of ATLAS. - Also lays the source-toggle seam: atlasSources() reads the (forthcoming) ATLAS gear's view-checkboxes (scroll→provenance, post→post), defaulting to both when absent. Verified: Jasmine renderAtlasRow struck spec + two FTs (struck row shows struck in ATLAS; redact-filtered rows absent from ATLAS) + the existing atlas aggregate FT, all green. [[project-room-game-views-carousel]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ced324081f |
game-views: replace CHAT with YARN (.fa-route) between SCROLL & POST; ATLAS timestamps match their source views
CHAT conceptually overlapped POST, so it's gone — replaced by a distinct YARN view (fa-route) shifted one slot left, between SCROLL and POST. Reelhouse order is now ATLAS | SCROLL | YARN | POST | PULSE. YARN is a stub (the shared [Feature forthcoming] partial in its .applet-scroll, like PULSE). Touched: the carousel + strip partials, the h2 reel words, the _base.scss data-active-view translateX reindex (yarn=-200%, post now -300%), room-views.js VIEW_ORDER, the Jasmine spec VIEWS, and the FT/IT order + stub assertions. The horizontal-wheel FT now expects scroll's neighbour to be YARN. ATLAS timestamps: the merged rows carry the ORIGINAL <time> from their source (.drama-event-time from SCROLL, .post-line-time from POST), but those source rules are feed/thread-scoped so the atlas copies rendered full-size inline. Made .atlas-row a flex row and restated the shared small / dim / right-aligned look on both source time classes so each timestamp reads the same as in the view it came from. Verified: 8 carousel FTs + carousel ITs + Jasmine (atlas merge + swipe machine) green. [[project-room-game-views-carousel]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6f5927083c |
game-views: forthcoming watermark no-overlap; reelhouse term; Text swipe-machine DOWN-hold-OVER + Jasmine-tested nav
Three things on the carousel: - CHAT/PULSE stub: the shared [Feature forthcoming] partial centres itself absolutely, landing on top of the flex-centred watermark icon. Override it to flow (position:static) inside .room-view-stub so the icon keeps the top slot and the label rests below — FT asserts the icon's bottom clears the label's top (no clip). - Adopt 'reelhouse' as the collective term for the fivefold applet-scroll carousel (class on #id_room_views + comments). - Text sub-btn swipe machine: when already reel'd down onto the reelhouse, slide straight over to POST (plain goToView); when starting up in the room (the hex), run smooth DOWN to the reelhouse, HOLD 0.5s, then OVER to POST unless already there — the hold beats the two motions apart (DOWN-then-OVER, never diagonal). Test rework: the from-hex OVER beat is a DELAYED (post-descent + hold) programmatic scroll, which headless Selenium drops/resets (works fine in a real browser), so the end-to-end land-on-POST can't be FT'd. Split it: the FT now asserts the reliable DESCENT beat (Text from the hex reveals the reelhouse + icon strip), and a new Jasmine swipe-machine spec pins the nav DECISION (from hex → POST after the descent+hold; inactive btn → no-op) against a fixture + mocked clock. room-views.js exposes init() so the spec can bind to the fixture. Verified: 8 carousel FTs + Jasmine (atlas merge + swipe machine) green. [[project-room-game-views-carousel]] [[feedback-ft-run-discipline]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1c7f7d0adf |
game-views: horizontal direction-aware title reel + smooth card slide for lateral nav
The carousel's lateral nav animated wrong: the h2 reel slid VERTICALLY (old word down, new word up — both visible at once), and a first attempt that put translateX+translateY on one transform produced a DIAGONAL blend on hex->views (ROOM+ATLAS+SCROLL all flashing, always landing on ATLAS). Fix — split the two axes onto NESTED elements so they never blend: - OUTER .gr-views-reel does translateY ONLY, gated on .is-scroll (the hex<->views vertical reel, in lockstep with ROOM sliding up/out). - INNER .gr-views-track does translateX -idx*100% (VIEW_ORDER atlas|scroll|post|chat|pulse), gated on data-active-view ALONE — so the active view's cell sits in the slot at ALL times, including at the hex. Default (pre-JS/unset) = SCROLL. Result: hex<->views is a pure vertical reel that lands on whatever view you left off on (POST returns to POST, no diagonal); lateral nav is a pure horizontal slide — old word out one side, new in from the other, direction from the translateX sign — same rusty linear() sequence as ROOM<->SCROLL, just left-right. Cards: goToView now smooth-scrolls (scrollTo behavior:smooth) instead of jumping scrollLeft, so the five .applet-scroll panes visibly SLIDE; an IO-suppression flag during the programmatic snap keeps the icon glow + reel from jittering through passed-over views (native touch-drag still updates via the IO). Initial land uses an instant placeView (no slide on arrival). Verified: 8 carousel FTs + the GAME ROOM<->SCROLL vertical-reel FT green; a 3-lens adversarial audit (vertical axis / horizontal axis+clipping / repo-wide regression sweep) returned holds with no findings. [[project-room-game-views-carousel]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
be7a8c17f0 |
game-views: card ends above the icon strip; CHAT/PULSE use the [Feature forthcoming] partial
- Deepen .room-view bottom padding to 3.5rem so the .applet-scroll card ends above the absolute icon strip (bottom 0.85rem, ~2.1rem tall) — the strip now stands on its own in a reserved lane beneath the card instead of overlapping its bottom edge / the WHAT HAPPENS NEXT buffer. - CHAT + PULSE stub bodies swap the ad-hoc 'Chat/Pulse opens soon.' line for the shared core/_partials/_forthcoming.html ([Feature forthcoming]); the identity icon + .room-view-stub wrapper stay, so the stub FT + styling are unaffected. [[project-room-game-views-carousel]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
28d1d126bb |
game-views: footer billboard icon fa-scroll → fa-receipt; pin the views strip to the aperture bottom
Two follow-ups to the carousel sprint: - The footer's billboard nav icon was fa-scroll, which now collides semantically with the carousel's SCROLL view; FA's billboard glyph is paywalled, so the billboard nav uses fa-receipt (ledger/scroll) instead. The footer FT selects by href, so navigation is unaffected. - #id_room_views_strip was position:fixed (viewport bottom) and rendered down in the fixed footer/burger zone. Switched to position:absolute so it anchors to .room-page's bottom — and since the aperture fills .room-page via inset:0, that IS the bottom of the views pane, clear of the fixed footer. Still a sibling of the aperture (escapes the scroll-card fade mask + scroll clip); .room-page's overflow:hidden doesn't clip it (stays in bounds). Strip-visibility + landscape-within-viewport FTs still green. [[project-room-game-views-carousel]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f036c8f461 |
game-views carousel: ATLAS/SCROLL/POST/CHAT/PULSE views in the room scroll pane — TDD
Unskips the 8 RED FTs from the prior commit (test_game_room_views.py) and lands the feature beneath them — the room's 2nd vertical snap pane becomes a horizontal scroll-snap carousel of five views, landing on SCROLL (2nd). Carousel core: _room_views.html (5 .room-view panes) + _room_views_strip.html (root-level icon strip, outside the aperture so it clears the scroll card's fade mask + scroll clip); room-views.js owns the horizontal axis — goToView (authoritative active-state) + an IntersectionObserver backing native swipe; horizontal wheel (deltaX / shift+wheel) advances a view while vertical wheel stays for feed scroll; icon-click snaps; the strip shows only while the views pane is on screen (vertical IO mirrors room-scroll.js). SCROLL still wraps _room_scroll.html, so the existing binary y-snap + provenance feed + GAME ROOM ⇄ GAME SCROLL title reel behave unchanged. Title reel: the .gr-swap reel gains the four extra view words; the active word is driven by data-active-view on the h2 (set by room-views.js), gated by .is-scroll (room-scroll.js) so ROOM shows at the hex. POST view: a room-scoped game-table thread. New Post.room FK + KIND_ROOM_THREAD (mirrors Brief.room) + Room.get_thread_post(); epic:room_post AJAX endpoint appends a Line (seated-gamer-gated, dup-rejected) and returns the rendered line partial. _post_line.html extracted from post.html and shared by both surfaces + the endpoint. The composer appends OPTIMISTICALLY (synchronous line so the POST + ATLAS views reflect it the instant OK is clicked, no dependence on the round-trip), then reconciles with the server's authoritative @handle/timestamp render; a rejection rolls the optimistic line back. ATLAS view: a live client-side merge of the SCROLL provenance rows + the POST thread rows, time-ordered, each row tagged data-source=provenance|post for end-of-sprint per-type styling. Rebuilds from the live DOM on activation + on every composer append. CHAT/PULSE are .room-view-stub placeholders (no backing model yet). Burger Text sub-btn lights .active on the table (text_btn_active from epic.room_view, unset on every other _burger.html surface) → room-views.js binds its active click to the swipe machine: DOWN to the views pane, RIGHT to Post. Coverage: 8 carousel FTs green; Jasmine RoomViewsSpec (atlas merge order/stability + row data-source); epic ITs (Room.get_thread_post, carousel markup, room_post endpoint 200/403/400/GET); 1636 ITs/UTs + the existing scroll FT green (no regression). Gotcha logged: build FormData(form) BEFORE clearing the input on optimistic submit — clearing first captures an empty text field → 400 → the line silently rolls back. [[project-room-game-views-carousel]] [[project-room-scroll-of-events]] [[project-room-title-scroll-reel-jun02]] [[feedback-jsonfield-exclude-sqlite-null]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
62743aabd0 |
post composer: restore validation-error reveal broken by the .composer-row wrap; CI FT cleanup — TDD
The OK-button composer redesign wrapped the line input in .composer-row, so the .form-control.is-invalid input is no longer a general SIBLING of its .invalid-feedback — the `&.is-invalid ~ .invalid-feedback` reveal (\_base.scss) silently stopped matching, so post-line validation errors rendered in the DOM but stayed display:none (invisible to users). Reveal via `.composer-row:has(.form-control.is-invalid) ~ .invalid-feedback`. Greens test_cannot_add_duplicate_lines + test_error_messages_are_cleared_on_input (both were catching this real regression, not flaky).
Harden WalletShopFreeDeckTest: the .tt-micro is briefly detached mid-HTMX-swap, so get_attribute('innerHTML') returns None and a bare assertIn raises TypeError — which wait_for does NOT retry. Coalesce to '' so it polls until the swap settles (explains the local-pass / CI-fail).
Delete test_core_styling.test_layout_and_styling: a Percival-era assertion that the post input is horizontally CENTRED in its section. The responsive .composer-row (input + OK btn) + the orientation-aware right-margin clamp intentionally removed that invariant (the input now lands in different spots per viewport). Zero behavioural coverage lost — the composer is covered by LineValidationTest + PostComposerOkButtonTest.
Skip GameViewsCarouselTest (red planning contract for the unbuilt Game-views carousel — see project-room-game-views-carousel).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
39a42a33a3 |
game-views carousel: red FTs for the ATLAS/SCROLL/POST/CHAT/PULSE sprint — TDD
Outside-in RED contract (test_game_room_views.py, game_room bucket) for the next sprint: the room's 2nd vertical snap pane becomes a horizontal carousel of 5 views reached by scrolling down from the hex — ATLAS | SCROLL | POST | CHAT | PULSE, landing on SCROLL (2nd). Covers: a root-level icon strip (hidden at the hex, shown in the views pane, SCROLL active + glowing, 5 icons in order); icon-click + horizontal-wheel nav with glow handoff + a data-active-view title reveal; #id_text_btn running the down-then-right swipe machine from the hex to the Post view; the Post view as a room-scoped thread reusing post.html's #id_post_table + #id_post_line_text composer; the Atlas view aggregating GameEvents + room posts (data-source=provenance|post); CHAT/PULSE as .room-view-stub placeholders; and the landscape strip clearing the scroll card's fade mask. No implementation yet — these fail until the feature lands; they are the build contract. Plan + decisions captured in memory (project-room-game-views-carousel). Builds on the GAME ROOM ⇄ GAME SCROLL title reel + the room scroll-of-events aperture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cf1965c439 |
game room title: GAME ROOM ⇄ GAME SCROLL reel on the scroll aperture — TDD
The h2's second slot becomes a two-word vertical reel: GAME stays put, ROOM rests in view, SCROLL is parked one notch below in the slot's bottom fade. room-scroll.js toggles `.is-scroll` on the h2 from the SAME IntersectionObserver that already watches the table-hex aperture's scroll pane — ROOM slides up & out under the navbar line while SCROLL rises out of the page-aperture gradient (reverses on scroll-up). Table-phase only; the gate phase stays a plain GAME ROOM. One translateY drives both orientations. Portrait: the word is a short horizontal row in a short slot. Landscape: writing-mode: vertical-rl (inherited from the rotated gutter wordmark) makes the word a tall letter-column, so the same translateY slides it ALONG the wordmark — the user-chosen landscape behaviour for free. Landscape uses a shallower --gr-fade + a letter inset so the space-between end-letters parked at the slot edges aren't dimmed by the dissolve. Motion is deliberately old & rusty: a single cubic-bezier can overshoot at most once and can't oscillate, so the easing is a CSS linear() curve — stall against the grime, jerk free, clunk PAST the mark, then a damped end-wobble into place. Exposed as --gr-ease / --gr-dur / --gr-fade knobs on .gr-swap. base.html's letter-splitter now also splits the two .gr-word words; the .gr-swap window ships data-letters-split="1" so the splitter skips it (no 'roomscroll' run). Reel SCSS is scoped to .gr-swap/.gr-word; `> span.gr-swap` ties `> span:last-child` at (0,4,3) and wins on later source order [[feedback-scss-import-order-specificity]]. TRAP: libsass does NOT strip `//` comments INSIDE a CSS custom-property value — they leak into the compiled output, making the linear() (hence the whole `transition` shorthand) invalid-at-computed-value-time, which silently resets to 0s/ease (no animation). Keep every annotation OUTSIDE the linear(). [[feedback-libsass-comment-in-custom-property]] Reusable .gr-swap seam: my_sea gets GAME SEA → GAME SCROLL via a one-line header swap once its sea-scroll pane is built (deferred — the sea scroll doesn't exist yet). Tests: 2 ITs (RoomScrollOfEventsTest) — reel markup renders in the table phase, stays plain in the gate phase; 1 FT (test_scroll_swaps_room_title_to_scroll) — scrolling the aperture toggles GAME ROOM ⇄ GAME SCROLL both ways. collectstatic'd room-scroll.js for the FT [[feedback-collectstatic-before-ft]]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
114f0fd0db |
composer applets: .applet-btn-panel behind the OK btn for contrast on the --duoUser felt — TDD
Giving New Game / New Post the --duoUser green felt made the green OK .btn-confirm hard to see. Wrap it in an .applet-btn-panel — a --priUser fill + faint --terUser border, mirroring .gate-roles-panel (_room.scss) with tighter padding so it stays snug beside the line input — so the button reads clearly against the felt. Applied in _applet-new-game.html and the shared _form.html (New Post now; room.html's composer inherits it when ported). Tests: GameboardViewTest.test_new_game_ok_button_in_applet_btn_panel (OK btn inside #id_applet_new_game .applet-btn-panel); NewPostTest.test_new_post_applet_has_ok_confirm_button extended to assert the panel wrap. The New Game OK btn stays clickable inside the panel (GatekeeperTest.test_founder_creates_room_and_sees_gatekeeper FT green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
22fc38b92b |
billboard applets view: stop the top-fade mask clipping the first applet
The billboard applets view (.billboard-page) was a plain page-level scroller, unlike .gameboard-page / .dashboard-page (flex columns whose applet grid fills the aperture and scrolls internally). So #id_billboard_applets_container (flex:1, from %applets-grid) had no flex parent — it grew to its full content height, and the shared %applets-grid `transparent 0% -> black 2%` top-fade became 2% of that tall box, fading away the top of the first applet (newly visible now that New Post carries the --duoUser felt). Fix — match the other two boards: - .billboard-page: display:flex; flex-direction:column; overflow:hidden (overrides %billboard-page-base's overflow-y:auto). - #id_billboard_applets_wrapper (the extra hx-swap wrapper the other boards don't have): display:flex; flex-direction:column; flex:1; min-height:0 — passes the flex height through so the grid is aperture-constrained with an internal scroll. Its top-fade is now the same small fade as gameboard/dashboard. Verified: BillboardAppletsTest.test_billboard_shows_three_applets + test_toggling_applets_keeps_content_and_persists_per_applet green (the pair-run flake was the known Selenium memory-pressure NoSuchWindow, not a regression — both pass in isolation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
430c3bf141 |
composer applets: --duoUser felt + masked h2 (matching My Sky / My Sea / My Sign)
The New Game and New Post composer applets (#id_applet_new_game / #id_applet_new_post) now take the same treatment as the My Sky / My Sea / My Sign applets: a --duoUser green-felt content bg with the rotated >h2 title bar masked back to the default dark. The h2 mask is an opaque --priUser base under the same two 0,0,0/0.125 overlays the %applet-box + its >h2 give every normal applet — so the title bars read identically dark instead of green-tinted, and it stays palette-correct on *-light palettes (no flat 0,0,0). The same mask was applied to the three #id_applet_my_* shells, whose h2 had been left as pure --priUser (lighter than the other applets). Composer line-inputs (#id_new_game_name / #id_new_post_text / #id_post_line_text) keep a --priUser fill + faint --secUser placeholder + 700 weight + --terUser focus, standing out against the felt like My Sky's form fields. Bundled: palette felt retunes (rootvars --terFor / --terPer). [[project-room-scroll-of-events]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
91f48384ff |
post composer: OK btn + orientation-aware right clamp; #id_post_line_text styling — TDD
The Billpost composer (#id_post_line_text) now matches the other composers and prepares the pattern for room.html. 1. OK .btn-confirm (#id_post_line_btn) added to the editable post-line form, in a flex .composer-row beside the input (read-only system Posts — note_unlock / tax_ledger / mail_acceptance — invite no response, so get no OK). #id_post_line_text also joins the composer-input styling selector in _applets.scss (700 weight + duoUser fill + terUser focus + priUser placeholder). 2. Orientation-aware right clamp on .post-line-form so the OK btn clears the bottom-right corner button: - portrait: margin-right 3.5rem — clears the gear (.post-page > .gear-btn, right:0.5rem, 3rem wide → 3.5rem slot). - landscape: margin-right 7.2rem — clears the burger slot (#id_burger_btn lands at right:4.2rem, 3rem wide → its left edge sits 7.2rem from the viewport's right edge; this also clears the bud at right:0.5rem). post.html carries no burger (room.html will) — the slot is reserved for parity. .composer-row is flex (input flex:1 + OK); the read-only input keeps width:100%. Bundled: rootvars --terPer felt retune (82,71,138). Tests: PostViewTest ITs (editable post renders the OK .btn-confirm in .composer-row; read-only system post does not); functional_tests/test_bill_post_composer FTs (portrait OK right edge <= gear left edge; landscape OK right edge >= 7.2rem in from the viewport edge). 12 PostViewTest ITs + 442 dashboard/billboard ITs + 2 composer FTs + test_bill_new_post FT (ENTER-submit through the composer-row) green. [[project-room-scroll-of-events]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d66d898f4d |
light palettes: --duoUser = --undUser felt so input/aperture fills blend with the page
On *-light palettes the dark-olive --duoUser felt (terFor) read too heavy as an input / aperture fill against the light page. The light-palette override block (body[class*="-light"]) now sets --duoUser: var(--undUser), so every --duoUser surface — e.g. oblivion-light's New Game / New Post composer inputs — uses the lighter forest felt and blends in. Overrides the :root --duoUser for every *-light palette (body[class*="-light"] out-specifies :root). Composer polish (_applets.scss): the New Game / New Post line inputs gain an explicit --duoUser background fill + a --priUser placeholder colour, alongside the 700 weight + terUser focus shift already added. Forest felt (priFor) retuned in rootvars to suit the lighter light-palette fill. [[project-room-scroll-of-events]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
39a2a49d7e |
New Post applet: kill #id_text remnant, add OK btn, bolden composer inputs — TDD
Prep for porting a post composer into room.html. Three changes on the New Game / New Post applets:
1. Renamed the old Percival #id_text → #id_new_post_text. _form.html (the shared post composer) is now parameterized on input_id (default id_new_post_text) + submit_id (default id_new_post_btn); the feedback div + aria-describedby track {{ input_id }}_feedback. _applet-new-post.html includes it with input_id="id_new_post_text". room.html will reuse the same partial with input_id="id_room_post_text". Refs updated: _scripts.html (initialize("#id_new_post_text")), Jasmine Spec.js, FT post_page.py. _form.html has exactly one includer.
2. Added an OK .btn-confirm submit to _form.html (flex .composer-row: line input + OK, validation feedback below), mirroring the New Game applet. ENTER still submits, so the existing add_post_line FTs stay green.
3. Composer-input styling in _applets.scss: #id_new_game_name, #id_new_post_text { font-weight: 700; &:focus { color: --terUser } } — mirrors .sky-form-col input. The duoUser fill / secUser default text / terUser border+glow on focus already come from .form-control; this adds the 700 weight + the focus text-colour shift. room's #id_room_post_text joins this selector list when it lands.
Tests: NewPostTest ITs (conventional id + aria-describedby, OK .btn-confirm). 440 dashboard+billboard ITs green; Jasmine spec green with the renamed id; test_bill_new_post FT green (renamed input + OK btn, ENTER submit).
[[project-room-scroll-of-events]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
4a4e60f668 |
room scroll-of-events: applet-box card styling + scroll-driven gear menu (Frame/Redact filter) — TDD
The room scroll pane now matches scroll.html: the feed sits in a .applet-scroll %applet-box card with the rotated room-name title; dropped the special --duoUser pane bg (the dark card sits on the room-page bg). Gear menu is now view-aware. #id_room_menu carries two panes: .room-menu-default (the existing NVM/DEL/BYE) + .room-menu-scroll (a Frame/Redact #id_scroll_filter_form, rendered only when the gear include gets scroll_filter — room.html passes scroll_filter=room.table_status). room-scroll.js (NEW) runs an IntersectionObserver on .room-scroll-pane (root=#id_room_aperture): scrolled to the feed -> show the filter pane; back on the hex -> show the default. The filter mirrors scroll.html (per-room localStorage, toggles .drama-event[data-label] display). Buffer-dots animation moved from the inline partial script into room-scroll.js. Other views keep their own menus, as asked: GATE VIEW (room_gate.html) includes _room_gear.html with nvm_url only (no scroll_filter, no room-scroll.js) -> NVM(->hex)/DEL/BYE; the cross/spread phase is a modal over the hex (scrollTop 0) -> default pane. Traps: applets.js caches gear.dataset.menuTarget at bind time, so you can't swap a gear's target to a 2nd menu — both panes live in ONE #id_room_menu and JS toggles visibility. .room-menu-default is display:contents so wrapping the existing controls doesn't change their layout (JS toggles none<->contents, not ''). Tests: +3 ITs (RoomScrollOfEventsTest — .applet-scroll card + room-name title, filter pane renders in table phase, filter absent in gate phase); +2 FTs (test_game_room_scroll — gear swaps to filter when scrolled to feed, unchecking Redact+OK hides struck rows). 8 scroll ITs + 4 scroll FTs green; 554 epic ITs/UTs green; gatekeeper DEL+BYE gear FTs green (the .room-menu-default wrap is layout-neutral). [[project-room-scroll-of-events]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a4adf9664b |
room scroll-of-events: table-hex aperture binary scroll-snaps to the room provenance feed — TDD
From Role Select onwards, scrolling DOWN in the table-hex aperture swaps the entire hex view for the room's GameEvent feed (mirrors my_sky's wheel<->form; scroll-snap-stop:always = no partial scroll). Reuses the Billscroll query + core/_partials/_scroll.html so the feed renders identically.
room.html: #id_room_aperture wraps .room-hex-pane (existing .room-shell + the _table_positions strip moved INSIDE so the circles scroll away with the hex) + .room-scroll-pane (includes new _room_scroll.html); 'is-scrollable' added iff table_status set.
_room_scroll.html (new) = the DRY seam for my_sea; includes the shared scroll partial + a tiny dots-animation script (no scroll-position persistence). room_view adds events/viewer/scroll_position (same query as billboard.views.scroll).
_room.scss: .room-aperture + .room-pane (height:100%, not min-height); .is-scrollable engages scroll-snap-type:y mandatory + per-pane scroll-snap-align:start & scroll-snap-stop:always; .room-scroll-pane styles #id_drama_scroll + .scroll-buffer { margin-top:auto } (pure-CSS bottom-pin).
Trap: the aperture & panes set NO z-index/transform/opacity/filter -> NO stacking context, so the position strip's z-130 still resolves in the root context, above the gate/sig overlays (z-100/120). Verified by gatekeeper FTs (token drop + circle/modal layering).
Deferred INDEFINITELY (user): rising-game-cost + max room membership + max simultaneous CARTE slots + in-slot token combinations — until CARTE is anything but a secret type of Trinket.
Tests: RoomScrollOfEventsTest (5 ITs — aperture wraps hex+scroll panes, is-scrollable from Role Select, feed renders + scoped to room, no scroll pane in gate phase); functional_tests/test_game_room_scroll.py (2 FTs — computed scroll-snap props + scroll-down reveals the feed). 551 epic ITs/UTs green; 2 new FTs green; gatekeeper (token-drop + circle layering) + role-select (card fan) FTs green.
[[project-room-scroll-of-events]] [[project-position-circle-tooltips]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
5229b9f96a |
slot/token tooltips: 'expires <relative>' (lowercase, .row-ts timescale) + per-slot token_cost on GateSlot + '+ <Token>' deposited-token list — TDD
Three pieces of housekeeping on the token tooltips: 1. Expiry format. relative_ts is now distance-based (abs gap from now), so it formats FUTURE expiries with the same timescale rules it already uses for past .row-ts timestamps (<24h time, <7d weekday, <1y 'dd Mon', else +year) — past behaviour unchanged (abs is a no-op for past). The FREE-token wallet tooltip (Token.tooltip_expiry) and the position-circle .tt-expiry both read 'expires <when>' (lowercase, no majuscule); the position tooltip is server-formatted via the filter (JS just copies the attr — no JS date logic). 2. Per-slot token count moved off the user's CARTE token onto the slot model. New GateSlot.token_cost (PositiveSmallIntegerField default 1) — the per-seat expenditure count. _gate_positions reads slot.token_cost instead of the CARTE Token.slots_claimed high-water mark, which wrongly showed '6' on every CARTE-covered seat. Every slot now reads 1 (a CARTE covers each seat at cost 1, like any token); the field only rises above 1 when the rising-game-cost feature lands. 3. Per-slot deposited-token list. Under the '<n> Token(s) deposited' header the tooltip now lists a '+ <Token name>' bulleted <ul> — one entry today (a slot ejects its token on any re-deposit, so combinations aren't yet possible). Derived from the slot's debited_token_type (e.g. 'carte' -> 'Carte Blanche', 'Free' -> 'Free Token'); a CARTE across all six seats shows '+ Carte Blanche' on each. token_types is a list, future-ready for token combinations + elevated per-slot cost. Rising-game-cost is NOT built (recon-confirmed), so the per-slot count is always 1 and the 2-token-slot FT is intentionally skipped per user. Tests: relative_ts future-date unit tests; FreeTokenTooltipTest rewritten for the relative format (real datetime, no MagicMock/strftime); wallet FT + the two CARTE token-count tests updated to per-slot semantics (1 + 'Carte Blanche'); FREE-slot IT asserts the token-list + 'expires '. Full suite 1606 green; 11 position-tooltip FTs + wallet tooltip FT green. [[project-position-circle-tooltips]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cbc4f4f323 |
position tooltips: titles read 'the Earthman' (article) + occupied gatekeeper circles now produce tooltips; FT flow dismisses the gameboard Brief — TDD
Three follow-ups from manual review: 1. Title article: the tooltip .tt-description (position circles + My Buds) prepends 'the ' so it reads 'the Earthman', matching the established '@handle the <Title>' convention + the visible bud-row. Touches _table_positions.html, _my_buds_item.html, and the async add-bud row builder in _bud_add_panel.html (was building data-tt-description without the article, diverging from the server-rendered rows). active_title_display always returns a value so the article is always well-formed. Tests updated: epic + billboard IT (data-tt-description='the Earthman'), the two My Buds FTs. 2. Initial-gatekeeper tooltips: under the gatekeeper .gate-backdrop, .position-strip circles are pointer-events:none, so mouseenter only reached a .gate-slot that contained a pointer-events:auto descendant (an OK/NVM/drop button). An occupied circle WITHOUT such a button never fired its tooltip. Re-enable .gate-slot.filled/.reserved (occupied, hoverable, no click action of their own) at (0,4,1) > the (0,3,1) suppressor; empty circles stay suppressed. room_gate already handled via .room-gate-page; Role Select covered by the same .role-select-backdrop variant. 3. FT flow: the @taxman 'Debits & credits' ledger Brief renders over the gameboard top and intercepts id_create_game_btn. Added dismiss_brief_if_present() before the create-game click in the gatekeeper + select_role FTs (the trinket FTs already carry their own dismisses). Verified: 11 position-tooltip FTs, gatekeeper drop, both My Buds FTs, select_role create-game FT all green; full IT/UT suite 1604 green. The gatekeeper-circle pointer-events couldn't be FT-asserted reliably (synthetic mouseenter bypasses pointer-events; real-hover is flaky) — verified via the compiled-CSS cascade + the drop FT confirming the OK button still clicks. [[project-position-circle-tooltips]] [[feedback-dismiss-brief-ft-helper]] [[feedback-scss-import-order-specificity]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5a39746853 |
position-circle tooltips: adversarial-review fixes — drop email leak, hide-on-hover-transition, surface #tokens, room_gate tooltip-only, N+1 hoist + specificity hardening — TDD
Follow-up to the position-circle tooltips sprint, addressing confirmed findings from a multi-agent adversarial review of the diff:
- Email leak (privacy): the hidden .slot-gamer span rendered the raw login email into DOM source on every filled circle — widened to room_gate this sprint. Now renders {{ gamer|at_handle }}; new IT asserts no occupant email anywhere in the page source.
- Stale hover state (position-tooltip.js): moving circle→circle accumulated .tt-pos-* classes on the portal (prior set never stripped), and circle→empty left the prior tooltip stranded. Now _hide() before _show() on every transition.
- Dead #tokens plumbing: data-tt-tokens was computed + rendered but never displayed. Surfaced as a .tt-tokens line in the portal.
- room_gate gather forms: the merged _gate_context let a CARTE owner drop/release gate slots from the renewal gate-view. Zeroed carte_next/nvm/is_last_slot so it's tooltip-only; new IT asserts no drop/release forms.
- N+1: hoisted the per-CARTE-slot token lookup into one carte_claims map; added select_related(significator) on seats + select_related(gamer) on gate_slots.
- SIG_SELECT seat override now gated on an EXPLICIT ?seat (no-param falls back to the canonical PC seat, not the lowest gate slot, so every SIG_SELECT surface agrees).
- Dropped dead is_self/is_bud dict keys (kept the locals + is_me_also).
- room-gate pointer-events override doubled to .room-gate-page.room-gate-page → (0,4,1), no longer a source-order tie with the (0,3,1) suppressor.
Tests: 11 position-tooltip FTs green (no skips); +2 ITs (no-email-in-source, room_gate-tooltip-only); full suite 1604 green. Deferred (noted in memory): in-UI seat switcher during SIG_SELECT, NVM-between-seats 409, gate_status/sea_partial enrichment split.
[[project-position-circle-tooltips]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
58280c63f5 |
my-sea spread: correct CROSS reversal direction — nonreversed (Emanation) faces right, Reversal faces left (swap of 5b6a1be)
Follow-up to |
||
|
|
5b6a1be347 |
my-sea spread: reversed CROSS card finally reads as reversed — upright cross top points left (270°), reversed points right (90°), 180° apart — TDD-adjacent CSS fix
The Celtic-Cross crossing card is rotated to lie landscape. Two equal-specificity rules set its rotation: `.sea-pos-cross .sea-card-slot` (upright) and `.sea-pos-cross .sea-card-slot--reversed` (reversed) — both (0,2,0). The upright rule sits LATER in source, so it won the cascade for reversed cross cards too: every crossing card rendered at 90° (top-right) and a reversed card never indicated reversal in the spread (the modal was fine). The inline comment claiming the reversed rule had 'higher specificity' was simply wrong. Fix (user-spec: reversed cross top points rightward): keep the reversed card at 90° (top-right) and flip the UPRIGHT to 270° (top-left), so the two read 180° apart. The reversed rule is re-specified as `.sea-pos-cross .sea-card-slot.sea-card-slot--reversed` (0,3,0) so it genuinely out-specifies the base rule and wins regardless of source order. [[feedback-scss-import-order-specificity]] Pure CSS in _card-deck.scss; covers both live draws (_fillSlot) and saved hands (_my_sea_slot.html), which share the .sea-card-slot--reversed/.sea-pos-cross classes. Modal reversal (.stage-card--reversed, 180°) untouched. No tests pin the rotation degrees. Verified in the compiled output.css cascade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
efbf98ecf2 |
per-seat sig for CARTE: ?seat targets the seat + a solo polarity group commits the sig on reserve — multi-gamer reserve→ready→countdown→confirm untouched — TDD
Workstream C of the position-circle tooltips sprint (sprint complete). Per-seat (not per-gamer) significators for a CARTE solo owner, WITHOUT flipping the SigReservation (room,gamer) unique constraint (which the recon confirmed would break 25+ multi-gamer sig tests + the channels flow). - sig_reserve resolves the active seat from ?seat=N (carried on the reserve URL) when the viewer owns it, else _canonical_user_seat — so the hold/commit targets the SELECTED seat. - When the polarity group is SOLO-owned (the viewer owns every PC/NC/SC or BC/EC/AC seat — a CARTE table), reserving commits seat.significator immediately: the 3-ready countdown can never complete solo. The committed sig persists through a NVM release (which only deletes the provisional row), so the viewer reserves each seat in turn. Advances to SKY_SELECT once every seat has a sig (mirrors sig_confirm's tail). Strictly gated to the solo case — a multi-gamer polarity group still rides the existing countdown contract untouched. - _role_select_context SIG_SELECT branch: overrides user_seat by ?seat (owned) so the overlay reflects the selected seat's role/polarity/deck, and carries ?seat on sig_reserve_url. - FT refined to the real reserve mechanic (in-card OK btn → .sig-reserved) — the RED spec's .sig-card.reserved / single-body-click was a placeholder; behavior verified is unchanged (per-seat sig persistence). Tests: CarteSeatSwitchTest.test_carte_saves_a_significator_per_seat FT green (all 4 CarteSeatSwitch + 7 PositionTooltip now green, no skips); 2 solo-CARTE sig ITs (SigReserveSoloCarteTest); full suite 1602 green; channels consumer tests 5 green (WS broadcast intact); multi-gamer SigReserveViewTest/sig_ready/sig_confirm untouched. [[project-position-circle-tooltips]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d190b37149 |
room ?seat=N seat-switch: CARTE gamer previews an owned seat's card-stack — data-active-slot + ineligible .fa-ban when it's not the table's turn — TDD
Workstream B of the position-circle tooltips sprint. _role_select_context consumes ?seat=N (threaded from room_view): a multi-seat (CARTE) gamer previews a specific owned seat. The card-stack's active slot becomes that seat; it stays 'eligible' only when the previewed seat is also the table's current turn (lowest unassigned seat), else it renders the ineligible .fa-ban. Strictly additive — a one-seat gamer never passes ?seat, and an unowned/garbage param is ignored, so the normal role-select flow (RoleSelectRenderingTest) is untouched. Tests: CarteSeatSwitchTest.test_switching_seat_loads_that_seats_role_view FT green; 2 new render ITs (seat-param-previews + no-seat-keeps-canonical); RoleSelectRenderingTest still green. [[project-position-circle-tooltips]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
30246cc94a |
position circles: rich .tt-pos-* hover tooltips on the gate-view + table circles — @handle/title/seat-sig/shoptalk/#tokens/expiry portal + CARTE me-also ?seat switch href — TDD
Workstream A of the position-circle tooltips sprint (green; B/C ride @skip-ped).
The numbered gate-position circles (1-6) gain rich hover tooltips mirroring the My Buds bud tooltip on every surface — and now render on room_gate.html (the GATE VIEW), which showed no circles before (the headline gap).
- _gate_positions(room, user, current_slot): per-circle .tt-pos-* state class (empty / gamer / gamer+bud / me-current / me-also) + data-tt-* payload (@handle via at_handle NOT email, title, seat significator rank/suit, bud shoptalk, deposited #tokens [CARTE slots_claimed else 1], seat-clock cost_current_until expiry). _viewer_current_slot resolves the viewer's acting seat (?seat override or canonical) to split me-current vs me-also.
- room_gate view merges _gate_context so _table_positions renders there; room_view threads ?seat into _role_select_context.
- _table_positions.html: .tt-pos-* appended AFTER role-assigned (keeps the 'gate-slot filled role-assigned' substring + class-before-data-slot regex intact for RoleSelectRenderingTest), data-tt-* attrs, me-also ?seat switch anchor.
- #id_position_tooltip_portal (page-root, position:fixed) + position-tooltip.js (hover/clamp/union-hide modeled on tray-tooltip.js); .tt-sign rank+suit stack; .tt-pos-* circle accents; room-gate pointer-events re-enable.
Tests: 7 PositionTooltipTest + 2 CarteSeatSwitchTest (tokens, me-also href) FTs green; 8 fast render-level ITs (PositionTooltip{,Carte}RenderTest); full suite 1598 green.
[[project-position-circle-tooltips]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
19471662ff |
position-circle tooltips: red FT spec (skipped) — gate-position circles get rich .tt-pos-* tooltips + CARTE seat-switch + per-seat SIG — TDD
Outer-loop FTs authored today; implementation lands tomorrow. Both classes are @skip-ped so the red spec rides into the repo without breaking the FT CI stage (we just rescued that pipeline); tomorrow's work removes the skip per-method as each behavior goes green. Spec encoded (user-spec 2026-06-01): - gate-position circles (1–6) gain rich hover tooltips mirroring the My Buds bud tooltip, on EVERY surface — initial gatekeeper, above the hex, AND the new GATE VIEW gate-view (room_gate.html renders no circles today: the headline red) - tooltip: @handle (.tt-title), title (.tt-description), NO email, a top-right .tt-sign stack of the SEAT significator (TableSeat. significator — per-seat, user-decided), bud shoptalk when the occupant is a bud, # tokens deposited (CARTE slots_claimed else 1), .tt-expiry (GateSlot.cost_current_until) - state classes: .tt-pos-empty / .tt-pos-gamer / .tt-pos-gamer.tt-pos-bud / .tt-pos-me-current / .tt-pos-me-also (renamed from -me-other per user). .tt-pos-me-also carries a ?seat=<n> switch href to load that seat's view (preview pos-4 ROLE state w. the .fa-ban atop the deck, or SAVE SIG per seat during Sig Select) - per-seat SIG: today SigReservation is per-(room,gamer) — the FT pins per-SEAT sig so a CARTE gamer picks a different sig per seat (tomorrow's green = SigReservation rework) FTs: PositionTooltipTest (8 — circle render on gate-view, me-current / gamer / bud+shoptalk / no-email / tokens+expiry / seat-sign / hover- portal) + CarteSeatSwitchTest (4 — me-also switch href, carte token count, ?seat= loads seat ROLE view, per-seat sig). game_room bucket. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
84d328171b | ci: re-trigger | ||
|
|
5447a26827 |
room seat cron backstop: expire_lapsed_room_seats sweeps idle mid-game tables — TDD
Phase 6 (final) of the room GATE VIEW + seat-renewal sprint. Cron backstop mirroring delete_stale_my_sea_draws — the lazy _expire_lapsed_seats already frees seats on every room/gate-view access, but a mid-game table nobody reopens past the grace window would keep its stuck seats forever. This command runs the same sweep over every room holding a timestamped FILLED slot. No flags; idempotent. Tests: ExpireLapsedRoomSeatsCommandTest (2) — frees a >2S lapsed seat + flags RENEWAL_DUE; no-op within grace. Full project suite 1590 ITs/UTs green. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0cd16861cd |
room auto-BYE: free seats lapsed past the renewal grace (2× renewal_period) → RENEWAL_DUE gamer-needed stub — TDD
Phase 5 of the room GATE VIEW + seat-renewal sprint. A seated gamer who never renews is evicted once their seat's cost passes the renewal-grace window (filled_at + 2*renewal_period; 14d at the 7d default). - _expire_lapsed_seats(room): mirrors _expire_reserved_slots — for each FILLED slot past 2S, blanks the GateSlot, blanks the matching TableSeat (keeps the row for seat-count integrity), records SLOT_RETURNED + retracts the prior SLOT_FILLED (scroll redact-pair symmetry), then flags the room RENEWAL_DUE. NULL filled_at is never expired (RESERVED holds / ORM fixtures / auto-admit trinkets) — protects every existing FILLED-slot test - lazy call sites: room_view, gatekeeper, room_gate (on access; mirrors the my-sea delete_stale pattern — no scheduler needed for active rooms) - room.html: RENEWAL_DUE renders a minimal #id_gamer_needed stub (_table_positions + _gatekeeper already suppressed for RENEWAL_DUE). Mid-game re-seat flow is a documented follow-on Tests: ExpireLapsedSeatsTest (10) — frees slot + blanks seat past grace; no-op within cost window / grace / for null filled_at; sets RENEWAL_DUE; records SLOT_RETURNED; lazy expiry on room_view + room_gate access; gamer-needed stub renders. 848 epic+gameboard ITs green. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4b3dc91e7f |
room center: GATE VIEW supersedes SCAN SIGS / CAST SKY / DRAW SEA / sig overlay when token cost lapses — ROLE pick survives grace — TDD
Phase 3 of the room GATE VIEW + seat-renewal sprint. When the viewer's own FILLED gate-slot cost has lapsed (filled_at past the cost-current window), the center hex shows a GATE VIEW button (→ room gate-view) instead of the phase affordances, so they must renew before advancing. - _role_select_context: adds viewer_cost_current / viewer_in_grace from the viewer's FILLED slot (no slot → current, defensive) - room.html: the ROLE card-stack renders OUTSIDE the cost gate (the gamer's own role pick survives the renewal grace — deposit privilege); GATE VIEW supersedes the rest of .table-center; #id_pick_sigs_wrap (SCAN SIGS, advancing the whole table) is gated on viewer_cost_current; the SIG/SKY/SEA overlays are gated too (they embed their trigger-btn ids in JS, so they must not render alongside GATE VIEW) - per user-spec: only the ROLE pick stays in grace; SCAN SIGS + every later phase get GATE VIEW Tests: RoomCenterSupersessionTest (9) — GATE VIEW supersedes sig overlay / CAST SKY / DRAW SEA / SCAN SIGS when lapsed, normal buttons when current; RoomRoleStackGraceTest (1) — card-stack (eligible) kept alongside GATE VIEW when lapsed. 838 epic+gameboard ITs green. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e78ba730e3 |
room gate-view: reuse the gatekeeper token-slot modal — CONT GAME → hex when satisfied / rails-renew when lapsed — TDD
Redesign of the room gate-view per user-spec 2026-05-31: drop the custom
seat-circle + countdown; render the EXACT gatekeeper modal instead
(title panel + animated status-dots + token-slot rails + roles panel).
- roles-panel .btn-primary is CONT GAME (→ table hex, same target as the
gear NVM) while the viewer's seat cost is current; absent once it
lapses, reappears after renewal re-satisfies the cost
- .gate-status-text: "<n> Token(s) Deposited" (literal "(s)" + the shared
. . . . dots loop) when satisfied; "Please Deposit Token" when not.
<n> = the room's deposited (FILLED) slot count
- token slot: .claimed (static rails) when current; .active rails that
POST to renew_token when lapsed
- seat circle + time-remaining removed — the hex's own .fa-chair carries
seat status & user/seat tooltips land next sprint
- room_gate view trimmed to {room, cost_current, deposited_count,
page_class}
- tests: RoomGateViewTest reworked (9) — CONT GAME→hex + deposited-count
status + no renew-form when current; "Please Deposit Token" + renew
rails + no CONT GAME when lapsed; NVM→hex; page-room; no seat/countdown
markup. 510 epic tests green
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
65689295a7 |
navbar: GATE VIEW swaps for CONT GAME on room pages (page-room) → room gate-view — TDD
Phase 0 of the room GATE VIEW + seat-renewal sprint. Mirrors the my-sea treatment: on any room page the self-referential CONT GAME is replaced by a GATE VIEW button that opens the room's renewal gate-view. - `room_view` page_class → "page-gameboard page-room"; the bare gameboard listing stays "page-gameboard" (no page-room) so CONT GAME persists there for returning to a recent room. - `_navbar.html` GATE VIEW branch fires on `page-my-sea` OR `page-room`; onclick routes, in precedence: page-room → epic:room_gate (room in context); my-sea-visit → visitor gate; else owner's sea gate. One consolidated branch (DRY) instead of two near-identical button blocks. Tests: RoomNavbarGateViewTest (4) — room page shows GATE VIEW not CONT GAME, links to room_gate, gate-view page also shows it, page-room marker present. 826 epic+gameboard ITs green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
516b917420 |
room gate-view: mid-game renewal area (room_gate) + renew_token endpoint + _room_gear nvm_url — TDD
Phase 4 of the room GATE VIEW + seat-renewal sprint. The 3rd-person mirror of my_sea_gate: a gate-view a seated gamer can open at any time to check token TIME REMAINING or RENEW, reachable even mid-game (the gatekeeper redirects to the table once table_status is set — this view does not). - `room_gate` view + `room/<uuid>/gate/view/` URL — renders the viewer's own seat/position circle, a live time-remaining ticker (counts down to cost_current_until, then to grace_expires_at in renewal grace), and a RENEW affordance. page_class carries `page-room` (drives the navbar GATE VIEW in Phase 0). No seat → "no seat" copy, no RENEW btn. - `renew_token` view + `room/<uuid>/gate/renew` URL — re-deposits a token into the viewer's already-FILLED slot via the existing `debit_token` (resets filled_at=now → restarts the cost-current window). Reuses select_token / debit_token wholesale; distinct from confirm_token, which needs a RESERVED slot. 402 when token-depleted; no-op redirect when the user holds no filled slot (already auto-BYE'd). - `room_gate.html` — reuses the gatekeeper's .gate-overlay/.gate-modal chrome (hand-rolled like my_sea_gate, inner content differs) + an inline countdown ticker mirroring the status-dots IIFE. - DRY: `_room_gear.html` now takes an `nvm_url` param (default the gameboard listing — room.html's own gear unchanged); the gate-view passes the table-hex URL so NVM returns to the hex, mirroring _my_sea_gear's contract. Tests: RoomGateViewTest (7) + RoomRenewTokenTest (6) — renders mid-game, own seat circle, data-cost-until, RENEW posts to renew_token, NVM→hex, page-room marker, no-seat render; renew resets filled_at + consumes FREE + records SLOT_FILLED, no-slot/GET redirects, 402 when depleted. 504 epic tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6fd515bc6d |
GateSlot seat-occupancy clock: cost_current / renewal-grace / grace_expired derived from filled_at + renewal_period — TDD
Phase 2 of the room GATE VIEW + seat-renewal sprint. Pure model properties (no migration, no new fields) layering a uniform seat clock on top of the existing per-token debit rules (which stay untouched): [A, A+S) cost_current play normally (A = filled_at) [A+S, A+2S) in_renewal_grace cost lapsed, seat held (S = renewal_period) [A+2S, ∞) grace_expired eligible for auto-BYE Uniform across ALL token types per user-spec (PASS/BAND/CARTE included) — keyed on filled_at only. A NULL filled_at (RESERVED slots, ORM-built fixtures) reads cost_current=True / grace_expired=False so nothing without a fill timestamp is ever evicted (protects existing FILLED-slot tests that set status via the ORM). renewal_span falls back to 7d when room.renewal_period is None. Tests: GateSlotCostCurrentTest — 11 UTs covering within/after span, null filled_at, until==filled+period, grace boundaries [S,2S), expiry at 2S, and the 7d span fallback. 491 epic tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1e70ffabd6 |
my-sea seating: owner stays seated the full 24h window (row-exists), not just while a hand is down — drop dead seat1_seated — TDD
Phase 1 of the room GATE VIEW + seat-renewal sprint. Decouples 1C seating from hand/deposit/paid state: the owner is seated as long as `active_draw_for` returns a row, i.e. for the full 24h after her most recent FREE or PAID draw (PAID DRAW resets created_at, so the window runs from the later of the two). A DEL'd row (empty hand, no paid credit) now keeps 1C seated until the row expires at 24h — previously 1C dropped to .fa-ban the instant she DEL'd, even mid-window. - `_my_sea_seats` `owner_seated` → `owner_draw is not None` (drives the owner's own landing hex + the live `sea_seats` broadcast). - `my_sea_visit` `owner_seated` → same rule, so the spectator hex and the owner's landing agree (was drawn-OR-paid, dropped `owner_paid`). - DRY: removed the dead `seat1_seated` context (the `seats` ring's `seat.present` has driven 1C since the multi-seat hex landed; the flag was never read by the template). Tests — TDD red→green: - flipped `test_seat_1c_not_seated_at_gate_view_after_del` → `..._seated_...`: DEL'd empty-hand row keeps 1C seated, center still GATE VIEW (1 check / 5 ban). - flipped FT `test_seat_1_banned_when_active_draw_has_empty_hand` → `..._seated_...` (asserts .fa-circle-check). - added `test_seat1_seated_context_key_removed` (DRY regression guard). - added spectator `test_owner_seated_with_empty_hand_no_payment`. - `test_owner_not_seated_without_draw_or_payment` unchanged (no row → still unseated). 318 gameboard ITs green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
86a349b64e |
wallet shop: free ($0) RWS + Fiorentine decks — FREE ITEM claim unlocks to Game Kit — TDD
- 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>
|
||
|
|
d8377b57bc |
my-sea cards: fix rotated significator/cross blur — drop the 4-shadow contour chain on rotated image cards, keep depth — TDD
The image-card contour stroke is 4 chained `drop-shadow()`s; each re-rasterizes the already-downscaled card (408→~64px), so on a ROTATED card the compounded re-sampling reads as BLUR. It's NOT the angle — even the 5° significator blurs, while the 90° cross blurs hardest; the upright COVER (same filter, no rotation) + the unrotated preview modal stay crisp. Verified live (dpr 1): a lone depth drop-shadow on a rotated card is crisp, the 4-shadow chain is not. Fix: the rotated image cards (`.sea-sig-card` -5° + `.sea-pos-cross .sea-card-slot` 90°/270°) drop the contour chain, keeping only the depth drop-shadow → crisp. Upright cards keep the full contour. Zeroing `--img-stroke-w` wouldn't help (the blur is the chained-shadow re-rasterization, not the stroke offset). RWS's contour was a redundant double-frame over its printed border anyway, so its rotated cards lose nothing visible. Verified in Firefox: the enlarged 5° significator renders crisp (sharp title + edges) with depth + printed border intact. Code architected by Disco DeDisco <discodedisco@outlook.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7e39740f9c |
my_sea_visit nav: phase-aware NVM (hex→bud page, draw→hex) + navbar GATE VIEW → visit gate + guard reposition on resize — TDD
Spectator guard-portal + NVM/GATE VIEW routing (user-spec 2026-05-30): - The leave-the-sea guard now confirms with OK (was NVM); `_my_sea_gear.html` gains an `nvm_handler` param so a caller can swap the default leave-nav. - my_sea_visit NVM is phase-aware (`mySeaVisitNvm`): on the DRAW/spread phase it flips back to the table hex (client toggle, stays in-ecosphere → no voice guard, mirroring the owner's picker→landing); on the table hex it LEAVES to the bud's page (`billboard:bud_page`) behind the shared voice-disconnect guard. - The navbar GATE VIEW opens THIS owner's visitor gatekeeper on my_sea_visit (+ its gate page, whose page_class also carries `page-my-sea-visit`), not the viewer's own sea gate; owner pages are unchanged. - showGuard now RE-POSITIONS the open guard on resize/orientationchange (rAF-throttled) so it follows its anchor instead of stranding at its show-time coords — a cross-cutting fix (every gear-menu guard) for the portal landing off-screen after an orientation flip relocated the gear menu. Coverage: MySeaVisitNavTest (navbar→visit gate, gear NVM→mySeaVisitNvm, bud URL, OK label) + MySeaOwnerNavbarGateUnaffectedTest (owner gate untouched). Verified live in Firefox: picker NVM returns to the hex; the guard followed its anchor from 882px→54px on a simulated orientation flip, staying in-viewport. Code architected by Disco DeDisco <discodedisco@outlook.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
571d5a84ae |
voice glow: regression spec — 3-min mute auto-disconnect stops the priRd/.fa-ban path + returns to the available nudge, live — TDD
The auto-disconnect already drives this asynchronously (burger-btn `_muteAutoDisconnect`
→ VoiceRoom.leave → _teardown → _notify{inCall:false,muted:false} → voice-glow render),
and the `voiceroom:ready` subscribe fix guarantees voice-glow receives it without a
refresh. Lock the muted→not-in-call transition behind a VoiceGlowSpec case: drops
`voice-muted` (priRd + .fa-ban) + restores the channel-available `voice-glow` nudge,
no longer "live" (no pulse/eq). Jasmine green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|