Skip to content

feat(catalog): add the seven 3D-motion pieces as installable registry blocks - #4056

Open
miguel-heygen wants to merge 60 commits into
mainfrom
feat/ad93-3d-motion-catalog
Open

miguel-heygen wants to merge 60 commits into
mainfrom
feat/ad93-3d-motion-catalog

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds seven 3D-motion pieces to the catalog as installable registry blocks: a leaf-parting title card, a glass-shard title, a code-slicing hero, a frosted camera-orbit sequence, a bevelled-cuboid carousel, an orbiting feature card and a wireframe portal title. Each installs like any catalog block (npx hyperframes add <id>), has a live detail page with source and install, and sits in its own "3D motion" section.

This PR is the whole catalog change. The engine script-ordering fix that the glass-shard-title render needed landed first, in #4133.

The pieces are contributed with their author's permission under this repository's license (CREDITS.md).

What changed

  • Seven items under registry/blocks/<id>/, tagged 3d-motion so the existing tag-driven grouping gives them their own section.
  • Preview videos and posters live on the HeyGen CDN, referenced by URL in each registry-item.json. Nothing binary was added for them.
  • Scripts now travel inside the catalog payload, because the docs host only serves fonts, images and JSON from public/, never .js, .mjs or .hdr (measured against a deployed preview). Shared libraries (gsap, three, RoomEnvironment, BufferGeometryUtils) go once into docs/public/catalog/vendor/*.json and are fetched and blob-loaded at runtime; item scripts are inlined as text; the one HDR is a data URI. Implemented in scripts/catalog-script-inlining.ts, one inliner per item that needs it.
  • processAssets embeds .js as a data URI for every item without an inliner, as before, so other catalog items regenerate unchanged.
  • CI now runs check:docs-catalog in the Lint job and a new "Catalog payloads" job that loads every payload a PR changes in headless Chrome (verify-catalog-payloads.ts --changed origin/main). A WebGPU piece cannot draw on a CI browser without an adapter, so that one error is ignored for payloads that use navigator.gpu; every other failure still fails the job.
  • scripts/verify-catalog-payloads.ts models the docs host's real extension allowlist, so a payload that loads locally but 404s on the host fails the check.
  • frost-sequence-camera-orbit/assets/motion.js is removed: no reference anywhere, and zero requests across a full play-through.
  • glass-shard-title loads its vendored engine through <script src> instead of an inlined copy that failed two lint rules.
  • source/ directories are excluded from hosted item directories.
  • The three large textures the pieces load (shards-atlas.png, ice-inclusions-generated.png, matcap-1.png) are hosted through the registry's files[].url field on the HeyGen CDN under content-addressed names, exactly like the carousel images: hyperframes add still writes them to the same local paths, and the catalog payload's inlined scripts point at the URLs. No PNG over the non-LFS limit is committed. ice-fracture-detail-v1.png was loaded by nothing and is deleted.
  • frost-sequence-camera-orbit ships only what it renders with: the iteration presets (approved-material-v1 to v7, current-test, the saved presets and their catalog) are removed, since nothing reads them at render. source/presets/ keeps the two files the source build imports.
  • .oxlintrc.json ignores the vendored bundles; CREDITS.md has one attribution line.

Verification

  • bun scripts/verify-catalog-payloads.ts --only <id> exits 0 for all seven pieces, plus liquid-glass-widgets, liquid-glass-notification and hw-write-title to show the shared generator change did not break other items.
  • Regenerating the seven payloads leaves git status clean.
  • Every preview video on the CDN was checked for real bytes and for frames whose brightness varies (a blank glass-shard-title video was found and replaced).
  • The three hosted textures were loaded as images with crossOrigin=anonymous from the deployed preview's origin and read back from a canvas without tainting, which is what WebGL needs; the CDN answers access-control-allow-origin: *.
  • Not verified: the frost piece drawing frames on the deployed docs site (needs a WebGPU browser after deploy).

Diff size

Far over the 1k-line guideline. Almost all of it is vendored bundles, fonts and textures across seven independent items. The hand-written surface is the manifests, the scripts/catalog-* changes and their tests.

Heavy pieces on a real GPU

The four WebGL/WebGPU pieces now draw across their timeline and flush the GPU inside window.__hf.buildReady, so shader links and texture uploads happen before the player reports ready instead of on the first visible frames. Measured with the tile path (srcdoc player, muted, play from 0, Apple GPU, headless Chrome for Testing, single run), max rAF gap / longest long task, before then after:

Piece Before After
canopy-part-title 117 ms / 66 ms 33 ms / none
wireframe-portal-title 133 ms / 144 ms 133 ms / none
cuboid-carousel 17 ms / none 17 ms / none
frost-sequence-camera-orbit 1217 ms / 1066 ms 983 ms / 875 ms

Frost: the remaining time is the build phase (geometry, distance fields, particle setup), not first-draw pipeline creation. The player reporting ready before the build gate resolves is a player readiness-gate fix in a separate PR.

Detail page: install command before the preview (deployed docs preview, headless Chromium)

The install command, its copy button and the "That writes ..." line now sit under the description and above the preview, as on the live catalog. Tabs are Preview, Code, Snippet, Docs with Preview selected. The Snippet tab holds the mount element with the values tuned in the panel; the duplicate Terminal command is gone. Frost Sequence Camera Orbit is a WebGPU composition: headless Chromium has no adapter, so its stage shows the recorded clip with the reason on the caption line and no Tune panel. Live WebGPU playback needs a real GPU Chrome.

Before

Before at 1440
Before at 390

After

After 1440 preview
After 1440 code
After 1440 snippet
After 1440 docs
After frost 1440 preview
After 390 preview
After 390 code
After 390 snippet
After 390 docs
After frost 390 preview

Catalog poster timing (deployed preview, head d7c75e4)

The pinned 3D motion group's six posters load eagerly at high priority. Measured from navigation start:

  • Chrome, warm cache (measured by a teammate): all six poster requests start at 211 ms; before the change they started at 6454 ms.
  • Headless Chromium on the devbox, cold cache: all six requests start at 1104 ms and finish between 1128 and 1251 ms (page load event at 762 ms), under the 1.5 s bar. Only 12 route prefetches ran on that page, 8 of them for card links.

Copy button layout shift, and a pale copied label

Every copy button (agent request, wiring, link, JSON, render cmd, install) changed size when its label swapped to "Copied", so the row's siblings slid left. Root fix is in the one shared copy control: label and "Copied" now sit in the same grid cell, the inactive one hidden, so the button never resizes. Measured before/after on the deployed preview at 1440/1280/1100/900/390: no box moves now at any width (four of five widths moved before).

The primary button's hover state was then found unreadable in the copied state (white text on a pale background), caused by a hover rule with lower CSS specificity than the generic one. Fixed on the same control; verified computed background stays dark through hover, click, and the copied state.

After, copied state, primary button (agent request):

copy-button-copied-state-899e66ce2

@mintlify

mintlify Bot commented Sep 18, 2026

Copy link
Copy Markdown

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

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Sep 19, 2026, 7:08 PM

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

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 7d089fd8. Note the head moved while I was reading (d09af6517d089fd8), so I re-verified against the new head rather than posting findings pinned to the old one.

First, the head move is clean — and worth recording, because it looks alarming by every cheap measure.

style(catalog): run oxfmt on the seven ported composition files is +5905/-1894 across 8 files, and the whitespace-stripped content of frost-sequence-camera-orbit.html shrinks by 30,064 bytes. A formatter adds whitespace; it does not delete a quarter of a file's real content, so I chased it. The cause is benign: the data-composition-variables blob on <html> was a double-quoted attribute holding &quot;-escaped JSON, and the formatter re-delimited the attribute with single quotes and decoded the entities (5,258 &quot; → 0). Same JSON, ~5 bytes saved per quote.

That rewrite has exactly one failure mode — an apostrophe anywhere in the JSON would now terminate the attribute early — so I executed the check rather than reasoning about it: for all seven pieces at this head, every single-quote-delimited attribute whose value starts [/{ parses as JSON, 0 failures. Structural token counts (<script>, <style>, function, const, gsap., addEventListener, data-composition-id) are identical across the move. So the reformat is semantically safe on all seven.

blocker — this PR's own CREDITS.md states a precondition that merging would bypass

The added "Third-party licenses" entry says the seven pieces "were ported from a reference implementation. License terms for these pieces are not confirmed upstream — do not assume this repository's Apache 2.0 license applies to this directory until confirmed."

I read that as an open question, not a disclosure that closes by being written down. This is a public Apache-2.0 repository; the pieces land as installable registry blocks (/registry/install) and the docs surface publishes hover previews for them. Merging is the step that redistributes ~251k lines whose terms the PR itself declines to assert, to people who will reasonably assume the repo's license covers what they install. Worth resolving upstream — or landing the blocks behind whatever gate the answer implies — before this goes in. Flagging it because it is the one finding here that a later commit cannot quietly fix.

important — vendored MIT copies ship without their notices, and unevenly within this same PR

  • frost-sequence-camera-orbit/assets/frost.js:1 declares its own contents: three@0.185.1 (MIT) + three-mesh-bvh@0.9.14 (MIT). That block's only licence file is assets/fonts/Geist-OFL.txt — a font licence. Neither MIT notice ships with the copy.
  • cuboid-carousel and orbit-card do ship assets/Three-LICENSE.txt. So the convention is established inside this PR and applied to two of the three blocks that need it.
  • gsap-3.14.2.min.js is vendored into four blocks (orbit-card, frost-sequence-camera-orbit, cuboid-carousel, code-slice-hero) with no GSAP licence file in any of them. GSAP's terms are not MIT, so it deserves naming explicitly rather than riding CREDITS.md's "Thanks also to … GSAP".
  • glass-shard-title/assets/glass-main.js (24,624 lines) ships with no header and no licence file, so its provenance can't be established from the tree at all.

important — frost.js is a build artifact whose source doesn't ship

Its first line reads "Generated by build.mjs; edit src/ and rebuild." There is no src/ and no build.mjs anywhere in this PR — I checked the full file list, not just that block. So the instruction in the file cannot be followed in this repository, and 78,870 lines of bundled three.js + BVH have no patch path here: a three.js security bump would have nowhere to land. motion.js (43,785 lines) is the same shape with no header at all. Either ship the source and build script, or record in the block's SKILL.md where upstream lives and how to regenerate.

noteglass-main.mjs (1,018 lines, authored, oxfmt-owned) sits beside glass-main.js (24,624 lines, built) with nothing in the tree relating them. Whatever produces the .js isn't in the repo, so the pair can drift silently and a reader can't tell which one is authoritative.

On the body's own split: "the hand-written part is small" holds as a line-count claim. I separated generated from authored by reading file headers rather than taking the split on faith, and the authored surface is the seven <piece>.html, glass-main.mjs, the per-block registry-item.json / presets/ / SKILL.md, and the seven docs/catalog/blocks/*.mdx. The thing that split surfaced is the .mjs/.js pair above.

Audited: the head-move delta (all 8 files, semantically), provenance and licence inventory per block, the generated-vs-authored split, CREDITS.md, .gitattributes LFS routing for the preview clips.
Trusting / not reviewed: the seven pieces' motion content and visual correctness, the check-docs-catalog parity checks, and the Mintlify preview — your own note says merge waits on your look at that, so I have deliberately not posted the approval that would release it.

Verdict: REQUEST CHANGES
Reasoning: the licensing precondition is stated by the PR itself, and three of the blocks that vendor MIT libraries ship without their notices. The reformat and the block wiring are fine; this is about what gets redistributed.

One mechanic so nobody is left guessing why it stays red: this repo sets dismiss_stale_reviews_on_push: false, so this review stays live across pushes and needs an explicit approval from this account to clear once it's addressed. Ping me and I'll turn it around quickly.

— Rames Jusso

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough pass. Both findings addressed at the latest commit:

Licensing precondition. The maintainer has confirmed the author's permission for these seven pieces under this repository's license. CREDITS.md now says that plainly instead of carrying the "not confirmed" caveat.

Vendored notices and frost's build artifacts. Every third-party library vendored across the seven blocks now ships its own license notice, consistently: GSAP (added to all four blocks that vendor it), three.js and the Geist font's OFL notice (added wherever they were missing), and three-mesh-bvh, opentype.js, and ClipperLib for frost-sequence-camera-orbit specifically, since that block pulls in more than the reviewed set caught.

For frost.js and motion.js: the real TypeScript source and the esbuild script that produce them now live in registry/blocks/frost-sequence-camera-orbit/source/ (not part of the installed file set — it's reference material, so a future dependency bump has somewhere to land). I ran npm install && npm run build against it and confirmed it reproduces working output with the same exported API the composition calls. It's a narrower slice than the original project's full dev environment (no test suite, no standalone tuning UI) — just the source and the two build-time checks that gate it.

One thing this surfaced that's outside this pass: glass-shard-title is the one block that doesn't vendor its two runtime libraries at all — it loads GSAP and d3-delaunay live from a CDN at render time, unlike the other six. Not fixing that here since it wasn't part of what you flagged, but wanted it on record rather than lost.

jrusso1020
jrusso1020 previously approved these changes Sep 18, 2026

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at the squashed head. My previous CHANGES_REQUESTED is cleared by this review — both findings are addressed.

A note on how I checked: "history squashed to one commit" would normally mean my earlier read carries over, so I compared the tree hashes rather than the commit list. They differ (bbc5bddfd32ee52f), so the squash carried content changes too and I re-read the delta. Worth knowing for next time — the squash also dropped glass-main.mjs and rebuilt frost.js/motion.js.

Strengths

  • The licence audit went past what I flagged. I named three@0.185.1 + three-mesh-bvh@0.9.14 and GSAP; you also found OpentypeJS and Clipper and shipped notices for both (registry/blocks/frost-sequence-camera-orbit/assets/). That's the enumeration done properly rather than to the letter of the review.
  • Every notice is wired into registry-item.json with a target, so it actually installs alongside the library it covers. A notice that sits in the repo but not in the manifest would look right in the diff and ship nothing — checking the manifest was the part I expected to be missed, and it wasn't.
  • source/build.mjs runs a TS preflight plus a headless WGSL shard-compile probe before bundling (source/tools/run-shard-compile.mjs). Catching shader failures without a GPU is a genuinely useful gate to have brought along.

Findings — none blocking

importantfrost-sequence-camera-orbit.html:3173-3174 publishes window.__hf.buildReady.frost = frost.ready, and nothing in the repo reads buildReady (those two lines are its only occurrences, repo-wide). The rig has real async startup (source/src/frost.ts:336,586), and the producer's actual gate is window.__renderReady — read at packages/producer/src/services/fileServer.ts:653 and packages/producer/src/parity-harness.ts:79-80, and recognised by the compiler at packages/core/src/compiler/htmlDocument.ts:22. No block in this PR sets it. So if the intent was to hold capture until the ice rig is built, this hook doesn't do that; if it's a debug handle, it's dead code. Worth naming which — and if it is meant to gate capture, the wired form is window.__renderReady = false up front, flipped after await frost.ready.

important — nothing ties the shipped bundle to the source that now accompanies it. source/build.mjs writes assets/frost.js, the README documents "edit src/ and rebuild", but no workflow in .github/workflows/ references frost, source/package.json is private: true and outside the workspace build, and .oxlintrc.json now excludes source/** from lint entirely. I verified the absence of a gate; I did not test whether the current bundle matches the current source. The result is two copies of the same logic with nothing checking they correspond — and the build is exactly where that silently diverges.

importantregistry/blocks/glass-shard-title/SKILL.md no longer matches its own directory after the squash. Line 82 still says to "rebuild with esbuild and re-inline after editing the .mjs source", but assets/glass-main.mjs was deleted here; the Files list still carries build-bundle.mjs (1 KB), which is absent at this head. The deletion itself is clean — the composition loads the built assets/glass-main.js and the manifest entry went with the file — but these are agent-facing instructions, so what's left points at two files that no longer exist. Same class as the frost finding I opened, just inverted: there the instruction outran the source, here the source was removed and the instruction stayed.

nit — the CodeQL "prototype-polluting function" alert on source/src/dials/defaults.ts:485 is still open at this head (the defaults.ts change in the squash is a comment rename, not a fix). I don't think it's exploitable: values only ever comes from the committed baked.json, withBaked has no callers outside its own file, and __proto__ already hits the k.startsWith("_") guard, while constructor can't recurse because functions don't satisfy typeof c === "object". But that mitigation is incidental — the guard was written to skip internal keys, not to stop pollution — so an explicit key check would both close the alert and make the protection intentional.

nit — the CREDITS.md line now reads "contributed with their author's permission under this repository's license", and the SKILL.md files drop their Source experiment: pointers. Dropping internal experiment names from a public repo is the house convention, so no issue there; the effect is just that CREDITS.md is the only remaining record and it doesn't name what was ported. If the upstream is nameable publicly, naming it would make the attestation auditable by someone who wasn't in this thread. To be explicit about my own position: I'm accepting the permission statement, not verifying it — an offline licensing confirmation isn't something a code review can audit.

Note, not a finding — GSAP's minified bundle was already vendored in this repo before this PR (skills/music-to-video/references/motion-primitives/assets/gsap.min.js, skills/talking-head-recut/assets/vendor/gsap.min.js both exist at the merge base), so how an Apache-2.0 repo redistributes an all-rights-reserved bundle isn't this PR's question to answer. I'd raised the missing notice; your GSAP-NOTICE.txt files now make these four blocks the only place in the repo that carries one. The two older copies still have none — worth a separate pass, not something to load onto this PR.

Verdict: APPROVE
Reasoning: The licensing precondition that blocked this — the PR's own CREDITS.md disclaiming its licence terms — is gone, and every vendored library now ships a notice that actually installs. The three important findings are drift and wiring issues that don't affect what renders, so they're better as follow-ups than as another round.

Gate mechanics, for whoever is tracking the merge: dismiss_stale_reviews_on_push is false on this repo, so my earlier CHANGES_REQUESTED survived the force-push and only an explicit approval from this account could clear it. This is that approval. Merge timing is yours — you'd mentioned the Mintlify preview.

— Rames Jusso

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Refresh at ec21c5831, five commits past the approved head 2cebedcb7:

  • License follow-ups: added the missing OFL notices for glass-shard-title's Cormorant Garamond font and frost-sequence-camera-orbit's Inter font, and restored glass-main.mjs (the real ESM source glass-main.js builds from) plus its build script, matching what the block's own SKILL.md already documented as build-time-only.
  • frost.js/motion.js/glass-main.js had been silently re-pretty-printed by the formatter (no ignore entry existed for these vendored bundles), inflating each by roughly a megabyte of pure whitespace with no semantic change. Re-minified and added the missing ignore entries so it can't recur.
  • Found and fixed a real rendering gap: four items (frost-sequence-camera-orbit, glass-shard-title, cuboid-carousel, orbit-card) rendered blank in the docs preview because their script assets were inlined as data URIs, which strands any relative import or fetch the script makes internally. Routed them through the existing host-the-directory mechanism, which also required fixing a byte-budget bug that was double-counting every asset against its install-layout mirror copy.
  • Added a headless-Chrome check (scripts/verify-catalog-payloads.ts) that loads every generated docs payload in real Chrome and fails on any 404 or page error. Proved it non-vacuous first: ran it against the pre-fix payloads and it failed on all four, naming the missing asset each time, before confirming a clean pass with the fix restored. New coverage, not present at the approved head.
  • Regenerated all seven items' docs preview payloads twice more in this range: once for stale pre-format snapshots, once for a payload built before an earlier privacy scrub. Repo-wide grep now confirms zero leaked internal naming across every tracked file.

lint:registry-items unchanged at 25 pre-existing failures, unrelated to these seven items and tracked separately. Re-requesting review.

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Correction to the refresh comment above: I said frost-sequence-camera-orbit's Inter font got an OFL notice added. That's only true of an intermediate commit in this range — a later commit in the same range found the font had zero runtime references and deleted it entirely (font + notice both), so at the current head there is no Inter font at all. The Cormorant Garamond notice for glass-shard-title is correct and still stands.

…what the block installs

CREDITS.md names every vendored dependency with its upstream and licence, and flags the two leaf
textures whose origin is still to be confirmed. Each SKILL.md Files table now comes from the block
manifest and its install and render paths point at compositions/<name>/. A test checks that every
path a SKILL.md names is a manifest file that exists.
…g, not by console wording

verify-catalog-payloads forgave a missing adapter only by matching the markup and the message, which
library-reported errors and widgets never satisfy. The webgpu tag in the item manifest is now the
owner: a tagged item ignores its page and console errors, an untagged one gets no forgiveness, and a
marker payload with no html is listed instead of crashing the run.
…render paths

CREDITS.md states the three.js version and vendored-or-CDN status per block, adds d3-delaunay,
Clipper on the CDN and the Google Fonts loads, and lists every texture with no known origin.
The SKILL.md test now also checks the mount and render paths against the manifest composition.
The two checks shared a copied manifest read, which fallow flagged as a clone with a high score.
… and check git not the disk

The compositions/ and *-ad/ ignore rules matched folder names under docs/public/catalog/items,
so 33 hosted fonts, textures and marks were generated but never committed and would 404 on
the docs host. The rules now have a negation for that tree and the files are added.

The drift check read the checkout, where git-ignored files still exist, so it passed locally
and failed only in CI. It now unpacks HEAD with git archive and compares that.
…rror, and test the wiring

The manifest webgpu tag now decides whether an item may lack an adapter, and the error wording
decides which lines are that absence. Any other page or console error on the item, and every
failed request, still fails. The per-item decision is one exported function the tests drive,
marker payloads are parsed by a tested helper, and the SKILL.md variable counts are checked
against their tables (cuboid-carousel said forty-nine for forty).
@miguel-heygen
miguel-heygen dismissed jrusso1020’s stale review September 19, 2026 20:03

Stale: requested on 7d089fd three heads ago; every point addressed and the head moved to 899e66c (install above preview, copy buttons fixed). Dismissed on the repo owner's word.

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.

3 participants