编辑公共栏 / Edit board
'
diff --git a/tests/test_ascii_art.py b/tests/test_ascii_art.py
index 927870f9..4b80bd2c 100644
--- a/tests/test_ascii_art.py
+++ b/tests/test_ascii_art.py
@@ -35,3 +35,32 @@ async def test_machine_home_and_public_board_have_no_visual_payload(signed_brows
board = await http.get('/_public-board')
assert set(board.json()) == set(projection(default()))
assert 'ascii' not in board.json()
+
+
+def test_home_art_has_ordered_copy_and_a_stable_viewport():
+ page = html()
+ assert page.index('class="signal-copy"') < page.index('class="signal-stage"')
+ assert page.index('class="signal-meta"') < page.index('data-content')
+ assert page.count('class="signal-viewport"') == 1
+ assert 'data-ascii-canvas hidden aria-hidden="true"' in page
+ for piece, label in (
+ ('galaxy', 'Galaxy / 星系'),
+ ('flow', 'Flow / 信号流'),
+ ('aurora', 'Aurora / 极光'),
+ ('community', 'Shared SVG / 公共作品'),
+ ):
+ assert f'data-ascii-piece="{piece}"' in page
+ assert f'aria-label="{label}"' in page
+ assert page.count('class="ascii-piece-name"') == 4
+ assert page.count('class="ascii-piece-label"') == 4
+
+
+def test_restyled_home_keeps_shared_text_and_svg_escaped():
+ board = projection(default())
+ board['text'] = '

字符仍然是文本'
+ board['svg'] = '
'
+ page = html(board)
+ assert board['text'] not in page
+ assert '<img src=x onerror=alert(1)> 字符仍然是文本' in page
+ assert board['svg'] not in page
+ assert 'data-ascii-default="false"' in page
diff --git a/vendor/ascii/msg-entry.ts b/vendor/ascii/msg-entry.ts
index 71bf130b..4131e1a6 100644
--- a/vendor/ascii/msg-entry.ts
+++ b/vendor/ascii/msg-entry.ts
@@ -64,6 +64,10 @@ function attach(surface: HTMLElement) {
if ((event as CustomEvent).detail.svgChanged) choose('community');
});
if (pause) new MutationObserver(play).observe(pause, {attributes: true, attributeFilter: ['aria-pressed']});
+ // A paused canvas still needs a new ink color when the reader changes theme.
+ // Restarting the renderer preserves elapsed scene time and its pause/motion gates.
+ new MutationObserver(play).observe(document.documentElement, {attributes: true, attributeFilter: ['data-theme']});
+ matchMedia('(prefers-color-scheme: dark)').addEventListener('change', play);
surface.dataset.asciiReady = 'true';
if (controls) controls.hidden = false;
choose(name);