← back to Crazy News Channel
Add screenrecord DEBUG-REPORT: confirms real story-grid DOM-detach race, clears 3 harness false positives, verifies Escape-priority both-open case is clean
8e9575f06aff69337e61634e8f115071afb54f82 · 2026-09-23 18:21:51 -0700 · Steve Abrams
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011iuURMrsPBwAqMc5LgvFLH
Files touched
A screenrecord/DEBUG-REPORT.mdA screenrecord/rec/escape-priority-verify/escape-priority-verify.mp4M screenrecord/rec/run2/run2.mp4A screenrecord/rec/run3/run3.mp4A screenrecord/rec/run4/run4.mp4
Diff
commit 8e9575f06aff69337e61634e8f115071afb54f82
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 23 18:21:51 2026 -0700
Add screenrecord DEBUG-REPORT: confirms real story-grid DOM-detach race, clears 3 harness false positives, verifies Escape-priority both-open case is clean
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011iuURMrsPBwAqMc5LgvFLH
---
screenrecord/DEBUG-REPORT.md | 170 +++++++++++++++++++++
.../escape-priority-verify.mp4 | Bin 0 -> 229461 bytes
screenrecord/rec/run2/run2.mp4 | Bin 1310768 -> 8314098 bytes
screenrecord/rec/run3/run3.mp4 | Bin 0 -> 12752308 bytes
screenrecord/rec/run4/run4.mp4 | Bin 0 -> 11537007 bytes
5 files changed, 170 insertions(+)
diff --git a/screenrecord/DEBUG-REPORT.md b/screenrecord/DEBUG-REPORT.md
new file mode 100644
index 0000000..59fe5fb
--- /dev/null
+++ b/screenrecord/DEBUG-REPORT.md
@@ -0,0 +1,170 @@
+# PANDEMONIUM-24 (`crazy-news-channel/index.html`) — screenrecord debug report
+
+Target: `file:///Users/macstudio3/Projects/crazy-news-channel/index.html`
+Method: Playwright chromium, 5 runs × distinct click-order combinations, video-recorded,
+every action appended to `screenrecord/debug-log.jsonl`. Two full harness executions were
+run (see "Harness notes" below) plus one targeted follow-up check; all three are preserved
+in the append-only log.
+
+## Highest-leverage fix (do this first)
+
+**`renderStories()` does a full `#storyGrid.innerHTML` rebuild on every escalation tick,
+and the tick loop fires this on almost every one of the ~40 staggered background stories
+advancing — which detaches a story card's `<a data-open-id>` out from under an in-flight
+click.** Confirmed twice, independently, across two separate full 5-run executions
+(`run3`, both passes), as a hard `Element is not attached to the DOM` exception — not a
+timeout, not ambiguous:
+
+```
+run3 (pass A): "open story 'unit4' (for escape test)" -> Element is not attached to the DOM
+run3 (pass B): "open story card 'sci-migratory-solar-birds'" -> Element is not attached to the DOM
+```
+
+The app already knows this pattern is bad: `updateLiveUpdateBlock()` (used for the *article*
+view) is explicitly written to patch one block in place "so no full re-render, no stolen
+focus/scroll position" — but `renderStories()` for the *grid* view got no equivalent
+treatment, even though the grid is the surface with the most clickable targets and the most
+frequent churn (any 1 of 40 stories advancing rebuilds the whole grid). A real visitor whose
+click lands during one of these rebuilds can have the click silently swallowed or, per this
+evidence, throw the browser-native "node is no longer in the tree" condition. Fix: diff-patch
+individual `.story-card` nodes on escalation (or debounce/batch `renderStories()` calls),
+the same way the article's live-update block already does.
+
+Secondary, softer evidence of the same race: a cluster of `scrollIntoViewIfNeeded` timeouts
+on story-card links throughout runs 0/1/2/4 that did **not** turn out to be this bug on
+closer inspection (see below) — but the mechanism (grid replaced out from under a pending
+locator) is the same failure class, just resolved by Playwright's retry before hard-erroring.
+
+## What did NOT turn out to be an app defect (important for an honest report)
+
+Chasing down every apparent failure, the large majority of `ok:false` rows across both
+passes trace to **three harness bugs in my own test script**, not the application. Naming
+them explicitly so they aren't miscounted as PANDEMONIUM-24 defects:
+
+1. **Category filter left non-"all" before later story-card clicks.** `catOrder` walked
+ forward ends on `"uncategorized"` (last element of the array); run0 and run2's scripted
+ sequences never reset the mood filter back to `"all"` before subsequently trying to open
+ specific story cards by id (e.g. `tuesdays`, a politics story). The app correctly hid
+ those cards — Playwright correctly couldn't find/scroll to them. Not a bug: my test
+ clicked a story that its own prior action had filtered out of the DOM.
+2. **Unsafe fallback in the admin-delete test helper.** When the "delete the story I just
+ created" locator (matched by headline text) found zero rows — legitimately, e.g. because
+ `resetToDefaults()` had already wiped it — my harness fell back to
+ `.admin-story-list [data-delete-id]:first()`, which deletes an **arbitrary, unrelated
+ story** (in run1, pass B, this silently deleted the real `tuesdays` seed story). All
+ subsequent `tuesdays` click failures in that run trace to my harness deleting the wrong
+ row, not to the app.
+3. **Admin panel not opened before hitting its controls.** The run3 seeded-shuffle block
+ and the run4 explicit sequence both called `blockAdminCreate`/`blockAdminReset` without
+ first calling `blockAdminOpen()` — clicking/filling controls inside a `hidden` `<section>`
+ times out by construction. Confirmed by inspecting the timeline: e.g. pass B run3,
+ `admin reset to defaults` failed at exactly 10.000s after the prior action with **no
+ `open admin panel` action anywhere before it** in that run.
+
+None of the three above are PANDEMONIUM-24 bugs — they're recorded in the log and named
+here so the next reload of this harness (or a human) doesn't re-report them as app defects.
+
+## Confirmed CLEAN: the Escape-key priority / admin+article both-open case
+
+The user's brief specifically called out Escape "from article, from admin," and the app's
+own source comment flags this as a fragile case: *"Escape: closes whichever 'layer' is
+currently open — admin panel takes priority over the article view **if somehow both are
+open**."* Neither of the two full 5-run passes actually exercised the both-open case (run4's
+"errored-first" branch fired instead, since prior runs already had errors queued), so I ran
+a small targeted follow-up (`run: "X-escape-priority-verify"` in the log,
+`rec/escape-priority-verify/escape-priority-verify.mp4`):
+
+1. Open admin panel → `adminHidden:false`.
+2. Click a story card while admin stays open → **both `adminHidden:false` AND
+ `articleHidden:false` simultaneously** (confirmed: the admin panel and the article view
+ are NOT mutually exclusive in this app — they're separate DOM siblings, so this
+ combined state is real and reachable by a normal user, e.g. open Admin, then click a
+ headline link without closing it first).
+3. Escape #1 → admin closes (`adminHidden:true`), article stays open (`articleHidden:false`),
+ focus lands on `#adminToggleBtn`. **Matches the documented priority exactly.**
+4. Escape #2 → article closes (`articleHidden:true`, hash cleared), focus correctly restored
+ to the `<a>` link that opened it (`restoreGridFocus`), not lost to `<body>`.
+
+No state leak, no focus trap. This is a genuinely well-built piece of the app — worth
+noting since the source comment reads defensively ("if somehow both are open") as if the
+author wasn't fully sure it would hold up; it does.
+
+## Full action tally
+
+- Pass A (initial fixed harness, tight 2.5–4s timeouts): 170 actions, runs 0–4, log at
+ `screenrecord/_pass-A/debug-log.jsonl`.
+- Pass B (generous 6–10s timeouts, to separate real races from host-load noise — this
+ machine showed a load average of ~48 during pass A): 170 actions, runs 0–4, log at
+ `screenrecord/debug-log.jsonl` (same file also holds the escape-priority follow-up).
+- The same `run3` "Element is not attached to the DOM" failure reproduced in **both**
+ passes despite the 2.5×–4× more generous timeout in pass B — that consistency, not the
+ raw failure count, is what elevates it from "flaky test" to "confirmed app race."
+- An aborted zero-th attempt (killed mid-run due to a harness bug that produced 30s
+ cascading timeouts) is preserved for reference at `screenrecord/_aborted-attempt0/`.
+
+## Order-dependence verdict
+
+Per-order breakdown of what actually surfaced:
+
+| Run | Order strategy | Real app issue? | Notes |
+|---|---|---|---|
+| run0 | DOM order | No | 1 harness bug (category left filtered) |
+| run1 | Reverse | No | 1 harness bug (bad admin-delete fallback wiped the wrong story) |
+| run2 | Toggles/klaxon-first | No | 1 harness bug (category left filtered) — but note klaxon rapid on/off/on/off ×4 + sound-toggle interplay was clean, no siren/interval leak observed (`state.klaxonAudioTimer` correctly cleared each toggle) |
+| run3 | Seeded shuffle (seed 3) | **Yes — the DOM-detach race** | Reproduced in both passes; also this run's shuffle happened to skip `blockAdminOpen()` (harness bug, separate issue) |
+| run4 | Errored-first replay | No | Inherited run3-era harness bugs (category-filter, admin-not-opened) by re-testing the same broken selectors; klaxon toggle at the very end was clean |
+
+The DOM-detach race is best characterized as **session-duration/timing-dependent rather
+than strictly click-order-dependent**: `state.elapsedSec` (and therefore how many of the 40
+staggered stories have ticked) advances on a real 1-second wall clock via `setInterval`
+regardless of what order the test clicks things in. It surfaced in `run3` in both passes
+most likely because run3 is far enough into each session (3 prior full browser
+contexts... no — each run is a fresh context, so more precisely: run3's own scripted
+sequence takes long enough, combined with dense escalation activity from many
+concurrently-staggered stories, to land a click during an active rebuild window). Any
+click order that takes long enough before reaching the story grid — admin-heavy or
+category-heavy sequences — increases exposure to this window.
+
+## Cross-reload debug trail (how to reproduce the confirmed defect)
+
+1. Load `index.html` fresh (any category, any prior state — doesn't matter, this is a
+ background-timer race, not a state leak).
+2. Let ~50–90 seconds of wall-clock elapse with the tab in foreground (or programmatically
+ drive several unrelated interactions first) so multiple of the 40 background stories are
+ actively escalating.
+3. Click (or Playwright-locate-then-click) any `[data-open-id]` story-card link right as an
+ escalation tick lands — `tick()` runs every 1000ms and calls `renderStories()` whenever
+ `anyStoryAdvanced` is true, which given 40 staggered stories is most seconds in the first
+ ~60–90s of a session.
+4. Expected: click opens the article. Observed (intermittently, evidenced twice): the click
+ target is destroyed mid-action (`Element is not attached to the DOM`) because
+ `#storyGrid.innerHTML` was just fully replaced.
+
+## Artifacts
+
+- `screenrecord/debug-log.jsonl` — pass B (170 actions) + the escape-priority follow-up,
+ append-only, canonical for future reloads.
+- `screenrecord/_pass-A/debug-log.jsonl` — pass A (170 actions), kept for the cross-pass
+ reproduction evidence cited above.
+- `screenrecord/_aborted-attempt0/` — killed zeroth attempt (harness-timeout cascade), kept
+ for completeness, not analyzed further.
+- Recordings (`.webm` originals + `.mp4` converted via ffmpeg, pass B):
+ - `screenrecord/rec/run0/run0.mp4` — DOM order
+ - `screenrecord/rec/run1/run1.mp4` — reverse order
+ - `screenrecord/rec/run2/run2.mp4` — toggles-first
+ - `screenrecord/rec/run3/run3.mp4` — seeded shuffle (contains the confirmed detach race)
+ - `screenrecord/rec/run4/run4.mp4` — errored-first replay
+ - `screenrecord/rec/escape-priority-verify/escape-priority-verify.mp4` — targeted
+ admin+article-both-open Escape-priority confirmation (clean pass)
+- `scripts/screenrecord.js` — the harness itself (kept in the repo per the "everything is
+ gitified" standing rule).
+
+## Coverage note (bounded, not skipped silently)
+
+The channel renders 40 breaking-story cards + 4 feature cards (44 total `[data-open-id]`
+targets). Each run tested a spread sample of 4 (first / ~1/3 / ~2/3 / last of the current
+DOM order) rather than all 44, to bound runtime — logged explicitly in the JSONL
+(`"skipped 40 of 44 story cards..."`) each run. All 8 category chips, the klaxon toggle, the
+sound-toggle checkbox, the full admin CRUD surface (create w/ validation, delete, reset), the
+admin open/close toggle, and both Escape-key exit paths were exercised in every run, not
+sampled.
diff --git a/screenrecord/rec/escape-priority-verify/escape-priority-verify.mp4 b/screenrecord/rec/escape-priority-verify/escape-priority-verify.mp4
new file mode 100644
index 0000000..9c03f35
Binary files /dev/null and b/screenrecord/rec/escape-priority-verify/escape-priority-verify.mp4 differ
diff --git a/screenrecord/rec/run2/run2.mp4 b/screenrecord/rec/run2/run2.mp4
index 262634e..2c4c500 100644
Binary files a/screenrecord/rec/run2/run2.mp4 and b/screenrecord/rec/run2/run2.mp4 differ
diff --git a/screenrecord/rec/run3/run3.mp4 b/screenrecord/rec/run3/run3.mp4
new file mode 100644
index 0000000..3e87130
Binary files /dev/null and b/screenrecord/rec/run3/run3.mp4 differ
diff --git a/screenrecord/rec/run4/run4.mp4 b/screenrecord/rec/run4/run4.mp4
new file mode 100644
index 0000000..e663c4c
Binary files /dev/null and b/screenrecord/rec/run4/run4.mp4 differ
← 2059f5a Fix WebKit header ghosting + 4 Cody holes: mood-desync, migr
·
back to Crazy News Channel
·
Fix story-grid click/focus race: keyed in-place patch + stab e1d52f1 →