stardust:reskin — same content, donor design
The user has a site (the content source) and a design that already
exists somewhere else (the donor: another live site, or a directory
of static HTML prototypes; Figma is future scope). Reskin rebuilds the
content site's pages so that every visible byte of content survives —
text, ordered images, CTAs, SEO metadata — while the surface comes
entirely from the donor's tokens and module vocabulary.
The two halves have different contracts:
- Content is non-negotiable. The gate is byte equality of
whitespace-normalized visible text, the ordered visible-image set,
and full metadata carry-over. Not "close" — equal, after an
explicitly declared, executable normalization ledger.
- Design is flexible. Content is re-laid-out onto donor modules; a
carousel may become a static card grid, a sidebar may become a band.
Structure (element counts, tag sequences) is informational, never
gating — a reskin re-structures markup by design.
The decisive rule, validated in the UC2-E1 experiment (a healthcare site ×
a fintech donor: 2281/2281 text bytes, 7/7 images, 47/47 slots, 13/13 metadata,
17/17 donor-token probe): the page is generated programmatically from
the captured content model — content strings are never retyped. Byte
fidelity then holds by construction and the content gate becomes a
regression check instead of a debugging tool.
Inputs
<content-url> — required. The site whose content is preserved.
- Donor, exactly one of:
--donor <url> — a live site whose design system is adopted.
--donor-dir <path> — a directory of local static HTML
prototypes (claude-design / Mobirise / Relume / Lovable / v0 /
hand-coded). Served on localhost and captured through the same
path — recipe in reference/donor-sources.md § Local prototypes.
--donor-figma <url> — not yet implemented. The contract is
defined (reference/donor-sources.md § Figma) but the adapter is
future scope. Tell the user exactly this: "Figma donors are not
implemented yet. Export the frames as static HTML prototypes (or
point me at a live staging URL of the design) and re-run with
--donor-dir / --donor. The Figma adapter — variables→palette,
text styles→type, frame screenshots→vision references, provenance
class figma-mcp — is specced in
skills/reskin/reference/donor-sources.md and will land in a
later release." Do not improvise a partial Figma capture.
--pages <slug,slug,...> — optional. Restrict the content capture
to specific pages. Default: the archetype set (one page per page
family; scale to siblings happens in Phase 6 via migrate).
--scope <selectors> — optional. Pre-declare the content-root
scope for a single-page run (comma-separated, ! suffix keeps a
scope whole; see reference/content-model.md § Scope declaration).
Without it, run the scope-discovery procedure per page.
Setup
- Run the master skill's setup (
../stardust/SKILL.md § Setup):
impeccable dep check, context loader, state read. Flow. Stamp
state.json.flow: "reskin" if unset
(../stardust/reference/state-machine.md § Flow keys); if another
flow is set, print it and the switch command
($stardust reskin --switch-flow) instead of running.
- Playwright import-resolvability probe — same contract as
../extract/SKILL.md § Setup:
node -e "import('playwright').then(()=>process.exit(0))" from the
project root; on failure
npm i -D playwright --no-save --legacy-peer-deps. Re-run the
probe at the start of every phase that renders — a --no-save
install is pruned by any later real npm i.
- Copy the scripts into the project. ESM resolves
import('playwright') from the script's directory and the plugin
tree ships no node_modules. Copy skills/reskin/scripts/* (all
five files — capture-content.mjs and dom-equality.mjs import
source-normalize.mjs as a sibling) byte-identical to
stardust/scripts/reskin/, and skills/diff/scripts/ live-session.mjs to stardust/scripts/diff/ — every reskin gate
script (capture-content, dom-equality, donor-probe, and
slot-coverage) imports it unconditionally at startup, regardless
of target type: without the copy each one exits 2 immediately,
even for --help or a local-file --rendered target. It supplies
ALL live-target hardening (real-Chrome UA + standard headers,
challenge detection, headed-stealth escalation), resolved from
../diff/ next to ../reskin/, so keep the two dirs siblings.
Run the copies.
- Origin collision — if
stardust/state.json records a different
site.originUrl, stop and ask before mixing sites, per
../extract/SKILL.md § Setup.
Procedure
Phase 1 — INGEST DONOR
The donor's design system is captured separately from the content
site, into stardust/canon-source/. Full recipes per donor type in
reference/donor-sources.md; summary:
- Live URL (
--donor <url>): invoke
$stardust extract <content-url> --design-source <donor-url> — the
existing skill, unchanged. It lands in stardust/canon-source/: the
donor's pages/, assets/ (screenshots included),
_brand-extraction.json, _crawl-log.json, and a descriptive
DESIGN.md + DESIGN.json, and stamps
state.json.designSource = { url, capturedAt, path } — see
../extract/SKILL.md § Cross-site brand sources.
- Local prototype dir (
--donor-dir <path>): serve the directory
on localhost (python3 -m http.server <port> --directory <path>)
and run the same --design-source capture path against the
localhost origin. Record the real provenance (localhost serve of
<path>) in canon-source/_crawl-log.json. Recipe details —
page listing, index-less directories, port hygiene — in
reference/donor-sources.md § Local prototypes.
- Figma (
--donor-figma): FUTURE. Surface the exact message from
§ Inputs and stop.
Then author two reskin-owned donor artifacts (contracts in
reference/donor-sources.md):
stardust/reskin/donor-tokens.json — the curated, probe-able
token sheet: palette roles, type ramp, layout metrics, button
specs, radii, shadows, motifs — every value a computed-style string
the Phase 5 probe can assert verbatim. Curated from
canon-source/DESIGN.json + _brand-extraction.json + the raw
computed styles; cites the donor page each value came from. On a
bounded donor capture (a single donor page, no full
--design-source run) there is no DESIGN.json,
_brand-extraction.json, or designSource stamp to curate from —
the token sheet is authored entirely from raw computed-style
sampling of the donor page, a first-class parallel path
(reference/donor-sources.md § Two first-class token-sourcing
paths).
stardust/reskin/donor-modules.md — the enumerated module
vocabulary (M1..Mn): one row per donor module with where-seen
screenshot evidence and an anatomy description. This is the closed
set the Phase 3 mapping brief maps onto.
The pin-one-reference-page rule (hard). Real donors run multiple
design systems concurrently — the experiment's donor served radius-4px
/ 1266px on its homepage and pill-radius / 1080px on older product
pages. Consolidating across them produces a chimera no live page ever
shipped, and the token probe then asserts against nothing. When donor
pages disagree on a token, pin ONE donor reference page per module
family, record it in donor-tokens.json (curatedFrom + a note
naming the reference page), and demote the other pages to
corroboration.
Phase 2 — CONTENT-MODEL CAPTURE
The new capability: a byte-oriented capture of each content page —
distinct from extract's design-oriented capture. Run the ported
capture script per page:
node stardust/scripts/reskin/capture-content.mjs \
--url <page-url> \
--scope '<sel1,sel2!,...>' \
--normalize stardust/reskin/normalize/<slug>.mjs \
--out stardust/reskin/content-model/<slug>/
It writes stardust/reskin/content-model/<slug>/content-model.json
(full contract in reference/content-model.md): per section-slot —
headings with levels, eyebrows and other leftovers, body paragraphs,
list items, CTAs with absolute hrefs, ordered visible images
(currentSrc + alt), plus the ordered stream (ordered) — the
same content as kind-tagged nodes in document order with nesting and
separator flags, the render surface Phase 4 consumes; page-level —
full SEO metadata (title,
description, canonical, OG, Twitter, JSON-LD, lang, favicon), the
whitespace-normalized visible text of the scope (the content-gate
reference string), and a full-page screenshot.
Two declarations are mandatory per page, because they are the
experiment's top two failure modes:
- CONTENT-ROOT SCOPE declaration. Naive scoping silently dropped
30% of the experiment page's content — the hero and a banner
carousel lived inside
<header>, outside the obvious #content
root, and byte equality would have "passed" against the incomplete
reference. Never trust main or #content blind. Run the
scope-discovery procedure (reference/content-model.md
§ Scope discovery): compare the captured scope text against the
whole-body text and the page screenshot, verify the h1 is inside
the scope, widen with comma-separated multi-scope selectors until
everything visible in the screenshot that isn't declared chrome is
in the model. Chrome exclusions (nav, footer) are declared deltas
(D1-style), not silent omissions. The capture script prints a
coverage line (bodyTextLen / scopeTextLen / h1InScope) to make
the check mechanical.
- EXECUTABLE NORMALIZATION LEDGER. Cookie-consent chrome removal,
carousel de-duplication (hidden slides are absent from
innerText;
clone slides duplicate it — rotating carousels break byte
determinism), and any page-specific cleanup live in a per-page
ledger module stardust/reskin/normalize/<slug>.mjs that extends
the shared default (scripts/source-normalize.mjs). The same
ledger file is passed verbatim to the capture and to every gate
(--normalize), so the gate measures exactly the normalization the
capture declared. The ledger is code, not prose. Format in
reference/content-model.md § Normalization ledger.
Phase 3 — MAPPING BRIEF
Author stardust/reskin/mapping.md — the cross-origin mapping brief.
Full contract and entry schema in reference/mapping-brief.md. Per
every content slot in the content model:
- assigned donor module id (from
donor-modules.md's closed
vocabulary) + a one-line rationale grounded in slot anatomy vs
module anatomy;
- status ∈
{mapped, new-module, chrome, carried-invisible} —
the last for sr-only/visually-hidden content inside the byte scope
(carried verbatim into an equivalent hidden element, no donor
module; reference/mapping-brief.md § Status semantics).
Gates before any rendering:
- ≥ 80% of content slots
mapped onto named donor modules. Below
that, the donor vocabulary doesn't cover this content — stop and
surface (see § Stop conditions).
- Every
new-module entry is explicitly composed from donor
tokens (name the tokens it borrows: input spec + primary button
spec, etc.) and listed in the brief's stats block. New modules are
never silently improvised mid-render.
- Chrome swaps documented — nav/footer replaced by donor chrome
carrying the source's links is a declared delta, excluded from the
content-gate scope.
- Casing / text-transform policy declared. The donor may render
eyebrows uppercase (or the source may — Chrome's
innerText
reflects text-transform, so captured text is rendered-case).
The underlying text stays byte-faithful; casing is applied via CSS
text-transform only, never by editing the string. Policy details
in reference/mapping-brief.md § Casing policy.
Composite source sections (one wrapper div holding two logical
regions) are split into atomic slots in the brief — section ≠ slot.
Phase 4 — PROGRAMMATIC RENDER
The decisive validated rule: the page is GENERATED from
content-model.json — never retyped by hand. Write a renderer
script per archetype (stardust/reskin/renderers/<archetype>.mjs,
modeled on the experiment's render-reskin.mjs) that:
- reads the page's
content-model.json and interpolates every
visible string, href, and image URL from the model (HTML-escaped) —
if a string appears in the output HTML but not in the model, the
renderer is wrong;
- takes each slot's structure from the ordered stream
(
sections[].ordered, reference/content-model.md § The ordered
stream): kind-tagged nodes in document order, nesting preserved
(a CTA wrapping its heading and vice versa), sep flags marking
zero-separator inline joins. The stream is innerText-consistent
by construction (its text nodes are sliced from the parent's
rendered innerText — the byte-gate basis), so emitting it
verbatim is safe. Emit nodes in stream order; emit
sep: "" neighbours with no whitespace between them (inline);
never reorder, and never reconstruct order or separators from
visibleText —
the first field run did exactly that (per-type arrays matched
greedily against visibleText as an oracle) and burned three
debug rounds on duplicate identical strings, both directions of
CTA/container nesting, and zero-separator inline li runs, all
of which the stream now captures structurally;
- renders
formControl nodes as equivalent controls, not text:
a <select> carrying the captured option texts verbatim, in
order; inputs/textareas carrying value/placeholder — restyled
with donor tokens (the new-module composition rules apply), never
flattened to prose and never dropped. A select's option text is
part of the source's innerText, so dropping the control fails
the byte gate with nothing structured to render from
(reference/content-model.md § Slot taxonomy, formControls);
- carries the metadata block verbatim into
<head> (title,
description, canonical, OG, Twitter, JSON-LD — including source
garbage like broken JSON-LD URLs: fidelity over repair; flag
such items for the human in the run report, never silently fix or
silently keep);
- takes its surface exclusively from
donor-tokens.json (emitted as
CSS custom properties) + the donor module patterns assigned in
mapping.md. Use the rendered-page conventions the Phase 5 probe
asserts against: content in <main>, .container for the measure,
.btn for the donor primary button (reference/gates.md
§ Rendered-page conventions);
- writes
stardust/reskin/pages/<slug>.html, self-contained.
Renderers may parse a single node's text apart (split a list-item row
into kicker/date/title) but must fail loudly (throw) when a parse
doesn't reproduce the node's text — a silent partial parse is a silent
content drop. A slot whose orderedVerified is false is inspected
before rendering, never trusted blind; when inspection shows stream
text genuinely absent from the slot's visibleText, the
sanctioned fallback is to drop that text before rendering and
record the drop in the model's provenance — filtering ghosts against
visibleText is the documented resolution, distinct from the
forbidden order-reconstruction move
(reference/content-model.md § When orderedVerified is false).
Phase 5 — GATES
Three gate families per page; commands, pass bars, tolerances, and
failure modes in reference/gates.md. Serve or file://-address the
rendered page and run:
(a) CONTENT GATE — all three must pass:
node stardust/scripts/reskin/dom-equality.mjs \
--source <page-url> --source-scope '<declared scope>' \
--normalize stardust/reskin/normalize/<slug>.mjs \
--rendered stardust/reskin/pages/<slug>.html --rendered-scope main \
--report stardust/reskin/reports/<slug>-content-gate.md
node stardust/scripts/reskin/slot-coverage.mjs \
--model stardust/reskin/content-model/<slug>/content-model.json \
--rendered stardust/reskin/pages/<slug>.html \
--report stardust/reskin/reports/<slug>-slot-coverage.md
dom-equality.mjs (vendored + adapted from github.com/aemcoder/skills,
Apache-2.0) gates on whitespace-normalized visible-text byte
equality and the ordered, URL-normalized visible-image set;
element count and tag sequence are reported but informational.
slot-coverage.mjs additionally proves every model slot present
(slot text substring, every CTA as a (text, absolute-href) pair,
every image) and asserts metadata carry-over field by field.
(b) DESIGN-ADOPTION GATE:
node stardust/scripts/reskin/donor-probe.mjs \
--tokens stardust/reskin/donor-tokens.json \
--rendered stardust/reskin/pages/<slug>.html \
--report stardust/reskin/reports/<slug>-donor-probe.md
Computed-style assertions of donor token values on the rendered page —
bg/fg, font-family token string, display weight, button spec,
container width, band palette, section rhythm. Tolerances (exact for
colors/radius/family; ±2px button padding; ±20px container; ±16px
section rhythm) documented in reference/gates.md § Tolerances.
Then the side-by-side judgment: open the rendered page next to the
donor reference-page screenshots (canon-source/assets/screenshots/)
and judge adoption with your eyes — the probe proves tokens, the
eyeball proves the page reads as the donor. Record the verdict.
(c) Sanity — no horizontal overflow at 1440 and 360 (donor-probe
runs this automatically; --widths to override).
One fix iteration per gate family — one for the content gate,
one for design-adoption/sanity. On a failure in a family, fix once,
re-run all gates (a fix for one gate can break another). Residual
failures after a family's iteration is spent are logged in
stardust/reskin/ledger.json under the page's residuals[] — never
silently absorbed, never looped on indefinitely. (Both field runs
support the budget: the validation experiment spent one iteration on
360px chrome; the smoke run spent exactly one per family — a
content-side separator emission fix and a sanity-side 360px chrome
fix. A single shared iteration would have forced a false residual;
per-family keeps the loop bounded without absorbing real failures.)
Phase 6 — HANDOFF
Reskin owns the archetype pages; scale and shipping are the existing
pipeline, unchanged:
- Scale via the stardust
migrate skill (../migrate/SKILL.md) with the
donor-pinned target (the state.json.designSource stamp Phase 1
wrote is exactly what direct/migrate read for donor pinning;
a bounded donor capture wrote no stamp — record the donor origin
in the handoff instead, reference/donor-sources.md § Two
first-class token-sourcing paths).
Same-type sibling pages render at the sibling tier against the
gated archetype. Content rules are the ones reskin already
enforces — ../migrate/reference/content-preservation.md is
inherited wholesale.
- Ship via the stardust
deploy and rollout skills, unchanged. Blocks
written for donor modules obey the Experience Workspace editability
contract (deploy SKILL.md § 8, EW1–EW10) and pass the EW gate
(block-roundtrip --ew) before they are done.
Reskin writes its own state under stardust/reskin/ — ledger.json
holds per-page status (captured → mapped → rendered → gated), gate
results, and residuals. It makes no changes to the core state
machine (../stardust/reference/state-machine.md); the only
state.json touch is the designSource stamp that extract itself
writes in Phase 1.
What reskin never does
- No content rewriting or summarizing. Not headlines, not body,
not CTA labels, not casing-by-editing. Faithful ≠ clean: source
garbage is carried verbatim and flagged, not fixed.
- No invented sections. Every rendered slot traces to a
content-model slot; every module traces to
donor-modules.md or an
explicit new-module entry in mapping.md.
- No donor-token drift. No colors, fonts, radii, or spacing
outside
donor-tokens.json. Licensed donor fonts are adopted as the
family token string with local fallback, never rebundled.
- No silent slot drops. Anything excluded from the gate scope is a
declared chrome delta or a ledger normalization — declared in
mapping.md, executable in the normalize module.
Stop conditions
- Donor capture fails (bot-block past the headed fallback,
prototypes dir unservable) — surface extract's error verbatim.
- Mapping ratio < 80% — the donor vocabulary can't carry this
content. Ask: widen the donor capture (more donor pages → more
modules) or accept a larger explicit new-module list.
- Scope undecidable — the content page's visible content cannot be
covered by any selector set (heavy shadow-DOM, iframe-embedded
content). Surface with the screenshot evidence.
- Figma donor requested — future scope; exact message in § Inputs.
Outputs
| Path |
Purpose |
stardust/canon-source/ |
Donor capture + descriptive DESIGN.md/json (extract --design-source, unchanged) |
stardust/reskin/donor-tokens.json |
Curated probe-able token sheet, pinned to one reference page |
stardust/reskin/donor-modules.md |
Enumerated donor module vocabulary (M1..Mn) with screenshot evidence |
stardust/reskin/content-model/<slug>/content-model.json |
Byte-oriented per-page content model (+ source screenshot) |
stardust/reskin/normalize/<slug>.mjs |
Per-page executable normalization ledger (shared capture ↔ gates) |
stardust/reskin/mapping.md |
The mapping brief: every slot → donor module, status, rationale |
stardust/reskin/renderers/<archetype>.mjs |
Programmatic renderers (model in, HTML out) |
stardust/reskin/pages/<slug>.html |
Rendered reskin pages |
stardust/reskin/reports/ |
Per-page gate reports + screenshots |
stardust/reskin/ledger.json |
Per-page status, gate results, residuals |
stardust/scripts/reskin/ |
Project-local copies of the five skill scripts |
References
reference/donor-sources.md — the three donor types and their
capture recipes; pin-one-reference-page; donor-tokens.json and
donor-modules.md contracts; the Figma future contract.
reference/content-model.md — the capture contract: slot taxonomy,
the ordered stream (the render surface), scope-discovery procedure,
normalization ledger format, provenance.
reference/mapping-brief.md — entry schema, the ≥80% gate,
new-module composition rules, casing policy.
reference/gates.md — both gates' commands, pass bars, tolerances,
failure modes, residual logging.
../extract/SKILL.md § Cross-site brand sources — the
--design-source donor-capture path Phase 1 delegates to.
../migrate/reference/content-preservation.md — the content rules
reskin inherits (and tightens to byte level).
../diff/SKILL.md — the pixel + structural probes; reuse them as a
supplementary build-side check after Phase 6 deploy, --profile eds.
1---2name: reskin-23description: Rebuild an existing site's pages with byte-faithful content on a separately-defined donor design system — another live site, or local static HTML prototypes (Figma donors are contract-defined, not yet implemented). Content fidelity is gated byte-level (text, ordered images, SEO metadata); design application is flexible (content re-laid-out onto donor modules). Use when the user says "reskin my site with this design", "apply this design system to my content", "restyle my site to look like <other site>", "new design, same content", "rebrand my site using these prototypes", or names a content site plus a design donor. NOT for redesigning from intent (that's the stardust core extract/direct/prototype chain) and NOT for keeping the current design while migrating (that's the replica flow).4license: Apache-2.05---67# stardust:reskin — same content, donor design89The user has a site (the **content source**) and a design that already10exists somewhere else (the **donor**: another live site, or a directory11of static HTML prototypes; Figma is future scope). Reskin rebuilds the12content site's pages so that every visible byte of content survives —13text, ordered images, CTAs, SEO metadata — while the surface comes14entirely from the donor's tokens and module vocabulary.1516The two halves have different contracts:1718- **Content is non-negotiable.** The gate is byte equality of19 whitespace-normalized visible text, the ordered visible-image set,20 and full metadata carry-over. Not "close" — equal, after an21 explicitly declared, executable normalization ledger.22- **Design is flexible.** Content is re-laid-out onto donor modules; a23 carousel may become a static card grid, a sidebar may become a band.24 Structure (element counts, tag sequences) is informational, never25 gating — a reskin re-structures markup by design.2627The decisive rule, validated in the UC2-E1 experiment (a healthcare site ×28a fintech donor: 2281/2281 text bytes, 7/7 images, 47/47 slots, 13/13 metadata,2917/17 donor-token probe): **the page is generated programmatically from30the captured content model — content strings are never retyped.** Byte31fidelity then holds by construction and the content gate becomes a32regression check instead of a debugging tool.3334## Inputs3536- `<content-url>` — required. The site whose content is preserved.37- Donor, exactly one of:38 - `--donor <url>` — a live site whose design system is adopted.39 - `--donor-dir <path>` — a directory of local static HTML40 prototypes (claude-design / Mobirise / Relume / Lovable / v0 /41 hand-coded). Served on localhost and captured through the same42 path — recipe in `reference/donor-sources.md` § Local prototypes.43 - `--donor-figma <url>` — **not yet implemented.** The contract is44 defined (`reference/donor-sources.md` § Figma) but the adapter is45 future scope. Tell the user exactly this: *"Figma donors are not46 implemented yet. Export the frames as static HTML prototypes (or47 point me at a live staging URL of the design) and re-run with48 `--donor-dir` / `--donor`. The Figma adapter — variables→palette,49 text styles→type, frame screenshots→vision references, provenance50 class `figma-mcp` — is specced in51 `skills/reskin/reference/donor-sources.md` and will land in a52 later release."* Do not improvise a partial Figma capture.53- `--pages <slug,slug,...>` — optional. Restrict the content capture54 to specific pages. Default: the archetype set (one page per page55 family; scale to siblings happens in Phase 6 via `migrate`).56- `--scope <selectors>` — optional. Pre-declare the content-root57 scope for a single-page run (comma-separated, `!` suffix keeps a58 scope whole; see `reference/content-model.md` § Scope declaration).59 Without it, run the scope-discovery procedure per page.6061## Setup62631. Run the master skill's setup (`../stardust/SKILL.md` § Setup):64 impeccable dep check, context loader, state read. **Flow.** Stamp65 `state.json.flow: "reskin"` if unset66 (`../stardust/reference/state-machine.md` § Flow keys); if another67 flow is set, print it and the switch command68 (`$stardust reskin --switch-flow`) instead of running.692. **Playwright import-resolvability probe** — same contract as70 `../extract/SKILL.md` § Setup:71 `node -e "import('playwright').then(()=>process.exit(0))"` from the72 project root; on failure73 `npm i -D playwright --no-save --legacy-peer-deps`. Re-run the74 probe at the start of every phase that renders — a `--no-save`75 install is pruned by any later real `npm i`.763. **Copy the scripts into the project.** ESM resolves77 `import('playwright')` from the *script's* directory and the plugin78 tree ships no `node_modules`. Copy `skills/reskin/scripts/*` (all79 five files — `capture-content.mjs` and `dom-equality.mjs` import80 `source-normalize.mjs` as a sibling) byte-identical to81 `stardust/scripts/reskin/`, **and** `skills/diff/scripts/82 live-session.mjs` to `stardust/scripts/diff/` — every reskin gate83 script (capture-content, dom-equality, donor-probe, **and**84 slot-coverage) imports it unconditionally at startup, regardless85 of target type: without the copy each one exits 2 immediately,86 even for `--help` or a local-file `--rendered` target. It supplies87 ALL live-target hardening (real-Chrome UA + standard headers,88 challenge detection, headed-stealth escalation), resolved from89 `../diff/` next to `../reskin/`, so keep the two dirs siblings.90 Run the copies.914. **Origin collision** — if `stardust/state.json` records a different92 `site.originUrl`, stop and ask before mixing sites, per93 `../extract/SKILL.md` § Setup.9495## Procedure9697### Phase 1 — INGEST DONOR9899The donor's design system is captured **separately** from the content100site, into `stardust/canon-source/`. Full recipes per donor type in101`reference/donor-sources.md`; summary:102103- **Live URL** (`--donor <url>`): invoke104 `$stardust extract <content-url> --design-source <donor-url>` — the105 existing skill, unchanged. It lands in `stardust/canon-source/`: the106 donor's `pages/`, `assets/` (screenshots included),107 `_brand-extraction.json`, `_crawl-log.json`, and a descriptive108 `DESIGN.md` + `DESIGN.json`, and stamps109 `state.json.designSource = { url, capturedAt, path }` — see110 `../extract/SKILL.md` § Cross-site brand sources.111- **Local prototype dir** (`--donor-dir <path>`): serve the directory112 on localhost (`python3 -m http.server <port> --directory <path>`)113 and run the **same** `--design-source` capture path against the114 localhost origin. Record the real provenance (localhost serve of115 `<path>`) in `canon-source/_crawl-log.json`. Recipe details —116 page listing, index-less directories, port hygiene — in117 `reference/donor-sources.md` § Local prototypes.118- **Figma** (`--donor-figma`): FUTURE. Surface the exact message from119 § Inputs and stop.120121Then author two reskin-owned donor artifacts (contracts in122`reference/donor-sources.md`):123124- `stardust/reskin/donor-tokens.json` — the **curated, probe-able125 token sheet**: palette roles, type ramp, layout metrics, button126 specs, radii, shadows, motifs — every value a computed-style string127 the Phase 5 probe can assert verbatim. Curated from128 `canon-source/DESIGN.json` + `_brand-extraction.json` + the raw129 computed styles; cites the donor page each value came from. On a130 **bounded donor capture** (a single donor page, no full131 `--design-source` run) there is no `DESIGN.json`,132 `_brand-extraction.json`, or `designSource` stamp to curate from —133 the token sheet is authored entirely from raw computed-style134 sampling of the donor page, a first-class parallel path135 (`reference/donor-sources.md` § Two first-class token-sourcing136 paths).137- `stardust/reskin/donor-modules.md` — the **enumerated module138 vocabulary** (M1..Mn): one row per donor module with where-seen139 screenshot evidence and an anatomy description. This is the closed140 set the Phase 3 mapping brief maps onto.141142**The pin-one-reference-page rule (hard).** Real donors run multiple143design systems concurrently — the experiment's donor served radius-4px144/ 1266px on its homepage and pill-radius / 1080px on older product145pages. Consolidating across them produces a chimera no live page ever146shipped, and the token probe then asserts against nothing. When donor147pages disagree on a token, **pin ONE donor reference page per module148family**, record it in `donor-tokens.json` (`curatedFrom` + a note149naming the reference page), and demote the other pages to150corroboration.151152### Phase 2 — CONTENT-MODEL CAPTURE153154The new capability: a **byte-oriented** capture of each content page —155distinct from extract's design-oriented capture. Run the ported156capture script per page:157158```bash159node stardust/scripts/reskin/capture-content.mjs \160 --url <page-url> \161 --scope '<sel1,sel2!,...>' \162 --normalize stardust/reskin/normalize/<slug>.mjs \163 --out stardust/reskin/content-model/<slug>/164```165166It writes `stardust/reskin/content-model/<slug>/content-model.json`167(full contract in `reference/content-model.md`): per section-slot —168headings with levels, eyebrows and other leftovers, body paragraphs,169list items, CTAs with **absolute** hrefs, ordered **visible** images170(`currentSrc` + alt), plus the **ordered stream** (`ordered`) — the171same content as kind-tagged nodes in document order with nesting and172separator flags, the render surface Phase 4 consumes; page-level —173full SEO metadata (title,174description, canonical, OG, Twitter, JSON-LD, lang, favicon), the175whitespace-normalized visible text of the scope (the content-gate176reference string), and a full-page screenshot.177178Two declarations are mandatory per page, because they are the179experiment's top two failure modes:1801811. **CONTENT-ROOT SCOPE declaration.** Naive scoping silently dropped182 30% of the experiment page's content — the hero and a banner183 carousel lived inside `<header>`, outside the obvious `#content`184 root, and byte equality would have "passed" against the incomplete185 reference. Never trust `main` or `#content` blind. Run the186 **scope-discovery procedure** (`reference/content-model.md`187 § Scope discovery): compare the captured scope text against the188 whole-body text and the page screenshot, verify the h1 is inside189 the scope, widen with comma-separated multi-scope selectors until190 everything visible in the screenshot that isn't declared chrome is191 in the model. Chrome exclusions (nav, footer) are declared deltas192 (`D1`-style), not silent omissions. The capture script prints a193 coverage line (`bodyTextLen / scopeTextLen / h1InScope`) to make194 the check mechanical.1952. **EXECUTABLE NORMALIZATION LEDGER.** Cookie-consent chrome removal,196 carousel de-duplication (hidden slides are absent from `innerText`;197 clone slides duplicate it — rotating carousels break byte198 determinism), and any page-specific cleanup live in a per-page199 ledger module `stardust/reskin/normalize/<slug>.mjs` that extends200 the shared default (`scripts/source-normalize.mjs`). The **same201 ledger file is passed verbatim to the capture and to every gate**202 (`--normalize`), so the gate measures exactly the normalization the203 capture declared. The ledger is code, not prose. Format in204 `reference/content-model.md` § Normalization ledger.205206### Phase 3 — MAPPING BRIEF207208Author `stardust/reskin/mapping.md` — the cross-origin mapping brief.209Full contract and entry schema in `reference/mapping-brief.md`. Per210**every** content slot in the content model:211212- assigned donor module id (from `donor-modules.md`'s closed213 vocabulary) + a one-line rationale grounded in slot anatomy vs214 module anatomy;215- status ∈ `{mapped, new-module, chrome, carried-invisible}` —216 the last for sr-only/visually-hidden content inside the byte scope217 (carried verbatim into an equivalent hidden element, no donor218 module; `reference/mapping-brief.md` § Status semantics).219220Gates before any rendering:221222- **≥ 80% of content slots `mapped`** onto named donor modules. Below223 that, the donor vocabulary doesn't cover this content — stop and224 surface (see § Stop conditions).225- **Every `new-module` entry is explicitly composed from donor226 tokens** (name the tokens it borrows: input spec + primary button227 spec, etc.) and listed in the brief's stats block. New modules are228 never silently improvised mid-render.229- **Chrome swaps documented** — nav/footer replaced by donor chrome230 carrying the source's links is a declared delta, excluded from the231 content-gate scope.232- **Casing / text-transform policy declared.** The donor may render233 eyebrows uppercase (or the source may — Chrome's `innerText`234 reflects `text-transform`, so captured text is *rendered-case*).235 The underlying text stays byte-faithful; casing is applied via CSS236 `text-transform` only, never by editing the string. Policy details237 in `reference/mapping-brief.md` § Casing policy.238239Composite source sections (one wrapper div holding two logical240regions) are split into atomic slots in the brief — section ≠ slot.241242### Phase 4 — PROGRAMMATIC RENDER243244The decisive validated rule: **the page is GENERATED from245`content-model.json` — never retyped by hand.** Write a renderer246script per archetype (`stardust/reskin/renderers/<archetype>.mjs`,247modeled on the experiment's `render-reskin.mjs`) that:248249- reads the page's `content-model.json` and interpolates **every**250 visible string, href, and image URL from the model (HTML-escaped) —251 if a string appears in the output HTML but not in the model, the252 renderer is wrong;253- takes each slot's structure from the **ordered stream**254 (`sections[].ordered`, `reference/content-model.md` § The ordered255 stream): kind-tagged nodes in document order, nesting preserved256 (a CTA wrapping its heading and vice versa), `sep` flags marking257 zero-separator inline joins. The stream is **innerText-consistent258 by construction** (its text nodes are sliced from the parent's259 rendered `innerText` — the byte-gate basis), so emitting it260 verbatim is safe. Emit nodes in stream order; emit261 `sep: ""` neighbours with no whitespace between them (inline);262 never reorder, and never reconstruct **order or separators** from263 `visibleText` —264 the first field run did exactly that (per-type arrays matched265 greedily against `visibleText` as an oracle) and burned three266 debug rounds on duplicate identical strings, both directions of267 CTA/container nesting, and zero-separator inline `li` runs, all268 of which the stream now captures structurally;269- renders `formControl` nodes as **equivalent controls**, not text:270 a `<select>` carrying the captured option texts **verbatim, in271 order**; inputs/textareas carrying value/placeholder — restyled272 with donor tokens (the new-module composition rules apply), never273 flattened to prose and never dropped. A select's option text is274 part of the source's `innerText`, so dropping the control fails275 the byte gate with nothing structured to render from276 (`reference/content-model.md` § Slot taxonomy, formControls);277- carries the metadata block verbatim into `<head>` (title,278 description, canonical, OG, Twitter, JSON-LD — including source279 garbage like broken JSON-LD URLs: fidelity over repair; **flag**280 such items for the human in the run report, never silently fix or281 silently keep);282- takes its surface exclusively from `donor-tokens.json` (emitted as283 CSS custom properties) + the donor module patterns assigned in284 `mapping.md`. Use the rendered-page conventions the Phase 5 probe285 asserts against: content in `<main>`, `.container` for the measure,286 `.btn` for the donor primary button (`reference/gates.md`287 § Rendered-page conventions);288- writes `stardust/reskin/pages/<slug>.html`, self-contained.289290Renderers may parse a single node's text apart (split a list-item row291into kicker/date/title) but must **fail loudly** (throw) when a parse292doesn't reproduce the node's text — a silent partial parse is a silent293content drop. A slot whose `orderedVerified` is `false` is inspected294before rendering, never trusted blind; when inspection shows stream295text genuinely absent from the slot's `visibleText`, the296**sanctioned fallback** is to drop that text before rendering and297record the drop in the model's provenance — filtering ghosts against298`visibleText` is the documented resolution, distinct from the299forbidden order-reconstruction move300(`reference/content-model.md` § When `orderedVerified` is false).301302### Phase 5 — GATES303304Three gate families per page; commands, pass bars, tolerances, and305failure modes in `reference/gates.md`. Serve or `file://`-address the306rendered page and run:307308**(a) CONTENT GATE** — all three must pass:309310```bash311node stardust/scripts/reskin/dom-equality.mjs \312 --source <page-url> --source-scope '<declared scope>' \313 --normalize stardust/reskin/normalize/<slug>.mjs \314 --rendered stardust/reskin/pages/<slug>.html --rendered-scope main \315 --report stardust/reskin/reports/<slug>-content-gate.md316node stardust/scripts/reskin/slot-coverage.mjs \317 --model stardust/reskin/content-model/<slug>/content-model.json \318 --rendered stardust/reskin/pages/<slug>.html \319 --report stardust/reskin/reports/<slug>-slot-coverage.md320```321322`dom-equality.mjs` (vendored + adapted from github.com/aemcoder/skills,323Apache-2.0) gates on whitespace-normalized visible-text **byte324equality** and the **ordered, URL-normalized visible-image set**;325element count and tag sequence are reported but informational.326`slot-coverage.mjs` additionally proves every model slot present327(slot text substring, every CTA as a (text, absolute-href) pair,328every image) and asserts metadata carry-over field by field.329330**(b) DESIGN-ADOPTION GATE:**331332```bash333node stardust/scripts/reskin/donor-probe.mjs \334 --tokens stardust/reskin/donor-tokens.json \335 --rendered stardust/reskin/pages/<slug>.html \336 --report stardust/reskin/reports/<slug>-donor-probe.md337```338339Computed-style assertions of donor token values on the rendered page —340bg/fg, font-family token string, display weight, button spec,341container width, band palette, section rhythm. Tolerances (exact for342colors/radius/family; ±2px button padding; ±20px container; ±16px343section rhythm) documented in `reference/gates.md` § Tolerances.344Then the **side-by-side judgment**: open the rendered page next to the345donor reference-page screenshots (`canon-source/assets/screenshots/`)346and judge adoption with your eyes — the probe proves tokens, the347eyeball proves the page *reads as* the donor. Record the verdict.348349**(c) Sanity** — no horizontal overflow at 1440 and 360 (donor-probe350runs this automatically; `--widths` to override).351352**One fix iteration per gate family** — one for the content gate,353one for design-adoption/sanity. On a failure in a family, fix once,354re-run all gates (a fix for one gate can break another). Residual355failures after a family's iteration is spent are logged in356`stardust/reskin/ledger.json` under the page's `residuals[]` — never357silently absorbed, never looped on indefinitely. (Both field runs358support the budget: the validation experiment spent one iteration on359360px chrome; the smoke run spent exactly one per family — a360content-side separator emission fix and a sanity-side 360px chrome361fix. A single shared iteration would have forced a false residual;362per-family keeps the loop bounded without absorbing real failures.)363364### Phase 6 — HANDOFF365366Reskin owns the archetype pages; scale and shipping are the existing367pipeline, unchanged:368369- **Scale via the stardust `migrate` skill** (`../migrate/SKILL.md`) with the370 donor-pinned target (the `state.json.designSource` stamp Phase 1371 wrote is exactly what `direct`/`migrate` read for donor pinning;372 a bounded donor capture wrote no stamp — record the donor origin373 in the handoff instead, `reference/donor-sources.md` § Two374 first-class token-sourcing paths).375 Same-type sibling pages render at the **sibling tier** against the376 gated archetype. Content rules are the ones reskin already377 enforces — `../migrate/reference/content-preservation.md` is378 inherited wholesale.379- **Ship via the stardust `deploy` and `rollout` skills**, unchanged. Blocks380 written for donor modules obey the Experience Workspace editability381 contract (deploy SKILL.md § 8, EW1–EW10) and pass the EW gate382 (`block-roundtrip --ew`) before they are done.383384Reskin writes its own state under `stardust/reskin/` — `ledger.json`385holds per-page status (`captured → mapped → rendered → gated`), gate386results, and residuals. It makes **no changes to the core state387machine** (`../stardust/reference/state-machine.md`); the only388`state.json` touch is the `designSource` stamp that extract itself389writes in Phase 1.390391## What reskin never does392393- **No content rewriting or summarizing.** Not headlines, not body,394 not CTA labels, not casing-by-editing. Faithful ≠ clean: source395 garbage is carried verbatim and *flagged*, not fixed.396- **No invented sections.** Every rendered slot traces to a397 content-model slot; every module traces to `donor-modules.md` or an398 explicit new-module entry in `mapping.md`.399- **No donor-token drift.** No colors, fonts, radii, or spacing400 outside `donor-tokens.json`. Licensed donor fonts are adopted as the401 family *token string* with local fallback, never rebundled.402- **No silent slot drops.** Anything excluded from the gate scope is a403 declared chrome delta or a ledger normalization — declared in404 `mapping.md`, executable in the normalize module.405406## Stop conditions407408- **Donor capture fails** (bot-block past the headed fallback,409 prototypes dir unservable) — surface extract's error verbatim.410- **Mapping ratio < 80%** — the donor vocabulary can't carry this411 content. Ask: widen the donor capture (more donor pages → more412 modules) or accept a larger explicit new-module list.413- **Scope undecidable** — the content page's visible content cannot be414 covered by any selector set (heavy shadow-DOM, iframe-embedded415 content). Surface with the screenshot evidence.416- **Figma donor requested** — future scope; exact message in § Inputs.417418## Outputs419420| Path | Purpose |421|---|---|422| `stardust/canon-source/` | Donor capture + descriptive DESIGN.md/json (extract `--design-source`, unchanged) |423| `stardust/reskin/donor-tokens.json` | Curated probe-able token sheet, pinned to one reference page |424| `stardust/reskin/donor-modules.md` | Enumerated donor module vocabulary (M1..Mn) with screenshot evidence |425| `stardust/reskin/content-model/<slug>/content-model.json` | Byte-oriented per-page content model (+ source screenshot) |426| `stardust/reskin/normalize/<slug>.mjs` | Per-page executable normalization ledger (shared capture ↔ gates) |427| `stardust/reskin/mapping.md` | The mapping brief: every slot → donor module, status, rationale |428| `stardust/reskin/renderers/<archetype>.mjs` | Programmatic renderers (model in, HTML out) |429| `stardust/reskin/pages/<slug>.html` | Rendered reskin pages |430| `stardust/reskin/reports/` | Per-page gate reports + screenshots |431| `stardust/reskin/ledger.json` | Per-page status, gate results, residuals |432| `stardust/scripts/reskin/` | Project-local copies of the five skill scripts |433434## References435436- `reference/donor-sources.md` — the three donor types and their437 capture recipes; pin-one-reference-page; `donor-tokens.json` and438 `donor-modules.md` contracts; the Figma future contract.439- `reference/content-model.md` — the capture contract: slot taxonomy,440 the ordered stream (the render surface), scope-discovery procedure,441 normalization ledger format, provenance.442- `reference/mapping-brief.md` — entry schema, the ≥80% gate,443 new-module composition rules, casing policy.444- `reference/gates.md` — both gates' commands, pass bars, tolerances,445 failure modes, residual logging.446- `../extract/SKILL.md` § Cross-site brand sources — the447 `--design-source` donor-capture path Phase 1 delegates to.448- `../migrate/reference/content-preservation.md` — the content rules449 reskin inherits (and tightens to byte level).450- `../diff/SKILL.md` — the pixel + structural probes; reuse them as a451 supplementary build-side check after Phase 6 deploy, `--profile eds`.