Rotator classification: reject element-bearing containers, non-destructive runtime

A progressively-growing feed panel (rows injected after cap tagging)
passed the rotator leaf guard - which only rejected containers with
CAPPED children - and the rotator runtime's textContent writes then
flattened 51 statically-emitted descendants to one text node. Three
layers: emission drops rotators whose IR target has element children;
the capture leaf guard rejects any element child (capped or not); the
DittoMotion runtime saves/restores cloned child nodes via
replaceChildren so even a misclassified rotator can't destroy structure.

425 tests pass (6 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-04 05:33:06 -07:00
co-authored by Claude Fable 5
parent ec4e4397cc
commit f16c3aa4e8
3 changed files with 168 additions and 6 deletions
+5 -2
View File
@@ -482,8 +482,11 @@ export async function captureMotion(page: Page, opts?: { observeMs?: number; log
const cap = el && (el as Element).getAttribute?.("data-cid-cap");
if (!cap) continue;
const elem = el as Element;
// only track small text-bearing elements (a rotating word/phrase, not a big block)
if (elem.querySelector("[data-cid-cap]")) continue; // has element children → not a leaf word
// only track small text-bearing elements (a rotating word/phrase, not a big block).
// Reject ANY element child, capped or not: rows injected at runtime (after cid-cap
// tagging, so uncapped) would otherwise defeat a capped-only check and let a
// multi-element panel pass as a "leaf word".
if (elem.firstElementChild) continue; // has element children → not a leaf word
const txt = norm(elem.textContent || "");
if (!txt || txt.length > 80) continue;
const e = changes.get(cap) ?? { texts: [], times: [] };