6.4 KiB
name, description, user-invocable
| name | description | user-invocable |
|---|---|---|
| asset-pipeline | Rules for how the clone pipeline names, organizes, and references downloaded assets. Covers folder layout under public/assets/cloned, hash-based filename convention, font strategy (self-host via next/font/local, fall back to system stack if licensing unclear), SVG inline-vs-file threshold, video format, and import paths. Load this when downloading assets and when referencing them in JSX. | false |
Asset Pipeline
All cloned assets live under the generated project's public/assets/cloned/. Never outside this tree. Never in the workspace .clone-workspace beyond the capture bundle.
Layout
<project>/public/assets/cloned/
images/ # jpg, png, webp, avif
videos/ # mp4 (only)
fonts/ # woff2 (preferred), woff, ttf
svg/ # .svg files when not inlined
lottie/ # .json animation data
Naming
Every file is renamed to <sha1-8>-<sanitized-original-name>. The sha1 is of the source URL (not content), truncated to 8 hex chars. Sanitization: lowercase, spaces → -, strip everything not in [a-z0-9.-].
Examples:
https://cdn.site.com/hero-bg.jpg→a1b2c3d4-hero-bg.jpghttps://fonts.site.com/inter-display-500.woff2→e5f67890-inter-display-500.woff2
Collisions are effectively impossible at 8 chars for any one site. If they happen, extend to 10.
The manifest records the full local_path for each asset — the generate agent reads that field, never reconstructs the path.
Fonts
Preferred: self-hosted via next/font/local. In src/app/layout.tsx:
import localFont from 'next/font/local';
const display = localFont({
src: [
{ path: '../../public/assets/cloned/fonts/e5f67890-inter-display-400.woff2', weight: '400', style: 'normal' },
{ path: '../../public/assets/cloned/fonts/abcd1234-inter-display-500.woff2', weight: '500', style: 'normal' },
],
variable: '--font-display',
display: 'swap',
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={display.variable}>
<body className="font-display">{children}</body>
</html>
);
}
Then in tailwind.config.ts:
theme: {
extend: {
fontFamily: {
display: ['var(--font-display)', 'sans-serif'];
}
}
}
If licensing is unclear (manifest marks license_hint: "licensed" or "unclear"), do NOT ship the font file. Substitute with the closest free alternative from a system stack and flag in the final report:
| Source font | Fallback |
|---|---|
| Any licensed sans | Inter from Google Fonts (via next/font/google) |
| Any licensed serif | ui-serif, Georgia, Cambria, "Times New Roman", serif |
| Any licensed mono | ui-monospace, SFMono-Regular, Menlo, monospace |
| Variable display font (licensed) | Inter or Manrope via next/font/google |
Record the substitution in the manifest's skipped[] with reason: "licensed_font_unclear" and log to logs/skipped.md.
SVGs
Inline as React component when all hold:
- Source size < 5KB
- SVG is used in-flow, not as a CSS
background-image - SVG has no
<script>tags (never inline external scripts)
Use @svgr/webpack or inline the markup by hand in a small component file src/components/icons/<Name>.tsx. Prefer hand-written components for simple icons — @svgr adds tooling complexity that rarely pays back.
Keep as file when:
- SVG ≥ 5KB
- SVG is used as
background-image(referenced from CSS) - SVG contains references to
<filter>or<pattern>that make it complex to inline
For file-kept SVGs, reference as <img src="/assets/cloned/svg/<hash>.svg" alt="" />. next/image does not accept SVGs without the dangerouslyAllowSVG flag — prefer raw <img> over enabling that flag.
Videos
Only MP4. Capture normalizes HLS/DASH to MP4 when possible; if the source is DRM-protected (Widevine / FairPlay / PlayReady signatures in HAR), skip with reason: "drm" in the manifest.
Embed as:
<video
src="/assets/cloned/videos/<hash>.mp4"
autoPlay
loop
muted
playsInline
poster="/assets/cloned/images/<hash>-poster.jpg"
/>
Images
next/image is preferred. Configure no remote domains — everything is local under /assets/cloned/, which Next.js serves as static assets automatically.
import Image from 'next/image';
<Image
src="/assets/cloned/images/abc1234-hero.jpg"
alt=""
width={1920}
height={1080}
priority={isAboveTheFold}
sizes="(max-width: 768px) 100vw, 1200px"
/>;
alt=""for decorative images. Preserve sourcealtwhen present.priorityon above-the-fold images (hero bg, logo).sizesmatches the responsive layout — omit if the image is fixed-width.
For background images (CSS background-image), keep as CSS with arbitrary Tailwind value:
<div className="bg-[url('/assets/cloned/images/abc1234-bg.jpg')] bg-cover bg-center" />
Lottie
Lottie JSON files go under lottie/. Import them as ES modules, not fetch at runtime — this eliminates flash and is under 100KB in nearly every case:
import animationData from '@/public/assets/cloned/lottie/<hash>.json';
If a Lottie is > 1MB, consider dynamic import with a <Suspense> boundary to defer.
Import paths
In Next.js, the public directory is served at the root. Use absolute paths starting with /:
- ✅
<img src="/assets/cloned/svg/logo.svg" /> - ✅
import data from "@/public/assets/cloned/lottie/x.json"(for ES import of JSON) - ❌ Never
../../../public/...from components - ❌ Never
https://source-site.com/...— those were downloaded
Flagging failures
If an asset download fails (404, timeout, CORS, auth), the orchestrator records it in logs/skipped.md with the URL and reason. Generation should render a placeholder of the captured dimensions rather than a broken <img> for any section that referenced the missing asset.
Cleanup rule
Do not delete assets the capture downloaded "just in case they are unused". The analyze agent records assets[].referenced_by[]; if the array is empty, leave the file in place — it might be referenced in a stage that has not run yet. Orphan cleanup is a post-run task, not part of generation.