Build Escourtly
KINDAreplaces $49/mosaves $588/yrback to the verdict
A demo recorder built on DOM snapshots, whose hard problems are capturing enough of a page that the replay renders without calling home, masking by default rather than by accident, and replaying untrusted captured markup without letting it run.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Builds the extension and runs the web application.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally, a few pounds a month managed
Why Snapshots are large documents and you will keep more versions of them than you plan to, which is a database question rather than a file question.
Get it Docker, your package manager, or a managed instance. open ↗
Verify
The application connects and migrations run - installfree
Why The capture half only runs as an extension, and the whole loop is untestable until one is loaded.
Get it Chrome, then enable developer mode on the extensions page.
Verify
The unpacked extension loads and its indicator appears on a page - have readyfree
Why Capture fidelity and masking are only testable against a real signed-in application, and it must be one you have permission to record.
Get it Your own product in a staging environment, seeded with obviously fake data.
Verify
A signed-in page captures and replays with the origin blocked - decidefree
Why Both concern other people's data appearing in your database, and both are decisions that quietly default to the worst option if nobody makes them.
Get it Write down what is masked by default, who may unmask, and how long unpublished captures are kept.
Verify
All three are in the configuration and the README
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- demos(id, slug, title, status, created_at, published_at, cta_label, cta_url) · slug is generated, not taken from the title - steps(id, demo_id, ordinal, snapshot_ref, target_selector, target_box_json, title, description, reviewed_at) - snapshots(id, document_compressed, bytes, asset_count, inline_failures_json, captured_url_host) · the host, not the full URL, because the path often carries identifiers - assets(snapshot_id, original_url, kind, bytes, inlined, failure_reason) · what got inlined and what did not, so a blank replay is diagnosable - redactions(step_id, selector, kind, applied_at) · every unmasking and every extra mask, recorded as an action somebody took - views(demo_id, session_hash, started_at, last_step, completed_at, cta_clicked_at) · first-party, no third-party script anywhere - A published demo is a set of frozen documents. Nothing in the replay path talks to the product that was captured, which is what makes it survive a deploy.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://user:redacted@localhost:5432/demos | Snapshots and analytics. Snapshots are the sensitive part. |
PUBLIC_BASE_URL | required | https://demos.example.com | Where published demos live. Used in embed snippets. |
SNAPSHOT_MAX_MB | required | 8 | Per step, after inlining. A capture over this is refused with the largest asset named. |
EMBED_FRAME_ANCESTORS | required | https://www.example.com https://example.com | Who may embed a demo. Empty means nobody but you. |
SLUG_BYTES | required | 12 | Randomness in a share link. The link is the access control. |
DRAFT_RETENTION_DAYS | required | 90 | Unpublished captures expire, because a draft is still captured data. |
LLM_API_KEYsecret | optional | sk-...redacted | Optional first-draft step descriptions. Without it you write them yourself and nothing else changes. |
The build, in order
Capture with masking on from the first click
The extension records a snapshot per click with input values and text masked by default.
The defaults mask passwords; your users' data is not a password.
Files
extension/record.tstest/masking.test.tsThe hotspot in the replay is only as good as what you captured about the click.
Files
extension/clicks.tsAny capture tool without a visible indicator is a surveillance tool by accident.
Files
extension/indicator.ts
done when · tick each as it passeswatch out- Recording with the library's defaults masks passwords and nothing else, so the first capture of a real admin screen carries live customer names and addresses into your database, where they are now your responsibility and your breach if it leaks. Set the masking options explicitly at capture, and write a test that captures a page of sample data and asserts none of it appears in the stored document.
Snapshots that render without calling home
Stylesheets, fonts and images are inlined, and whatever fails is recorded rather than silent.
The replay has to be self-contained or it is a screenshot with extra steps.
Files
extension/inline.tsA blank replay with a reason is a bug; one without is a mystery.
Files
src/snapshots/assets.tsSnapshots grow without limit and the ceiling is what keeps the database usable.
Files
extension/limits.ts
done when · tick each as it passeswatch out- A snapshot that still references the origin's asset host renders perfectly on your machine, where you are signed in and inside the network, and renders as unstyled text for the prospect outside it, or three weeks later when the asset hash changes on a deploy. Inline everything at capture, record the failures, and refuse to publish a step whose stylesheet did not inline.
A review screen nobody can skip
Every captured text node is seen by a person before the demo can be published.
The gate is the feature; a checklist in the documentation is not.
Files
src/review/gate.tstest/review-gate.test.tsSomeone decided to reveal that. The record says who and when.
Files
src/review/redactions.tsPeople skim a page and read a list.
Files
src/review/TextList.tsx
done when · tick each as it passeswatch out- Making review optional means it happens for the first demo and never again, and the demo that leaks a customer name is always the sixth one, made in a hurry before a call. Gate publishing on a review timestamp per step, and reset every timestamp when a step is recaptured.
Replaying captured markup without running it
The snapshot renders as a page with a working hotspot and none of its scripts execute.
Captured markup is input from another application. Treat it that way.
Files
src/replay/Frame.tsxA demo nobody can drive with a keyboard excludes people for no reason.
Files
src/replay/Hotspot.tsxA blank frame reads as a broken product, which is the opposite of the point.
Files
src/replay/Fallback.tsx
done when · tick each as it passeswatch out- Turning on script execution in the replay frame to make an interactive widget look right removes the protection that stops captured markup from running, and the library's own canvas replay option carries exactly that warning about adding script permission to the frame. Keep scripts off, accept that some widgets render as static, and say so in the product rather than trading a sandbox for a hover effect.
Sharing, counting and deletingproduct builder
A demo has a link, an embed, first-party counts and a deletion that really deletes.
The link is the access control, so it has to be worth something.
Files
src/publish/slug.tsYou decide which sites can frame your demo, not the internet.
Files
src/publish/headers.tsAn orphaned snapshot is the captured data you promised to remove.
Files
src/demos/delete.tstest/orphans.test.ts
done when · tick each as it passeswatch out- A demo slug derived from the customer's name, published with no login, tells anybody who guesses it which prospects you are talking to, and slugs derived from titles are guessable by design. Generate slugs with enough randomness that guessing is impractical, and make unpublish take effect immediately rather than at the next cache expiry.
That is the whole plan for Escourtly. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Custom domains and certificates.
- CRM synchronisation.
- Canvas, video and cross-origin frame capture.
- Lead capture forms inside the demo.
- capture fidelity on canvas, cross-origin iframes, shadow DOM and lazily loaded assets
- no-login share links and embeds that hold up under real traffic
- viewer funnel analytics and lead capture
- CRM sync to Salesforce, HubSpot and Pipedrive
- custom domains with certificates handled for you
- Branching walkthroughs, once single-path replay has survived real prospects
- A guided tour overlay on the live product, which is a different tool sharing the step model
- Per-step timing in the analytics, which tells you where people stop rather than that they did
Need the files? The project pack on the verdict page hands your agent the whole brief · more screen recording.