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 from file://. The "send a demo" artifact.
  • site/<flow>/poster.jpg and site/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 ". They are written at generation time, alongside the internal player, and only for a flow that passed.

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#3 opens 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>:3 opens 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.