Generated-code hygiene: listener cleanup, shared cn(), whitespace collapse, probe stripping, sub-pixel snapping, clone-origin metadata, dependency pinning
- DittoWire/Accordion/DropdownMenu runtime templates: AbortController-based listener cleanup, idempotent effects, orphan panel removal on unmount - Emit a single src/lib/utils.ts cn() instead of a copy per component file - Collapse captured whitespace runs under white-space:normal (pre* preserved) - Tag source-injected font-metric probe nodes at capture; drop them from IR - Snap sub-pixel arbitrary lengths (integer within 0.1px, else 1 decimal) - Emit SITE_ORIGIN (src/lib/site.ts, env-overridable): sitemap/robots/ canonical/og:url/JSON-LD resolve against the clone's own origin instead of the source domain - Pin lottie-web in generated package.json whenever DittoLottie is emitted 144 tests pass (21 new), typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
dfcd4a6563
commit
77be868ee3
@@ -44,6 +44,10 @@ export type RawNode = {
|
||||
computed: RawStyle;
|
||||
bbox: RawBBox;
|
||||
visible: boolean;
|
||||
// A font-metric / measurement scratch node injected by the SOURCE site's own JS (typography
|
||||
// libraries, FontFaceObserver): absolutely positioned, parked far off-screen, and non-painting.
|
||||
// Never user-visible; excluded from emission so it doesn't ship as page markup.
|
||||
probe?: boolean;
|
||||
sizing?: RawSizing;
|
||||
before?: RawStyle;
|
||||
after?: RawStyle;
|
||||
@@ -286,6 +290,23 @@ export function collectPage(opts?: { maxNodes?: number } | void): PageSnapshot {
|
||||
return true;
|
||||
};
|
||||
|
||||
// A measurement/probe scratch node: out-of-flow (absolute/fixed), parked far off-screen
|
||||
// (≥10000px beyond any edge — real drawers/sr-only content never live out there), AND
|
||||
// non-painting (visibility:hidden / collapse / opacity:0). The two together are exclusive to
|
||||
// font-metric / measurement scratch elements the source's own JS injects (a11y sr-only text
|
||||
// stays visibility:visible so AT can read it, so it never matches). Tagged so emission drops it.
|
||||
const OFFSCREEN_PROBE_PX = 10000;
|
||||
const isProbe = (cs: CSSStyleDeclaration, bbox: RawBBox): boolean => {
|
||||
if (cs.position !== "absolute" && cs.position !== "fixed") return false;
|
||||
const nonPainting = cs.visibility === "hidden" || cs.visibility === "collapse" || parseFloat(cs.opacity || "1") === 0;
|
||||
if (!nonPainting) return false;
|
||||
const rightEdge = bbox.x + bbox.width;
|
||||
const bottomEdge = bbox.y + bbox.height;
|
||||
const pageH = round2(scrollEl.scrollHeight);
|
||||
return rightEdge <= -OFFSCREEN_PROBE_PX || bottomEdge <= -OFFSCREEN_PROBE_PX
|
||||
|| bbox.x >= OFFSCREEN_PROBE_PX + vpW || bbox.y >= OFFSCREEN_PROBE_PX + pageH;
|
||||
};
|
||||
|
||||
const serializeElement = (el: Element): RawNode | null => {
|
||||
if (nodeCount >= MAX_NODES) { truncated = true; return null; }
|
||||
const tag = el.tagName.toLowerCase();
|
||||
@@ -401,6 +422,7 @@ export function collectPage(opts?: { maxNodes?: number } | void): PageSnapshot {
|
||||
computed,
|
||||
bbox,
|
||||
visible: isVisible(el, cs, bbox),
|
||||
...(isProbe(cs, bbox) ? { probe: true } : {}),
|
||||
...(sizing ? { sizing } : {}),
|
||||
children: [],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user