Files
hyperframes/docs/public/catalog-index.json
T
Miguel Ángel 7affa4a4e9 fix: handle player loop and render exit (#617)
## Problem

Two newly reported runtime issues break common local workflows:

- Fixes #615: `<hyperframes-player loop>` reaches the final frame, receives a paused runtime state, and stays paused instead of wrapping.
- Fixes #616: `hyperframes render` can finish writing the output and print `Render complete`, but still remain alive when a non-essential handle keeps Node's event loop open.

The catalog block also used old VPN branding and slug/file names that should now be neutral. Renaming registry items also exposed a catalog-preview CI bug where deleted registry paths were treated as still-renderable changed items.

## What this fixes

- detects player completion from the previous playing state before mutating the parent `_paused` cache from the runtime's final state
- wraps looping players back to `0` and immediately resumes playback even when the runtime posts `isPlaying: false` at the end frame
- keeps non-looping players dispatching the existing `ended` flow
- lets the CLI command path schedule a short unref'd `process.exit(0)` after a successful local or Docker render
- keeps `renderLocal()` importable for tests and internal callers without forcing process exit unless the CLI command explicitly opts in
- adds regression coverage for the player loop end-state and successful render exit scheduling
- renames the VPN catalog block to `vpn-youtube-spot` across registry, docs route, install command, composition filename, asset filename, composition id, and timeline key
- keeps visible block/app copy friendly and named `VPN`
- updates catalog-preview CI to ignore deleted registry paths when computing changed preview items

## Root cause

The player message handler updated `_paused = !data.isPlaying` before checking for end-of-composition loop behavior. The runtime's legitimate final-frame state has `isPlaying: false`, so the existing `currentTime >= duration && !paused` loop branch was skipped.

For render completion, the CLI returned after `printRenderComplete()`, leaving process lifetime entirely to Node's active handles. Most local renders in this checkout drain cleanly, but the reported npm flow shows a sleeping parent process after output is already complete. The CLI now schedules a short unref'd successful exit only from the command path after user-visible render work has completed.

The catalog block issue was content/metadata drift: registry/docs/code identifiers still used the old slug, so the catalog route, install command, composition id, file names, and source prompt did not match the requested neutral VPN naming. The preview workflow used plain `git diff --name-only`, which includes deleted paths during renames; it now filters to added/copied/modified/renamed live paths.

## Verification

### Local checks

- `bun run build:hyperframes-runtime`
- `bun run --filter @hyperframes/player test -- src/hyperframes-player.test.ts`
- `bun run --filter @hyperframes/cli test -- src/commands/render.test.ts`
- `bun run --filter @hyperframes/player typecheck`
- `bun run --filter @hyperframes/cli typecheck`
- `bunx oxfmt --check packages/player/src/hyperframes-player.ts packages/player/src/hyperframes-player.test.ts packages/cli/src/commands/render.ts packages/cli/src/commands/render.test.ts`
- `bunx oxlint packages/player/src/hyperframes-player.ts packages/player/src/hyperframes-player.test.ts packages/cli/src/commands/render.ts packages/cli/src/commands/render.test.ts`
- `bun run --filter @hyperframes/player build`
- `bun run --filter @hyperframes/studio build`
- `bun run --filter @hyperframes/cli build`
- `bunx oxfmt --check registry/blocks/vpn-youtube-spot/vpn-youtube-spot.html registry/blocks/vpn-youtube-spot/registry-item.json registry/registry.json docs/catalog/blocks/vpn-youtube-spot.mdx docs/docs.json docs/public/catalog-index.json`
- `bunx oxlint registry/blocks/vpn-youtube-spot/vpn-youtube-spot.html registry/blocks/vpn-youtube-spot/registry-item.json registry/registry.json docs/catalog/blocks/vpn-youtube-spot.mdx docs/docs.json docs/public/catalog-index.json`
- `bunx oxfmt --check .github/workflows/catalog-previews.yml`
- `BASE_SHA=26b8e2a9853eb1a8f77c05fb0c8f0903cdb2cf18; git diff --name-only --diff-filter=ACMR "$BASE_SHA"...HEAD -- registry/blocks/ registry/components/ ...` returns only `vpn-youtube-spot`
- `npx tsx scripts/sync-schemas.ts --check`
- `npx mint validate` from `docs/`
- `npx mint broken-links` from `docs/`
- `git diff --check`
- Lefthook pre-commit: format pass
- Lefthook commit-msg: commitlint pass

### Browser verification

- Built the player bundle and served a real local reproduction using the built player, the built HyperFrames runtime, and GSAP.
- Used `agent-browser` to open the page, click `Seek near end`, and wait through the end-frame transition.
- Verified the browser state after playback: `stuck=false`, `looped=true`, and playback continued after wrapping from ~4s back to the start.
- Served `registry/blocks/vpn-youtube-spot/vpn-youtube-spot.html` locally, used `agent-browser` to seek the timeline, and verified `window.__timelines` contains `vpn-youtube-spot`, not `goonvpn-youtube-spot`.
- Served the docs locally with Mintlify, opened `/catalog/blocks/vpn-youtube-spot`, and verified the install command is `npx hyperframes add vpn-youtube-spot` with no old slug visible.

### Composition verification

- `bun run --filter @hyperframes/cli dev lint /var/folders/3n/hxk3qmnd0tl284jtcy66w6dw0000gn/T/hf-vpn-renamed-w027if` returned 0 errors and 1 existing large-composition warning.
- `bun run --filter @hyperframes/cli dev validate /var/folders/3n/hxk3qmnd0tl284jtcy66w6dw0000gn/T/hf-vpn-renamed-w027if --timeout 5000` returned 0 console errors; it reported existing non-fatal contrast audit warnings from the block styling.
- `bun run --filter @hyperframes/cli dev render /var/folders/3n/hxk3qmnd0tl284jtcy66w6dw0000gn/T/hf-vpn-renamed-w027if --output /tmp/hf-vpn-renamed-proof.mp4 --fps 30 --quality draft --workers 1 --no-browser-gpu` completed successfully.
- `ffprobe -v error -show_entries format=duration,size -of default=noprint_wrappers=1 /tmp/hf-vpn-renamed-proof.mp4` reported `duration=7.000000`.

### Render verification

- Ran a real 1920x1080, 5-second render with `--gpu --workers 6 --quality draft --fps 24`.
- Verified the command printed `Render complete` and the parent process exited with code `0` in the wrapper: `RENDER_EXIT_PROOF code=0 signal=null sawComplete=true`.

## Notes

- I could not reproduce the exact indefinite #616 render hang on this checkout; both tiny and GPU/6-worker local renders exited cleanly before and after the patch. The CLI guard still addresses the reported leaked-handle failure mode because it fires only after successful render completion.
- Browser proof artifacts were local-only: `/tmp/hf-player-loop-proof-final.png`, `/tmp/hf-player-loop-proof-final.webm`, `/tmp/hf-vpn-code-rename-proof.png`, `/tmp/hf-vpn-code-rename-proof.webm`, `/tmp/hf-vpn-doc-route-rename-proof.png`, and `/tmp/hf-vpn-doc-route-rename-proof.webm`.
- The renamed composition render artifact was local-only: `/tmp/hf-vpn-renamed-proof.mp4`.
- The CLI exit guard is only enabled by the `render` command's top-level local/Docker calls. Direct test/internal calls to `renderLocal()` do not force process exit unless they pass `exitAfterComplete: true`.
2026-05-04 23:31:30 +02:00

584 lines
18 KiB
JSON

[
{
"name": "app-showcase",
"type": "block",
"title": "App Showcase",
"description": "Fitness app product showcase with three floating smartphone screens",
"tags": [
"showcase",
"app",
"3d"
],
"href": "/catalog/blocks/app-showcase",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/app-showcase.png"
},
{
"name": "apple-money-count",
"type": "block",
"title": "Apple Money Count",
"description": "Apple-style finance counter that counts from $0 to $10,000, flashes green, and bursts money icons with sound.",
"tags": [
"showcase",
"finance",
"kinetic",
"youtube",
"sfx"
],
"href": "/catalog/blocks/apple-money-count",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/apple-money-count.png"
},
{
"name": "chromatic-radial-split",
"type": "block",
"title": "Chromatic Radial Split",
"description": "Shader transition with chromatic aberration radial split",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/chromatic-radial-split",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/chromatic-radial-split.png"
},
{
"name": "cinematic-zoom",
"type": "block",
"title": "Cinematic Zoom",
"description": "Shader transition with dramatic zoom blur",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/cinematic-zoom",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/cinematic-zoom.png"
},
{
"name": "cross-warp-morph",
"type": "block",
"title": "Cross Warp Morph",
"description": "Shader transition with cross-warped morphing",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/cross-warp-morph",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/cross-warp-morph.png"
},
{
"name": "data-chart",
"type": "block",
"title": "Data Chart",
"description": "Animated bar + line chart with staggered reveal, NYT-style typography, and value labels",
"tags": [
"data",
"chart",
"statistics"
],
"href": "/catalog/blocks/data-chart",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/data-chart.png"
},
{
"name": "domain-warp-dissolve",
"type": "block",
"title": "Domain Warp Dissolve",
"description": "Shader transition with fractal noise domain warping",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/domain-warp-dissolve",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/domain-warp-dissolve.png"
},
{
"name": "flash-through-white",
"type": "block",
"title": "Flash Through White",
"description": "Shader transition with white flash crossfade",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/flash-through-white",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/flash-through-white.png"
},
{
"name": "flowchart",
"type": "block",
"title": "Flowchart",
"description": "Animated decision tree with SVG connectors, sticky-note nodes, cursor interaction, and typing correction",
"tags": [
"diagram",
"flowchart",
"interactive"
],
"href": "/catalog/blocks/flowchart",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/flowchart.png"
},
{
"name": "glitch",
"type": "block",
"title": "Glitch",
"description": "Shader transition with digital glitch artifacts",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/glitch",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/glitch.png"
},
{
"name": "vpn-youtube-spot",
"type": "block",
"title": "VPN YouTube Spot",
"description": "Snappy Apple-style YouTube insert showing a phone finding and installing a friendly VPN app with sound effects.",
"tags": [
"app",
"showcase",
"youtube",
"sfx"
],
"href": "/catalog/blocks/vpn-youtube-spot",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/vpn-youtube-spot.png"
},
{
"name": "grain-overlay",
"type": "component",
"title": "Grain Overlay",
"description": "Animated film grain texture overlay using CSS keyframes — adds warmth and analog character to any composition",
"tags": [
"texture",
"grain",
"overlay",
"film"
],
"href": "/catalog/components/grain-overlay",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/grain-overlay.png"
},
{
"name": "gravitational-lens",
"type": "block",
"title": "Gravitational Lens",
"description": "Shader transition with gravitational lensing distortion",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/gravitational-lens",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/gravitational-lens.png"
},
{
"name": "grid-pixelate-wipe",
"type": "component",
"title": "Grid Pixelate Wipe",
"description": "Transition effect where the screen dissolves into a grid of squares that fade out with staggered timing — use between scenes",
"tags": [
"transition",
"wipe",
"grid",
"pixelate"
],
"href": "/catalog/components/grid-pixelate-wipe",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/grid-pixelate-wipe.png"
},
{
"name": "instagram-follow",
"type": "block",
"title": "Instagram Follow",
"description": "Animated Instagram follow overlay with profile card and follow button",
"tags": [
"social",
"overlay",
"instagram"
],
"href": "/catalog/blocks/instagram-follow",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/instagram-follow.png"
},
{
"name": "light-leak",
"type": "block",
"title": "Light Leak",
"description": "Shader transition with cinematic light leak overlay",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/light-leak",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/light-leak.png"
},
{
"name": "logo-outro",
"type": "block",
"title": "Logo Outro",
"description": "Cinematic logo reveal with piece-by-piece assembly, glow bloom, tagline fade-in, and URL pill",
"tags": [
"branding",
"outro",
"logo"
],
"href": "/catalog/blocks/logo-outro",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/logo-outro.png"
},
{
"name": "macos-notification",
"type": "block",
"title": "macOS Notification",
"description": "Animated macOS-style notification banner with app icon and message",
"tags": [
"social",
"overlay",
"notification"
],
"href": "/catalog/blocks/macos-notification",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/macos-notification.png"
},
{
"name": "north-korea-locked-down",
"type": "block",
"title": "North Korea Locked Down",
"description": "Realistic map zoom into North Korea with a red scribble circle, locked-down pop-up label, and reddish editorial wash.",
"tags": [
"showcase",
"map",
"annotation",
"youtube",
"kinetic"
],
"href": "/catalog/blocks/north-korea-locked-down",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/north-korea-locked-down.png"
},
{
"name": "nyc-paris-flight",
"type": "block",
"title": "NYC Paris Flight",
"description": "Apple-style realistic map animation with a plane flying from New York to Paris, marker circle, landing pop, and sound effects.",
"tags": [
"showcase",
"travel",
"map",
"youtube",
"sfx"
],
"href": "/catalog/blocks/nyc-paris-flight",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/nyc-paris-flight.png"
},
{
"name": "reddit-post",
"type": "block",
"title": "Reddit Post Card",
"description": "Animated Reddit post card overlay with upvotes and comments",
"tags": [
"social",
"overlay",
"reddit"
],
"href": "/catalog/blocks/reddit-post",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/reddit-post.png"
},
{
"name": "ridged-burn",
"type": "block",
"title": "Ridged Burn",
"description": "Shader transition with ridged turbulence burn effect",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/ridged-burn",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/ridged-burn.png"
},
{
"name": "ripple-waves",
"type": "block",
"title": "Ripple Waves",
"description": "Shader transition with concentric ripple wave distortion",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/ripple-waves",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/ripple-waves.png"
},
{
"name": "sdf-iris",
"type": "block",
"title": "SDF Iris",
"description": "Shader transition with signed distance field iris reveal",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/sdf-iris",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/sdf-iris.png"
},
{
"name": "shimmer-sweep",
"type": "component",
"title": "Shimmer Sweep",
"description": "Animated light sweep across text or elements using a CSS gradient mask — ideal for AI accents and premium reveals",
"tags": [
"text",
"shimmer",
"highlight",
"effect"
],
"href": "/catalog/components/shimmer-sweep",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/shimmer-sweep.png"
},
{
"name": "spotify-card",
"type": "block",
"title": "Spotify Now Playing",
"description": "Animated Spotify now-playing card with album art and progress bar",
"tags": [
"social",
"overlay",
"spotify"
],
"href": "/catalog/blocks/spotify-card",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/spotify-card.png"
},
{
"name": "swirl-vortex",
"type": "block",
"title": "Swirl Vortex",
"description": "Shader transition with swirling vortex distortion",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/swirl-vortex",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/swirl-vortex.png"
},
{
"name": "thermal-distortion",
"type": "block",
"title": "Thermal Distortion",
"description": "Shader transition with heat haze thermal distortion",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/thermal-distortion",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/thermal-distortion.png"
},
{
"name": "tiktok-follow",
"type": "block",
"title": "TikTok Follow",
"description": "Animated TikTok follow overlay with profile card and follow button",
"tags": [
"social",
"overlay",
"tiktok"
],
"href": "/catalog/blocks/tiktok-follow",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/tiktok-follow.png"
},
{
"name": "transitions-3d",
"type": "block",
"title": "3D Transitions",
"description": "Showcase of 3D perspective flip and rotate transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-3d",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-3d.png"
},
{
"name": "transitions-blur",
"type": "block",
"title": "Blur Transitions",
"description": "Showcase of blur-based transitions between scenes",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-blur",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-blur.png"
},
{
"name": "transitions-cover",
"type": "block",
"title": "Cover Transitions",
"description": "Showcase of cover/uncover slide transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-cover",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-cover.png"
},
{
"name": "transitions-destruction",
"type": "block",
"title": "Destruction Transitions",
"description": "Showcase of destructive break-apart transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-destruction",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-destruction.png"
},
{
"name": "transitions-dissolve",
"type": "block",
"title": "Dissolve Transitions",
"description": "Showcase of dissolve and fade transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-dissolve",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-dissolve.png"
},
{
"name": "transitions-distortion",
"type": "block",
"title": "Distortion Transitions",
"description": "Showcase of warp and distortion transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-distortion",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-distortion.png"
},
{
"name": "transitions-grid",
"type": "block",
"title": "Grid Transitions",
"description": "Showcase of grid-based tile transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-grid",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-grid.png"
},
{
"name": "transitions-light",
"type": "block",
"title": "Light Transitions",
"description": "Showcase of light-based glow and flash transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-light",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-light.png"
},
{
"name": "transitions-mechanical",
"type": "block",
"title": "Mechanical Transitions",
"description": "Showcase of mechanical shutter and iris transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-mechanical",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-mechanical.png"
},
{
"name": "transitions-other",
"type": "block",
"title": "Other Transitions",
"description": "Showcase of miscellaneous creative transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-other",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-other.png"
},
{
"name": "transitions-push",
"type": "block",
"title": "Push Transitions",
"description": "Showcase of push and slide transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-push",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-push.png"
},
{
"name": "transitions-radial",
"type": "block",
"title": "Radial Transitions",
"description": "Showcase of radial wipe and reveal transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-radial",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-radial.png"
},
{
"name": "transitions-scale",
"type": "block",
"title": "Scale Transitions",
"description": "Showcase of scale and zoom transitions",
"tags": [
"transition",
"showcase"
],
"href": "/catalog/blocks/transitions-scale",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/transitions-scale.png"
},
{
"name": "ui-3d-reveal",
"type": "block",
"title": "3D UI Reveal",
"description": "Perspective 3D reveal animation for UI elements",
"tags": [
"showcase",
"3d",
"reveal"
],
"href": "/catalog/blocks/ui-3d-reveal",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/ui-3d-reveal.png"
},
{
"name": "whip-pan",
"type": "block",
"title": "Whip Pan",
"description": "Shader transition simulating a fast camera whip pan",
"tags": [
"transition",
"shader"
],
"href": "/catalog/blocks/whip-pan",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/whip-pan.png"
},
{
"name": "x-post",
"type": "block",
"title": "X Post Card",
"description": "Animated X/Twitter post card overlay with engagement metrics",
"tags": [
"social",
"overlay",
"twitter"
],
"href": "/catalog/blocks/x-post",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/x-post.png"
},
{
"name": "yt-lower-third",
"type": "block",
"title": "YouTube Lower Third",
"description": "Animated YouTube subscribe lower third with avatar and channel info",
"tags": [
"social",
"overlay",
"youtube"
],
"href": "/catalog/blocks/yt-lower-third",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/yt-lower-third.png"
}
]