feat(registry): add 25 image carousel blocks - #3790
Open
miga-heygen wants to merge 8 commits into
Open
Conversation
Five carousel families, each with 5 style variants: - Orbit (1–5): image cards on a spinning 3D Fibonacci sphere - Path (1–5): cards following animated CSS motion paths - Circle (1–5): circular carousel layouts - Vision (1–5): Apple Vision-style spatial presentations - Text Circle (1–5): circular carousels with text overlays All blocks are 1920×1080 at 6s, with 12–24 configurable image slots. Includes catalog preview thumbnails for each block. Co-Authored-By: Jake Moran <jake.moran@heygen.com>
The registry is served straight out of this repository (DEFAULT_REGISTRY_URL points at raw.githubusercontent.com), so every byte a block ships is permanent history. The 25 carousel blocks added 421 JPEGs, 27.7 MiB in a checkout, and made the diff 472 files. Only 23 of those images were distinct: the same 12-24 placeholders were copied into every block. files[] entries gain an optional `url`. When set, the installer fetches the bytes from there instead of joining the registry base. `path` does not change and still says where the file lands relative to the item, so composition HTML, target mirroring and `hyperframes add` behave exactly as before. Keys are content-addressed, so the 396 manifest entries resolve to 23 objects, and a changed image gets a new URL rather than a stale one cached behind `immutable, max-age=31536000`. The catalog preview renderer copies an item's directory and renders it, so it needs the same materialisation step. Without it the preview draws every card blank and reports success, which is worse than failing. Also drops registry/catalog/, 25 hand-made thumbnails referenced by nothing; catalog previews are rendered by CI and served from docs/images/catalog. Verified: all 23 objects return 200 from the CDN with hashes matching their keys; `hyperframes add carousel-orbit-1` against a local registry installs 24 real JPEGs; the preview render produces the album art, and produces blank cards when the fetch step is removed.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
`oxfmt --check .` covers the whole tree, and these 25 files were never run through it. The pre-commit hook only formats staged files, so nothing local caught it.
… images These 25 blocks had no Catalog page. Every other item in the registry has one, so they shipped invisible: installable by name, unfindable by browsing. Generating them naively undid the change they were added by. The Catalog payload copies an item's assets into docs/public/, which is tracked, so the 396 images this PR just removed came back as 43 MB one directory over — worse than the 3 MB they started as, because each block got its own copy. The copy exists because these compositions assemble `img.src` at run time out of a variable value, so there is no `src="..."` in the markup for the payload's asset scan to resolve. An unpredictable path can only be satisfied by serving every file beside it, which is what `needsOwnDirectory` asks for. An absolute URL needs no directory: the scan already skips any `https:` reference. So for the payload path only, hosted files are left undownloaded and the composition's variable defaults are rewritten to their URLs. The preview renderer still downloads them, because it paints real frames and a missing file is a blank card. The explorer posts every value to the preview frame on mount, including untouched ones, so the page's variable list carries the URLs too. Left as local paths they would have overridden the payload's own defaults and asked the frame for a file that was deliberately never published. Result: 25 pages, 25 payloads, zero bytes of image added. Verified: a spike item declaring no assets at all rendered its 24 covers from the CDN, proving the variable-default path; payload generation for a carousel block now writes no item directory and no shared asset; the preview render still produces the album art. mint validate and mint broken-links pass on the new pages. test:scripts is green.
Two functions rather than one: finding the composition and rewriting its variable defaults are separate jobs, and inlining the mode branch pushed prepareProjectDir past the complexity gate it was already sitting on. Behaviour is unchanged. Re-verified both paths after the split: the payload for a carousel block still writes no item directory and no shared asset, and the preview render still produces the album art.
25 image carousels landed in Showcases and were 53% of it, so the scenes that shelf exists for disappeared underneath them. That is the same shape the 24 editor themes made, and it gets the same fix they got. Keyed on the first tag, which is this file's stated grouping rule, rather than on the name. `screen-flow-carousel` leads with `product-demo` and stays on the shelf that says what it is for; a future carousel that is not named `carousel-*` still lands here. Showcases 47 -> 22, Carousels 25, and no existing item changed shelf.
Its ring was centred at x=3832.6 in a 3840-wide composition, so it sat on the right edge and most of it fell outside the frame. Only a few cards were ever visible, cropped, with two thirds of the composition empty. The exported path carried absolute coordinates from a layout that was never recentred. Shifting the four vertices by (-1912.597, -4.340) puts the ring on the composition centre. Handles are relative, so only the anchor points move and the shape is unchanged. carousel-text-circle-5 shares the identical path and had the identical fault. The other three circle variants sit within 12% of centre, which reads as authored placement rather than the same bug, so they are left alone.
The Catalog preview plays the payload, not the composition on disk, so recentring the source changed nothing a reader sees. The payload still carried the old vertices and the ring still hung off the right edge of the frame. Verified the consumer this time, not just the producer: both payloads now resolve to a path centre of x=1920. The other 23 rebuild byte-identical, so the formatting pass did not reach them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds 25 image carousel blocks to the registry catalog across 5 families, each with 5 style variants:
Each block ships a standalone 1920×1080 / 6s composition and a
registry-item.jsonwith 12–24 configurable image slots.Their placeholder images are hosted on the CDN rather than committed, which needs one small addition to the registry format (below).
Why
The registry is served straight out of this repository —
DEFAULT_REGISTRY_URLpoints atraw.githubusercontent.com— so every byte a block ships stays in the history forever. Compositions are text and cost nothing. Images are not.These 25 blocks would have added 421 JPEGs: 27.7 MiB in a checkout, and a 472-file diff nobody can review. Only 23 of those images are distinct; the same 12–24 placeholders were copied into every block directory.
How
files[]entries gain an optionalurl. When set, the installer fetches the bytes from there instead of joining the registry base:{ "path": "assets/carousel-images/one.jpg", "url": "https://static.heygen.ai/hyperframes-oss/registry-assets/223d162eed10f08f.jpg", "target": "assets/carousel-images/one.jpg", "type": "hyperframes:asset" }Three decisions worth calling out:
pathdoes not change. It still says where the file lands relative to the item, so composition HTML, target mirroring andhyperframes addall behave exactly as before. Only the source of the bytes moves. Users still get real local files, so rendering stays offline and deterministic.Keys are content-addressed. The 396 manifest entries resolve to 23 objects, and a changed image gets a new URL rather than a stale one served from the edge — the objects are published
immutable, max-age=31536000, so a re-upload under an old name would keep serving old bytes until the TTL expired. No invalidation step needed as a result.The catalog preview renderer needed the same step. It copies an item's directory and renders it, and
cpSynconly carries what is committed. Without materialising hosted files first, the preview draws every card blank and reports success — worse than failing. Containment reuses the existingisContainedIncheck, since manifests arrive from pull requests.scripts/host-registry-assets.ts(bun run host:registry-assets <item>) does the upload and the manifest rewrite. Contributors without AWS access commit assets normally and a maintainer runs it before merge;docs/contributing/catalog.mdxdocuments this.Also drops
registry/catalog/, 25 hand-made thumbnails referenced by nothing — catalog previews are rendered by CI and served fromdocs/images/catalog.Result: 472 files → 87, and zero binaries.
Catalog pages
These 25 blocks had no Catalog page. Every other item in the registry has one, so they shipped invisible: installable by name, unfindable by browsing.
Generating them naively undid the change above. The Catalog payload copies an item's assets into
docs/public/, which is tracked, so the 396 images removed here came straight back as 43 MB one directory over, worse than the 3 MB they started as, because each block gets its own copy.That copy exists because these compositions assemble
img.srcat run time from a variable value. There is nosrc="..."in the markup for the payload's asset scan to resolve, and an unpredictable path can only be satisfied by serving every file beside it.An absolute URL needs no directory, since the scan already skips any
https:reference. So on the payload path only, hosted files are left undownloaded and the composition's variable defaults are rewritten to their URLs. The preview renderer still downloads them: it paints real frames, and a missing file there is a blank card.25 pages and 25 payloads, zero bytes of image added.
Test plan
Three new tests on
assetSourceUrlcovering the plain path, the hosted path, and rejection of a non-https://URL. Verified non-vacuous: forcing the plain-path branch fails 2 of the 3.bun run --filter @hyperframes/cli test— 3068 passed, 1 skippedbun run --filter @hyperframes/core test— 2659 passednode scripts/lint-registry-items.mjson one block per family — 0 errorsEnd-to-end, on real infrastructure:
hyperframes add carousel-orbit-1against a locally served registry installs 24 real JPEGs (filereports JPEG for all 24, 1.6 MB total).generate-catalog-previews.tsrenderscarousel-orbit-1,carousel-vision-1andcarousel-path-3with the images present. Removing the fetch step renders blank white cards and still exits 0, which is the failure the step exists to prevent.mint validateandmint broken-links --check-redirectspass with the 25 new pages, and all 521 doc pages return 200 on a localmint dev.bun run test:scripts— 86 passed.Not covered
texture-mask-textand several font files); those are a follow-up, deliberately kept out of this diff.scripts/generate-catalog-pages.test.tshas one failing case, "still encodes the first load with encodeURIComponent". It fails identically onorigin/mainand no script inpackage.jsonruns that file, so CI has never reported it. Untouched here, and worth its own fix.