Files
open-design-crew[bot] 323246a64c Website Clone: first-party Home entry, fidelity, real-clone example cards + tracking (#5178)
* Add Website Clone home entry with bundled web-clone skill and root-relative asset preview fix

* Retrigger CI

* Open the site entry after website-clone turns

* Harden web-clone skill asset and color fidelity

Recon now scrolls the full page and captures @font-face rules, real font/image
resource URLs, computed section palette, and resolved root variables. Asset
harvest is rebuilt on the browser network stack so cross-origin CDN images,
fonts, and media (hotlink-protected included) all land locally, with a
self-hosted fonts.css and an originalUrl→localPath manifest. Clone audit gains
--recon --strict fidelity gates (fonts self-hosted, images downloaded, key
section colors exact) that exit 2 on violations, and SKILL.md makes all three
a mandatory delivery gate.

* Default web-clone mode to faithful reproduction when unconfirmed

A real nike.com run showed the agent silently downgrading to visual-homage
mode (placeholder brand, typography blocks instead of photography, approximated
palette) because the user skipped the turn-1 confirmations and the site was a
copyright-heavy commercial SPA. Fidelity is the point of this scenario: default
to faithful reproduction, land real assets first, record trademark/copyright
replacements as a pre-deploy checklist for the user to decide in Step 6, and
never skip script recon just because the target looks bot-sensitive.

* Streamline web-clone runs: skip discovery with URL, exempt brand constraints, actionable playwright setup

Three run-path optimizations for the Website Clone scenario:

1. POST /api/projects derives metadata.skipDiscoveryBrief for web-clone
   projects whose pending prompt already carries the target URL — the URL is
   the brief, so the turn-1 question form no longer stalls the run (an
   explicit client flag still wins). Covered by
   project-create-web-clone-discovery.test.ts.
2. Prompt composition skips the design-system authoritative block and craft
   rules for intent=web-clone runs: the fidelity target is the original
   site, not the project brand; brand application belongs to the skill's
   Step 6 replacement phase.
3. The plugin's playwright-loader now also resolves from the process cwd
   (so 'npm i -D playwright' in the project root works for scripts staged
   under .od-skills/) and fails with exact fix commands; SKILL.md documents
   the staged-path resolution and forbids downgrading to eyeballing because
   of missing deps.

* Swap the copyright guardrail for faithful reproduction on web-clone runs

The official designer prompt's 'don't recreate copyrighted designs — help
the user build something original instead' bullet made web-clone agents
silently rebrand clones (Nike → placeholder brand, original SVGs instead of
real photography) even after the skill mandated faithful defaults: a system
prompt guardrail outranks skill blocks. For intent=web-clone runs the bullet
is swapped for a faithful-reproduction rule that routes trademark/copyright
caution through a pre-deploy replacement checklist (NOTES.md) the user owns.
Prompt tests guard both directions of the substitution.

Also: recon now captures scroll-feel signals (scroll-behavior, scroll-snap
rule count, sticky/fixed census) and SKILL.md makes matching the original's
scroll behavior (smooth-scroll libs, snap, sticky) a fidelity requirement.

* Extract web-clone into a shared global skill referenced by ref

The Website Clone workflow (SKILL.md + 14 recon/harvest/audit scripts +
references) moves from the example-web-clone plugin into the global skill
registry at skills/web-clone/. example-web-clone now references it via
od.context.skills:[{ref:'web-clone'}] instead of shipping its own copy, so the
upcoming per-site clone example plugins can all share one skill rather than
each duplicating a 16KB SKILL.md and its scripts.

Pinned-plugin skill resolution (server.ts) previously only loaded a plugin's
LOCAL ./SKILL.md; a {ref} skill loaded nothing, so the body never entered the
prompt and the scripts never staged. It now falls back to resolving the ref
against the global registry, loading the body and registering the skill dir for
cwd staging. Verified end-to-end in a live run: skills/web-clone stages to
.od-skills/web-clone-<hash>/ with SKILL.md + all scripts.

guard.ts residual-JS allowlist follows the scripts to their new home; a
structural test locks the manifest<->skill contract.

* Add 7 Website Clone example-prompt cards for the Home clone rail

nexu.io, open-design.ai, anthropic.com, vercel.com, resend.com, linear.app,
raycast.com — each a thin example plugin that references the shared web-clone
global skill (od.context.skills:[{ref:'web-clone'}]) so none duplicate the
workflow. Each seeds the composer with a faithful-clone prompt for its URL and
ships a self-contained example.html preview built from a real recon capture of
the target homepage, so the gallery card shows what the card clones. They
surface under the Website clone chip's example-prompt rail (verified in a live
desktop run) and inherit the chip's intent:'web-clone' metadata, so clicking one
runs through the full fidelity path (skip discovery, brand exemption, faithful
reproduction, script recon/harvest/audit).

* Polish clone example-card previews with a framed hero capture

The first pass inlined a downscaled full-page screenshot cropped mid-content —
it read as a raw screenshot dump. Each preview is now a crisp above-the-fold
hero capture (1440x900 DPR2, 16:10) presented inside a browser-window chrome
(traffic lights + host URL pill) on a brand-tinted gradient, so the gallery
card reads as an intentional site preview. Verified by rendering the previews
headless.

* Cache the plugin list so Home remounts don't grey the create rail

HomeView re-fetched + parsed the large /api/plugins payload on every remount,
leaving the create-rail chips disabled for 1-2s each time. A module-level cache
(listPluginsFresh, 10s TTL) serves warm mounts without the network round trip so
pluginsLoading clears within a frame; the first render stays cheap (empty list)
to keep the click off the critical path. Also hide the base example-web-clone
plugin from the example rail (the per-site clone-* cards are the real examples).

* Ship real self-contained clones as example-card previews + align seed prompt

Each clone-* example.html is now a real, self-contained reproduction of its
target site (inlined CSS/fonts/images via document.styleSheets serialization so
CSS-in-JS/Next.js SPAs like Vercel/Raycast capture correctly, oversized icon
sprites dropped, rasters downscaled to stay under the 5MB preview cap, plus a
scroll-reveal motion layer). Preview renders the real HTML; Remix copies it into
a project as index.html. Manifests reuse the base web-clone useCase.query with a
{{targetUrl}} default per site, so picking a card seeds the same preset prompt
with the URL filled in instead of the marketing description.

* Shrink clone example previews ~33% (dedup + drop EOT + WebP)

Three lossless-to-near-lossless passes on the self-contained clones:
- Dedup repeated data-URIs (same image used in <img> + css url() N times): store
  each once as a :root CSS var, point url() at var(), hydrate img[src] from the
  var via a tiny script. Resend alone shed 1.8MB.
- Drop leftover EOT/fontobject font data-URIs (IE-only, redundant with woff2).
- Re-encode inlined raster images to WebP (~50% smaller than JPEG at equivalent
  quality) via cwebp q72.
Total across the 7 cards: ~23MB → ~15.5MB, every card now under 3MB (was up to
5MB). Image hydration + fidelity verified by headless render.

* Lead create rail with Website clone; seed example cards with just the URL

- Move 'web-clone' to the front of CREATE_RAIL_ORDER so Website clone is the
  first create-rail card, ahead of Slides (deck).
- Website-clone example cards (Clone Nexu, Clone Vercel, …) now seed the
  composer with the same short 'clone this site: <url>' scaffold the chip uses
  (localized webClonePromptSeed prefix + the card's targetUrl), e.g.
  'Website URL to clone: https://nexu.io', instead of the multi-paragraph
  build spec. Picking the chip with an empty composer already seeds the bare
  prefix for the user to paste a URL after.
- Fix a test-only regression from the Home plugin-list cache: the module-level
  visible-plugins cache leaked a case's mocked /api/plugins payload into later
  cases via listPluginsFresh, greying the rail / stranding scenarios on
  'Template None'. Add resetPluginsCache() and clear it in a global afterEach.

* Track web-clone example-card picks (example_prompt / example_open_project)

The Website-clone example cards (Clone Nexu / Vercel / …) had no analytics on
pick or remix, so the site-clone funnel could not be broken down per example.
Wire the two clicks the contracts already type (埋点文档 row 116):

- useExamplePlugin → ui_click area=chat_composer element=example_prompt
- duplicateExamplePlugin (Remix) → element=example_open_project

Both carry chip_id (web-clone) + plugin_id (example-clone-<site>) + plugin_type
for per-site breakdown. The created project's own project_kind=web_clone (already
derived from metadata.intent via projectKindToTracking) still rides
project_create_result / run_created, so 'how many projects are website clones' is
answerable without extra work. No contracts change — the element enum and the
web_clone project_kind already existed. Adds a behavioral test asserting both
events fire with the right triplet + attribution.

* Mirror web-clone copyright-guardrail swap into contracts/BYOK prompt

PR #5178 blocking review (nettee/looper): the daemon prompt special-cases
intent='web-clone' (faithful-reproduction rule) but the contracts prompt stack —
which apps/web uses for API/BYOK runs — imported OFFICIAL_DESIGNER_PROMPT directly
and never routed web-clone, so BYOK website-clone runs kept the original 'build
something original' guardrail. Daemon and BYOK now behave identically:
- contracts/prompts/official-system.ts: add COPYRIGHT_GUARDRAIL_BULLET,
  WEB_CLONE_COPYRIGHT_GUARDRAIL_BULLET, renderOfficialDesignerPrompt (mirrors the
  daemon renderer; contracts base has no execution placeholders so it only swaps
  the bullet).
- contracts/prompts/system.ts: composeSystemPrompt routes the charter through
  renderOfficialDesignerPrompt({ webCloneFidelity: metadata.intent==='web-clone' }).
- Add system-prompt-web-clone.test.ts covering both modes + the byte-identical
  bullet guard.

* Complete web-clone BYOK prompt parity + rebase relative JS asset refs

Two remaining blocking issues from PR #5178 looper re-review:

1. contracts composeSystemPrompt still appended the active-design-system
   'authoritative' block + visual-direction override unconditionally, while the
   daemon suppresses both for intent==='web-clone'. A BYOK web-clone run with an
   active design system was still told the design system wins, fighting faithful
   reproduction. Gate both sections behind !isWebCloneRun; add contracts tests
   asserting web-clone BYOK prompts drop the design-system guidance.

2. rewriteInlinedScriptAssetRefs only handled root-relative literals. When
   js/app.js is inlined into the HTML entry, a sibling lookup like
   fetch('./data.json') / new Worker('./worker.js') / import('./chunk.js')
   resolved against the HTML root instead of js/. Take the source script path and
   rebase confirmed relative refs against it (mirrors the CSS pass), membership-
   confirmed; add unit cases for ./sibling, ../parent, and escaping/missing.

* Website-clone examples: plain text prompt cards, drop embedded HTML + Remix

The 7 site example cards embedded a full self-contained clone of each target site
(anthropic.com, vercel.com, …) as example.html and offered a one-click Remix that
copied that markup into a project. Shipping verbatim copies of other companies'
sites is an IP risk, so replace them with plain text prompt cards:

- Delete plugins/_official/examples/clone-* (7 dirs, incl. the example.html
  reproductions and the Remix-enabling preview.entry).
- Add a 'web-clone' entry to HOME_PROMPT_EXAMPLES (en + zh-CN, others fall back to
  en): 7 text cards that fill the composer with 'Website URL to clone: <url>'.
  With the site plugins gone, the web-clone chip's preset rail is empty so these
  static text cards render instead. Clicking one fills the URL prompt and (via
  the existing usePromptExample) still fires ui_click element=example_prompt
  chip_id=web-clone — no plugin_id / no Remix / no example_open_project.
- Remove the now-dead webCloneCardTargetUrl seed override; refresh stale comments.
- Rewrite HomeView.web-clone-tracking test for the text-card path (asserts text
  cards render, no plugin-preset / no remix affordance, and the pick still tracks).

The base example-web-clone skill + web-clone run behavior (intent, fidelity
prompt, discovery skip) are unchanged.

* Give web-clone example cards a site logo + domain, not raw prompt text

The 7 Website-clone examples rendered as near-identical white boxes each showing
'Website URL to clone: https://<site>' — visually flat and hard to scan. Render
them as a logo gallery instead: each card shows the site's own favicon (resolved
at render time via a public favicon service — no third-party brand assets are
bundled) in a rounded tile above the bare domain (nexu.io, vercel.com, …). A
blocked/failed icon fetch falls back to a lettered monogram tile. Click behavior
is unchanged: the full 'Website URL to clone: <url>' prompt is still what fills
the composer and fires ui_click element=example_prompt. Non-URL prompt examples
(other chips) keep the plain text card.

* Web-clone examples: keep only open-design, add kimi

Legal review flagged brand-copy risk in the example rail. Drop the
nexu / anthropic / vercel / resend / linear / raycast site prompts and
keep just open-design.ai plus kimi.com (official Kimi partnership).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 03:28:06 +00:00

297 lines
6.4 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Website Clone Preview</title>
<style>
:root {
color-scheme: light;
--bg: #f7f3ef;
--ink: #201f1d;
--muted: #77716a;
--line: #d9d2ca;
--accent: #d45f3f;
--green: #23b381;
--panel: #fffaf5;
--shadow: 0 24px 70px rgba(55, 42, 31, 0.14);
}
* {
box-sizing: border-box;
}
html,
body {
min-height: 100%;
margin: 0;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--ink);
}
body {
display: grid;
place-items: center;
padding: 32px;
}
main {
width: min(1080px, 100%);
display: grid;
gap: 28px;
}
.hero {
display: flex;
justify-content: space-between;
gap: 28px;
align-items: end;
}
h1 {
margin: 0;
font-size: clamp(42px, 7vw, 84px);
line-height: 0.9;
letter-spacing: 0;
max-width: 680px;
}
.kicker {
margin: 0 0 14px;
color: var(--accent);
font-size: 13px;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.copy {
width: min(330px, 100%);
margin: 0 0 4px;
color: var(--muted);
font-size: 18px;
line-height: 1.45;
}
.stage {
position: relative;
min-height: 440px;
border: 1px solid var(--line);
background: var(--panel);
box-shadow: var(--shadow);
overflow: hidden;
}
.browser {
position: absolute;
width: 48%;
min-width: 300px;
border: 1px solid var(--line);
background: #fff;
box-shadow: 0 18px 50px rgba(44, 34, 25, 0.14);
}
.browser.source {
left: 7%;
top: 56px;
}
.browser.clone {
right: 7%;
bottom: 52px;
}
.bar {
height: 34px;
border-bottom: 1px solid var(--line);
display: flex;
align-items: center;
gap: 6px;
padding: 0 12px;
color: var(--muted);
font-size: 12px;
}
.dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: var(--line);
}
.dot.hot {
background: var(--accent);
}
.page {
padding: 22px;
min-height: 198px;
}
.stripe {
height: 12px;
border-radius: 999px;
background: #ece5de;
margin-bottom: 12px;
}
.stripe.short {
width: 52%;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-top: 18px;
}
.tile {
min-height: 72px;
border: 1px solid var(--line);
background: #fbf7f2;
}
.tile.accent {
background: #f3d0c3;
border-color: #dda58f;
}
.transfer {
position: absolute;
left: 50%;
top: 50%;
width: 168px;
height: 168px;
transform: translate(-50%, -50%);
border: 1px solid var(--line);
background: #201f1d;
color: #fffaf5;
display: grid;
place-items: center;
text-align: center;
font-size: 13px;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
box-shadow: 0 20px 54px rgba(32, 31, 29, 0.22);
}
.transfer span {
display: block;
margin-top: 8px;
color: var(--green);
font-size: 30px;
letter-spacing: 0;
}
.steps {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.step {
border-top: 2px solid var(--line);
padding-top: 12px;
color: var(--muted);
font-size: 14px;
line-height: 1.35;
}
.step strong {
display: block;
margin-bottom: 4px;
color: var(--ink);
font-size: 15px;
}
@media (max-width: 760px) {
body {
padding: 18px;
}
.hero,
.steps {
grid-template-columns: 1fr;
display: grid;
}
.browser {
position: relative;
width: auto;
min-width: 0;
}
.browser.source,
.browser.clone {
inset: auto;
}
.stage {
display: grid;
gap: 16px;
padding: 16px;
}
.transfer {
position: relative;
inset: auto;
width: auto;
height: 96px;
transform: none;
}
}
</style>
</head>
<body>
<main>
<section class="hero" aria-label="Website clone overview">
<div>
<p class="kicker">Web clone skill</p>
<h1>Recreate the site from a real URL.</h1>
</div>
<p class="copy">Recon the source, capture routes and interactions, build the clone locally, then audit the result.</p>
</section>
<section class="stage" aria-label="Website clone flow preview">
<article class="browser source" aria-label="Source website">
<div class="bar"><span class="dot hot"></span><span class="dot"></span><span class="dot"></span> source.example</div>
<div class="page">
<div class="stripe"></div>
<div class="stripe short"></div>
<div class="grid">
<div class="tile accent"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>
</div>
</article>
<div class="transfer">Evidence first<span>-></span></div>
<article class="browser clone" aria-label="Local clone">
<div class="bar"><span class="dot hot"></span><span class="dot"></span><span class="dot"></span> local clone</div>
<div class="page">
<div class="stripe"></div>
<div class="stripe short"></div>
<div class="grid">
<div class="tile accent"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>
</div>
</article>
</section>
<section class="steps" aria-label="Workflow steps">
<p class="step"><strong>1. URL</strong>Start from the site the user wants to reproduce.</p>
<p class="step"><strong>2. Recon</strong>Crawl routes, assets, styles, and interaction evidence.</p>
<p class="step"><strong>3. Build</strong>Create a local HTML/CSS/JS reproduction.</p>
<p class="step"><strong>4. Audit</strong>Compare, remove tracking, and document gaps.</p>
</section>
</main>
</body>
</html>