← Files RendemoARCHIVED FILE
skills/sandbox-demos/SKILL.md
24.6 KB · Oct 3, 2026 · 06:24 UTC
---
name: sandbox-demos
description: This skill should be used when creating or editing a SANDBOX DEMO — a hosted, autoplayable demo built from a site Rendemo crawled into a replica — rather than a codebase tour. Use it when the user says "make a demo of <some website>", gives a URL and no codebase, mentions `rendemo_crawl_site`, a crawl, a sandbox or a replica, or is authoring against an existing sandbox demo — and before `rendemo_add_sandbox_demo_step`, `rendemo_list_sandbox_pages`, `rendemo_find_in_sandbox` or `rendemo_check_sandbox_demo`. A sandbox demo writes no markers into customer source and has no lockfile to commit. Tours are reserved for codebase guidance authored with `author-a-tour`. Covers surveying what the crawl actually captured, choosing steps and target elements that survive a rebuild, writing the step card, and verifying before publish.
version: 2.4.0
---
<!-- GENERATED by `npm run skills:generate` from lib/copilot/skills/sandbox-demos/SKILL.md.
Edit that file, not this one. `npm run skills:check` fails the build if they have drifted. -->
# Sandbox demos
A sandbox demo and a codebase tour can both guide a visitor step by step, but they are different
products and are authored almost nothing alike. On a codebase tour you write a `data-rendemo` marker into the customer's source and the
overlay finds it. **A sandbox has no source anyone can edit** — its pages are a build artifact,
extracted from a crawl and served from our storage — so a sandbox step instead records the target
element's identifying *facts*, and a build pass re-resolves those facts against the current markup
and stamps the marker itself, at publish and again after every rebuild.
Everything below follows from that. You are not writing markers; you are choosing elements durable
enough to be re-found later, and you cannot see the page — you are reading an index of it.
`demo-craft` governs how the finished demo behaves: cards, emphasis, pointers, effects, and the
player. Read it too. This skill covers what is unique to a sandbox-backed demo: surveying replica
pages, choosing durable targets, validating them after rebuilds, and publishing the hosted result.
## Ask before you dress it
The look of the demo is the author's call, not yours — and "the author" is the person you are
talking to, not the model's taste. Before styling anything (and ideally while the crawl runs, when
you have their attention and nothing else to do), ask two or three short questions and then commit
the answers with `rendemo_set_demo_presentation_theme`:
1. **Vibe** — offer the two or three themes that fit their product and let them pick. The menu:
- `broadsheet` — editorial, hairline rules, paper tone. Analytics, research, fintech; anything sold on credibility.
- `console` — terminal: monospace labels, hard corners. Developer tools, infra, security, APIs.
- `atrium` — glassy, architectural, mostly negative space. Premium B2B with a design-conscious buyer.
- `ledger` — spreadsheet discipline, tabular figures. Fintech, accounting, insurance, compliance.
- `kiosk` — presentation scale, type sized for a room. Sales calls, trade-show loops, TV playback.
- `sticker` — anti-corporate: solid ink borders, hard offset shadows. PLG, consumer, creator tools.
- `vellum` — the calm one, built for light product UI. Wellness, HR, education, health.
- `signal` — the utility default, tuned for unknown products at unknown embed sizes.
- `marquee` — cinematic: letterboxed, chapter cards, no persistent chrome. Launch films, landing-page autoplay.
- `blueprint` — technical drawing: thin strokes, numbered callouts. Hardware, robotics, logistics, IoT.
2. **Motion** — `motionIntensity`: `calm` (settle, no theatre), `cinematic` (the default sweep), or
`kinetic` (fast, for short attention). One sentence: "calm, cinematic, or fast and punchy?"
3. **Accent** — `accentMode`: `brand` (their colour does the pointing — the usual right answer),
`neutral`, or `contrast`. If they name a hex, that is a brand kit question (`rendemo_design_brand_style`).
Two rules around the questions. **Suggest, don't interrogate**: lead with your read ("your site is
dark and developer-facing — I'd put this in `console` with cinematic motion; want that or something
calmer?") so the person can just say yes. And **never silently default**: `signal` exists for when
they truly don't care, but they get asked once before you decide that.
## Getting the sandbox
**The first step is a question, not a crawl.** Ask the author whether the demo should show anything
behind a login — their dashboard, their account, their data. People rarely volunteer this, and the
public marketing site is rarely the product they actually want demoed. Behind a login (or unsure)
means `rendemo_start_browser_crawl` and the extension capture flow below; public pages only means
`rendemo_crawl_site`.
`rendemo_crawl_site` walks a public site's `sitemap.xml` with headless Chrome and chains a sandbox
build behind it. Two facts govern how you wait:
- **It is slow and the slowness is normal.** 40 pages took 239 seconds in production, and the
sandbox build takes several minutes more. Poll `rendemo_get_crawl_status` every 15–30 seconds and
trust its `elapsedSeconds` over your own sense of how long you have been waiting. Do not conclude
a crawl has failed because it is still running.
- **The sandbox comes back unpublished, and authoring never needs it published.** Every read tool
and `rendemo_add_sandbox_demo_step` work on a draft sandbox. Publishing it puts a replica of someone
else's site on a public URL and spends a slot from the plan's shared published-artifact budget, so
it is a deliberate act with its own moment — see **Publish order**, which is *not* "never".
Re-crawling an existing crawl-sourced project keeps its sandbox id and slug, so pass `projectId`
when refreshing a site that already backs a demo. A fresh crawl of the same URL is a different
sandbox, and demos pointed at the old one keep pointing at the old one.
**Re-crawling a sandbox that is already PUBLISHED does not replace the live pages.** It writes a
*staged* build and leaves the live replica exactly as it was — a re-crawl is a new recording of a
site that may have changed since a human reviewed it, so it cannot reach viewers on its own. The job
reports success and the served pages do not move; that is the design, not a failure. Read the staged
build with `rendemo_review_staged_sandbox` and apply it with `rendemo_promote_staged_sandbox` — see
**Applying a re-crawl** below. (A DRAFT sandbox has nothing to protect, so its rebuild replaces the
replica directly and nothing stages. So does a rebuild of an extension-recorded sandbox, which
re-extracts the very recording a human already reviewed — that is how you apply a new redact
pattern.)
**When the headless crawl cannot reach the pages, drive a browser instead.** A sitemap crawl fetches
as a stranger's bot: it never sees anything behind a login, and it honours `robots.txt`, which rules
out a great many of the products worth a sandbox (`app.superx.so` serves `Disallow: /`). If the
crawl comes back with everything dropped as `robots-disallow`, or with N copies of a sign-in screen,
that is the signal — not a reason to retry it.
`rendemo_start_browser_crawl` hands you a crawl id, a **capture code**, and a `snippet` — and it is
the right call **whether or not you can drive a browser**. Never tell the author a signed-in
walkthrough is impossible from chat, and never send them to a cloud or hosted browser (none can run
the capture snippet):
- **You have no browser tools** (ChatGPT and most clients): call it anyway, give the author the
capture code, and have them paste it into the Rendemo Chrome extension's *Capture pages for a
sandbox* popup mode, signed in, on the site. That paste is their whole job: from then on
`rendemo_capture_pages({ crawlId, url, urls })` opens each url you name in that signed-in tab and
captures it, holding the connection open while it works. Call it again with no `urls` while pages
are in flight; a url refused as a login wall needs them to sign in there first. They can still
click *Capture this page* for anything you would not know to ask for. Then you finish the crawl.
- **You can drive the author's own browser** — their real, local browser, already signed in: per
page — navigate, **let it finish rendering**, then evaluate the snippet with that page's index.
The page uploads itself and returns a small receipt. A cloud or hosted browser you opened
yourself is not that browser: it holds none of their sessions, and Google refuses sign-ins from
automated browsers — asking the author to log in inside one strands them at a login wall.
Either way, close with `rendemo_finish_browser_crawl`, which queues the same sandbox build the
headless path ends in — so `rendemo_get_crawl_status` and everything downstream behave identically.
The settle wait is the part that goes wrong. The recorder snapshots whatever is on screen the
instant it arms, and extraction dedups on that snapshot — so arming before the page has rendered
captures an empty shell, and every empty shell collapses into one. That is the one-page sandbox
failure, and it looks like a successful crawl.
## Knowing what you are working with
Three tools stand in for list, grep and read. Use them in that order; each exists because the one
before it cannot answer the question.
**`rendemo_list_sandbox_pages` — always first.** Returns every page's file name (the exact address
the other two expect), the URL it was recorded from, its size in bytes, and which single page is the
`entry` a visitor lands on. It never opens a page, so it cannot tell you what is *on* one. What it
tells you is the shape of the capture: how many pages, which sections of the site survived, and
where the weight is.
**This list is the whole world.** A page that is not in it does not exist for this demo — not
"harder to reach", genuinely absent. A crawl driven by `sitemap.xml` routinely misses everything
behind a login, everything rendered only after an interaction, and anything the sitemap omits. So
read the list as a statement of what the demo *can* be about, before you have a story in mind. If
the sequence the customer asked for needs a page that was never captured, say so then — not after
five steps are authored.
**`rendemo_find_in_sandbox` — grep one page.** `pattern` is a literal, case-insensitive substring,
never a regex: paste back a button's label or a fragment of an href without escaping anything. It
searches the whole page, not the read window, so it finds text that sits 4,000 lines down. Every hit
carries its line number and a **nodeId** — the sandbox's answer to a marker, and the address a step
names as its target. This is the fastest path from "the pricing page must have a Start free trial
button" to a target.
**`rendemo_read_sandbox_page` — read a bounded slice.** 200 lines by default, 400 maximum, ever: a
captured page can reach several megabytes and 175,000 elements, and returning one whole would be
useless to you and expensive for everyone. Each line is one element's start tag with its attributes
verbatim, indented by nesting depth, plus that element's own leading text and its nodeId. `offset`
is a 1-based line number, and the reply reports `totalLines` plus the offset to resume at.
Reach for `read` when you need **structure** — what surrounds a candidate, whether the thing you
found is the button or a wrapper three levels up — and for `find` when you already know the text.
Paging an entire multi-megabyte page 400 lines at a time is almost always the wrong instinct: read
the entry page's first few hundred lines to learn the site's structural vocabulary (what its nav,
its cards, its buttons look like in markup), then grep the rest by label.
## Choosing steps worth taking
This is the part no tool checks and the part that decides whether the demo is any good. A demo is
not a table of contents for the crawl. Some discipline that survives contact with a real replica:
- **Follow one visitor's path, and start where they start.** The `entry` page is where the demo
opens; a sequence that begins three pages in reads as arriving mid-sentence. Steps may move
between pages — a sandbox step's route is that page's replica address — but each move should be
one a visitor would actually make, in the order they would make it.
- **Every step must answer "and then what?"** The failure mode of an auto-authored demo is a step
per landmark: here is the nav, here is the hero, here is the footer. Those are locations, not
moments. A step earns its place by showing something the visitor would not have understood on
their own — what a control does, what a number means, why this screen is where the work happens.
- **Chrome is not content.** Cookie banners, consent dialogs, newsletter modals, social icons,
footers and legal links all survive a crawl and are all present in the markup. None is ever the
subject of a step.
- **Three to seven steps is a focused demo. Fifteen is a sitemap with cards on it.** Spend the count on the
path that reaches the thing the site is selling.
- **A replica is static.** Nothing you point at changes state: forms do not submit, filters do not
filter, and a link to a page the crawl never captured goes nowhere. Prefer `explain` framing over
a `do: "click"` that promises an outcome the sandbox cannot deliver, and never build a step whose
payoff is a page that is not in the manifest.
## Choosing an element that will still be there
The step's target has to be re-findable after the sandbox rebuilds and every byte of the page
changes underneath it. The server enforces a floor and you should aim well above it.
**The floor, enforced at authoring time:** an element is refused unless it carries at least one of
`id`, `data-testid`, `aria-label`, `name`, `placeholder`, `href`, or visible **text**. A nearby
heading does not count — it is context that breaks a tie between two candidates, not identity. The
refusal says exactly which of these are missing, so a bare `<div>` fails immediately rather than
resolving today and vanishing at the next rebuild.
Above the floor, what actually makes a target good:
- **Point at the control, not its wrapper.** A sandbox demo's geometry comes from the resolved replica element: the highlight is exactly the
element you named, and there is no `rect` to tighten afterwards the way a demo has. A `spotlight`
that reads as vague is almost always a step pointed one or two levels too high. Read the
surrounding lines and pick the `<button>`, not the `<div>` that lays it out.
- **Prefer an id or a test id to text.** Text is real identity and often the only thing available on
a crawled marketing page, but it is also the thing most likely to be edited between crawls.
- **Two steps may not share an element.** Both anchors resolve to the same start tag, only one
marker can be stamped onto it, and the later step reports `claimed` — a real failure with an
obvious fix: retarget one of them.
- **Ambiguity is scored, not guessed.** Two near-identical candidates come back `ambiguous` with the
runner-up's score, which is your signal that the target needs something more specific.
**Node ids are recomputed on every read and never stored.** A nodeId from a read you did before a
rebuild — or before any other change to that page — is not an address any more, and the refusal
message will tell you so. Re-find, then author. Do not carry node ids across a re-crawl.
## Writing the card
A sandbox demo uses the normal demo player and its authored card surface. These render:
`title` · `blurb` · `eyebrow` · `advanceLabel` · `successMessage` · `recoveryHint` ·
`presentation` · `emphasis` · `emphasisColor` · `choices`
Everything else — `variant`, `size`, `width`, `placement`, `image`, `button`, `narration`, `rect`,
`showProgress`, every replay-camera field — saves silently and no visitor ever sees it.
`rendemo_update_step` and `rendemo_style_steps` name the ignored fields in their result rather than
confirming them; read that report instead of assuming the change landed.
`rendemo_add_sandbox_demo_step` takes `title`, `blurb`, `emphasis` and `lens` inline — dress the step as you
add it rather than leaving every step on the bare default and fixing it later. The rest go on with
`rendemo_update_step`, and the card recipe with `rendemo_set_step_presentation`.
How to write the two that matter:
- **`title` names what the visitor is looking at, in their words, not the site's.** It is not the
element's label repeated back — a card reading "Start free trial" beside a button reading "Start
free trial" has spent a step to say nothing.
- **`blurb` earns the step.** One or two sentences carrying the thing that is not visible: what
happens next, what the number means, why this is the screen where the work gets done. If the blurb
can be deleted without the visitor losing anything, delete the step.
- **`advanceLabel` should say what the visitor is about to do** ("See the dashboard") rather than
"Next", on any step where the next step moves pages.
- **`successMessage` and `recoveryHint` only matter on a step with a `do`.** On a static replica most
steps have no `do` at all, and writing a recovery hint for an action nobody performs is noise.
- **`stepType` renders nowhere, and is not inert.** On a sandbox demo it is what gates the camera:
only an `explain` step is eligible for `rendemo_direct_step`'s `shot` / `settleShot`. A step left
`action` stays at 1x no matter how it is framed. Camera moves are worth two or three steps in a
demo and no more.
The mark follows the same scarcity rule as everywhere else: `ring` for most steps, `wash` or
`dim-siblings` for a region, and `spotlight` for the one or two steps the demo exists to reach. A
dim on every step is shouting.
## Verify, then publish in the right order
**1. `rendemo_check_sandbox_demo`.** Re-scores every sandbox-anchored step against the sandbox's
current pages using the same scorer the real stamping pass uses, and writes nothing. A step reported
`resolved` will stamp; `ambiguous` or `absent` will not. Run it after any rebuild or re-crawl and
before publishing. It refuses on a demo with no sandbox-anchored steps, which is itself a useful
signal that you authored `route` steps by mistake.
**2. Publish the source SANDBOX.** Authoring and validation work while it is private, but an external
draft-preview link needs a hosted page to open. Publishing the source makes its replica world-visible
and spends a published-artifact slot, so ask first.
**3. `rendemo_get_sandbox_demo_preview`** when a reviewer is ready to look. The link is a bearer token —
anyone holding it sees the draft without signing in — and it expires 30 minutes after issue. Mint it
when they are ready, not in advance.
**4. Publish the sandbox demo.** The demo has nothing to run on until the replica it points at is live, so
`rendemo_publish_demo` on a sandbox demo returns **409 `sandbox_not_published`** if the sandbox is
still a draft or has been taken down.
**5. `rendemo_review_demo_frames` — actually look at it.** Everything above this line is blind.
`rendemo_check_sandbox_demo` proves a step's target still *resolves*; it says nothing about whether
the card can be read, whether it covers the very element it points at, or whether the emphasis
rendered at all. On a crawl-built demo those are the failures that actually happen, because the card
is landing on someone else's page design rather than one you chose.
This photographs the live demo at each step and hands you the images. Look, fix what is genuinely
wrong, republish — the slug is kept, so the link survives. Two rules: the camera **cannot see blur**
(headless Chrome drops `backdrop-filter`, so a glassy theme photographs flatter than it renders —
never "fix" that), and a step that reads well needs nothing.
Point it at the sandbox DEMO — the project whose steps you authored — not at the source sandbox,
which has no steps to photograph.
Four more refusals, all deliberate, all 422 unless noted:
- **`sandbox_tour_mixed_targets`** — legacy wire code: some steps are sandbox-shaped and some are plain `route` steps.
`rendemo_add_sandbox_demo_step` only writes sandbox-shaped steps, so this normally identifies an
older mixed project. The response names every step and its
shape.
- **`sandbox_tour_mixed_sandboxes`** — legacy wire code: the steps name more than one sandbox. Refused rather than
quietly using the first, because a page id is unique only within one capture, so a step from
another sandbox can coincidentally score against the wrong one's HTML.
- **`sandbox_tour_unresolved`** — legacy wire code: at least one step did not stamp. The issues list gives each
marker, its page and its code; this is exactly what step 1 exists to catch earlier.
- **`sandbox_strip_refused`** (409) — a page carrying a stale marker from a previous demo version
declined the rewrite because removing it would have changed more than the marker attribute. Not a
step failure; a refusal to corrupt the replica.
After any step change the demo must be published again — until then the live demo still describes
the old shape.
## Applying a re-crawl to a published sandbox
A re-crawl of a published sandbox stages instead of replacing (see **Getting the sandbox**), so
there is one extra step and it is deliberately a human one.
**1. `rendemo_review_staged_sandbox`.** Read-only. Says whether a staged build is pending, when it
finished, how many pages it holds, what the redaction pass found and which concerns were raised. If
it reports nothing pending, either the sandbox was a draft (the rebuild already replaced the replica)
or the build has already been promoted.
**2. Actually look.** A redaction report with no concerns is not a review. The detector only flags a
person's name when it sits beside a dollar amount, so an all-zeros report routinely ships with real
names still in the pages — that has happened. Read pages with `rendemo_read_sandbox_page`, or put
the report in front of the person who owns the site, before deciding.
**3. `rendemo_promote_staged_sandbox`.** Copies the staged pages over the live replica; every viewer
sees the new crawl from that moment. It takes `acknowledgeReviewedStagedBuild: true`, which is the
claim that step 2 happened — do not set it on your own judgement of a clean report. Promotion
re-verifies server-side that the staged pages are marked redacted and refuses if they are not.
It publishes nothing and spends no published-artifact budget: the sandbox was already live, this
only changes which build is. **Not reversible** — the replaced build is not kept.
**4. Read what it could not re-point.** A re-crawl shifts every page id, so promotion re-points each
step at the new pages as part of the same call and reports what it managed:
`plans.draftStepsUnresolved` is the count it could not map. Those steps will report `absent` — find
them fresh with `rendemo_find_in_sandbox` and re-target them.
**5. Re-check every demo built on that sandbox, then republish it.** Run
`rendemo_check_sandbox_demo` even when nothing was reported unresolved: re-pointing gets a step onto
the right *page*, and whether its target element is still findable *on* that page is a separate
question only the scorer answers.
Do **not** reach for `rendemo_unpublish_sandbox` → re-crawl → `rendemo_publish_sandbox`. That was
the old workaround and it takes the demo off the air in between.
## Tools
`rendemo_crawl_site` (or `rendemo_start_browser_crawl` → `rendemo_finish_browser_crawl` when the
pages need a signed-in browser) → `rendemo_get_crawl_status` → `rendemo_list_sandbox_pages` →
`rendemo_find_in_sandbox` / `rendemo_read_sandbox_page` → `rendemo_create_sandbox_demo` →
`rendemo_add_sandbox_demo_step` (`sandbox: { demoId, page, node }`) → `rendemo_update_step` /
`rendemo_set_step_presentation` / `rendemo_direct_step` → `rendemo_check_sandbox_demo` →
`rendemo_get_sandbox_demo_preview` → `rendemo_publish_sandbox` → `rendemo_publish_demo` →
`rendemo_review_demo_frames` (look at it, fix, republish).
Refreshing a published sandbox adds one more: `rendemo_crawl_site` (with `projectId`) →
`rendemo_get_crawl_status` → `rendemo_review_staged_sandbox` → `rendemo_promote_staged_sandbox` →
`rendemo_check_sandbox_demo` → `rendemo_publish_demo`.
`sandbox.demoId` is the **source sandbox's** id, never the sandbox-demo project's own — the two are different rows
and passing the wrong one reads as "not found in this workspace".
SHA-256: 131f3c3e869ccb95ac0828ccd5174ff5bb1f1b5be84308376371f0c285ac5b8b