Gallery & publishing
The gallery
Point Specreel at a directory of traces and it renders a gallery:
specreel test-results/ -o site/ --bundle
site/index.html— the gallery: one card per flow, each with a demo + test pill (the same artifact), a real end-state thumbnail, duration, and a freshness badge. A filter box (type to match titles) + All / Fresh / Failing quick filters sit above the grid for large galleries.site/<flow>/demo.html— a self-contained player per flow.site/gallery.html— one portable file: the whole gallery + every player inlined, hash-routed, opens straight fromfile://. The "send a demo" artifact.site/<flow>/poster.jpgandsite/og.jpg— the share images a pasted link unfurls with (see sharing & embedding).site/manifest.json— machine-readable index (per-flow stats,public,sig,healed,poster).
Players
Each player has Prev / Play / Next, keyboard arrows, a step list, and:
- a 🔊 read-aloud toggle. Two modes:
- Free / default — narration via the browser Web Speech API (no audio files). It
auto-selects the best natural/neural voice your browser ships (Edge and Chrome
have excellent free ones) instead of the robotic system default, with a small
voice picker to choose. Quality is browser/OS-dependent.
- Studio voiceover (--voice) — pre-render real neural TTS audio per step
(OpenAI TTS, BYO OPENAI_API_KEY) and embed it in the player. When present, the
player plays the studio audio (and the "Play all" tour waits for each clip to
finish) and falls back to the browser voice per step on any gap. See the CLI ref.
- a speed control (1× → 1.5× → 2× → 0.75×, remembered per browser). Each step
already holds long enough to read its caption, narration included; the author's
pace: knob scales every hold (config).
- ⤓ MP4 / ⤓ GIF download links when the build exported them (--mp4 / --gif);
the manifest lists them as mp4 / gif per flow.
- click-to-zoom + drag-to-pan on the frame, to inspect detail (click again to reset);
- in the bundle, a ▶ Play all flows tour that chains every flow back-to-back;
- a Link button that copies a link to the current step, and full screen (or f) — see
sharing & embedding.
(Auto-zoom to the active element per step is still deferred — vanilla Playwright traces don't carry element rectangles, only DOM snapshots; the zoom above is manual.)
Freshness / "updated" badge
On each build, Specreel diffs every flow's step signature against the previous
manifest.json. A flow that changed but still passes gets an amber
⟳ updated badge — the UI moved, the test stayed green, the demo refreshed. A CI
self-healing step can also force it via healed: true in config.
Customer-facing showcase
One build serves two audiences. The gallery above is the engineering view: every
flow, failures front and center, build metadata — a health dashboard. --showcase
(or showcase: true in config) additionally emits
site/showcase/, the customer view: only flows marked public: true that are
passing, with no failure states or test jargon — just titled demos, a search box,
and the one thing worth keeping from CI: a "verified · build N · date" provenance
badge, because "re-verified on every release" is the pitch.
specreel test-results/ -o site/ --showcase
# site/index.html <- full gallery (internal)
# site/showcase/index.html <- curated gallery (customer-facing)
Why a separate directory instead of a filter? The players embed their frames — a failing or internal flow "hidden" with CSS in a shared page would still ship its video bytes, flow names, and error captions to anyone who views source. Curation happens at generation time: what's excluded is absent, not unlinked. For the same reason the showcase directory is rebuilt from scratch each time — a flow that was public last build but is failing or private now doesn't linger.
Semantics:
- A failing public flow is dropped for that build (the CLI says so). If no
public flow passes, no showcase is emitted at all.
- site/showcase/manifest.json lists only what's shown, with no health fields.
- With bundle: true, the showcase also gets its own curated gallery.html.
- The top-level manifest.json gains "showcase": true so tooling (and Specreel
Cloud) knows the build carries one.
The players inside showcase/ are rendered for customers too — from the same
template as the internal players, flipped by a data-mode attribute, so the two
never drift: your product name and logo in place of the specreel wordmark, your
accent color, "Step 3 of 10" kickers instead of CHECK / ACTION / NAVIGATE, no
step-kind pills, an intro that says "Watch the demo", and an outro with your
call to action (showcase_cta_label + showcase_cta_url) as the primary button
— plus the one engineering fact worth keeping, "verified
Brand it with the showcase_* keys (configuration):
headline, tagline, accent color, an embedded logo, a call to action, or a raw CSS
file for full control. For a build with no specreel branding anywhere — the internal
gallery included — set branding: minimal; for pages that must not load Google
Fonts (offline viewers, a strict CSP, GDPR) set fonts: system. Both are in
configuration.
Set it up — pick your path
Open-source CLI (static hosting, you control the URLs):
1. In specreel.yml, set showcase: true and mark the customer-ready flows
public: true.
2. Render: specreel test-results/ -o site/ — the curated render lands in
site/showcase/.
3. Share it, either way:
- Two URLs from one publish — specreel publish site/ --to ghpages serves the
full gallery at / and the curated one at /showcase/; share the latter.
- Public host gets the showcase only —
specreel publish site/showcase --to dir:/var/www/demos, so the internal view
never leaves CI.
Specreel Cloud (one URL, membership does the gating):
1. Same config — showcase: true + public: true flows. Hosted runs honor your
specreel.yml; CLI publishes carry the showcase automatically.
2. Publish (--to cloud) or let a scheduled run do it.
3. Project page → Public gallery view → showcase. Visitors at
/g/<org>/<project>/ now get only the curated render; org members keep the full
gallery at the same URL. Details:
cloud.
Publishing
Deploy a generated gallery to a real URL and get an <iframe> embed snippet:
# GitHub Pages (needs a GitHub remote): clean single-commit gh-pages force-push
specreel publish site/ --to ghpages
# pushed gh-pages -> origin
# URL (enable once: Settings → Pages → Branch: gh-pages /root):
# https://<you>.github.io/<repo>/
# Or copy into any static webroot / synced folder
specreel publish site/ --to dir:/var/www/demos
# Or push to Specreel Cloud — a hosted gallery with a dashboard + analytics
specreel publish site/ --to cloud --project my-app \
--cloud-url https://app.specreel.dev --token scl_xxx
Prefer zero setup? Just send site/gallery.html — it's one self-contained file.
For the hosted option (private galleries, view analytics, no CI), see Specreel Cloud.
To publish automatically on every green build, use the GitHub Action.
Sharing & embedding
A demo link should behave like a video link: unfurl with a picture, open at the moment you meant, and sit quietly inside someone else's page.
Link previews (og:image)
Every render writes a share image next to each page, from frames it already has:
site/<flow>/poster.jpg (the flow's settled end state) and site/og.jpg (a tile of
the gallery's flows — four or more and Pillow installed; otherwise the first passing
flow's poster). The showcase gets its own showcase/og.jpg; manifest.json lists
each flow's poster. Without Pillow the trace's own JPEG frame is the poster.
Unfurlers need an absolute image URL, so the og:image tag is only written when
Specreel knows where the gallery will live: pass --url https://… or set public_url:
in specreel.yml. Without one the pages keep a text-only card.
Specreel Cloud sets it at serve time for every hosted gallery, review link and
custom domain — nothing to configure.
Deep links
site/<flow>/demo.html#3opens the player at step 3 (the intro is skipped — the sender meant that step).site/gallery.html#<flow-slug>opens a flow in the bundle;#<flow-slug>:3opens it at step 3.- The Link button in the control bar copies the link to the current step (embed parameters stripped).
Embed parameters
Append to a player URL (they combine):
| Parameter | Effect |
|---|---|
?autoplay=1 |
Skip the intro and start playing. Starts muted — browsers block sound before a click, and an embed shouldn't talk unasked; the 🔊 toggle still works. |
?loop=1 |
Replay from the first step instead of showing the outro. In the bundle the Play-all tour loops too. |
?chrome=0 |
Hide the header, step list and controls — just the frame, its caption and a slim progress bar. For an <iframe> on a landing page. |
A video-style embed of one flow (what specreel publish prints for your URL):
<iframe src="https://acme.github.io/app/signup/demo.html?autoplay=1&loop=1&chrome=0"
width="800" height="500" style="border:0;border-radius:12px"
title="Sign up" loading="lazy" allowfullscreen></iframe>
allowfullscreen lets the full-screen button work inside the frame.
Full screen and keys
The full-screen button (or the f key) makes the frame full screen; Esc leaves
it. Arrow keys step,
space plays/pauses, a click on the frame plays/pauses, a double-click zooms.
Notifications
Post a build summary to Slack (BYO incoming webhook):
specreel test-results/ -o site/ --notify "$SLACK_WEBHOOK" --url https://acme.github.io/app/
or set notify_webhook: / public_url: in specreel.yml, or SPECREEL_SLACK_WEBHOOK.