Commit Graph
35 Commits
Author SHA1 Message Date
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
Michael ColeandClaude Opus 4.8 63df38105d Make CLI success output copy-paste-safe; add --serve, docs hub, key hygiene
A successful clone printed a bare, timestamped app path that wrapped mid-word
in the terminal, so users' `cd` failed and `npm install && npm run dev` ran in
the wrong directory — a confusing failure from a successful clone. Plus there
was no first-party "see it locally" path, no central docs page, "clone" was
conflated with `git clone`, and key hygiene wasn't stated.

CLI (compiler):
- Add compiler/src/cliSummary.ts: a copy-paste-safe success block on stderr — a
  single QUOTED `cd "<app>" && npm install && npm run dev` line that survives
  terminal wrapping — plus the AGENTS.md safe-to-edit pointers (content.ts,
  components/). The machine-readable {"event":"done",...} JSON stays on stdout.
- Add --serve (npm install + npm run dev in the generated app) and --open
  (also open the browser at the detected dev URL). Wired into single + multi.
- Add writeLatestPointer(): refresh a runs/<site>/latest symlink to the newest
  run so there's a stable, timestamp-free cd/script target. The reuse/regen
  scanners already filter to digit-prefixed run dirs, so `latest` is ignored.
- Tests: cliSummary (quoting, single-line command, Next/Vite roots, stable-path
  preference, --serve/--open hint) and runsLayout (symlink write + in-place
  refresh, timestamp-free stable path).

Docs:
- Add docs/README.md as a central documentation index.
- State prominently that ditto "cloning" = generating a codebase from a live
  URL, NOT `git clone` (no source repo required), in README + docs hub.
- Add "keys are secrets: use $DITTO_API_KEY, never inline/commit, rotate
  anytime" beside the key/auth examples in README + docs/SERVICE.md.
- Document --serve/--open and the latest symlink in README + compiler/README.

Note: the live ditto.site/docs web route and the dashboard's inline-token
snippet live in the marketing-site repo and still need a change there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 17:10:02 -04:00
Michael ColeandClaude Opus 4.8 638a0516c5 Add ditto unpack CLI to turn clone result JSON into a project tree
The REST API returns a clone as a giant `files` map JSON blob with no
first-party way to use it — users were left staring at kilobytes of
escaped JSON with no path from "you got JSON" to "here's a project".

Add packages/cli: a zero-dependency `ditto` CLI whose `unpack` command
walks the files{} map from POST /v1/clones (or GET .../result) and
writes a real project tree to disk:

- text files written from inline content;
- binary assets materialized from inline base64, else fetched from their
  reference URL using $DITTO_API_URL / $DITTO_API_KEY (--no-fetch writes
  just the text tree and lists skipped assets);
- path-traversal guards (clone results are untrusted) and sha256
  integrity checks;
- reads JSON from stdin ("-") so a curl response pipes straight in;
- clear errors for queued jobs with no files yet, and a tip pointing at
  the /bundle endpoint when assets can't be fetched.

Document the pipe-from-curl one-liner prominently next to the REST
examples in README.md and docs/SERVICE.md, add a package README, a
Repository Map row, a root `unpack` script, and a CHANGELOG entry.
Covered by 9 tests (text tree, stdin, inline base64, live URL fetch with
auth + relative-URL resolution, --no-fetch, traversal refusal, queued-job
detection, sha256 mismatch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:40:28 -04: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
dependabot[bot]andGitHub 9d2c411b12 Bump actions/setup-node from 4 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-30 17:43:08 +00: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
devteamaegisandclaude-flow 9a89fed365 feat(compiler): Lottie in multi-page output + materialize inline animationData
Two follow-ups to the single-page Lottie support:

1. Multi-page (clone-site). generateSiteApp emitted only DittoWire; it now wires
   DittoLottie per route exactly like wires — builds the route's lottie spec,
   emits the import + JSX, writes DittoLottie.tsx once, and injects lottie-web
   into the site package.json when any route replays an animation (the write
   moved after the route loop so the dep reflects actual usage). cloneSite now
   captures motion on full route captures (motion: full) so lotties are observed;
   light sibling probes skip it. Per-route gating verified: a route with no
   animation gets no DittoLottie.

2. Inline animationData. When a lottie is only in memory (data-driven init, no
   fetchable URL), capture now writes the JSON to the assets store as a real,
   content-hashed .json so it materializes to /assets/cloned/lottie like any
   source — instead of embedding the blob in the page spec. buildLottieSpec then
   resolves it to a local path; the spec carries path, not animationData.

Verified end-to-end: a 2-page fixture emits DittoLottie only on the animated
route with lottie-web added; a data-driven inline fixture materializes
fd7c…json and the spec references it by path.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-30 00:52:09 -04:00
devteamaegisandclaude-flow 048fd8cdad feat(compiler): capture and replay Lottie animations
lottie-web renders vector animations from a JSON document at runtime. As
third-party JS it falls outside the declarative CSS path and the WAAPI / reveal /
marquee probes, so clones shipped the empty container and the animation was gone.
This adds a Lottie subset to Stage 5 motion: capture the deterministic part — the
source JSON (materialized local file, or inline animationData) plus playback
config — and emit a fixed DittoLottie client that re-mounts lottie-web on the
cid'd container.

Capture (compiler/src/capture/lottie.ts): in-page detector covering lottie-web's
registry (animationData in memory), <lottie-player>/<dotlottie-player>, Elementor
data-settings (source_json.url), and generic markup. Keyed by data-cid-cap.
motion.ts merges it into MotionCapture; capture.ts registers source URLs as assets
so the existing fallback fetch downloads + materializes them to /assets/cloned/lottie.

Generate (compiler/src/generate/lottie.ts): buildLottieSpec resolves cap->cid and
source URL->materialized local path (or embeds inline animationData), then emits
DittoLottie + wire/import helpers, mirroring DittoMotion. app.ts writes the
component, wires import+JSX, and injects lottie-web into the generated package.json.

Delivery (compiler/src/cli.ts): stripDeliveryDataCids now treats DittoLottie as a
runtime consumer, so its container keeps a semantic data-ditto-id anchor instead of
being stripped (without this the animation never finds its mount point).

Lottie-free clones stay byte-identical. Verified end-to-end: cloning a page with a
<lottie-player> scrapes the JSON, emits a wired DittoLottie, and lottie-web mounts
and plays the captured animation (rendered SVG viewBox matches the source JSON).
Unit tests cover buildLottieSpec resolution + drop rules.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-30 00:30:52 -04: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
dependabot[bot]andGitHub a2113214d2 Bump actions/checkout from 4 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 22:12:40 +00:00
Samraaj Bath 66dfdcc58d Initial commit 2026-06-29 15:11:48 -07:00