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>
This commit is contained in:
@@ -3288,3 +3288,69 @@ class ExpireLapsedRoomSeatsCommandTest(TestCase):
|
||||
call_command("expire_lapsed_room_seats")
|
||||
slot.refresh_from_db()
|
||||
self.assertEqual(slot.status, GateSlot.FILLED)
|
||||
|
||||
|
||||
class RoomScrollOfEventsTest(TestCase):
|
||||
"""The table-hex aperture gains a 2nd scroll-snap section: scrolling down
|
||||
past the hex reveals the room's provenance feed (the same GameEvent stream
|
||||
the Billscroll page shows). Available from Role Select onwards — the gate
|
||||
phase (no `table_status`) shows no scroll. Mirrors my_sky's wheel<->form
|
||||
binary scroll-snap toggle; built to DRY-template onto my_sea next.
|
||||
|
||||
The shared row list comes from `core/_partials/_scroll.html` (the same
|
||||
partial the Billscroll page uses), so the feed renders identically here."""
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(email="founder@test.io", username="disco")
|
||||
self.client.force_login(self.user)
|
||||
self.room = Room.objects.create(
|
||||
name="Whataburgher", owner=self.user,
|
||||
gate_status=Room.OPEN, table_status=Room.ROLE_SELECT,
|
||||
)
|
||||
TableSeat.objects.create(
|
||||
room=self.room, gamer=self.user, slot_number=1, role="PC",
|
||||
)
|
||||
# A welcome line (system-authored) + a deposit line — feed content.
|
||||
GameEvent.objects.create(room=self.room, verb=GameEvent.ROOM_CREATED)
|
||||
GameEvent.objects.create(
|
||||
room=self.room, actor=self.user, verb=GameEvent.SLOT_FILLED,
|
||||
data={"token_type": "carte", "token_display": "Carte Blanche",
|
||||
"slot_number": 1, "renewal_days": 7},
|
||||
)
|
||||
self.url = reverse("epic:room", kwargs={"room_id": self.room.id})
|
||||
|
||||
def test_aperture_wraps_hex_and_scroll_panes(self):
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertIn("room-aperture", content)
|
||||
self.assertIn("room-hex-pane", content)
|
||||
self.assertIn("room-scroll-pane", content)
|
||||
|
||||
def test_aperture_is_scrollable_from_role_select(self):
|
||||
# `is-scrollable` engages the binary scroll-snap (2 panes present).
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertIn("room-aperture is-scrollable", content)
|
||||
|
||||
def test_scroll_pane_renders_the_event_feed(self):
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertIn("id_drama_scroll", content)
|
||||
self.assertIn("Welcome to Whataburgher!", content)
|
||||
self.assertIn("deposits a Carte Blanche for slot 1", content)
|
||||
|
||||
def test_scroll_feed_scoped_to_this_room(self):
|
||||
other = Room.objects.create(
|
||||
name="Elsewhere", owner=self.user,
|
||||
gate_status=Room.OPEN, table_status=Room.ROLE_SELECT,
|
||||
)
|
||||
GameEvent.objects.create(room=other, verb=GameEvent.ROOM_CREATED)
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertNotIn("Welcome to Elsewhere!", content)
|
||||
|
||||
def test_no_scroll_pane_in_gate_phase(self):
|
||||
"""The gate phase (table_status unset) shows no scroll — the feed is
|
||||
reachable only from Role Select onwards."""
|
||||
gate_room = Room.objects.create(name="Gatehouse", owner=self.user)
|
||||
content = self.client.get(
|
||||
reverse("epic:gatekeeper", args=[gate_room.id])
|
||||
).content.decode()
|
||||
self.assertNotIn("room-scroll-pane", content)
|
||||
self.assertNotIn("is-scrollable", content)
|
||||
|
||||
Reference in New Issue
Block a user