Skip to content

feat(catalog): play the real composition on catalog pages - #3168

Merged
miguel-heygen merged 8 commits into
mainfrom
feat/catalog-player-previews
Aug 10, 2026
Merged

feat(catalog): play the real composition on catalog pages#3168
miguel-heygen merged 8 commits into
mainfrom
feat/catalog-player-previews

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

What

Catalog pages stop showing an uploaded MP4 of a block and start playing the block itself, live, in <hyperframes-player>.

164 of the 168 items switch over. The other 4 keep their video and the generator says which and why.

Why

The previews were recordings, published to the CDN by hand. That made them a second artifact to keep in sync with the registry, and it meant a reader could not scrub, pause, or inspect the thing they were about to install.

An earlier attempt to fix this shipped the previews as .html files under docs/public and took every catalog page's preview down in production. That failure is what set the constraints this change is built around.

How

Two constraints came out of probing the deployed docs host directly, one page per strategy:

  1. docs/public publishes only JSON and image files. .html, .js, .css and .txt return 404 with no build error, so a preview shipped as an HTML file 404s in production while its page still serves 200.
  2. The MDX renderer strips unknown custom elements, so a <hyperframes-player> written into a page never reaches the DOM. Nothing rewrites the inside of a srcDoc document, and an external <script src> does execute.

So each page carries one small bootstrap iframe. Inside it, the player loads from the CDN, fetches the composition from /public/catalog/<type>/<slug>.json, and mounts it via srcdoc.

Assets are inlined as data URIs rather than copied next to the payload. A data URI is just a string in JSON, so the host's file-type restriction stops applying to fonts, scripts and models, and a preview never renders half-dressed while assets are still in flight.

scripts/generate-catalog-previews.ts grew a direct-run guard because the payload generator imports its item discovery, and an unguarded main() would render every preview on import.

What still uses a video

Item Reason
blue-sweater-intro-video 5.8 MB payload, it embeds real footage
macos-tahoe-liquid-glass 6.0 MB payload, it embeds a 3D model
texture-mask-text builds its mask paths at runtime, so they cannot be resolved statically
caption-blend-difference fails to render on main already, unrelated to this change

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)

scripts/catalog-payload-assets.test.ts covers the reference matching and inlining. Both of its rules are pinned by a negative control: removing the leading-character rule fails one test, disabling the extension filter fails another. That regex had already been wrong twice, matching GLSL shader source assigned to vertSrc and url(%23noise) filter references.

Verified on this PR's preview deployment rather than locally, since the failure mode being fixed only appears once deployed.

Repo size

docs/public/catalog is 18 MB: payloads plus a shared, content-addressed asset directory.

Assets are written once and linked rather than inlined per item. Which types can be linked was settled by fetching one file of each from a deployed preview:

Type Published from docs/public
.json, .png, .jpg, .webp, .svg yes
.woff2, .wav, .mp4 yes
.glb no
.html, .js, .css, .txt no

Anything in the "no" rows still travels inside the payload as a data URI, because a link that 404s is worse than a larger payload.

Two passes are needed, not one. File references are rewritten as they are found, but compositions also arrive with their fonts already embedded as data URIs, which no reference scan sees. Those are pulled out too when they are large enough to be worth a request. Output is content-addressed, so regenerating an unchanged item produces no new blobs.

Catalog previews were uploaded MP4s, published by hand to the CDN. Each page
now embeds the composition itself, running in <hyperframes-player>, so a
preview is the block rather than a recording of it.

The composition is delivered as JSON under docs/public/catalog, and the player
is mounted inside an iframe. Both are forced: the docs host publishes only JSON
and images out of docs/public, and its MDX renderer strips unknown custom
elements, so a player written into the page never reaches the DOM and an .html
payload 404s in production.

Assets are inlined as data URIs, which sidesteps the file-type restriction for
fonts, scripts and models alike. 164 of 168 items build a payload; the rest
keep their MP4 and say so.
@mintlify

mintlify Bot commented Aug 10, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview Aug 10, 2026, 7:54 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Fallow audit report

Found 15 findings.

Duplication (4)
Severity Rule Location Description
minor fallow/code-duplication scripts/catalog-payload-assets.ts:154 Code clone group 1 (6 lines, 2 instances)
minor fallow/code-duplication scripts/catalog-payload-assets.ts:207 Code clone group 1 (6 lines, 2 instances)
minor fallow/code-duplication scripts/generate-catalog-payloads.ts:135 Code clone group 2 (12 lines, 2 instances)
minor fallow/code-duplication scripts/generate-catalog-previews.ts:433 Code clone group 2 (12 lines, 2 instances)
Health (11)
Severity Rule Location Description
major fallow/high-crap-score scripts/catalog-payload-assets.ts:79 'localReferences' has CRAP score 56.0 (threshold: 30.0, cyclomatic 7)
major fallow/high-crap-score scripts/catalog-payload-assets.ts:127 'processAssets' has CRAP score 90.0 (threshold: 30.0, cyclomatic 9)
minor fallow/high-crap-score scripts/catalog-payload-assets.ts:200 'out' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
critical fallow/high-crap-score scripts/generate-catalog-pages.ts:305 'textureSampleWord' has CRAP score 148.4 (threshold: 30.0, cyclomatic 24)
minor fallow/high-crap-score scripts/generate-catalog-payloads.ts:54 'buildPayload' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
minor fallow/high-crap-score scripts/generate-catalog-payloads.ts:98 'parseArgs' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
minor fallow/high-crap-score scripts/generate-catalog-payloads.ts:112 'main' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
critical fallow/high-crap-score scripts/generate-catalog-previews.ts:73 'discoverItems' has CRAP score 306.0 (threshold: 30.0, cyclomatic 17)
critical fallow/high-crap-score scripts/generate-catalog-previews.ts:155 'prepareProjectDir' has CRAP score 420.0 (threshold: 30.0, cyclomatic 20)
critical fallow/high-crap-score scripts/generate-catalog-previews.ts:383 'parseArgs' has CRAP score 110.0 (threshold: 30.0, cyclomatic 10)
minor fallow/high-crap-score scripts/generate-catalog-previews.ts:410 'main' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)

Generated by fallow.

Comment thread scripts/catalog-payload-assets.ts Fixed
Fonts were being base64'd into every payload that used them, so a handful of
files cost tens of megabytes in the repository to say the same thing over and
over. Assets are now written once, content-addressed, under
docs/public/catalog/assets and linked.

Which types can be hosted was settled by fetching one file of each from a
deployed preview: woff2, wav, mp4, svg and the image formats are published,
glb is not. Types the host drops still travel inside the payload, because a
link that 404s is worse than a larger payload.

Also fixes the four type errors the scripts typecheck caught, all of them
unchecked index access on a split() result.
continue;
}

const bytes = readFileSync(source);
const dest = join(target.dir, name);
if (!existsSync(dest)) {
mkdirSync(target.dir, { recursive: true });
writeFileSync(dest, bytes);
const dest = join(target.dir, name);
if (!existsSync(dest)) {
mkdirSync(target.dir, { recursive: true });
writeFileSync(dest, bytes);
@miguel-heygen
miguel-heygen merged commit 536165b into main Aug 10, 2026
53 of 55 checks passed
@miguel-heygen
miguel-heygen deleted the feat/catalog-player-previews branch August 10, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants