Commit Graph
35 Commits
Author SHA1 Message Date
Samraaj BathandClaude Fable 5 0dd71a68d9 Popup dismissal hardening: shadow roots, overlay units, scroll-lock recovery
- Overlay detection resolves effective z-index through stacking ancestors,
  treats a zero-size max-z wrapper plus its full-viewport descendant as one
  removable unit, pierces open shadow roots, and relaxes the z floor when
  the page is scroll-locked; a lock persisting after dismissal reports
  blocking even with no detected overlay
- clickDismiss runs in every frame (close buttons live in cross-origin
  popup iframes), matches decline/no-thanks/aria-label close affordances,
  and re-runs before every snapshot AND screenshot to catch late-mounting
  dialogs
- rootUnclamp triggers from IR content extent (a scroll-lock collapses
  captured scrollHeight) and strips vendor-injected body position/overflow
  locks when firing
- Pollution gate fails on the scroll-lock contradiction: scrollHeight
  pinned to one viewport while IR content spans multiple
- Frame graft skips popup-creative vendor hosts; inline embedded signup
  forms remain graftable (regression-tested)

322 tests pass (24 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 01:44:12 -07:00
Samraaj BathandClaude Fable 5 23505ee229 Validation server: HTTP Range support, bounded settle, crash-proof reporting
- serveStatic answers single-range requests with proper 206/Content-Range
  (416 for unsatisfiable), so Chromium streams large media in bounded
  chunks instead of holding one throttled progressive request open past
  the navigation budget
- goto policy: waitUntil "load" + up to 10s of sustained-quiet polling
  replaces the hard networkidle timeout - long autoplaying media can no
  longer make validation unreachable (applied to renderApp, probe widths,
  and the interaction gate)
- validateRun catches render crashes and records them as runtime errors,
  so a failed render writes a report with a failed gate 0 instead of
  crashing reportless

292 tests pass (14 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 01:26:22 -07:00
Samraaj BathandClaude Fable 5 b445b62a69 Fix flex-basis 0% collapse, fluid max-width centering, letter-spacing snap, video frame normalization
- Keep basis/h/min-h [0%] literal (0% content-sizes on an indefinite axis;
  0px is definite zero) and fold grow + zero-basis into flex-1 - a
  flex:1 1 0% child no longer collapses to height 0 in auto-height columns
- Width centering accepts per-viewport caps (max-width: min(Npx, fluid))
  with symmetric gaps -> mx-auto + banded max-w instead of literal banded
  margins that drift off-centre between captured widths; non-painted
  viewports no longer poison the inference
- letter-spacing keeps sub-0.1px precision (no zero snap); style gate
  normalizes Chromium's letter-spacing "normal" <-> 0px serialization
- Videos pause + seek to frame 0 before every source AND clone screenshot,
  removing playback-time nondeterminism from perceptual evidence

278 tests pass (19 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:59:48 -07:00
Samraaj BathandClaude Fable 5 15d1e7a9e3 Canvas raster fallback, CDP rest-state screenshots, lottie overlay containment
- Visible canvases (>=48px) are captured as PNG stills at capture time
  (toDataURL, element-screenshot fallback for tainted/webgl) and emitted as
  cid-carrying <img> elements filling the captured box, so canvas-driven
  bands no longer render blank
- Source AND clone full-page screenshots now capture via CDP
  Page.captureScreenshot with captureBeyondViewport: no scroll-stitching,
  no scroll events, so scroll-linked animations can't smear mid-scrub
  states into perceptual evidence (pixel-identical to the old path on
  static pages; Playwright fallback retained)
- DittoLottie's runtime overlay stays position:absolute/inset:0 for its
  whole life and the host carries data-ditto-lottie with a scoped
  svg/canvas fit rule, so the runtime animation always fills the pinned
  per-viewport box instead of escaping to aspect height

259 tests pass (12 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:36:08 -07:00
Samraaj BathandClaude Fable 5 e33354cfb5 Neutralize animation-owned transforms across viewports; probe-veto flex auto-width conversion
- Nodes carrying an infinite CSS animation at ANY viewport get their frozen
  mid-animation transform zeroed at EVERY viewport (the @keyframes owns the
  property at runtime); animOwnedProps is now viewport-aware so breakpoint-
  gated marquees can't bake a mid-scroll translateX into other bands
- contentSizedFlexRow/contentSizedFlexItemAuto honor the sizing probe: a
  flex item whose captured width the probe proved load-bearing
  (wAuto=false) is never converted to width:auto, preserving narrow
  centered text columns

247 tests pass (7 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:18:56 -07:00
Samraaj BathandClaude Fable 5 d5a713d31f Honor sizing-probe verdicts in height emission; pin lottie mount heights
- heightFlows() bails when the capture probe proved a height authored-
  explicit (hAuto=false, hFill=false), so circular parent/child flow
  reasoning can no longer drop 100vh heroes to 0px; the probe verdict now
  vetoes the drop through both OR'd paths at the call site
- Space-distributing flex columns (space-between/around/evenly, center,
  flex-end) disqualify the content-driven read - the box height is load-
  bearing for the distribution
- Lottie mounts pin their captured per-viewport height like other replaced
  content, so aspect-fit runtime svgs can't inflate past the captured box
- DittoLottie forwards the placeholder's data-cid onto the runtime svg/
  canvas so the media stays addressable after the swap

240 tests pass (9 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 23:43:36 -07:00
Samraaj BathandClaude Fable 5 6a930d57b1 Section decomposition: recursive descent with semantic band detection
detectSections gave up on real pages: <body> swallowed everything, a single
oversized-wrapper expansion pass required >=3 candidates, and a 62px navbar
failed the 64px height bar - so nearly every clone shipped as one section
and one monolithic component.

Rewritten around a shared recursive core (detectSectionNodes): containers
covering >50% of the page are wrappers to descend into; a split is accepted
only when >=2 stacked bands tile >=80% of the container (side-by-side
columns and overlays never split; truly single-band pages legally stay 1).
Semantic tags get a 24px bar with nav-evidence detection for thin bars.
The validator's section list and the emitted section components now consume
the same root set, so gate-5 bands and sections/*.tsx files always agree.
Naming is evidence-based and deterministic: navbar/header/hero (only when
actually near the top), recipe names (logo-cloud, product-grid...), heading
slugs, contact/media, positional fallback.

Frozen-capture probes: 1 -> 6..17 sections across the reference runs, with
the old whole-page "hero-section" monoliths splitting into honest bands.

231 tests pass (13 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 23:31:07 -07:00
Samraaj BathandClaude Fable 5 d3ec154bae Capture & emission fidelity wave: dotLottie support, scroll-state isolation, authored-geometry trust, honest quality scoring
Capture:
- Preserve real asset extensions (.lottie no longer truncated); extract the
  default animation JSON from dotLottie ZIP containers at materialization
  (minimal built-in ZIP reader, no new deps)
- Reset scroll + settle before every viewport snapshot so scroll-linked
  styles can't bake into captured computed values
- Marquee detection now requires content overflow (>1.35x), sustained
  velocity across separated windows, scroll independence (jiggle test), and
  genuine content repetition — kills scroll-settle-lerp false positives
- Sizing probe trusts authored explicit heights (px/vh/calc) over circular
  parent/child reflow verdicts, fixing dropped 100vh heroes

Normalize/generate:
- Canonicalize identity transforms to "none" per viewport; emit explicit
  transform resets across bands when any band has a real transform
- DittoLottie keeps the captured placeholder until DOMLoaded; failed loads
  no longer blank the container
- mx-auto only for true auto-margin centering; literal per-band margins are
  preserved (fixes full-bleed regressions)
- Spacing-scale snap tightened to 0.25px (3.5px stays p-[3.5px]); emit
  whitespace-nowrap for wrap-vulnerable single-line text leaves
- Grid recipes no longer override captured track counts or column spans;
  responsive re-flow plans apply only when geometry agrees

Quality scoring:
- Rewritten 6-dimension rubric (payload, decomposition, duplication,
  semantics, hygiene, runtime) with catastrophe caps and per-dimension
  reporting; thresholds are documented calibration guides

218 tests pass (74 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 23:09:16 -07:00
Samraaj BathandClaude Fable 5 77be868ee3 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>
2026-07-03 22:51:50 -07:00
Samraaj BathandGitHub dfcd4a6563 Merge pull request #14 from ion-design/clone-fidelity-fixes
Clone-fidelity fixes from the new audit reviews
2026-07-03 21:54:48 -07:00
Samraaj BathandClaude Fable 5 afab12ff14 Fix mobile chip-strip overlap, reveal-replay stagger, scroll-linked text-fill
Three clone-fidelity fixes from manual review of the acceptance screenshots.

Fix 1 — mobile nav chip-strip overlap (generate/css.ts). A horizontally
scrollable flex strip (overflow-x:auto flex <ul>) of nowrap chips collapsed
because the base viewport reported min-width:0px on the chips (a mobile-only
strip that is 0-wide at desktop), so the generator emitted `min-w-0`, letting
the chips shrink below their content instead of overflowing — the nowrap text
then collided. Suppress `min-w-0` for a nowrap flex item whose flex parent
scrolls horizontally; scoped away from legitimate min-w-0 truncation (whose
parent does not scroll-x). Verified on a fresh ooni.com clone: 0 chip overlaps
at 375, scrollWidth 776 > clientWidth 375.

Fix 2 — reveal-replay stagger leaves tiles unpainted (generate/motion.ts).
DittoMotion re-hid each revealed element and replayed its entrance on
scroll-into-view preserving the captured delay/duration, so a fast scroll /
full-page screenshot caught most grid tiles mid-entrance. Cap the replayed
delay (<=300ms) and duration (<=600ms), settle each tile per-element a bounded
time after it enters view, and make the global failsafe settle (not re-animate)
so nothing stays hidden. Validator settle path (__dittoMotionStop) unchanged.
Verified: cropin cotton renders 12/12 crop tiles in a normal full-page pass;
motion gate reveals 24/24.

Fix 3 — scroll-linked text-fill frozen at end state (capture/stabilize.ts,
capture/capture.ts, generate/css.ts). A view-timeline text-fill
(animation-duration resolves to `auto`) was baked filled: the clone has no
scroll timeline, so emitting the animation made it jump to its end keyframe via
fill-mode:both. Capture-side, cancel scroll/view-timeline animations before each
snapshot so the DOM records the at-rest state; generation-side, suppress the
animation-* props when animation-duration is `auto` (we render the at-rest
state, never replay a scroll-linked animation). Teach the motion gate to exclude
these from the static-CSS expectation so the gate stays honest. Verified on a
fresh ooni clone: the "world's no.1 pizza ovens" em is white/gray (rgb 240,240,
240), not filled yellow, at rest; motion gate still passes.

Tests: +7 fixtures (css scroll-strip + scroll-timeline, reveal-replay caps,
motion-gate scroll-timeline exclusion); 123/123 compiler tests green, full test
suite green, typecheck clean. No gate regression on the fresh ooni run vs the
20260704-024247 baseline (all gate pass/fail unchanged; perceptual within
0.0004; motion restored to pass).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 21:46:28 -07:00
Samraaj BathandClaude Fable 5 f3d433c024 Teach motion gate about reveal-replay; auto-provision missing harness deps
- motionGate: a captured CSS entrance animation the clone replays through
  a DittoMotion reveal (re-hide on mount, restart @keyframes on scroll)
  is graded by the scroll-reveal check, not the static-CSS expectation —
  previously every such cid failed emitted=false. Also drive every
  captured reveal instead of the first 16 (pages with more reveals could
  never pass; this was the spurious "reveals 16/24").
- render/harness: the validator copies app sources over the preinstalled
  harness without installing deps, so a Lottie clone's injected
  lottie-web crashed webpack ("build failed" with no real signal).
  Missing app deps are now installed into the harness pinned to the
  app's exact version (--no-save, idempotent), with a clear gate issue
  on install failure.

Cotton motion gate: 33 static + 10 replayed + 24/24 reveals -> pass.
Cropin home build gate: pass (was webpack Module not found).
116/116 compiler tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 20:22:42 -07:00
Samraaj BathandClaude Fable 5 7f99b76f66 Fix build-breaking ident rewrite, blank-iframe grafting, and layout-gate regressions
Round-3 fixes from gate validation + human visual review of fresh
cropin.com and ooni.com clones:

- app.ts: section data-var rename used an unanchored substring replace,
  so Tile2_data matched inside MediaTile2_data and emitted a corrupted
  Mediatile2Data usage (ReferenceError at prerender). Word-boundary
  regex; every map site now shares one derivation.
- graft.ts: about:blank iframes were blanket-skipped, missing the
  Klaviyo newsletter form (mounted into a blank same-origin frame via
  JS) — the audit's #1 complaint. Blank frames now graft; the
  visibility gate still excludes 0-size tracking pixels.
- walker.ts: isVisible() now rejects boxes wholly outside the viewport
  (off-left always; off-right only when the page isn't horizontally
  scrollable; fixed boxes fully above/below), so a closed slide-in
  drawer's contents no longer count as expected text (91.8% -> 93.4%).
- css.ts: four sizing-regime corrections — pin captured px for
  circular shrink-0 slides (Splide slide chain collapsed 0x0);
  flex-basis:100% for full-width shrink-0 slides; keep fixed-px grid
  templates for scrolling track lists (repeat(N,1fr) squished a
  50-track carousel); single full-bleed fixed track -> minmax(0,1fr);
  keep authored heights whose in-flow children are fill children
  (aspect-video heroes inflated 240 -> 720px); width:100% for
  inset-spanned aspect boxes.

Gate scores: ooni home 88.7 -> 93.3 (responsive 32 fails -> pass),
pizza-ovens 90.8 -> 97.6 (perceptual 46.7% -> 17.2%).
107/107 compiler tests, all workspaces green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 19:42:34 -07:00
Samraaj BathandClaude Fable 5 199359d0a6 Wave 4: reveal settling + replay, 206/magic-byte media fix, hidden-geometry banding, bare-zero utility gating
Capture (4a):
- stabilize: dwell-scroll settling pass + known-library pre-reveal
  neutralization after lazy promotion, so every viewport snapshot records
  the post-reveal steady state (about-page sections no longer captured
  blanket-hidden: 24 base `invisible` wrappers -> 0)
- probeReveals: extended to the visibility + entrance-animation family;
  DittoMotion replays them (JS re-hide on mount, IntersectionObserver
  reveal, 4s force-reveal failsafe) so SSR/non-JS still shows content
- assets: 206 range-response bodies are no longer stored as full assets
  (full-fetch fallback) + video magic-byte validation - the about-page
  hero video is now a valid 6.0MB ftyp mp4 instead of 18.9KB of garbage

Generate (4b):
- css: hidden-node geometry policy - a visibility:hidden box with a 0x0
  captured bbox at a band becomes display:none there; an OCCUPYING hidden
  box gets the captured per-viewport geometry instead of the baked
  canonical one. Also covers the ancestor-hidden-at-base case (the
  cropin.com/cotton slider arrow: hide inherited from an elementor-widget
  ancestor at every width parked a desktop left:548px box inside a 375px
  viewport -> body.scrollWidth 585; now 375 on both verified pages)
- tailwind: bare-zero utility gating - font-size:0 -> text-[0px] and
  letter-spacing:0 -> tracking-[0px] (text-0/tracking-0 are silently
  invalid in Tailwind v4; fixes permanently-visible map labels on the
  cotton Global-presence section)

Verified: 87/87 compiler tests green, full-workspace suite green,
regenerated cropin.com + /cotton/ builds measure 375px scrollWidth at a
375px viewport, and a fresh /about/ clone renders all reveal sections
with scroll-replay against the live site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 18:33:10 -07:00
Samraaj BathandClaude Fable 5 9aed2540aa Clone-fidelity fix waves 1-3 (+ wave 4 in flight): capture settling, media, iframes
Fixes from the cropin.com / ooni.com audit reviews:
- walker: preserve whitespace-only text in inline elements ("ofthe" bug);
  capture ::placeholder styles for inputs
- css: emit visibility (hidden-at-rest wordmark artifact); exempt list
  markers from inherited elision (lost bullets); ::placeholder rules
- seo: sanitize og:type to Next's enum (render crash -> dev badge leak);
  generated next.config disables devIndicators
- capture/stabilize: promote lazy-loader data attrs before snapshots
  (collapsed sections, viewport-inconsistent captures); settle autoplay
  carousels to home slide before every snapshot; force-reveal hidden
  videos for poster shots + log failures
- generate: ship downloaded video files as local <video src>; keep
  picture>source through IR prune with srcset rewrite (mobile-crop-on-
  desktop heroes); pin Tailwind named screens to computeBands boundaries
- capture/graft: capture cross-origin iframe subtrees (Klaviyo signup
  forms) with element-screenshot fallback; asset download retry +
  visual-assets-missing reporting

Checkpoint commit: wave 4 (reveal settling, 206 range-response fix,
hidden-geometry banding, bare-zero utility gating) is mid-implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 17:45:42 -07:00
Samraaj BathandGitHub da537e68b8 Merge pull request #9 from ion-design/lottie-animation-capture
feat(compiler): capture and replay Lottie animations
2026-07-03 14:05:40 -07:00
Samraaj Bath 13ce4b89a1 Fix Lottie replay for Next builds and fallback sources 2026-07-03 14:03:42 -07:00
Samraaj Bath 7e192863b4 Merge remote-tracking branch 'origin/main' into lottie-animation-capture 2026-07-03 13:33:03 -07:00
Samraaj BathandGitHub 905fb14e2d Merge pull request #12 from michaelgbcole/updates
Add ditto unpack CLI to turn clone result JSON into a project tree
2026-07-03 13:18:02 -07:00
Samraaj Bath e28cc57c85 Fix unpack quickstart docs and hash verification 2026-07-03 13:16:05 -07:00
Samraaj BathandGitHub 4de6e244a0 Merge pull request #11 from ion-design/codex/readme-methodology-link
[codex] Add methodology doc and homepage link
2026-06-30 12:58:51 -07:00
Samraaj Bath 74219bb921 Add methodology doc and site link 2026-06-30 12:24:59 -07:00
Samraaj BathandGitHub 46d8c150d3 Merge pull request #2 from ion-design/dependabot/github_actions/actions/setup-node-6
Bump actions/setup-node from 4 to 6
2026-06-30 11:03:03 -07:00
Samraaj BathandGitHub b4d7ee6ff6 Merge pull request #10 from ion-design/codex/remove-opencode-sidecar
[codex] Remove opencode sidecar tooling
2026-06-30 10:53:29 -07:00
Samraaj Bath 893da2e332 Remove opencode sidecar tooling 2026-06-30 10:48:40 -07:00
Samraaj BathandGitHub bae9688e2b Merge pull request #1 from ion-design/dependabot/github_actions/actions/checkout-7
Bump actions/checkout from 4 to 7
2026-06-30 10:41:16 -07:00
Samraaj Bath d27f34bea8 Update hosted signup docs 2026-06-29 22:21:24 -07:00
Samraaj Bath 7d0e85ddb4 Allow www signup origin 2026-06-29 20:39:57 -07:00
Samraaj Bath cf8af6d936 Add verified email API key signup 2026-06-29 20:35:16 -07:00
Samraaj Bath 49a6541b24 Add public API key signup 2026-06-29 19:53:39 -07:00
Samraaj BathandGitHub 2b9ebd9a9b Merge pull request #8 from ion-design/codex/readme-usage-guide
[codex] Simplify README usage guide
2026-06-29 16:31:56 -07:00
Samraaj Bath 12dc7d9220 Simplify README usage guide 2026-06-29 16:29:39 -07:00
Samraaj BathandGitHub ca476b1a0f Merge pull request #5 from ion-design/codex/open-source-readiness
[codex] Prepare repo for open source
2026-06-29 16:22:59 -07:00
Samraaj Bath 3f7c9ed227 Prepare repo for open source 2026-06-29 16:14:44 -07:00
Samraaj Bath 66dfdcc58d Initial commit 2026-06-29 15:11:48 -07:00