← Plugin catalog
Creativity

Rendemo

JACOB LAWRENCE GARGARO v1.0.0

Publisher description

From the marketplace listing

Rendemo turns product recordings into interactive demos. Polish and publish the demos you record with the Rendemo browser extension, turn any website into a hosted click-through sandbox demo, and build interactive product tours that run inside your own live app — then embed any of them on your site with one script tag.

Language: English · Automatically detected from descriptions.

Files & skills

File archives

Plugin package7 files · 42.6 KBBrowse files →
Skill instructions
author-a-tour54.9 KB

View saved version →

---
name: author-a-tour
description: 'Use when the user asks to create or change a CODEBASE TOUR inside their own live product, or mentions data-rendemo markers, rendemo.tours.json, rendemo_create_tour, rendemo_add_tour_step, rendemo_list_tours, rendemo_probe_tour_targets, or the tour lockfile. A tour always runs in customer code and requires source markers. A crawled hosted replica is a sandbox demo and must use the sandbox-demos skill instead.'
version: 1.0.0
---

# Author a Rendemo product tour

A **tour** is step-by-step guidance on the user's own live product, which they perform for real.
It always uses `data-rendemo` markers in customer source and a `rendemo.tours.json` lockfile.
Recorded demos and hosted sandbox demos are demos, not tours.

**This skill covers two visits.** Building a tour that does not exist yet is sections 1-10. Changing
one that does — add a step, reword one, reorder them, drop one, retire the whole thing — is section 0
and "Editing an existing tour". `rendemo_list_tours` is what tells you which visit this is, and it is
the first call either way.

**This procedure assumes a repo.** If the user gives a URL or existing sandbox instead of customer
source, they are asking for a sandbox demo. Load `sandbox-demos` and use the sandbox-demo MCP tools;
do not create a tour project as a substitute.

## Do not open with a preamble

Someone who typed "build a tour of our app" has already decided. Opening with an essay on what a tour
is and five things it cannot do spends their attention before they have anything to attach it to, and
it is the single most common way this procedure wastes someone's time.

**Reading their repo needs no permission.** It is free, reversible and invisible. Go and read it. The
first thing in this procedure that is *not* free is writing markers into their source, and that is
where the gate belongs — see section 2, where you hand them a table of real elements in their real
product that they can actually judge. A step table is disclosure someone can act on. An absent-features
list is not.

So the first reply is: one clause of readiness, one clause of what you are about to do, then work.

**Write it from this template rather than composing it fresh.** Prohibitions do not survive contact
with a first turn — "do not narrate the machinery" has been stated twice and still produced *"I'll
start by checking what tours already exist here"*, which is a sentence about your own procedure that
tells the user nothing. A shape is harder to drift from than a rule:

> Signed in to **{workspace}**. Reading the repo for the {intent} sequence…

and, when the request names an audience, one clause more:

> Signed in to **{workspace}**. Worth knowing up front: who sees a tour is a conditional render in
> your own code — Rendemo has no rule engine — so "{their audience}" becomes your app rendering the
> element only when {condition}. Reading the repo for the {intent} sequence…

What is banned is naming your own apparatus: which skill or file you are in, which step of a
procedure you have reached, that you are "checking first", that you are about to hand off. Say what
you **found** and what you are **doing**. `rendemo_list_tours` still runs first — it is just not
something the user hears about unless it changed the answer.

## What a tour is — reference, not a script to read aloud

It runs in the product through one element:

```html
<script src="https://www.rendemo.com/embed.js"
        data-demo="<workspace>/<tour-slug>" data-mode="tour" async></script>
```

That resolves each step's `data-rendemo` marker on the live DOM, draws the same step card the published
demo draws, advances when the viewer does the real thing (`data-rendemo-do`), waits for a target that
has not appeared yet, skips steps whose `route` is not the current page, and resumes on the right step
across navigations and reloads.

**The five limits, and where each one belongs.** Every one is real and a developer rolling this out to
real users needs all of them — but not in one block, and not before they have seen the tour. Each
becomes a sentence that changes a decision they are actually making, at a specific moment:

| Limit | Say it at |
| --- | --- |
| No targeting rule engine — conditional rendering is the primitive; `routes` filters pages, not people | §10 — **unless the request names an audience, and then the first reply** (see below) |
| Progress is `localStorage` — `user` scopes it per browser, never syncs across devices | §10, with the `user` attribute |
| Analytics observe Do-Not-Track and nothing else — no consent API | §7, before publishing |
| Branches are viewer-chosen, never rule-evaluated | §5, and only if they want a fork |
| Kill switch stops it being *served* in 30s; it does not reach open tabs | "Retiring a tour", or whenever they ask how to stop it |

Most of these are already written into those sections. Reaching them early buys nothing: the user
cannot evaluate "no rule engine" before they have seen a step table, and by the time it matters they
will have forgotten you said it.

**The one exception, and it is not rare — it is most onboarding tours.** Deferring the targeting
limit is right for "show me around the app" and wrong for "show this to users who don't have a
recording yet". When the request *names an audience* — anything of the shape "users who…", "trial
accounts", "admins", "first-time visitors", "people who haven't done X" — the limit is not a
footnote about the element, it is a fact about **the thing they just asked for**, and disclosing it
at §10 means they learn at the end that the headline requirement was never Rendemo's to satisfy.

So: if the request names an audience, the first reply says, in one clause, that **who sees a tour is
a conditional render in their own code** — Rendemo has no rule engine and cannot know their users —
and then names the specific condition you will be asking them to write, e.g. "so this becomes: your
app renders the element only when the recording count is zero." Then keep going and scan the repo.
It costs one sentence and it is the difference between a constraint and a surprise.

Two things follow from it that must not be improvised:

- **That conditional is host code, and it is nobody's to check.** `rendemo check` does not see it,
  the lockfile does not describe it, the kill switch does not reach it, and if it regresses the tour
  shows to everyone or to no one with nothing reporting it. Say that once, when you hand the gate
  over — not as a disclaimer, as the reason it belongs in their code review.
- **Do not write it silently.** If the gate needs a component, propose it in the step table alongside
  the markers so it is approved as part of the same change, not slipped in.

The detail behind each, for when its moment arrives:

1. **Targeting: a primitive plus two declarative cases, and no rule engine.**
   The primitive is yours and it is the honest primary answer: **a developer conditionally rendering
   the element is targeting.** If only trial admins should see it, render it only for them — Rendemo
   cannot know your roles and should not try. On top of that, two attributes:
   `routes="/app,/app/projects"` limits which pages a tour may run on at all (re-evaluated on every
   navigation, so it starts when the visitor arrives on one) and `when="always"` replays a tour the
   viewer already finished, for a "Take the tour" button. **Still absent:** any rule engine — no "new
   users only", no per-plan, per-role or feature-flag condition, no percentage rollout. Do not describe
   `routes` as segmentation; it is a page filter.
2. **User identity: opt-in, and it does not sync.**
   `data-user="u_123"` on the tour's script tag keys progress by that id and sends
   it with the analytics events, so two people sharing a browser no longer share one position and
   completion is a fact about a person. The id is **whatever opaque string you choose** — Rendemo never
   resolves it to anybody and collects nothing else about them. Absent, everything behaves as it always
   did, which is what an anonymous marketing page needs. **Still absent:** progress is still stored in
   `localStorage`, so `user` makes it per person *within a browser* — it is not a server-side profile,
   and **switching device or clearing storage still restarts the tour.**
3. **Analytics: it reports to the same place a published demo does.**
   The tour beacons `demo_loaded`, `step_shown`, `step_completed`, `step_dwell` and `demo_completed` to
   Rendemo's existing demo-analytics ingest, over `sendBeacon`, so a step completion is not lost to the
   navigation the step itself caused. Completion rate, per-step drop-off, dwell and abandons all appear
   in that demo's own analytics — no new dashboard to find. A dismissal is recorded as the step's dwell
   reason, and a give-up as `unresolved` with **no** completion. The DOM events (`rendemo:ready` /
   `:step` / `:complete` / `:close`) are unchanged and still the host's hook. **Still absent:** the only
   privacy posture observed is **Do-Not-Track** (which is all the replay player observes either — there
   is no consent API and no cookie banner in this path), and nothing is sent at all when the payload
   has no project to attribute it to. Say this plainly to anyone with a consent regime to satisfy.
4. **Branching: the plan's own choices, honoured.**
   A step can offer up to three author-declared paths (`choices`, set with `rendemo_update_step`), each
   jumping to another step id — forwards, or backwards for a "show me that again". Afterwards the tour
   continues linearly from wherever the branch landed, and progress resumes on the chosen path.
   **Still absent:** branches are **viewer-chosen, never rule-evaluated** — there is no "if they are on
   the Pro plan, go to step 5". And a step whose target never appears still gives up **visibly** after a
   bounded wait (a card saying so, with "Skip this step" and Close) and shows **no** branch buttons:
   branching is author-declared paths, not error recovery.
5. **The kill switch: per demo, per workspace, and 30 seconds.**
   `rendemo_take_demo_offline` (or **Take offline** in the library's ⋯ menu) stops one tour being
   served; **Settings → Product tours** turns off every tour in the workspace at once, which is what
   you reach for when a release has moved the UI out from under every tour's markers. Neither deletes
   anything — turning it back on serves the identical artifact. The payload is cached `s-maxage=30`, so
   propagation is bounded at **30 seconds** rather than the five minutes (plus an hour-long stale
   window) it used to be. **Still absent:** it is a "stop serving it" switch, not a "reach into open
   tabs" one — a visitor whose tour is already running finishes it. Removing the element still works
   too.

Also true: a tour is **not visible in the studio** (the studio needs a capture; a tour has none), and no
replay artifact is built for it — no HTML, no poster, no export, no localization. Say this when they
ask where to look at it; the answer is the **preview link** in step 6.

**The one thing worth raising before the repo scan** — because it is the only one that can make this
whole procedure the wrong answer for them — is that a tour writes `data-rendemo` attributes into their
source and **those must ship to production**. Someone who cannot change the product's code cannot have
a tour, and should hear that in a clause now rather than after you have proposed nine steps. If they
can change their code, this needs no acknowledgement: say it and keep going.

If they want something a visitor **watches** rather than performs for real — a marketing page, a sales
follow-up — that is a published replay demo installed with `<rendemo-demo>`, a different skill
(`embed-a-demo`).

## 0. Is this a first visit or a second one?

**Call `rendemo_list_tours` first, always.** It returns every tour in the workspace with its
`projectId`, slug, step count and whether it is live — replay demos excluded, because they are a
different artifact and a marker step cannot be added to a recording.

**Do not narrate this call.** "First, let me check whether a matching tour already exists" is a
sentence about your own procedure; the user learns nothing from it. Make the call, then say what it
found only if it changes what happens next — an existing tour they might have meant is worth a
sentence, an empty workspace is not.

If the intent is about a tour that already exists, **skip everything below and go to "Editing an
existing tour"**. Name only the constraints their specific change touches.

If nothing matches, this is a first visit: continue from step 1.

## Editing an existing tour

Four changes, and one of them has a trap.

**Reword a step** — `rendemo_update_step({ projectId, stepId, title?, blurb?, … })`. `rendemo_get_plan`
gives you the step ids. The tour renders `title`, `blurb`, `eyebrow`, `advanceLabel`,
`successMessage`, `recoveryHint`, `emphasis`, `emphasisColor` and `choices` and nothing else; the tool
names anything it dropped.

**Add a step** — `rendemo_add_tour_step` appends, so a step that belongs in the middle is *append then
reorder*. It returns the exact attribute to write; write it into source as a reviewable diff, the same
as the first time.

**Reorder** — `rendemo_reorder_steps({ projectId, order })`. Pass the **complete** ordered list of step
ids; a partial list drops the missing ones from the story order. Reordering touches no source at all —
the markers say *where*, the order says *when*.

**Remove a step — the one with the trap.** `rendemo_remove_tour_step({ projectId, stepId })` deletes it
from the plan and returns `markerToStrip` / `attributeToStrip`. **The marker does not remove itself.**
Left in source it becomes an `orphan-marker` and `rendemo check` fails the build for whoever pushes
next — the safety feature turning into a nuisance exactly when someone is backing out of something.
So:

1. Call the tool.
2. In the **same diff**, delete `data-rendemo="<tour>/<step>"` from the element, along with any
   `data-rendemo-do` or `data-rendemo-wait` on it. If the element only existed to be marked — a
   wrapper someone added for the tour — say so and let the user decide whether the element goes too.
3. Show the user that one diff: the step gone, the attribute gone.

The tool refuses two cases rather than producing a broken tour, and both need a decision from you, not
a retry:

- **Another step's branch choice points at this one.** A dangling `goToStepId` is dropped silently by
  the payload builder, so the button would just vanish for the viewer. Edit those choices with
  `rendemo_update_step` first — the message names which steps.
- **It is the last step.** A tour with no steps cannot be previewed or published. Retiring the whole
  tour is a different, deliberate act — `rendemo_remove_tour`, see "Retiring a tour" below.

**After ANY of the four, in this order and without skipping:**

Before preview or publish, call `rendemo_review_step_presentations` and resolve every target,
role, required-content, copy-fit, and responsive finding. This review is a publishing contract,
not optional polish. A first or last action remains an action; never choose Scene Break or
Outcome Stage merely because of sequence position.

1. **Preview** — `rendemo_get_tour_preview`. Same 30-minute bearer link, same rule that the markers
   must exist in the build you point it at. A removal is the case where this matters most: it is the
   only way to see that the tour still reads as a sequence with the step gone.
2. **Publish again** — `rendemo_publish_demo`, after asking. **Until you do, the live tour is the old
   one.** Editing the plan changes nothing a visitor sees.
3. **Regenerate the lockfile** — `rendemo_get_tour_lockfile({ projectId, lockfile })`, passing the
   current `rendemo.tours.json` so other tours survive. Until you do, the committed lockfile still
   describes the steps you changed, and `check` verifies the old shape.
4. **Run the check** — `npx rendemo check` — and report its real exit code.

Skipping 3 after a removal is the specific way to leave CI failing on a marker that is already gone.

## 1. Find the sequence in the code

Read the repo, do not interview the user. You are looking for the ordered sequence of real elements a
new user touches: the entry route, the primary action on it, the route it navigates to, and so on.

Useful evidence, roughly in order of confidence: route files and their paths, `data-testid`
attributes, form submit handlers, primary/CTA button components, existing onboarding or empty-state
copy, and `href`s between the pages.

**In a large repo, delegate the scan to a subagent** and ask it to return only the candidate step list
with `file:line` for each target element. A full-repo read floods the main context and you need that
context for the approval conversation.

## 2. Propose the steps and get approval — mandatory

Present a table before creating anything:

| # | step slug | route | target element (`file:line`) | `do` | what the card says |
|---|---|---|---|---|---|

Rules for the proposal:

- Step slugs and the tour slug are lowercase letters, digits and hyphens, no leading or trailing
  hyphen. An invalid slug is rejected by the tools, not silently fixed.
- `do` is `click`, `type`, or `hover`, and only when that action is what completes the step. Omit it
  and the step waits for an explicit Next. A tour where every step is Continue is a slideshow; aim for
  at least half of them being something the user really does.
- Every target must be an element **that exists in source now**, with the line you found it at. If
  you could not confidently locate a target, say so and leave it out — a marker on a
  nearly-right element points at the wrong thing forever, and an unplaced step is the safer failure.
- **Ask two questions of every target before you propose it**, because both have runtime-only answers
  that `check` cannot give you:
  1. *How many of these are in the DOM at once?* One line of source inside a `.map()` is N elements at
     runtime, and the tour treats an ambiguous marker as a **missing** target. That step needs `match`
     (`"first"`, `"last"`, or a 1-based integer).

     **The precedence, because it decides the commonest case and reads backwards if you guess:
     resolution filters to the elements the visitor can SEE, and only then applies `match`.** So
     `match` disambiguates among visible elements — a hidden duplicate never shifts what `"last"` or
     `nth: 2` means, and a copy in a closed drawer does not make an unmatched marker ambiguous.

     Which settles the **responsive** case, and settles it the opposite way to intuition: a control
     rendered twice for two breakpoints — a `hidden md:flex` sidebar and a phone nav — **can and
     should carry the same marker on both.** One is on screen at a time, so the viewport does the
     disambiguating. Give that step `match: "visible"`: at runtime it behaves exactly as no `match`
     does (which is already correct), and it is what tells the offline `check` that two occurrences
     in source are deliberate rather than a mistake. Do **not** reach for `"first"` here — it passes
     the check too, but it says source order decides when the viewport does, and it silently pins the
     step to whichever branch the bundler happened to emit first.

     Never conclude that a tour must be desktop-only because a nav is duplicated. That is a solved
     shape, and dropping the nav steps costs the user a tour on every phone for no reason.
  2. *Is it visible when the step is reached?* Resolution **prefers a visible match**: among several
     elements carrying the marker it picks the one on screen, and if the only match is invisible it
     treats the step as not-yet-present and keeps waiting, then gives up visibly on timeout. So a tab
     panel toggled with a `hidden` class rather than unmounted no longer anchors the card to a zero
     rect in the corner of the viewport — but it does mean the step shows nothing until the visitor
     opens that tab. Still give it a `recoveryHint` naming how to get there. A `wait` is now optional
     rather than the fix, and is worth adding only when the real precondition is something visibility
     cannot express (a fetch settling, a form becoming valid). Note the visibility test reads whether
     the element renders a box at all, not how big it is, so a legitimately 0x0 icon button resolves.
- **Prefer a target you cannot mark cleanly over restructuring their UI.** If the natural anchor is
  produced by a shared component that does not forward props, either mark a different element or add a
  narrow attribute-only pass-through to that component (Rendemo's own studio needed one for its
  toolbar popovers: a single `marker` prop spread onto the trigger button, not a `...rest`). Never wrap
  the element in a new `<div>` to hang the attribute on — that injects a layout box into their CSS,
  which is the whole reason `demo()` is an attribute spread and not a component.

### Ask the open decisions ONE AT A TIME. "Go" is not an answer to four questions.

A step table almost always surfaces decisions the scan cannot settle: a nav that only exists on
desktop, a shared component that needs a prop to carry a marker, a CI assertion that will go red, a
gate component to restore. It is tempting to write those up as prose and end with "say go" — and what
comes back is `go`, which answers none of them. You then pick defaults for all four, and the user has
made a decision they did not know they were making. That is the single most common way this procedure
ships something the user would have chosen differently.

So:

- **The step table gets one approval.** That is the gate on writing markers, and a yes/no fits it.
- **Every open decision is asked as its own question, with the options named.** If the harness offers
  a structured choice, use it — one round trip, four answers. If it does not, number them and ask for
  numbered answers.
- **Never bundle a configuration question into the approval.** "Say go — and where does your app
  run?" invites a one-word reply that loses the second half.
- **If a decision comes back unanswered, say which default you took and why, in the same breath as
  the work.** A default chosen aloud can be corrected; a default chosen silently cannot.

### Find out where the app runs — and that it ANSWERS — before you write a marker

`baseUrl` is asked for at preview time, which is far too late to discover that nothing is serving it.
The preview is the first moment anything in this procedure needs a running app, and by then you have
written attributes into their source, created a tour, and started a 30-minute clock.

So ask where their app runs as part of the same round trip as the step table, and **confirm something
answers there** before writing markers — a single request is enough. If nothing does, say so and let
them start it. Getting a dev server up can be its own small ordeal (an empty `node_modules` in a
worktree, a build that needs a real install), and it is much cheaper to hit that before the source
edits than between the markers and the preview.

If they cannot run the app anywhere yet, that is fine — say plainly that the tour will be authored
blind and cannot be previewed until it runs, and let them decide whether to continue.

### If you are about to restore something that was deliberately deleted, ask first

Markers, pass-through props and mount components are sometimes *removed on purpose* — a cleanup, a
rollback, a decision the user made last week and has not forgotten. Reading them back out of git and
re-creating them is not a neutral act, and "it was deleted in b503c91" is a fact you already have
from the scan.

Before restoring anything the history shows was deliberately removed, name the commit and ask whether
it should come back. One sentence. If they say yes it costs nothing; if they say no you have avoided
quietly reverting their own decision.

Then stop and wait. **Writing markers modifies the customer's source**, and the tour slug you agree on
is baked into every one of those attributes — renaming it later orphans all of them.

## 3. Create the tour and its steps

- `rendemo_create_tour({ name, tourSlug })` → `projectId`. Every marker for this tour starts
  `<tourSlug>/`.
- `rendemo_add_tour_step({ projectId, step, route, do?, wait?, match?, title?, blurb? })` once per
  step, in order. Each call returns the **exact attribute string** to place. Use what it returns; do
  not compose the attribute yourself.
- `match` is flat on the wire: `"first"`, `"last"`, or a positive integer meaning the 1-based nth
  element. Pass it for every target you answered "more than one" to in step 2 — it is the only way to
  express that, and it cannot be added later by editing `rendemo.tours.json`.
- Two steps cannot share a marker; the tool refuses the duplicate.

## 4. Write the markers into source

Put the returned attribute on the element the step points at:

```jsx
<button data-rd="onboarding/new-project" data-rd-do="click">New project</button>
```

- **`data-rd` and `data-rendemo` are the same attribute** (likewise `data-rd-do` / `data-rendemo-do`
  and `data-rd-wait` / `data-rendemo-wait`). Both resolve identically in the scanner, the runtime and
  the lockfile, and a page may mix them. `rendemo_add_tour_step` returns the short form as
  `attribute` and the long one as `attributeLong` — **match whatever the repo already uses**, and
  prefer the short form only in a repo with no markers yet. Consistency inside one codebase beats
  brevity.
- `data-rd-do` is optional (what completes the step). `data-rd-wait="<selector>"` is optional (a
  selector that must exist before the step is reachable). **Neither is read at runtime** — the
  overlay takes `do` and `wait` from the published plan, and these attributes state the same fact on
  the element so a reader does not have to open the studio. Editing them by hand changes nothing;
  change the step and republish.
- **Markers must ship to production.** They are what the tour anchors to at runtime, on every
  visitor's page. Do not strip them in a production build, and do not put them behind a dev-only flag.
- Rendemo's own repo has a zero-runtime helper, `demo(id, opts?)` in `lib/flow/marker.ts`, that
  spreads the same attributes: `<button {...demo("onboarding/new-project", { do: "click" })}>`. It
  exists **only in a Rendemo checkout or a repo that vendored that module.** In any other repo, write
  the plain attributes — do not import a module that is not there.
- Present the marker edits as a reviewable diff and let the user read it before you continue.

## 5. Author the copy

Tour steps use `rendemo_update_step` for title, explanation, emphasis, and choices. Presentation is
authored through the same registry-backed workflow used by Studio:

1. Call `rendemo_list_presentation_recipes`.
2. Call `rendemo_suggest_step_presentations` for a varied sequence based on each step's job.
3. Present the proposed sequence for approval.
4. Apply it with `rendemo_apply_presentation_direction`; use `rendemo_set_step_presentation` only for
   a focused correction. That tool also owns bounded per-step typography, `textStyles` for each
   visible text block, and semantic `border` controls (treatment, motion, width, radius, two colors,
   speed, and direction). Use animated borders to communicate direction, progress, or completion;
   do not add them to every step.
   Text styles support font family, 10-96px size, weight, alignment, color, italic, and underline. These
   are the same semantic fields written by Studio's direct on-card text editor.
5. Set demo-wide material, density, accent behavior, and motion intensity with
   `rendemo_set_demo_presentation_theme`. The card-to-target `targetBeam` defaults to false; enable
   it only when a connector materially improves target clarity.
6. When the user approves one card's visual styling and wants consistency, call
   `rendemo_apply_step_presentation_style_to_all` with that step as the source. This copies visual
   modules only; do not replace the demo's recipe sequence or content.
7. Run `rendemo_review_step_presentations` and open its signed real-render review URL before publish.

Recipes are compositions, not skins. Beacon, Magnifier, Margin Note, Action Dock, Flowline,
Spotlight, Control Room, Decision Canvas, Proof Stack, Journey Map, Scene Break, and Outcome Stage
have distinct anatomy, responsive modes, copy budgets, motion, and target relationships. Do not
flatten a tour into one repeated recipe when the story changes jobs.

The compatibility matrix is a hard contract. Never assign a recipe to an unsupported step role or
invent a material, motion signature, target
relationship, content block, arbitrary CSS rule, percentage size, or free position. If MCP rejects a
combination, choose a supported combination from the recipe manifest instead of working around it.
Target-aware recipes must keep the measured target clear; full-stage narrative recipes belong on
transition or outcome steps, not click steps.

`choices` is worth authoring on a tour: each is a button on the card that jumps to another step id, so
"are you setting this up for yourself or for a team?" is a real fork rather than a paragraph asking the
viewer to skip ahead themselves. Keep it to genuinely different paths — the tool refuses a choice
pointing at a step that does not exist, and the payload silently drops one pointing at a step the tour
is not serving.

**Say this the first time they want a fork, and only then:** branches are **viewer-chosen, never
rule-evaluated**. There is no "if they are on the Pro plan, go to step 5" — the viewer picks by
clicking. And a step whose target never appears gives up **visibly** rather than taking a branch;
branching is author-declared paths, not error recovery.

What changes a tour's presentation: its recipe, permitted modules, demo presentation theme, and
per-step emphasis. Border motion must communicate direction, progress, target handoff, or completion;
steady states stay calm and reduced motion removes travel and pulsing without collapsing hierarchy.

Copy is Rendemo's, not the repo's: source declares *where and in what order*, Rendemo owns *what it
says*. Do not write card text into the source files.

Write copy that teaches. A title that names the control ("Click a clip to open its step") and a blurb
that says why it matters beats a label. `successMessage` is what the viewer sees when they get it
right; `recoveryHint` is the only thing they get when the step gives up, so it must name where the
control actually is.

## 5b. Probe the targets — before the preview link exists

```
rendemo_probe_tour_targets({ projectId, baseUrl })
```

It fetches each step's route from the running app and reports, per step, whether the tour would find
its marker there. Run it **before** minting a preview link, every time. It costs seconds, spends none
of the link's thirty minutes, and it catches the three failures that otherwise consume a person's
review:

- markers not deployed to the host you are about to point them at,
- a route that renders a sign-in stub, so the step's element is not there at all,
- a marker resolving to several elements with no `match`, which never anchors.

**Read `absent` correctly, and say it correctly.** The probe sees the HTML the server sends a
signed-out stranger. A marker rendered after sign-in, or only on the client after hydration, is
genuinely missing from that response and genuinely present for the real visitor. `absent` therefore
means *look here*, not *broken*.

That distinction is the whole value, so pass it on rather than swallowing it: when you hand over the
preview link, **name the steps the reviewer must be signed in to see.** A reviewer who walks ten
steps and finds six reporting a missing target, with no warning, reports six bugs — and every one of
them costs a round trip to explain away. Told first, they sign in and review ten steps once.

If a step is `absent` for a reason that is *not* auth or hydration, fix it before previewing. A
preview is for judging copy and anchoring; it is not the place to discover the markers are not
deployed.

## 6. Preview the draft — before you offer to publish

**This step comes before publishing, and that ordering is the point.** Publishing used to be the only
way to see a tour, which meant making it live to find out whether it was right. Preview is also the
only thing that can catch bad copy: the lockfile carries no card text, so no offline check can tell you
that a tour's cards say nothing useful.

```
rendemo_get_tour_preview({ projectId, baseUrl })
```

It returns a URL like `http://localhost:3000/projects?rendemo_preview=<token>` — the route the tour's
**first** step expects, on the host you named, with a signed token in the query string. Opening it runs
the **current draft**: nothing is published, no analytics are recorded, progress is kept out of a real
visitor's storage, and every card carries a persistent **"Preview — draft, not live"** badge so a draft
is never mistaken for the live thing.

- **Ask where their app runs.** `baseUrl` defaults to `http://localhost:3000`; a link pointed at the
  wrong host looks exactly like a broken tour. A dev server, a staging deploy and production are all
  valid targets.
- **The link is a bearer token and expires 30 minutes after it is ISSUED — not after it is first
  opened.** Anyone holding it sees the draft, with no sign-in. Two consequences, and the first is the
  one that actually bites: **mint it at the moment the reviewer is ready to look.** Issuing it and
  then running an install, a build, or a deploy spends the window on work the reviewer never sees, and
  they get a link that dies mid-review. Do the probe, get the app running, get yourself to the point
  where the only thing left is a person looking — *then* call this. Second: say the bearer property
  when you hand it over, and re-issue rather than trying to extend one. Re-issuing is free and
  instant; if it lapses while they are reviewing, just mint another.
- **A tour that has never been published previews fine.** That is the case preview matters most for —
  you cannot inspect the first version of a tour by publishing it.
- The tool refuses if no step has a route yet: there would be no page to open. Add the steps first.
- The one gate a preview token does **not** lift is the workspace kill switch. If **Settings → Product
  tours** is off, the preview 404s — an operator who turned tours off was not saying "except drafts".

**THE HONEST LIMIT — say it before you hand the link over.** A preview sends the draft **plan** to a
browser. It cannot send the draft **markup**, which lives in the user's application and not in
Rendemo. So the `data-rendemo` markers have to already exist in whatever build is answering at
`baseUrl`:

- **Dev server:** immediate. Save the file, reload, the step anchors.
- **Staging or production:** the commit that adds the markers has to be **deployed there first**.
  Preview against a build that predates the markers and every step correctly reports a target it
  cannot find — a real failure with a cause that has nothing to do with the tour.

The usual order therefore is: write the markers, preview against the dev server, deploy the markers
with their normal release, then publish.

The same applies to the element itself. `embed.js` reads the preview token off the page URL and acts on
it only where a `<rendemo-demo … mode="tour">` element is actually mounted, so the two lines from
step 10 must be in the build being previewed too. Putting them in the dev build is free; **do not ship
that element to production before the tour is published** — an element pointing at an unpublished tour
renders a visible "This product tour could not be loaded." note to every real visitor of those routes.

Hand over the link, say what to look for (the copy, where each card anchors, whether the `do` steps
advance when they really click), and **wait**. Fix what they report — `rendemo_update_step` and the card
tools take effect on the next load of the same link, because the preview serves the draft itself rather
than a copy of it, and it is never CDN-cached. Only when they say the tour is right do you move on.

## 7. Publish — mandatory checkpoint

Ask before calling `rendemo_publish_demo({ projectId })`. Publishing claims the tour's slug
**workspace-wide** and writes the published plan and its hash. It is the point after which the markers
in the repo and the published tour are contractually tied together — and it is the **last** authoring
step, not the way to see your work.

**This is where the analytics fact belongs, in the sentence that asks.** A live tour beacons
`demo_loaded`, `step_shown`, `step_completed`, `step_dwell` and `demo_completed` into that demo's
existing analytics — and the only privacy posture on that path is **Do-Not-Track**. There is no consent
API and no cookie banner here. Anyone with a consent regime to satisfy needs that before they say yes,
not after; it is one clause, and this is the moment it is actionable.

The publish is validated, and a refusal says what to do. Two of them still need a decision from you
rather than a retry:

- **A validation refusal (422)** lists **each bad step and why** — a missing or malformed target, a
  marker naming a different tour, two steps sharing one. Fix exactly the steps it names, then publish
  again.
- `slug_taken` — another artifact in this workspace already holds that slug. Publish is **refused
  rather than renamed**, because renaming would orphan every marker already committed. Pick a
  different `tourSlug` — which means going back to step 2, since every marker in source changes too.
- `approval_required` — this workspace gates publishes behind review.
- **A locale-pinned tour publish is refused (400).** Never pass a `locale` when publishing a tour.

A successful tour publish reports the demo id, slug, step count and plan hash — and **no URL**, because
there is nothing to open. Carry the plan hash to step 8; there is no link to give the user.

## 8. Write `rendemo.tours.json`

- **Read the repo root's existing lockfile first** — `rendemo.tours.json`, or the older
  `rendemo.flow.json` if that is what the repo still has. If one exists, pass its full text as
  `lockfile` to `rendemo_get_tour_lockfile({ projectId, lockfile })`. **One file describes every tour
  in the repo** — writing a single-tour file silently stops checking the others' markers. The tool
  refuses an unparseable input rather than replacing it, and reports which other tours it preserved.
- Write the returned `contents` to `rendemo.tours.json` at the repo root and commit it. A repo still on
  the old filename should be moved to the new one; `rendemo check` reads either, preferring the new.
- **Never hand-edit this file.** It describes the *published* tour, which is what lets the check run
  offline with no token.

## 9. Verify

The check scans source for markers and confirms every lockfile step resolves to exactly one — offline,
no auth, source-only. Exit `0` pass, `1` step failures, `2` bad or missing lockfile.

Run `npx rendemo check` in the repo you're working in. That is the normal, correct way to run it — the
package is published on npm as `rendemo`, currently `0.2.0`, and this needs no install. **`0.2.0` or
newer is required**, because that is the version that reads the `rendemo.tours.json` name.

**General rule: if the repo you are in has its own package named `rendemo`, `npx` will resolve that
local package instead of the published CLI, and the check will fail oddly** — something like `could
not determine executable to run`, or the shell reporting `rendemo` as an unrecognized command — even
though nothing about the tour or the lockfile is wrong. You cannot know in advance which repo you are
in, so if `npx rendemo check` fails in a way that doesn't look like a real step failure, read the repo
root's `package.json` `name` field. If it is `rendemo`, either install the CLI as a devDependency and
run `./node_modules/.bin/rendemo check`, or use whatever script that repo defines for the check (the
Rendemo repo itself, whose package is named `rendemo-app` and does *not* collide, offers
`npm run tour:check`). If the name isn't `rendemo`, don't assume this is the cause — report the actual
error instead.

`--help` and `--version` both exit 0.

If you cannot run it, say so plainly rather than reporting the tour as verified.

Failures name what to do:

- `missing-marker` — a lockfile step has no marker in source. The output names the exact attribute to
  write.
- `duplicate-marker` — the marker appears more than once **in source** and the step has no `match`.
  Either de-duplicate, or the step needs `match` — which means going back to `rendemo_add_tour_step`
  and republishing, then regenerating the lockfile. Never edit the lockfile to add it.
  The inverse has no failure to name it: a marker inside a `.map()` is one occurrence in source, so
  check passes and the tour then finds several elements and gives up. Only step 2's first question
  catches that.
- `orphan-marker` — a marker in source that no step references. Delete it or add the step. **If you
  find orphans you did not create, do not just mention them.** Reporting "there are six orphan markers
  under `sample-waypoint`, pre-existing, not something I touched" hands someone a problem and no
  handle. Say what they are, then offer the one command that clears them —
  `npx rendemo remove sample-waypoint --dry-run` — and let them decide. It is their repo and their
  call, but the difference between a finding and a fix is one sentence.
- **An unknown-tour warning on a *passing* run** — source has markers for a tour this lockfile does not
  describe, so nothing about that tour is being checked. Regenerate the lockfile, passing the current
  one, unless another team owns those markers. (The CLI still prints this one warning under its
  pre-rename code name and wording. It is the same check, not a different one.)

**The blind spot this check has, which `rendemo_list_tours` can see and the check cannot:** a tour
that is *published* but absent from the lockfile is invisible here. The check only verifies what the
lockfile describes, so a published tour whose markers were stripped from source passes silently —
green CI, and a live tour anchored to nothing. If §0's listing showed a published tour that the
lockfile does not mention, say so; it is a real broken state and nothing else will report it.

### Do not leave the repo knowingly red

If your change breaks something in this repo — a CI assertion that counts steps or tours, a snapshot,
a fixture — **fix it in the same change.** Flagging it twice and fixing it zero times leaves a branch
that fails its own check, and "I'll update that line after you publish" is a promise the user now has
to remember for you.

When a fix genuinely cannot land yet because it depends on an output that does not exist until after
publish (a lockfile, a plan hash), say exactly that, name the file and line, and **come back to it in
the same session** once the dependency exists. Ending the session with it still red is not an option;
if you must, the final report has to lead with it, not bury it.

The check **prints** each tour's `planHash` and cannot verify it — it is offline, so it has no way to
ask whether that is still the published plan. Treat the printed hash as something a human can diff.
There is no staleness detection here; do not tell the user the check proves the tour is current.

## 9b. Offer the CI step — do not just tell them it exists

The check is only a safety net once it runs on every push. You have already written files into this
repo; wiring up the one line that runs the check is the same kind of act and the same kind of diff.

**Detect what they use before offering anything.** Look for, in this order:

| Found | Where the step goes |
| --- | --- |
| `.github/workflows/*.yml` | a `- run: npx rendemo check` step in the existing job, after `checkout` |
| `.gitlab-ci.yml` | a `script:` line in an existing job |
| `.circleci/config.yml` | a `- run: npx rendemo check` step |
| `Jenkinsfile`, `azure-pipelines.yml`, `.drone.yml`, `bitbucket-pipelines.yml` | say you recognised it and offer the equivalent one-liner |
| nothing | offer a minimal GitHub Actions workflow, and say plainly that you are adding CI to a repo that has none |

**Never overwrite an existing workflow.** Show the exact diff — one added line in almost every case —
and get a yes. If a step running `npx rendemo check` is already there, say so and add nothing.

The step itself, for GitHub Actions:

```yaml
      - run: npx rendemo check
```

It needs `actions/checkout` before it and nothing else: no token, no network, no `npm ci`, no Node
version pin beyond what the job already has. It exits 1 with a `file:line` when a marked element is
deleted, which is the entire reason the lockfile exists. Put it **early** in the job — it takes about
a second, and failing there beats failing after a full build.

Two things to say when you offer it, because both change the answer:

- **`npx` fetches the CLI on each run** unless they install it. If their CI is offline or pins
  dependencies, offer `npm install -D rendemo` and `npx rendemo check` instead, and mention that
  **0.2.0 or newer** is required for the `rendemo.tours.json` name.
- **A repo whose own `package.json` is named `rendemo`** shadows the CLI; there the step must be
  `./node_modules/.bin/rendemo check` with the devDependency installed.

If they decline, do not argue. Say what they are choosing: a deleted element silently breaks the tour
for every user, and nothing will tell them.

## Retiring a tour

Taking a tour offline is only half of retiring it. `rendemo_take_demo_offline` is the **kill switch** —
instant, ungated, "stop serving this now", touches no files, and is the right tool when a live tour is
pointing at UI that just moved. **Say its one limit as you use it:** it stops the tour being *served*,
bounded at 30 seconds by the payload's cache — it does **not** reach into a tab where the tour is
already running, and that visitor finishes it. Someone reaching for a kill switch is reaching for it
under pressure and needs to know what it does not cover. But its markers stay in source, so
`rendemo check` then fails the build
with an `orphan-marker` for every one of them: the safety feature turning into a nuisance exactly when
someone is backing out.

**The user can do this without you, and they should be told so once.** `npx rendemo remove <tour-slug>`
strips every marker for that tour from their source and takes its entry out of `rendemo.tours.json`,
offline, with no token and no MCP — so removal keeps working in a checkout with no Rendemo sign-in at
all. `--dry-run` first, `--all` for every tour. It prints the embed element rather than deleting it
(shared layout, their call), leaves test files alone and names them, and exits 1 rather than guessing
at a `demo()` call it cannot remove whole. It does not take the tour offline server-side — that needs
auth — but once the markers and the element are gone nothing is asking for the payload. Requires
**rendemo 0.5.0 or newer**.

Use it when you are removing a tour from a repo you are already working in: it does the tedious half
(finding every marker) in one pass and produces the same reviewable diff you would have written.

`rendemo_remove_tour({ projectId, lockfile })` is the retirement done through the MCP, and is what to
use when the tour must also stop being **served**. It does all three halves in one reviewable change:

1. takes the tour offline (it stops being served within 30 seconds),
2. returns **every** `data-rendemo` marker to strip from source,
3. returns `rendemo.tours.json` with this tour's entry removed and **every other tour preserved** —
   pass the current file's contents as `lockfile`, or it returns only the markers and you edit the
   file yourself. It refuses an unparseable lockfile rather than replacing it.

Then remove the `<rendemo-demo … mode="tour">` element if it was placed for this tour alone, and run
`npx rendemo check`: with the markers gone and the entry gone it passes, and with either half missing
it does not. That asymmetry is the point of doing both in one commit.

**Nothing is deleted.** The plan and the published plan are kept, so publishing again later serves the
identical tour. Say that — "retire" sounds permanent and it is not.

## 10. Install the element

A published, verified tour still shows nobody anything until the element is on the page. If you placed
it in a dev build for the preview in step 6, this is the point at which it is safe to ship it — the
tour is published now, so a real visitor gets the tour rather than the "could not be loaded" note.

Hand the user the two lines and say where they go — the layout or route that the tour's **entry route**
belongs to, so the element is present when the tour starts and stays mounted across the pages the steps
span:

```html
<script src="https://www.rendemo.com/embed.js"
        data-demo="<workspace>/<tour-slug>" data-mode="tour" async></script>
```

`mode="tour"` renders no box and reserves no space — the card is drawn in its own fixed layer. The
element fetches a second script (`/embed-tour.js`) and the tour's payload from
`/site/<workspace>/<tour-slug>/tour`, both public and cacheable. If the tour cannot be resolved the
element says so, in the page and in the console — it never silently renders nothing.

`mode="guide"` is still accepted as a silent alias of `mode="tour"`, because a customer's HTML can be
served from a CDN cache long after `embed.js` updates. Never write it into new code.

The optional attributes, all three of them:

```html
<script src="https://www.rendemo.com/embed.js"
        data-demo="<workspace>/<tour-slug>" data-mode="tour"
        data-user="u_123"                    <!-- opt-in identity; any opaque string you choose -->
        data-routes="/app,/app/projects"     <!-- pages this may run on at all -->
        data-when="always"                   <!-- replay a finished tour; default is once -->
        async></script>
```

Writing the element yourself stays correct where the script tag cannot sit at the right place — a
React layout, a template slot. Every `data-*` above is the same attribute without the prefix, and
`src` aliases `demo`: `<rendemo-demo src="<workspace>/<tour-slug>" mode="tour">`.

Facts a host integrating this will hit immediately, so say them:

- **Mounting is starting.** There is no `open` attribute and no start button; the tour begins in
  `connectedCallback` and tears down on unmount. A host that gates the tour behind a button expresses
  that by rendering the element or not — and that conditional render **is** the targeting primitive.
- **A finished or dismissed tour renders nothing, forever — unless you say `when="always"`.** The
  default is right for onboarding and wrong for a "Take the tour" button; `when="always"` is that
  button, and it restarts a finished or dismissed tour from the top while still resuming a run that is
  genuinely in progress. Reaching into `localStorage` to delete the progress key before mount still
  works and is what `components/StudioTour.tsx` predates this attribute by doing, but it is no longer
  necessary. (That key keeps its pre-rename spelling on purpose — renaming it would restart every tour
  already in progress in every visitor's browser.)
- **`routes` is evaluated per navigation, `user` and `when` at mount.** A `user` swap remounts the
  tour (somebody signing in), so it reads that person's progress rather than the last one's.
- **`user` does not make progress follow anyone.** It keys and tags progress, but the storage is still
  `localStorage` — so it is per person *within a browser*, not a server-side profile. A second device,
  a private window or cleared storage restarts the tour from step one. Say this whenever you hand over
  the `user` attribute; someone reading `user="u_123"` will otherwise assume it syncs, and it does not.
- **There is no rule engine behind any of this.** No "new users only", no per-plan, per-role or
  feature-flag condition, no percentage rollout. Who sees the tour is the conditional render above;
  `routes` filters pages, not people. Never describe it as segmentation.

Do not add it for them without asking: it is a change to a shared layout that affects every visitor of
those routes, and `routes` narrows *where* it runs, not *who* it runs for.

**Say how to undo all of this, in one line, here.** You have just written attributes into their source
and a lockfile into their repo, and "how do we get this out again?" is a fair question to answer
before it is asked rather than after:

```bash
npx rendemo remove <tour-slug> --dry-run
```

It strips every marker for the tour and its lockfile entry, offline, with no token — so it keeps
working whether or not Rendemo is still in the picture. One line. Do not expand it into a paragraph
about kill switches; that belongs in "Retiring a tour", where someone who actually wants it will be.

## Report honestly at the end

State: the tour slug, the steps and their markers, that the user saw the preview and approved it, that
it is published, that the lockfile is committed, whether the check actually ran and its exit code, and
whether the element is installed or still needs to be.

Three more, each of which is a thing the user would otherwise discover later:

- **Anything still red or still owed.** A CI assertion you had to defer, a step you could not resolve,
  a decision you defaulted because it came back unanswered. Lead with it. A report that reads as
  finished while the branch fails its own check is the one failure mode that costs trust rather than
  time.
- **The gate, if the tour is audience-scoped.** Name the component and the condition, and say once
  that it is host code no Rendemo surface checks — not to hedge, but because it is the line in the
  change that needs a human reviewer.
- **Which steps the reviewer had to be signed in to see**, if the probe found any. It explains the
  preview they just walked and stops the same question next time.

**Do not replay all five limits here.** Each was said at the moment it mattered, and a closing recital
of things already disclosed is the same wall of text moved to the end — it reads as hedging a tour you
just shipped. Name only the two that govern what they do *next*, in one line: **who sees it is your own
conditional render** (there is no rule engine, and `routes` filters pages, not people), and **progress
does not follow anyone to a second device**.

The end of this procedure is a live, decay-proof, measured tour for everyone who loads those routes and
is not filtered out by your own conditional render — not a segmented onboarding programme.
drive-and-record9.1 KB

View saved version →

---
name: drive-and-record
description: Use when the user wants a demo of their signed-in product and wants YOU to do the walkthrough rather than record it themselves or tell you which buttons to click — "make a demo of my app and you drive it", "record a demo of my product without me clicking", "walk through my dashboard yourself and record it", "drive my signed-in session and build the demo". The agent REHEARSES the workflow unrecorded on the user's own signed-in tab through the Rendemo Chrome extension, PROPOSES the take as a numbered plan the user approves in chat, then RECORDS that plan as one clean take that auto-directs — no free-roam replica to leak, so it is the right route for a data-dense product (CRM, inbox, leads). Read it BEFORE rendemo_start_drive_session / rendemo_drive / rendemo_propose_drive_plan / rendemo_record_drive_plan: it covers the intake questions to ask first, the paste steps to hand the user, the unrecorded read→decide→act rehearsal, how to write the plan and get a yes, the Record click, and the hand-off to the build pass. A user who would rather click through it themselves wants a plain recording (`new-demo`).
version: 2.0.0
---

<!-- GENERATED by `npm run skills:generate` from lib/copilot/skills/drive-and-record/SKILL.md.
     Edit that file, not this one. `npm run skills:check` fails the build if they have drifted. -->

# Rehearse, propose, record

The user has asked for a demo of their product and they do NOT want to walk it themselves, and they
do NOT want to tell you which buttons to click. You drive their signed-in product tab. What comes
out is a real recording — real screens, real transitions, real data — and because it is a recording
there is no free-roam replica for anyone to wander into, which makes this the SAFE choice for a
product whose screens are full of other people's data: a CRM, an inbox, a leads list.

The shape of the work is the shape a person uses: **rehearse first, then perform.** You explore the
product with the recorder OFF until you know the path. You write that path down as a plan and show
it to the user. Once they say yes, the extension performs the plan as ONE clean take — every step
back-to-back, a beat between them, nothing else. The recording holds the performance, never the
thinking. (The first takes that recorded the thinking were five minutes long with seconds of frozen
screen between clicks and every failed retry in the footage. That is what this shape prevents.)

This is one rung of the capture ladder in `new-demo`. Reach for it when all three are true: the demo
is behind a login, the user wants the agent to do the walkthrough rather than record it themselves,
and you can drive their browser through the extension. If the user would rather click through it
themselves, that is a plain recording (`new-demo`), and it is better when they have it in them.

## Ask first — two or three short questions, then get out of the way

You are about to navigate a product you have never seen toward a goal only the user knows. Ask
before you start. Keep it to three, lead with your read so they can just say yes, and never turn it
into an interview:

1. **What is this demo for?** Selling to prospects, onboarding new users, teaching one feature. The
   answer sets the story shape and the theme, exactly as in `new-demo`'s intent table.
2. **What is the one outcome it should land on?** The screen or moment that makes the case — the
   populated dashboard, the sent campaign, the analytics that prove it works. This is the payoff you
   steer toward; everything in the plan is in service of reaching it.
3. **Anything I must not touch?** A real destructive control, a customer's private record, a live
   send. You already refuse logout/delete-shaped navigation and destructive-looking clicks, but the
   user knows their product's landmines.

Do not ask a fourth question, and do not ask them to plan the steps — the steps are yours to find.

## Starting the session

`rendemo_start_drive_session({ url })` with the signed-in entry url. It returns a code. Hand the
user these steps verbatim — do not paraphrase:

1. Have the Rendemo Chrome extension installed and connected to your Rendemo account.
2. Open your product in Chrome and sign in the way you normally would.
3. Click the Rendemo extension icon and choose **Let my AI drive this tab**.
4. Paste the code, and approve the one-time permission prompt. Chrome will show a "Rendemo is
   debugging this browser" bar — expected; it goes when the session ends.
5. Keep that tab in front and tell me once you are connected. Nothing is recorded yet.

Then wait for them to say they are connected. Your first `rendemo_drive` also tells you whether they
are: a "session has not started" result means the code is not pasted yet — not a stopping point,
call it again with the SAME session. Never mint a second code; that abandons the first.

## Phase 1 — rehearse: the read, decide, act loop, unrecorded

Nothing is recorded here, so explore freely. The rule is simple: **you choose every target; you
never ask the user which one.** Each `rendemo_drive` returns what the page looks like after the
action, including a `read` outline where every button, link and field carries a selector in `«…»`.
That outline is your map.

1. **Read the landing.** `rendemo_drive({ sessionId, url, action: { op: "read" } })`.
2. **Decide the next move toward the outcome** the user named. Pick a target from the outline by
   its selector, or by its visible text.
3. **Act.** `navigate`, `click`, `type`, `scroll`. The extension resolves the target and clicks the
   real element with trusted input.
4. **Read again**, and repeat. Backtrack when a path dead-ends. Note which selectors and texts
   actually worked — those are what the plan will use.

Rehearse until you can name the path from the entry screen to the payoff in five to ten visible
actions, and you have SEEN each of them work. Do not propose a step you have not performed.

When the outline does not show a control you need — a canvas, a JS-only widget with no link or
role — say so plainly and ask the user to click that ONE step themselves, then carry on.

## Phase 2 — propose the plan, and wait for a yes

`rendemo_propose_drive_plan({ sessionId, url, title, steps })`. Each step is a visible action with a
one-sentence `purpose` — what the viewer learns from it — and optionally a longer `settleMs` after
a slow transition. Never a `read`; reads are dead time and the tool refuses them.

The plan is the demo's story, so write it like one:

- **One path to the payoff.** Five to ten steps. A plan that opens every menu is a sitemap.
- **Every step earns its place.** If it does not carry the viewer toward the outcome, cut it.
- **Land on the outcome.** The last step is the screen the user named, held for a beat.
- **Use what worked.** The exact selector or text you clicked in rehearsal, not a guess.
- **Purposes become captions.** Write each one as the sentence a caption would carry.

The tool returns the plan as a numbered list. **Show it to the user and ask them to approve or
change it.** Do not record until they say yes. If they want changes, propose again — the new plan
replaces the old.

## Phase 3 — record the take

Once approved: `rendemo_record_drive_plan({ sessionId, url })`, then tell the user in one line to
click **Record the plan** in the Rendemo extension popup (Chrome needs their click to start the
recorder). The tool holds open through their click and the take; if it comes back "waiting for the
click" or "recording", call it again — not a stopping point. It returns when the take is done and
uploading, with a per-step result. A step that fails stops the take there and says which one; what
was recorded still uploads. Rehearse that step again, fix the plan, propose, and record again after
the user approves.

## Then the build

`rendemo_watch_captures()` — no crawlId — holds open until the project lands and its story director
finishes. If it says nothing yet, call it again. Then `rendemo_get_plan` and the `new-demo` build
pass: each step's caption can start from the purpose you wrote, so the copy is already half done.
Set the theme and motion to match what the demo is for, write the title and end cards, review the
frames, and publish once the user agrees. `demo-craft` governs the step scene.

## Waiting is a tool call, not a turn

`rendemo_drive`, `rendemo_record_drive_plan` and `rendemo_watch_captures` hold the connection open
and return when something happens. When one comes back still-not-done, call it again immediately.
Do not summarise the wait, do not ask the user to check, and do not end your turn.

## Tools

`rendemo_start_drive_session` → (hand over the paste steps) → `rendemo_drive` (`read` → decide →
act, repeated, unrecorded) → `rendemo_propose_drive_plan` (show the list, get a yes) →
`rendemo_record_drive_plan` (they click Record the plan) → `rendemo_watch_captures` →
`rendemo_get_plan` → the `new-demo` build pass → `rendemo_publish_demo`.
`rendemo_finish_drive_session` ends a session early — abandon a rehearsal, or stop a take.
embed-a-demo7.86 KB

View saved version →

---
name: embed-a-demo
description: 'This skill should be used when the user asks to "embed a demo", "add our demo to the pricing page", "install a Rendemo demo", "put the product demo on the site", mentions `<rendemo-demo>`, `embed.js`, or `rendemo_get_embed`, or wants an interactive Rendemo demo rendered inside their own app or marketing site. Sequences the whole install: pick the demo, detect the framework, write the wrapper, place the script tag.'
version: 0.3.0
---

# Install a published Rendemo demo on a site

A **demo** is a recorded replay a visitor **watches** in an iframe. If what the user actually wants is
step-by-step guidance on their own live product, which they **perform for real**, that is a **tour** —
a different artifact and a different skill (`author-a-tour`). Check that before you install anything;
embedding a recording for someone who asked to guide their users is a visible mistake.

This works end to end today. `https://www.rendemo.com/embed.js` is live, the `<rendemo-demo>`
custom element ships `inline` and `modal` modes, and `rendemo_get_embed` returns install code per
framework. Nothing in this procedure is aspirational.

The MCP cannot touch a filesystem. Every tool here hands back **text you write**.

## 1. Find the demo

Call `rendemo_list_projects`. Each project reports `demo: { id, status, protected }` or `null`.

- **Exactly one plausible match** by name and the user's stated intent → say which one you picked and
  keep going.
- **Ambiguous, or several published demos** → list name / step count / status and ask which. Do not
  guess: embedding the wrong demo on a pricing page is a visible mistake on a public page.
- `status` is not `published` → **stop and ask.** `rendemo_publish_demo` makes the demo
  world-visible at a public URL. That is not yours to decide. Offer it, wait for a yes.
- `demo: null` → the project has no demo yet. Publishing is the only path, so the same checkpoint
  applies.
- `protected: true` → **stop before writing anything.** A password-protected demo cannot be embedded
  at all: the access cookie is dropped inside a third-party iframe, so the visitor loops back to the
  password form forever. This is not a caveat to mention afterwards — it makes the whole install
  pointless, and the files you would write are files that can never work. Offer the alternatives
  instead: link to `/d/<id>` or `/site/<workspace>/<slug>` in a new tab, or remove the password.

## 2. Detect the framework — do not ask, look

`rendemo_get_embed` takes exactly these values, and passing the wrong one produces a snippet that
will not compile:

| value | evidence to look for |
|---|---|
| `next-app-router` | `next` in dependencies **and** an `app/` directory containing `layout.tsx`/`layout.jsx` |
| `next-pages` | `next` in dependencies **and** `pages/_app.tsx`/`_app.jsx`, no `app/` router |
| `react` | `react` in dependencies, no `next` |
| `vue` | `vue` in dependencies |
| `svelte` | `svelte` in dependencies |
| `html` | no package.json, or plain static HTML |

Read `package.json` and glob for the router directory. If a repo has both `app/` and `pages/`
(a mid-migration Next app), pick the router that owns the page the demo is going on, and say which
you picked and why.

`mode` is your call from the intent: `inline` for "put the demo on the page", `modal` for
"a Watch-the-demo button". Ask only if the intent genuinely does not say.

## 3. Get the code

`rendemo_get_embed({ projectId, framework, mode })`. It fails with a clear message if the project has
no demo or the demo is not published — that means you skipped step 1, go back.

It returns `scriptTag`, `scriptLocation`, `snippet`, `wrapper`, `url`, `posterUrl`, and `events`.
**Use the returned strings verbatim.** Do not retype them from memory or from this file; the tool is
the single source of the contract and this skill is not.

## 4. Write the files

Three writes, in this order:

1. **The wrapper.** Write `wrapper` to the path in its first-line comment (e.g.
   `components/RendemoDemo.tsx`, `components/RendemoDemo.vue`, `src/lib/RendemoDemo.svelte`).
   `wrapper` is empty for `html` — there is nothing to wrap; skip this step.
   - If the file already exists, **read it first and diff.** A repo that already has a
     `RendemoDemo` wrapper is already installed; you are probably adding a second placement, not
     a second wrapper. Overwriting a hand-adjusted wrapper is a silent regression.
2. **The script tag.** Put `scriptTag` at `scriptLocation`. It must load once per document, not once
   per demo — if the tag is already there, do not add a second one.
   - `scriptLocation` is a starting point, not a law: if the repo already loads third-party scripts
     through a local convention (Next's `next/script`, a `<Scripts>` component, a CMS head block),
     follow that convention and put the same URL there instead. Say what you did.
3. **The snippet.** Put `snippet` where the demo should appear. In modal mode every framework snippet
   except `html` references an `open` state variable — declare it and wire it to whatever button the
   user meant, in that page's own idiom. For `html` the snippet is the bare element; the host
   controls the modal by adding and removing the `open` attribute itself.

## 5. The wrapper rule — this is a hard constraint

A wrapper may do **exactly two things**: forward props to attributes, and bridge the element's
declared events to callbacks. The five events are the whole surface: `rendemo:ready`,
`rendemo:step`, `rendemo:complete`, `rendemo:lead`, `rendemo:close`.

Nothing else. No loading states, no retry logic, no analytics, no visibility heuristics, no
attribute munging.

The reason is not style. The wrapper lives in the customer's repo, where Rendemo cannot patch it.
Anything that belongs in `embed.js` can be fixed for every site at once; the same logic in a wrapper
is frozen until that customer redeploys. If a user asks for behaviour that does not fit those two
things, say that it belongs in `embed.js` and does not go in the wrapper.

## 6. Verify, then report the caveats that apply

- **Typecheck.** In a TypeScript repo, `<rendemo-demo>` is not a known JSX element and the generated
  React wrapper does **not** declare it. Run the repo's typecheck. If it errors on the unknown
  element, add a JSX intrinsic-element declaration for `rendemo-demo` in the wrapper file
  (`declare module "react" { namespace JSX { interface IntrinsicElements { "rendemo-demo": … } } }`)
  — Rendemo's own `components/RendemoDemo.tsx` does exactly this and is the reference.
- **CSP.** If the host page sets a strict `script-src`, it must allow `https://www.rendemo.com` or
  the element never upgrades and its fallback link renders instead of the demo. Grep for a CSP in
  middleware / headers config and say plainly whether you found one.
- **Password protection was already handled in step 1** — `protected: true` stops the install before
  any file is written, because an embedded protected demo can never work. If you reached this section
  with files written, step 1 was skipped.
- **`mode="tour"` is not this skill's job.** It is a real, working mode — but it renders a *tour*,
  anchored to `data-rendemo` markers in the host's own source, with no iframe and no footage. It has
  its own procedure (`author-a-tour`) with approval gates, because it writes into the customer's
  source. Never set `mode="tour"` on an embed you install here: pointed at a replay demo it 404s and
  the element shows a "could not be loaded" note.

## What not to do

- Do not hand-write the snippet, the script tag, or the wrapper. Every one of them comes from
  `rendemo_get_embed`, and a hand-written copy drifts the moment the contract changes.
- Do not pin a version of `embed.js`. There is one URL, no versioning, by design.
- Do not publish a demo to make this procedure work. Ask.
new-demo17 KB

View saved version →

---
name: new-demo
description: This skill should be used when someone asks for a product demo, walkthrough, tutorial, onboarding flow or "show how my product works" and there is no Rendemo project yet — including "make me a demo", "demo my app", "turn this into an interactive demo", "record a walkthrough of X", or a bare product URL with a request to demo it. Read it BEFORE choosing how to capture (a user recording with the Rendemo Chrome extension beats a browser capture code, which beats a headless crawl) and before rendemo_crawl_site, rendemo_start_browser_crawl or rendemo_list_projects on an empty workspace. Covers the two questions to ask first, the intent-to-theme recipes, the extension handoff script to hand the user verbatim, the build pass from auto-directed draft to published link, and translating plain-words feedback into edits — so the user never has to open Rendemo at all.
version: 1.0.0
---

<!-- GENERATED by `npm run skills:generate` from lib/copilot/skills/new-demo/SKILL.md.
     Edit that file, not this one. `npm run skills:check` fails the build if they have drifted. -->

# Starting a demo from a conversation

Someone has asked you for a demo. They have not asked for a project, a capture, a sandbox, a theme
or a publish step — those are our nouns, and every one you make them learn is a reason to give up.
The job is to get from "I want a demo of X" to a live link they can send someone, with the person
doing exactly one thing: showing you their product once.

Assume they will never open Rendemo. Everything below exists to make that assumption true.

**One rule before any of it: waiting is a tool call, not a turn.** Recording, crawling and building
all take minutes, and every tool here that waits on one — `rendemo_watch_captures`,
`rendemo_get_crawl_status` — holds the connection open and returns when something actually happens.
When one comes back still-not-finished, call it again immediately. Do not summarise the wait, do not
ask the user to check, and do not end your turn: nothing on the other side will resume it, and the
work you were waiting on completes into an empty room. A result that says NOT A STOPPING POINT means
exactly that.

## Two questions, then get out of the way

Ask both before any tool call. They are cheap, they are the only things you cannot infer, and each
one decides something you would otherwise get wrong.

**1. What is this demo for?** Not "what should it cover" — what job it does. The answer picks the
story shape, the theme, the motion and the ending, all at once:

| They want | The story is | Open on | End on | Theme candidates | Motion |
|---|---|---|---|---|---|
| To sell it — prospects, outbound, a website | the shortest path to the payoff | a title card naming the outcome | book a demo / talk to us | `atrium` `broadsheet` `kiosk` | `cinematic` |
| To onboard new users | first-run order, nothing clever | wherever a new account actually lands | into the product itself | `vellum` `signal` | `calm` |
| To teach one feature | one screen, deep | the screen the feature lives on | related docs, or back to the app | `signal` `console` | `calm` |
| A launch or landing-page loop | a highlight reel that autoplays | the most striking moment in the product | sign up / join the waitlist | `marquee` `sticker` | `kinetic` |
| To answer "how do I…" for support | the exact task, no detours | the task's entry point | back to support / the next task | `ledger` `vellum` | `calm` |

A theme is not optional polish — **publishing refuses until one is set**, deliberately, so that no
two demos default into looking alike. Intent is how you choose one well instead of guessing.

**2. Does the demo need to show anything behind a login?** Their dashboard, their account, their
data. People rarely volunteer this, and the public marketing site is almost never the product they
meant. It decides the capture route below, and getting it wrong costs a whole crawl.

**Suggest, don't interrogate.** Lead with your read so they can just say yes: "Sounds like a sales
demo — I'd open on the outcome and end on a Calendly link, in `atrium` with cinematic motion. Want
that, or something calmer?" Two questions is the budget. Anything else you would like to know
(accent colour, step count, tone) you should decide, do, and offer to change afterwards.

## The capture ladder — a recording first

Three ways a product gets into Rendemo. They are not equivalent, and the order matters:

1. **A recording** (Rendemo Chrome extension) — the person clicks through their own product and the
   extension records the real thing: rrweb DOM stream plus video, real cursor, real transitions,
   real data, pages behind a login included. Auto-direction runs the moment it uploads. **This is
   the best demo Rendemo can make, and it should be your default ask for anything that is a
   product.**
   - *If they want YOU to do the walkthrough* rather than click through it themselves — "make the
     demo, I don't want to drive" — that is **agent-driven recording**: they paste one code and you
     rehearse the workflow unrecorded on their signed-in tab, propose the take as a plan they approve,
     then record that plan as one clean take. Same real take, same auto-direction; you navigate it.
     See `drive-and-record`. A person who knows their product still tells its story best, so offer
     this as the alternative, not the default.
2. **A browser capture code** (`rendemo_start_browser_crawl`) — the person pastes one code into the
   extension, signed in as themselves, and **you** capture the pages through it with
   `rendemo_capture_pages`. Static replica, no real interaction, but it reaches signed-in pages when
   a recording is not on the table, and costs them one paste. See `sandbox-demos`.
3. **A headless crawl** (`rendemo_crawl_site`) — no human involved at all, public pages only,
   honours robots.txt. Right when the demo genuinely *is* the marketing site, or when nobody is
   available to record.

Reach down the ladder only for a reason. "They are in a chat and I have no browser" is **not** a
reason — the person has a browser, and every route here is one they drive. The reasons that count:
they do not have access to the product, nobody is at a keyboard, or the subject really is a public
website.

### Handing off a recording — give them this, not a paraphrase

Do not explain the extension. Give the steps:

1. Install the Rendemo Chrome extension (https://www.rendemo.com/extension) and connect it to your
   Rendemo account.
2. Open your product in Chrome and sign in the way you normally would.
3. Click the Rendemo extension icon and hit **Record this tab**.
4. Click through what you would show someone — five to eight screens is the sweet spot. Do not
   worry about being smooth or fast; dead air gets cut.
5. Hit **Stop**, then **Send to Rendemo**. (Stop alone does not upload — the take stays in the
   extension until Send.)
6. That is everything — I am watching for it to land, so you do not need to tell me when you are
   done.

Step 5 is the one that gets dropped, and dropping it looks exactly like a broken product: they
believe they have sent a recording and nothing ever arrives. Always name both buttons.

### Then wait — with `rendemo_watch_captures`, not with the user

The moment you have given those steps, call `rendemo_watch_captures`. It **holds the connection
open** for up to two minutes and returns when a recording lands, so waiting is something you do
inside a tool call rather than something you ask a person to announce. It also waits out the story
director, so what comes back is a project with a plan already worth reading.

**If it returns "nothing yet", call it again immediately with the `sinceIso` it gave you.** That
result is not news, and it is not a stopping point. This is the single most common way this whole
flow fails: the agent reports "I'll check back once you've finished recording" and ends its turn —
and nothing checks back, because a chat has no timer and no scheduler. The person finishes a
perfectly good recording into silence. Two or three consecutive waits is a normal recording.

The tool tells you when the situation has genuinely changed: after about ten minutes it stops
telling you to keep waiting and starts telling you to ask whether they pressed **Send to Rendemo**,
because at that point a slow take is no longer the likely explanation.

### What is already happening while you wait

Registering a capture queues two jobs: a prewarm, and a **`direct` pass — the story director**. So
by the time they say "done", the project usually already has steps, captions and a first cut of the
narrative. **Read it with `rendemo_get_plan` before you touch anything.** You are editing a draft,
not authoring from an empty file, and re-authoring what auto-direction already did is the most
common way to make a demo worse and slower at the same time.

### A capture-code session: they paste once, you capture

One route down the ladder the roles flip. The person pastes the capture code from
`rendemo_start_browser_crawl` into the extension, signed in, on the site — and that paste is the
whole of their job. From then on **you** name the pages: `rendemo_capture_pages({ crawlId, url,
urls })` opens each url in that signed-in tab, captures it, and holds the connection open while it
does. Send the product's real screens (the dashboard, a list, a detail view, settings), not the
marketing site, and do not ask them to click anything per page.

Read the result the way you read a watch. It says whether the extension has joined at all — "nobody
has pasted the code yet" is a different situation from "working", and the tool names which — then
lists every url as captured, failed, or still in flight. In flight means call it again with no
`urls`; that is not a stopping point. A url refused as a **login wall** means the tab is not signed
in for that page: ask them to sign in there, then re-issue it. Pages they capture by hand with
"Capture this page" land in the same crawl; `rendemo_watch_captures({ crawlId })` counts everything
that reached storage either way, and is the cross-check before you finish.

There is still no event meaning "finished" — the sandbox has enough when the story does. Call
`rendemo_finish_browser_crawl` then; it verifies against storage rather than trusting the number you
pass. Do not wait for the code to expire.

## The build pass

In order. Each step assumes the one before it.

1. **`rendemo_get_plan`** — read the story that already exists. Decide what to cut before what to add.
2. **Tighten the copy.** Auto-direction names screens accurately and generically. Rewrite titles to
   say what the viewer is looking at in *their* words, and blurbs to carry the thing that is not
   visible. If a blurb can be deleted without loss, delete the step.
3. **`rendemo_set_demo_presentation_theme`** — `themeId`, `motionIntensity` and `accentMode` from
   the intent table, confirmed by their answer. Required before publish.
4. **`rendemo_set_title_card`** — the cover, which now renders as its own beat before step 1. Name
   the outcome, not the product.
5. **`rendemo_set_end_card`** — see below. Never skip this one.
6. **Check it**: `rendemo_director_lint` for geometry, `rendemo_review_step_presentations` for
   whether the cards can actually be read. Lint passing is not evidence a step looks right.
7. **`rendemo_publish_demo`** — world-visible, so confirm once, at this moment, not in advance.
8. **Look at it.** `rendemo_review_demo_frames` photographs the live demo step by step and hands you
   the images. See below — this is the step that catches what the others cannot.
9. **Hand back the URL** and say what you decided that they might want to veto.

### Look at the demo before you call it done

Everything up to here is blind. `rendemo_director_lint` reads geometry and passes while a card is
unreadable, sits over a busy region, or covers the very thing it points at.
`rendemo_render_step_card` draws the real card but over a stand-in shell, so it never sees the card
against the real screen. Every card defect this product has shipped lived in exactly that gap.

`rendemo_review_demo_frames` opens the published demo at each step, waits for the card to finish
arriving, and returns real screenshots as images. Look at them and fix what is actually wrong: copy
you cannot read, a card covering its target, emphasis that vanished into a light UI, text that
clipped. Then republish — same link.

Two rules that decide whether this helps or hurts:

- **The camera cannot see blur.** Headless Chrome drops `backdrop-filter`: the dim paints and the
  blur does not, so a glassy theme photographs flatter and harder-edged than a viewer ever sees it.
  Judge legibility and contrast; never "the glass looks wrong". The tool repeats this in every
  result because it is the one way a visual check makes a demo worse.
- **A step that reads well needs nothing.** The failure mode of a review pass is finding something
  to do. If five frames are fine, say they are fine.

It photographs up to six steps per call and needs the demo published, because it photographs the
real artifact at its real URL. That is why publishing comes first: publish, look, fix, republish —
the slug is kept, so nobody is ever handed a link that breaks in between.

### The end card is the difference between a demo and a video

A demo that stops has spent the whole view and asked for nothing. Before you ask them what the CTA
should be, go and find it — then ask with the answer already filled in:

- Grep the capture or sandbox for a booking or signup link: `calendly`, `cal.com`, `hubspot`,
  `/demo`, `/signup`, `/get-started`, `/contact`, `/trial`.
- Match it to the intent: sales ends on a meeting, onboarding ends *inside* the product, a launch
  loop ends on signup.
- Then: "I found `calendly.com/them/demo` — using that for the button unless you say otherwise."

One question, already answered, is not the same as an interrogation. Publishing warns when a demo
has no ending; treat that advisory as a defect you caused, not a note.

### Say what you chose

When you hand back the link, name two or three decisions the person can reverse: a step you cut,
a mark you chose, the ending you wired. It reads as craft rather than automation, and it is how
they learn what is adjustable without reading a manual.

## Iterating in their words

They will not say "change `scene.mark` to `spotlight`". Translate:

| They say | It usually means | Reach for |
|---|---|---|
| "step 2 is slow" | an unearned step, or a camera move on a step that did not need one | delete the step, or `rendemo_direct_step` |
| "too corporate" / "too plain" | the theme is wrong for who they are | `rendemo_set_demo_presentation_theme` |
| "I can't read that" | a card over a busy region, or an emphasis that vanishes on a light UI | `scene` `scale`/`weight`, or `spotlight` over `ring` |
| "wrong order" | the story, not the capture | `rendemo_reorder_steps` |
| "it just ends" | no end card | `rendemo_set_end_card` |
| "it doesn't look like us" | brand colour, not theme | `rendemo_design_brand_style` |

Then republish. **The slug is kept across republish**, so the link they already sent to someone
keeps working and shows the new version — "same link, already live" is always true, and worth
saying, because everyone assumes otherwise.

## Do not send them to the Studio to fix something you can fix

The Studio exists and is good, and needing it is a failure of this flow. Two exceptions worth
naming out loud: anything that needs their eyes on a frame ("does this look right to you?"), and
anything that needs a credential. Everything else — copy, order, theme, camera, ending, publish,
embed, tracking links — you can do from here.

One real hazard if they do open it: **opening `/projects/<id>` in the Studio while you are mid-pass
can revert your draft edits**, because the Studio autosaves the plan it loaded. Finish your pass,
publish, then invite them in.

## Where to go next

- `demo-craft` — the step scene: cards, marks, ties, entrances, and the rules that silently discard
  a change you thought you applied. Read before any styling pass.
- `sandbox-demos` — everything specific to a crawl-backed sandbox: surveying pages, durable targets,
  publish order.
- `brand-and-look` — brand kits and colour.
- `camera-direction` — zoom, framing and when a move is worth it.

## Tools

`rendemo_watch_captures` (until a recording lands and its direction finishes) → `rendemo_get_plan` →
`rendemo_update_step` / `rendemo_reorder_steps` →
`rendemo_set_demo_presentation_theme` → `rendemo_set_title_card` → `rendemo_set_end_card` →
`rendemo_director_lint` / `rendemo_review_step_presentations` → `rendemo_publish_demo` →
`rendemo_review_demo_frames` (look at it, fix, republish) →
`rendemo_get_embed` / `rendemo_create_tracking_link` → `rendemo_get_demo_analytics`.

No recording available: `rendemo_start_browser_crawl` (signed-in pages, capture code) or
`rendemo_crawl_site` (public site), then `sandbox-demos`.
sandbox-demos24.6 KB

View saved version →

---
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".
Package details

Publisher declarations from the archived package. These are separate from our research and the live service's terms.

Package author
JACOB LAWRENCE GARGARO

Package observed Oct 2, 2026.

Technical details
First seen
Sep 30, 2026 · 22:02 UTC
Last seen
Oct 2, 2026 · 00:00 UTC
Collection status
Collected

plugin_asdk_app_6a8e5c09f7788191b376df6d421b1ca8

Download plugin data (JSON)