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>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// Game-views carousel — the "reelhouse" (the fivefold applet-scroll carousel)
|
||||
// in the table-hex aperture's 2nd vertical snap pane.
|
||||
// Five horizontal views — ATLAS | SCROLL | POST | CHAT | PULSE — reached by
|
||||
// Five horizontal views — ATLAS | SCROLL | YARN | POST | PULSE — reached by
|
||||
// scrolling DOWN from the hex, landing on SCROLL (the 2nd). This module owns
|
||||
// the HORIZONTAL axis; room-scroll.js still owns the vertical hex<->views
|
||||
// `.is-scroll` title toggle + the feed gear/filter. Responsibilities:
|
||||
@@ -18,7 +18,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var VIEW_ORDER = ['atlas', 'scroll', 'post', 'chat', 'pulse'];
|
||||
var VIEW_ORDER = ['atlas', 'scroll', 'yarn', 'post', 'pulse'];
|
||||
var DEFAULT_VIEW = 'scroll';
|
||||
|
||||
// ── pure helpers (exposed for Jasmine) ─────────────────────────────────
|
||||
|
||||
@@ -3438,20 +3438,21 @@ class RoomViewsCarouselTest(TestCase):
|
||||
def test_renders_five_view_panes_in_order(self):
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertIn("id_room_views", content)
|
||||
for view in ("atlas", "scroll", "post", "chat", "pulse"):
|
||||
for view in ("atlas", "scroll", "yarn", "post", "pulse"):
|
||||
self.assertIn(f'data-view="{view}"', content)
|
||||
# Order: atlas precedes scroll precedes post precedes chat precedes pulse.
|
||||
# Order: atlas precedes scroll precedes yarn precedes post precedes pulse.
|
||||
positions = [content.index(f'room-view--{v}')
|
||||
for v in ("atlas", "scroll", "post", "chat", "pulse")]
|
||||
for v in ("atlas", "scroll", "yarn", "post", "pulse")]
|
||||
self.assertEqual(positions, sorted(positions))
|
||||
|
||||
def test_renders_root_level_icon_strip(self):
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertIn("id_room_views_strip", content)
|
||||
for view in ("atlas", "scroll", "post", "chat", "pulse"):
|
||||
for view in ("atlas", "scroll", "yarn", "post", "pulse"):
|
||||
self.assertIn(f'data-view="{view}"', content)
|
||||
self.assertIn("fa-scroll", content)
|
||||
self.assertIn("fa-book-atlas", content)
|
||||
self.assertIn("fa-route", content)
|
||||
|
||||
def test_scroll_view_still_wraps_the_provenance_feed(self):
|
||||
content = self.client.get(self.url).content.decode()
|
||||
@@ -3472,7 +3473,7 @@ class RoomViewsCarouselTest(TestCase):
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertIn("opening move", content)
|
||||
|
||||
def test_chat_and_pulse_render_stubs(self):
|
||||
def test_yarn_and_pulse_render_stubs(self):
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertIn("room-view-stub", content)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from apps.drama.models import GameEvent
|
||||
from apps.epic.models import Room
|
||||
from apps.lyric.models import User
|
||||
|
||||
VIEW_ORDER = ["atlas", "scroll", "post", "chat", "pulse"]
|
||||
VIEW_ORDER = ["atlas", "scroll", "yarn", "post", "pulse"]
|
||||
|
||||
|
||||
class GameViewsCarouselTest(FunctionalTest):
|
||||
@@ -91,7 +91,7 @@ class GameViewsCarouselTest(FunctionalTest):
|
||||
# ── tests ────────────────────────────────────────────────────────────
|
||||
def test_icon_strip_hidden_at_hex_shown_in_views_with_scroll_active(self):
|
||||
"""At the table-hex the strip is hidden; scrolling down reveals it with
|
||||
five icons in order [atlas, scroll, post, chat, pulse] and SCROLL (2nd)
|
||||
five icons in order [atlas, scroll, yarn, post, pulse] and SCROLL (2nd)
|
||||
active + glowing."""
|
||||
self._open()
|
||||
self.assertFalse(
|
||||
@@ -134,11 +134,11 @@ class GameViewsCarouselTest(FunctionalTest):
|
||||
self._open()
|
||||
self._scroll_to_views()
|
||||
self.assertEqual(self._active_view(), "scroll")
|
||||
# Horizontal wheel right → next view (post).
|
||||
# Horizontal wheel right → next view in line (yarn, between scroll & post).
|
||||
self.browser.execute_script(
|
||||
"document.querySelector('#id_room_views').dispatchEvent("
|
||||
"new WheelEvent('wheel', {deltaX: 120, deltaY: 0, bubbles: true}));")
|
||||
self.wait_for(lambda: self.assertEqual(self._active_view(), "post"))
|
||||
self.wait_for(lambda: self.assertEqual(self._active_view(), "yarn"))
|
||||
|
||||
def test_text_btn_from_hex_swipes_down_to_the_reelhouse(self):
|
||||
"""The burger fan's #id_text_btn (fa-keyboard) is active on the table;
|
||||
@@ -213,8 +213,8 @@ class GameViewsCarouselTest(FunctionalTest):
|
||||
self.assertTrue(atlas.find_elements(
|
||||
By.CSS_SELECTOR, "[data-source='post']"))
|
||||
|
||||
def test_chat_and_pulse_render_as_stubs(self):
|
||||
"""CHAT (fa-comments) + PULSE (fa-chart-pie) are stub views this sprint —
|
||||
def test_yarn_and_pulse_render_as_stubs(self):
|
||||
"""YARN (fa-route) + PULSE (fa-chart-pie) are stub views this sprint —
|
||||
each renders a placeholder, no backing model yet. The watermark icon
|
||||
rests ABOVE the [Feature forthcoming] label (the shared partial centres
|
||||
itself absolutely, so without the stub override it would overlap the
|
||||
@@ -226,7 +226,7 @@ class GameViewsCarouselTest(FunctionalTest):
|
||||
return self.browser.execute_script(
|
||||
"return document.querySelector(arguments[0]).getBoundingClientRect();", css)
|
||||
|
||||
for view in ("chat", "pulse"):
|
||||
for view in ("yarn", "pulse"):
|
||||
self._click_icon(view)
|
||||
self.wait_for(lambda v=view: self.assertTrue(
|
||||
self._in_viewport(f".room-view[data-view='{v}']")))
|
||||
|
||||
@@ -79,7 +79,7 @@ describe("RoomViews atlas row rendering", () => {
|
||||
// scrolling. (The descent beat itself + plain goToView are covered by the FT.)
|
||||
describe("RoomViews swipe machine", () => {
|
||||
let aperture, viewsEl, strip, textBtn;
|
||||
const VIEWS = ["atlas", "scroll", "post", "chat", "pulse"];
|
||||
const VIEWS = ["atlas", "scroll", "yarn", "post", "pulse"];
|
||||
|
||||
function tag(name, attrs) {
|
||||
const n = document.createElement(name);
|
||||
|
||||
@@ -410,12 +410,12 @@ body {
|
||||
.gr-word--base { transform: translateY(-100%); } // up & out
|
||||
.gr-views-reel { transform: translateY(0); } // rises in
|
||||
}
|
||||
// Horizontal cell (VIEW_ORDER atlas|scroll|post|chat|pulse) —
|
||||
// Horizontal cell (VIEW_ORDER atlas|scroll|yarn|post|pulse) —
|
||||
// keyed on the active view ALONE so it holds at the hex too.
|
||||
&[data-active-view="atlas"] .gr-views-track { transform: translateX(0); }
|
||||
&[data-active-view="scroll"] .gr-views-track { transform: translateX(-100%); }
|
||||
&[data-active-view="post"] .gr-views-track { transform: translateX(-200%); }
|
||||
&[data-active-view="chat"] .gr-views-track { transform: translateX(-300%); }
|
||||
&[data-active-view="yarn"] .gr-views-track { transform: translateX(-200%); }
|
||||
&[data-active-view="post"] .gr-views-track { transform: translateX(-300%); }
|
||||
&[data-active-view="pulse"] .gr-views-track { transform: translateX(-400%); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,13 +238,30 @@ html.sea-open #id_aperture_fill {
|
||||
flex-direction: column;
|
||||
|
||||
.atlas-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.4rem;
|
||||
padding: 0.3rem 0;
|
||||
font-size: 0.9rem;
|
||||
border-inline-start: 2px solid transparent;
|
||||
padding-inline-start: 0.5rem;
|
||||
|
||||
.atlas-row-time { font-size: 0.7rem; opacity: 0.5; margin-inline-start: 0.4rem; }
|
||||
.atlas-row-who { font-weight: bold; color: rgba(var(--quaUser), 1); margin-inline-end: 0.3rem; }
|
||||
.atlas-row-who { font-weight: bold; color: rgba(var(--quaUser), 1); flex-shrink: 0; }
|
||||
.atlas-row-body { flex: 1; min-width: 0; overflow-wrap: anywhere; }
|
||||
// The merged rows carry the ORIGINAL <time> from their source row
|
||||
// (.drama-event-time from SCROLL, .post-line-time from POST). Those
|
||||
// source rules are scoped to the feed/thread, so restate the shared
|
||||
// small / dim / right-aligned look here so each timestamp reads the
|
||||
// same as it does in the view it came from.
|
||||
.drama-event-time,
|
||||
.post-line-time {
|
||||
flex-shrink: 0;
|
||||
margin-inline-start: auto;
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.5;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
// Distinct per-source accent (border tint) — the styling hook the
|
||||
// contract reserves for end-of-sprint polish.
|
||||
|
||||
@@ -79,7 +79,7 @@ describe("RoomViews atlas row rendering", () => {
|
||||
// scrolling. (The descent beat itself + plain goToView are covered by the FT.)
|
||||
describe("RoomViews swipe machine", () => {
|
||||
let aperture, viewsEl, strip, textBtn;
|
||||
const VIEWS = ["atlas", "scroll", "post", "chat", "pulse"];
|
||||
const VIEWS = ["atlas", "scroll", "yarn", "post", "pulse"];
|
||||
|
||||
function tag(name, attrs) {
|
||||
const n = document.createElement(name);
|
||||
|
||||
@@ -25,6 +25,15 @@
|
||||
{% include "apps/gameboard/_partials/_room_scroll.html" %}
|
||||
</div>
|
||||
|
||||
{# YARN — stub view this sprint (no backing model yet). The route icon + #}
|
||||
{# the shared [Feature forthcoming] partial stand in. #}
|
||||
<div class="room-view room-view--yarn" data-view="yarn">
|
||||
<div class="applet-scroll room-view-card">
|
||||
<h2>{{ room.name }}</h2>
|
||||
<div class="room-view-stub"><i class="fa-solid fa-route"></i>{% include "core/_partials/_forthcoming.html" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# POST — the room-scoped game-table thread. Same #id_post_table + composer #}
|
||||
{# markup as post.html (shared _post_line.html), but the composer appends #}
|
||||
{# via the epic:room_post AJAX endpoint (room-views.js) so the carousel #}
|
||||
@@ -53,14 +62,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# CHAT + PULSE — stub views this sprint (no backing model yet). The #}
|
||||
{# identity icon + the shared [Feature forthcoming] partial stand in. #}
|
||||
<div class="room-view room-view--chat" data-view="chat">
|
||||
<div class="applet-scroll room-view-card">
|
||||
<h2>{{ room.name }}</h2>
|
||||
<div class="room-view-stub"><i class="fa-solid fa-comments"></i>{% include "core/_partials/_forthcoming.html" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{# PULSE — stub view this sprint (no backing model yet). The chart icon + #}
|
||||
{# the shared [Feature forthcoming] partial stand in. #}
|
||||
<div class="room-view room-view--pulse" data-view="pulse">
|
||||
<div class="applet-scroll room-view-card">
|
||||
<h2>{{ room.name }}</h2>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
{# the same reason the position strip + tooltip portals live at room-page root. #}
|
||||
{# Hidden at the hex; room-views.js adds `.is-visible` while the views pane is #}
|
||||
{# on screen, mirrors the active view's glow, and snaps the carousel on click. #}
|
||||
{# Five icons L→R: ATLAS · SCROLL (default) · POST · CHAT · PULSE. #}
|
||||
{# Five icons L→R: ATLAS · SCROLL (default) · YARN · POST · PULSE. #}
|
||||
<div id="id_room_views_strip" class="room-views-strip" aria-hidden="true">
|
||||
<button type="button" class="room-view-icon" data-view="atlas" aria-label="Atlas"><i class="fa-solid fa-book-atlas"></i></button>
|
||||
<button type="button" class="room-view-icon is-active" data-view="scroll" aria-label="Scroll"><i class="fa-solid fa-scroll"></i></button>
|
||||
<button type="button" class="room-view-icon" data-view="yarn" aria-label="Yarn"><i class="fa-solid fa-route"></i></button>
|
||||
<button type="button" class="room-view-icon" data-view="post" aria-label="Post"><i class="fa-solid fa-keyboard"></i></button>
|
||||
<button type="button" class="room-view-icon" data-view="chat" aria-label="Chat"><i class="fa-solid fa-comments"></i></button>
|
||||
<button type="button" class="room-view-icon" data-view="pulse" aria-label="Pulse"><i class="fa-solid fa-chart-pie"></i></button>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
all times, including at the hex. Result: hex⇄views is a pure vertical reel
|
||||
landing on wherever you left off; lateral nav is a pure horizontal slide
|
||||
(old word out one side, new in from the other). base.html splits `.gr-word`.
|
||||
{% endcomment %}{% block header_text %}<span>Game</span>{% if room.table_status %}<span class="gr-swap" data-letters-split="1"><span class="gr-word gr-word--base">room</span><span class="gr-views-reel"><span class="gr-views-track"><span class="gr-word gr-word--atlas">atlas</span><span class="gr-word gr-word--scroll">scroll</span><span class="gr-word gr-word--post">post</span><span class="gr-word gr-word--chat">chat</span><span class="gr-word gr-word--pulse">pulse</span></span></span></span>{% else %}<span>room</span>{% endif %}{% endblock header_text %}
|
||||
{% endcomment %}{% block header_text %}<span>Game</span>{% if room.table_status %}<span class="gr-swap" data-letters-split="1"><span class="gr-word gr-word--base">room</span><span class="gr-views-reel"><span class="gr-views-track"><span class="gr-word gr-word--atlas">atlas</span><span class="gr-word gr-word--scroll">scroll</span><span class="gr-word gr-word--yarn">yarn</span><span class="gr-word gr-word--post">post</span><span class="gr-word gr-word--pulse">pulse</span></span></span></span>{% else %}<span>room</span>{% endif %}{% endblock header_text %}
|
||||
|
||||
{% block content %}
|
||||
<div class="room-page" data-room-id="{{ room.id }}"
|
||||
|
||||
Reference in New Issue
Block a user