2d4a2c5b5c887d6b33d5e81535980afc02980893
824 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
668105aeeb |
my-sea voice: persist mute across in-sea nav/refresh + 3-min muted auto-disconnect; fix first-connect glow/mute race — TDD
MUTE PERSISTENCE (user-spec 2026-05-30) — a voice mute used to vanish on any
in-sea navigation/refresh (the mesh tears down + auto-rejoins unmuted). Now the
mute is stamped server-side + re-applied on rejoin, with a 3-min muted →
auto-disconnect window:
- `User.voice_muted_at` (timestamp, not a bare bool, so the 3-min window anchors
here) + migration. Per-user, not per-seat: the owner has no seat row, and a
user is in ≤1 voice room at a time, so this uniformly covers owner + visitor.
- POST `/voice/mute` {muted} sets/clears it (new voice app views.py + urls.py,
mounted at `voice/` in core/urls). my_sea + my_sea_visit pass the timestamp to
`#id_voice_btn` as `data-voice-muted-at`.
- voice-mesh.js gains `setMuted(m)` (set vs. toggleMute's flip), honoured by
join's post-getUserMedia `_applyMute`. burger-btn.js: a mute toggle POSTs the
state + arms a client timer; the auto-rejoin re-applies the persisted mute +
re-arms the timer from the stored timestamp (so the 3-min spans navigations,
not resets); an elapsed window on rejoin auto-disconnects instead of rejoining;
a fresh manual join clears any stale mute. On timeout: leave voice + clear.
FIRST-CONNECT GLOW/MUTE RACE (user-reported) — `setOnStateChange` pushes the
current state immediately on subscribe, and voice-glow.js often subscribes
MID-JOIN (getUserMedia pending → inCall=false). Its `setVoiceState` only ever
DELETED `voice.dataset.inCall` (never re-set it) — wiping the join-vs-mute flag
burger-btn.js had just set, so the next click re-joined instead of muting (which
also dropped the peer + killed the equalizer). Two fixes:
- voice-glow keeps `dataset.inCall` SYMMETRIC (set on true, delete on false), so
the mid-join false is restored once the stream resolves → mute works on first
connect.
- voice-glow subscribes reliably on AUTO-REJOIN too (no click to trigger its
poll): voice-mesh.js dispatches `voiceroom:ready` on singleton creation +
voice-glow listens, so the glow is mesh-driven (peer-count equalizer) after a
refresh, not just the in-call-class fallback.
Coverage:
- ITs: VoiceMuteViewTest (login/405/invalid-json guards, stamp on true, clear on
false, re-mute restamps, missing-key=false). voice+lyric 164 green.
- Jasmine: BurgerSpec mute persistence (muteRemainingMs window, rejoin re-mute,
expired-window auto-disconnect, toggle-persists + 3-min fires, manual-join
clears); VoiceGlowSpec dataset.inCall sync (sets on in-call, clears on not,
restores after a mid-join false→true). All green.
- Live multi-party voice (mic/2-device) left to manual verification.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
de4dcd7979 |
my-sea deck glow: single (monodeck) stack matches the levity/gravity --ninUser halo — make every deck match
Bring the single/non-polarized deck stack's hover/active glow in line with the tuned --ninUser halo levity + gravity already use (0.5rem blur / 0.5rem spread / 0.3 alpha) so every deck reads identically (user-spec 2026-05-30). Added a per-deck `$_glow-single` var (parity w. `$_glow-levity`/`$_glow-gravity`, each independently tunable). The monodeck keeps its own neutral --terUser hover border; only the glow box-shadow changed. Code architected by Disco DeDisco <discodedisco@outlook.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b7d871388e |
my-sea deck-stack + spread-card glow: unify hover-reveal / click-persist + --ninUser halo — TDD
Unify the glow/FLIP interaction across the owner picker (my_sea) + the read-only spectator (my_sea_visit), then carry the same selection halo onto the spread cards + deck-stack faces. DECK STACK (user-spec 2026-05-30) — the owner revealed the FLIP only on click (persisted) but never on hover; the spectator revealed it on hover but never persisted. Now BOTH do both: - `.sea-stack-ok` reveal is a single shared rule in _card-deck.scss — opacity fades in on hover/focus (ephemeral) OR via the JS-set `.sea-deck-stack--active` class (click-persist, same class the face-glow rides). The owner's inline `display` toggling is gone (`_showOk`/`_hideOk` just flip `--active`); the spectator's hover-only override in _gameboard.scss is removed. - Interactivity stays gated on `--active`, NOT hover: hover is a purely VISUAL preview (matching the spectator's disabled FLIP). This preserves the owner's two-step deal — were the FLIP click-through on hover, a single stack-click would land on the centred FLIP + deal early (caught by the draw FT). - Spectator persist wired in my_sea_visit.html (click a stack → `--active`, click elsewhere clears); its FLIP stays `.btn-disabled` (read-only). SPREAD CARDS — the same hover-glow + active-persist now on EVERY spread card, building on the cover/cross rules. The prior `.sea-card-slot--focused` glow (0-1-0) was silently overridden by the filled-card drop-shadow ladder (up to 0-4-0) and never rendered (verified live); `!important` (consistent w. the existing `opacity:1 !important` there) makes the halo win on hover + focus. The halo is symmetric (rotation-invariant). No colour change — box-shadow only. DECK FACE HALO — the levity + gravity stack glows now mirror the card halo's tuned geometry (0.5rem blur / 0.5rem spread / 0.3 alpha), each in its own polarity colour (--ninUser / --quaUser); single keeps its own tone. Verified live in Firefox: deck FLIP persists on click + fades on hover; the card halo wins over the drop-shadow on hover/focus across crown/cover/(reversed-)cross; levity/gravity deck glows match the card halo. Draw FTs green (single-draw, hand- completion, AUTO DRAW, auto-drawn-slot reopen) — the two-step deal + card focus survive the display→opacity switch. Code architected by Disco DeDisco <discodedisco@outlook.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7e876557aa |
my-sea spectate: broadcast spread on modal-close + sequence the spectator's AUTO DRAW reveal — TDD
Two follow-ons to the spectate spread-sync, both over the `mysea_<owner>` consumer:
SPREAD ON MODAL-CLOSE — the spread only reached spectators piggy-backed on the
first `sea_draw`, so a visitor sat on a stale layout until a card landed. The
owner's SPREAD modal now broadcasts her chosen spread the moment she closes it
(backdrop / Escape / guard-OK) — before any draw:
- new hand-less `sea_spread` event: view `_notify_sea_spread` → consumer relay →
visitor `_applySpread` (re-lays-out `[data-spread]` + re-captions, no hand
touched).
- new POST `/gameboard/my-sea/spread` the modal-close handler calls, guarded by
`_lastSpread` so re-opening + closing without a change doesn't re-broadcast.
When an active row has an EMPTY hand it also persists the spread onto the row
(so a fresh spectator load lands right too) — stays within the "spread locks
at first card" policy; never overwrites a drawn hand's spread.
SEQUENCED AUTO DRAW — AUTO DRAW commits all six cards in ONE POST (navigate-away
safety) → one `sea_draw` carrying the whole hand, so the spectator saw them pop
in at once ("async as intended, but not in sequence"). The visitor's `_applyHand`
now reveals only the freshly-added entries, one per ~420ms tick (in DRAW_ORDER,
first immediately) — a lone manual-draw card still reveals instantly. Already-
shown cards (`_isShown` by slot card-id) are left untouched, so a cumulative
re-broadcast never re-animates.
Coverage:
- ITs: MySeaSpreadBroadcastViewTest — login/405/unknown-spread guards, broadcast
call, empty-hand persist, no-overwrite-of-drawn-spread, broadcast-failure
resilience.
- channels: spectate consumer relays the hand-less `sea_spread` event.
- Live-verified in Firefox: a 3-card hand fills 1 slot synchronously then the
rest after the stagger; user visually confirmed the full deal sequence + the
modal-close spread propagation.
311 gameboard ITs + 7 spectate channels green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9678d187b4 |
my-sea spectate: live spread-sync + owner seat-ring push + visit caption fix — TDD
Three fixes to the my-sea spectator (bud-sea), all flowing over the existing `mysea_<owner>` spectate consumer: VISIT CAPTIONS (.sea-pos-label) — two bugs left every CROWN/COVER/… caption blank on my_sea_visit: - empty-hand: `label_by_position` was built from `latest_draw_slots`, which returns [] when the owner's hand is empty (only a significator placed) — so an owner mid-setup showed no captions, while her OWN my_sea (whose JS seeds labels from the POSITION_LABELS constant) showed them. Now the view pulls captions straight from POSITION_LABELS[spread], drawn-cards-independent. - `--seciUser` typo (used once, never defined) → invalid colour dropped → the labels inherited the body colour, contrasting on some palettes but blending into the felt on others (read as "missing"). → `--secUser`. SPREAD-SYNC — the owner's live draw pushed only the hand, not the spread, so a post-DEL spread switch landed the new cards into the OLD spread's cells (the asymmetry the user hit: owner on desire-obstacle-solution, visitor still laid out as escape-velocity). The spread now rides each `sea_draw` broadcast; `_applySpread` re-sets `data-spread` (CSS keys cell visibility off it), re-captions from a server-sourced POSITION_LABELS json_script, + clears stale fills before `_applyHand` repopulates against the right layout. OWNER-SIDE LIVE SEAT PUSH — the owner's my_sea now subscribes to her own spectate WS for `sea_seats`, so visitors arriving (deposit → 2C-6C) / leaving (BYE) appear without a refresh, same broadcast the spectators get. The visit page's inline `_renderSeats` is hoisted into my-sea-seats.js as the shared `mySeaRenderSeats(seats, myToken)` (+ `mySeaConnectSeatRing`); each page passes its own self-token (owner page passes '' — her 1C isn't --self server-side). Coverage: - ITs: MySeaVisitEmptyHandLabelsTest (captions present + rendered for an empty hand); MySeaLockHandViewTest broadcast test asserts the spread arg; spectate consumer test asserts the hand+spread relay (channels). - Jasmine: 6 new MySeaSeatsSpec cases for mySeaRenderSeats (per-seat rebuild, --self by token, owner-page no-self, no-duplicate re-render, one-shot flare). - Live-verified in Firefox: captions paint khaki on the brown palette; a desire-obstacle-solution sync flips data-spread + relabels Solution/Obstacle/ Desire + hides leave/cover/lay. [[feedback-jsonfield-exclude-sqlite-null]] not implicated; spread map is a plain dict lookup. 304 gameboard ITs + Jasmine green. Code architected by Disco DeDisco <discodedisco@outlook.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
877e0f544a |
my-sea: seated chairs settle to --secUser; owner sees all seated members; gear menu column; visit hex scales — TDD
Four my-sea / my_sea_visit fixes from user feedback. 1. Seated-chair snap-back: `.my-sea-landing .table-seat.seated .fa-chair` forced PERMANENT --terUser + --ninUser glow, out-specifying _room.scss's --secUser settle — so a seated chair eased in (the .seat-just-seated flare) then SNAPPED back to the glow. Removed it; the steady look is now the _room.scss --secUser as spec'd. The viewer's --self marker moves off the chair onto the position label so the chair can rest at --secUser. 2. Owner multi-seat: my_sea.html's landing rendered a hardcoded 1C-only seat loop, so the owner only ever saw herself even after refresh. It now renders the shared `_my_sea_seats(request.user)` ring — owner 1C + present visitors 2C-6C — the same list the spectator + broadcasts use. (Live owner-side push is a follow-on; this fixes the on-refresh case.) 3. Gear sea menu: NVM + BYE laid out in a ROW because the BYE form is display:contents + applets.js force-sets the menu to display:block on open (can't flex the menu itself). Wrap them in the shared `.menu-btns` flex container and override it to a COLUMN in portrait / ROW in landscape (DRY — same container the room/applet menus use). 4. Visit hex scale: my_sea_visit didn't load room.js, so scaleTable() never ran and the table-hex rendered unscaled (unlike the owner's my_sea). Load room.js on the visit page too. 62 gameboard ITs (gear NVM + owner-seat + visit) green. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0693a422d2 |
my-sea: second spectate broadcast — seat ring updates live on deposit / BYE — TDD
Extends the async-witness WS so a visitor joining (deposit) or leaving (BYE) pushes the seat ring to the other watchers — they see members come + go without a refresh, same channel as the live draw. - views.py: `_my_sea_seats(owner)` extracted (owner 1C + present invitees 2C-6C by deposit order, sans per-viewer is_self) — used by BOTH the my_sea_visit render (layers is_self on) AND a new guarded `_notify_sea_seats(owner_id)` broadcast. Fired from my_sea_visit_insert_token (seat taken) + my_sea_visit_leave (seat freed). - consumers.py: MySeaSpectateConsumer gains a `sea_seats` handler. - my_sea_visit.html: the WS client re-renders the `.table-seat` ring from a `sea_seats` message, re-marking the viewer's own --self chair via the embedded seat token + re-firing the one-shot seated glow (localStorage-gated). Tests: +1 channels relay IT (sea_seats received) + 2 view ITs (deposit / BYE each broadcast the ring). Existing multi-seat ITs stay green on the refactored helper. Client re-render needs live 3-party verification on staging. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |