[object Object]

← back to Crazy News Channel

Chaos Index: serialize live announcements, reserve 100% for meltdown, fix standby re-arm

679e2f48a237643ad56c45668458a6ab97862c3c · 2026-09-23 19:53:38 -0700 · Steve Abrams

Cody gate on 0e2f7d8..f961c67, each hole reproduced before fixing:
- announce() was last-write-wins on one shared polite live region; a story
  update and a chaos band change in the same tick landed 0ms apart, so a
  screen reader heard only the last. Now a FIFO drains one message per
  1.2s. Measured gap: 0ms -> 1209ms.
- The +10 klaxon bump could clamp the gauge to 100% ("Post-Journalism
  100%") while PLEASE STAND BY never fired, since standby keys off every
  story concluded. 100% is now reserved for that same condition (99% max
  otherwise). Repro: 100 -> 99 with 1 story unconcluded; true meltdown
  still reads 100 and flashes.
- Standby re-arm within 2.9s didn't restart the animation and the first
  arm's untracked timeout hid the second mid-cycle. Timer is now tracked
  and cleared, animation restarted via reflow. Verified visible after the
  old timer would have fired, hidden on the new one.
Regressions pass: overflow sweep 4 widths x Chrome/WebKit, hash freeze,
Cody fixes 4/4, arcs, attribute escaping, grid race 30/30, reduced motion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011iuURMrsPBwAqMc5LgvFLH

Files touched

Diff

commit 679e2f48a237643ad56c45668458a6ab97862c3c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 19:53:38 2026 -0700

    Chaos Index: serialize live announcements, reserve 100% for meltdown, fix standby re-arm
    
    Cody gate on 0e2f7d8..f961c67, each hole reproduced before fixing:
    - announce() was last-write-wins on one shared polite live region; a story
      update and a chaos band change in the same tick landed 0ms apart, so a
      screen reader heard only the last. Now a FIFO drains one message per
      1.2s. Measured gap: 0ms -> 1209ms.
    - The +10 klaxon bump could clamp the gauge to 100% ("Post-Journalism
      100%") while PLEASE STAND BY never fired, since standby keys off every
      story concluded. 100% is now reserved for that same condition (99% max
      otherwise). Repro: 100 -> 99 with 1 story unconcluded; true meltdown
      still reads 100 and flashes.
    - Standby re-arm within 2.9s didn't restart the animation and the first
      arm's untracked timeout hid the second mid-cycle. Timer is now tracked
      and cleared, animation restarted via reflow. Verified visible after the
      old timer would have fired, hidden on the new one.
    Regressions pass: overflow sweep 4 widths x Chrome/WebKit, hash freeze,
    Cody fixes 4/4, arcs, attribute escaping, grid race 30/30, reduced motion.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_011iuURMrsPBwAqMc5LgvFLH
---
 index.html | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/index.html b/index.html
index 7d94990..0caaa48 100644
--- a/index.html
+++ b/index.html
@@ -1357,10 +1357,26 @@ const els = {
   standbyFlash: document.getElementById("standbyFlash"),
 };
 
+// Serialized: every caller shares one polite live region, and two writes in
+// the same frame collapse into the last one (a story update and a chaos band
+// change in one tick lost the story update). Messages queue and drain one at
+// a time, spaced so a screen reader finishes each before the next lands.
+const announceQueue = [];
+let announceDraining = false;
 function announce(text) {
+  announceQueue.push(text);
+  if (!announceDraining) drainAnnounce();
+}
+function drainAnnounce() {
+  const next = announceQueue.shift();
+  if (next === undefined) { announceDraining = false; return; }
+  announceDraining = true;
   els.srLive.textContent = "";
   // re-set on next frame so repeated identical strings still get announced
-  requestAnimationFrame(() => { els.srLive.textContent = text; });
+  requestAnimationFrame(() => {
+    els.srLive.textContent = next;
+    setTimeout(drainAnnounce, 1200);
+  });
 }
 
 function visibleStories() {
@@ -1565,7 +1581,11 @@ function computeChaos() {
   // BREAKING klaxon integration: an active klaxon reads as its own bump on
   // the escalation gauge, independent of story stages.
   if (state.breaking) pct += 10;
-  return Math.max(0, Math.min(100, pct));
+  // 100% is reserved for true meltdown (every story concluded), the same
+  // condition that fires PLEASE STAND BY, so the gauge never reads 100%
+  // while the standby card stays silent.
+  const allConcluded = STORIES.every((s) => s.stageIndex === s.stages.length - 1);
+  return Math.max(0, Math.min(allConcluded ? 100 : 99, pct));
 }
 
 function chaosBandFor(pct) {
@@ -1612,9 +1632,13 @@ function updateChaosUI() {
   const allConcluded = STORIES.length > 0 && STORIES.every((s) => s.stageIndex === s.stages.length - 1);
   if (allConcluded && !state.standbyFlashShown && !state.reducedMotion) {
     state.standbyFlashShown = true;
+    clearTimeout(state.standbyTimer);
     els.standbyFlash.hidden = false;
+    // restart the animation even if a previous arm hasn't finished
+    els.standbyFlash.classList.remove("flashing");
+    void els.standbyFlash.offsetWidth;
     els.standbyFlash.classList.add("flashing");
-    setTimeout(() => {
+    state.standbyTimer = setTimeout(() => {
       els.standbyFlash.hidden = true;
       els.standbyFlash.classList.remove("flashing");
     }, 2900);

← f961c67 Fix real horizontal overflow from the chaos-tier-3 broadcast  ·  back to Crazy News Channel  ·  auto-data-snapshot: 2026-09-23T20:04:48 (1 data files) — scr 5d6d91c →