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>
This commit is contained in:
@@ -48,9 +48,12 @@
|
||||
? '<span class="atlas-row-who">' + escapeHtml(r.whoText) + '</span>'
|
||||
: '';
|
||||
var time = r.timeHtml || '';
|
||||
// Carry the source row's struck state (a retracted/redacted SCROLL log)
|
||||
// so the merged copy reads the same strikethrough it does in SCROLL.
|
||||
var bodyCls = 'atlas-row-body' + (r.struck ? ' struck' : '');
|
||||
return '<div class="atlas-row atlas-row--' + r.source + '" data-source="'
|
||||
+ r.source + '">' + who
|
||||
+ '<span class="atlas-row-body">' + r.bodyHtml + '</span>' + time
|
||||
+ '<span class="' + bodyCls + '">' + r.bodyHtml + '</span>' + time
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
@@ -317,31 +320,56 @@
|
||||
return t ? t.outerHTML : '';
|
||||
}
|
||||
|
||||
// Which sources the ATLAS gear has enabled (its source checkboxes map
|
||||
// each reelhouse view → a row source: scroll→provenance, post→post).
|
||||
// Defaults to both when the gear form isn't on the page yet.
|
||||
function atlasSources() {
|
||||
var form = document.getElementById('id_atlas_source_form');
|
||||
if (!form) return { provenance: true, post: true };
|
||||
var VIEW_SOURCE = { scroll: 'provenance', post: 'post' };
|
||||
var on = {};
|
||||
form.querySelectorAll('input[name="views"]:checked').forEach(function (cb) {
|
||||
var src = VIEW_SOURCE[cb.value];
|
||||
if (src) on[src] = true;
|
||||
});
|
||||
return on;
|
||||
}
|
||||
|
||||
function buildAtlasFeed() {
|
||||
var atlas = document.getElementById('id_room_atlas');
|
||||
if (!atlas) return;
|
||||
var srcOn = atlasSources();
|
||||
var rows = [];
|
||||
document.querySelectorAll('.room-view--scroll .drama-event').forEach(function (ev) {
|
||||
var body = ev.querySelector('.drama-event-body');
|
||||
rows.push({
|
||||
source: 'provenance',
|
||||
ts: nodeTime(ev, '.drama-event-time'),
|
||||
whoText: '',
|
||||
bodyHtml: body ? body.innerHTML : ev.innerHTML,
|
||||
timeHtml: nodeTimeHtml(ev, '.drama-event-time'),
|
||||
if (srcOn.provenance) {
|
||||
document.querySelectorAll('.room-view--scroll .drama-event').forEach(function (ev) {
|
||||
// Honour the SCROLL redact/frame filter: a log hidden there
|
||||
// (display:none) must not surface in the merged ATLAS either.
|
||||
if (getComputedStyle(ev).display === 'none') return;
|
||||
var body = ev.querySelector('.drama-event-body');
|
||||
rows.push({
|
||||
source: 'provenance',
|
||||
ts: nodeTime(ev, '.drama-event-time'),
|
||||
whoText: '',
|
||||
bodyHtml: body ? body.innerHTML : ev.innerHTML,
|
||||
// Struck (retracted/redacted) carries over from SCROLL.
|
||||
struck: !!(body && body.classList.contains('struck')),
|
||||
timeHtml: nodeTimeHtml(ev, '.drama-event-time'),
|
||||
});
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('.room-view--post #id_post_table .post-line').forEach(function (li) {
|
||||
var who = li.querySelector('.post-line-author');
|
||||
var txt = li.querySelector('.post-line-text');
|
||||
rows.push({
|
||||
source: 'post',
|
||||
ts: nodeTime(li, '.post-line-time'),
|
||||
whoText: who ? who.textContent.trim() : '',
|
||||
bodyHtml: txt ? txt.innerHTML : '',
|
||||
timeHtml: nodeTimeHtml(li, '.post-line-time'),
|
||||
}
|
||||
if (srcOn.post) {
|
||||
document.querySelectorAll('.room-view--post #id_post_table .post-line').forEach(function (li) {
|
||||
var who = li.querySelector('.post-line-author');
|
||||
var txt = li.querySelector('.post-line-text');
|
||||
rows.push({
|
||||
source: 'post',
|
||||
ts: nodeTime(li, '.post-line-time'),
|
||||
whoText: who ? who.textContent.trim() : '',
|
||||
bodyHtml: txt ? txt.innerHTML : '',
|
||||
timeHtml: nodeTimeHtml(li, '.post-line-time'),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!rows.length) {
|
||||
atlas.innerHTML =
|
||||
'<p class="event-empty"><small>The atlas gathers . . .</small></p>';
|
||||
|
||||
@@ -52,6 +52,12 @@ class GameViewsCarouselTest(FunctionalTest):
|
||||
data={"token_type": "carte", "token_display": "Carte Blanche",
|
||||
"slot_number": 1, "renewal_days": 7},
|
||||
)
|
||||
# A retracted SIG_READY (struck → data-label="redact") so the Atlas can
|
||||
# be checked for SCROLL-style strikethrough + redact-filter coupling.
|
||||
GameEvent.objects.create(
|
||||
room=self.room, actor=self.viewer, verb=GameEvent.SIG_READY,
|
||||
data={"retracted": True, "card_name": "The Nomad"},
|
||||
)
|
||||
self.room.table_status = Room.ROLE_SELECT
|
||||
self.room.gate_status = Room.OPEN
|
||||
self.room.save()
|
||||
@@ -213,6 +219,45 @@ class GameViewsCarouselTest(FunctionalTest):
|
||||
self.assertTrue(atlas.find_elements(
|
||||
By.CSS_SELECTOR, "[data-source='post']"))
|
||||
|
||||
def test_atlas_provenance_rows_carry_scroll_strikethrough(self):
|
||||
"""A retracted/redacted SCROLL log reads the same strikethrough in the
|
||||
merged ATLAS as it does in SCROLL (`.atlas-row-body.struck`)."""
|
||||
self._open()
|
||||
self._scroll_to_views()
|
||||
self._click_icon("atlas")
|
||||
self.wait_for(lambda: self.assertTrue(
|
||||
self._in_viewport(".room-view[data-view='atlas']")))
|
||||
atlas = self.browser.find_element(
|
||||
By.CSS_SELECTOR, ".room-view[data-view='atlas']")
|
||||
self.assertTrue(atlas.find_elements(
|
||||
By.CSS_SELECTOR, ".atlas-row-body.struck"))
|
||||
|
||||
def test_atlas_omits_redact_logs_filtered_out_on_scroll(self):
|
||||
"""Unchecking Redact in the SCROLL gear filter hides the struck logs in
|
||||
the feed — and they must not surface in the merged ATLAS either."""
|
||||
self._open()
|
||||
self._scroll_to_views() # lands on SCROLL → gear shows the log filter
|
||||
gear = self.browser.find_element(
|
||||
By.CSS_SELECTOR, ".gear-btn[data-menu-target='id_room_menu']")
|
||||
self.browser.execute_script("arguments[0].click();", gear)
|
||||
self.wait_for(lambda: self.assertTrue(
|
||||
self.browser.find_element(By.ID, "id_scroll_filter_form").is_displayed()))
|
||||
redact = self.browser.find_element(
|
||||
By.CSS_SELECTOR, "#id_scroll_filter_form input[value='redact']")
|
||||
if redact.is_selected():
|
||||
self.browser.execute_script("arguments[0].click();", redact)
|
||||
self.browser.execute_script("arguments[0].click();", self.browser.find_element(
|
||||
By.CSS_SELECTOR, "#id_scroll_filter_form button[type='submit']"))
|
||||
|
||||
self._click_icon("atlas")
|
||||
self.wait_for(lambda: self.assertTrue(
|
||||
self._in_viewport(".room-view[data-view='atlas']")))
|
||||
atlas = self.browser.find_element(
|
||||
By.CSS_SELECTOR, ".room-view[data-view='atlas']")
|
||||
# Redact logs (the only struck rows) were filtered out → none in ATLAS.
|
||||
self.assertFalse(atlas.find_elements(
|
||||
By.CSS_SELECTOR, ".atlas-row-body.struck"))
|
||||
|
||||
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
|
||||
|
||||
@@ -70,6 +70,15 @@ describe("RoomViews atlas row rendering", () => {
|
||||
expect(row).toContain("<em>safe</em>"); // body trusted (server-escaped)
|
||||
expect(row).toContain("<script>"); // who escaped
|
||||
});
|
||||
|
||||
it("marks struck (redacted) provenance rows so they read line-through like SCROLL", () => {
|
||||
const struck = window.RoomViews.renderAtlasRow(
|
||||
{ source: "provenance", bodyHtml: "withdraws yos Carte Blanche", whoText: "", timeHtml: "", struck: true });
|
||||
expect(struck).toContain("struck");
|
||||
const plain = window.RoomViews.renderAtlasRow(
|
||||
{ source: "provenance", bodyHtml: "deposits a Carte Blanche", whoText: "", timeHtml: "" });
|
||||
expect(plain).not.toContain("struck");
|
||||
});
|
||||
});
|
||||
|
||||
// The Text sub-btn swipe machine drives the reelhouse to GAME POST. Its from-
|
||||
|
||||
@@ -247,7 +247,14 @@ html.sea-open #id_aperture_fill {
|
||||
padding-inline-start: 0.5rem;
|
||||
|
||||
.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; }
|
||||
.atlas-row-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
// Struck (retracted/redacted) provenance rows read the same
|
||||
// strikethrough they do in SCROLL (.drama-event-body.struck).
|
||||
&.struck { text-decoration: line-through; opacity: 0.5; }
|
||||
}
|
||||
// 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
|
||||
|
||||
@@ -70,6 +70,15 @@ describe("RoomViews atlas row rendering", () => {
|
||||
expect(row).toContain("<em>safe</em>"); // body trusted (server-escaped)
|
||||
expect(row).toContain("<script>"); // who escaped
|
||||
});
|
||||
|
||||
it("marks struck (redacted) provenance rows so they read line-through like SCROLL", () => {
|
||||
const struck = window.RoomViews.renderAtlasRow(
|
||||
{ source: "provenance", bodyHtml: "withdraws yos Carte Blanche", whoText: "", timeHtml: "", struck: true });
|
||||
expect(struck).toContain("struck");
|
||||
const plain = window.RoomViews.renderAtlasRow(
|
||||
{ source: "provenance", bodyHtml: "deposits a Carte Blanche", whoText: "", timeHtml: "" });
|
||||
expect(plain).not.toContain("struck");
|
||||
});
|
||||
});
|
||||
|
||||
// The Text sub-btn swipe machine drives the reelhouse to GAME POST. Its from-
|
||||
|
||||
Reference in New Issue
Block a user