mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-14 18:01:20 +08:00
sync/hyperframes-codegen-87a9cead
19 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3a7fcd10e0 |
feat(registry): add 25 image carousel blocks (#3790)
* feat(registry): add 25 image carousel blocks (5 families × 5 variants) 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> * refactor(registry): host carousel block images on the CDN 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. * style(registry): format the carousel composition HTML `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. * feat(catalog): publish the carousel blocks without republishing their 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. * refactor(catalog): split the hosted-asset step out of prepareProjectDir 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. * feat(catalog): give the carousels their own shelf 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. * fix(registry): centre the circle-5 carousel path in its composition 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. * fix(catalog): rebuild the circle-5 payloads after recentring the path 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. * feat(catalog): promote Carousels to its own section It was a shelf inside Scenes & demos, which is where a scene type belongs by kind but not by weight. At 25 items it is larger than Data & charts (17) and Blocks (13), each of which is already a section holding a single shelf, so the catalog's own precedent puts it one level up. Pulling it out also takes the largest section in the catalog from 120 items to 95, which is the reason the shelf was added in the first place. The two circle-5 pages change because their embedded source block carries the recentred path; nothing else in them moved. --------- Co-authored-by: Jake Moran <jake.moran@heygen.com> Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com> |
||
|
|
406bf316a3 |
fix(catalog): make component previews answer their variables panel (#3323)
* fix(catalog): make component previews answer their variables panel Every control on a component's catalog page did nothing. Asking caption-camera-follow for a violet accent rendered gold, and so did green and blue, on 166 of the 168 components that declare variables. A component ships a snippet, which is what the page hands you to paste and which carries the declaration plus the script that turns a chosen value into a CSS custom property, and a demo.html which stages and animates it. The preview is built from the demo, and the demo was authored as a copy of the snippet rather than a reference to it. The copies drifted until almost none of them carried the declaration or the reader, so the payload for that page never contained the word violet at all. Components come in two shapes, so the repair does too. 123 ship a snippet that registers its own paused timeline. That snippet is a whole piece, so their preview is now built from it and carries markup, variables and motion together. 45 are markup plus a commented recipe, where the demo owns the motion. Those demos now carry the snippet's declaration, reader and var-driven CSS in the registry itself, written by scripts/catalog/sync-demo-variables.ts. Nothing is patched in at build time. A test runs that tool in dry mode and fails when a demo has drifted again, naming the command that repairs it. It also asserts it inspected more than a hundred components, because a check that silently matches nothing is how this rotted in the first place. Measured by rendering every payload in a real player rather than by reading markup: payloads declaring their variables go from 2 of 168 to 168 of 168, previews that animate go from 166 to 167, and nothing that moved stopped moving. ascii-render-pass and star-rating-fill render a still frame when built from their snippet, so they keep the demo path as a recorded exception and stay in the state they were already in. * refactor(catalog): give the preview pipeline one lookup and one entrypoint guard Follow-up on the same branch, no behaviour change: 42 tests still pass and `sync-demo-variables --check` still reports all 168 components clean. The payload generator and the demo sync had each grown their own copy of "given a component directory, find the snippet and the demo". Both now call `componentFiles`, which is the same duplication-by-copying that broke the previews in the first place. Both catalog generators also carried a byte-identical 12-line guard for "only run main() when this file is the entrypoint". That clone was already in the tree, but nothing had touched both files at once before, so it had never surfaced. It is now `runAsCommand`, and the sync script's variant of the same condition is `isEntrypoint`. The rest is flattening: the layering guards read as a table of conditions instead of a chain, the reporting splits by what it reports, and the entry resolution comes out of `buildPayload` rather than being spliced into it. Also runs the formatter over the demos this branch rewrote. Whitespace only, and `notes-typing` is the only component demo that renders pre-formatted text, which this does not touch. |
||
|
|
de4062a933 |
fix: create temp dirs with mkdtemp, not a name built from Date.now() (#3241)
* fix: create temp dirs with mkdtemp, not a name built from Date.now() Closes nine open `js/insecure-temporary-file` alerts — the technically correct ones. An audit of all 29 open alerts for that rule split them three ways: - 19 false positives: the write lands inside a directory the caller already made with `mkdtempSync`, and CodeQL's dataflow reaches `tmpdir()` without seeing the mkdtemp in between. - 1 mitigated: `fontCompression.ts` writes with `flag: "wx"` and only takes the tmpdir branch inside Lambda, where /tmp is single-tenant. - 9 real, and these are them. A name built from `Date.now()` under the shared temp dir, followed by `mkdirSync`, is guessable to the millisecond AND leaves a window between choosing the name and creating it, so on a shared machine another user can pre-create or symlink the path first. `mkdtempSync` closes both halves: it picks the random suffix and creates the directory 0700 in one syscall. Same shape, one line shorter, and the alerts go away rather than being dismissed. Six sites in `normalize.test.ts` (its `mkdirSync` import goes with them), one in `generate-catalog-previews.ts` — that single construction accounted for three alerts, since the other two were writes into the directory it made. No shared helper. `mkdtempSync` is already the stdlib primitive for exactly this, and the two callers live in different packages, so a wrapper would need a home in core to serve one CLI test and one build script — more indirection than the line it saves. Deliberately not touching the other 20: excluding the rule repo-wide would hide this class of bug from future code, which is the reason these are fixed rather than silenced. * fix: track the wav temp dir for cleanup and finish the mkdtemp sweep The wav helper pushed the file path into `dirs`, so `afterEach` removed `tone.wav` and left the directory it had just made — four per suite run. Push the directory and derive the file path from it. Measured: the old code leaks 4 directories per run, the new code leaks 0. Three sites still built a predictable name and then created it. CodeQL never flagged them — its dataflow reaches the template preview writes through a `readdir` walk and does not connect them back to the `tmpdir()` root — so the alert list was narrower than the pattern, and closing only the alerts would turn the rule green while the shape survived where nothing would re-flag it. `generate-template-previews.ts` is the near-twin of the file this change started from, and the other two are producer dev entry points. All three use the path only through the variable, so the random suffix changes nothing. Catalog previews now call the existing `createCatalogPreviewTempDir` instead of repeating its body. That test was in no runner, so it pinned uniqueness and mode 0700 on a function nothing called; adding it to `test:scripts` alongside a real caller makes it load-bearing. The rationale for the primitive moves to the helper, which is now the only place it lives. * ci: re-run catalog previews when the temp-dir module changes Routing the renderer through `createCatalogPreviewTempDir` made that module part of its runtime path, and the workflow already states the rule for the sibling case: a module the renderer imports has to appear in the trigger, or a change to it alone never re-runs the job that exercises it. Add it to the `paths:` filter and to the renderer canary, so a PR touching only the temp-dir allocation still renders both shape canaries. Verified against this branch's own range: the previous argument list does not report the file, so a helper-only PR was invisible to both checks. |
||
|
|
1ae2067b8d |
feat(catalog): put the variables panel back, on payloads (#3199)
* feat(catalog): put the variables panel back, on payloads The panel drove its preview by loading an .html from docs/public, a type the host does not publish, so it showed an empty frame in production and was parked when the catalog was re-landed. It now mounts the same JSON payload the plain player uses and re-mounts it as values change, injecting them as window.__hfVariables into the composition head before any of its scripts run, which is where the runtime reads overrides from. Doing it in the markup rather than after load is what stops the composition initialising with the wrong values first. 172 items with variables get the panel back; the playhead carries across a change so a tweak mid-shot does not jump back to frame zero. * fix(docs): drop the unused url form and the needless escapes * fix(docs): the panel cannot reference a binding beside the export * feat(catalog): make importing an SVG the obvious move A reader arrives at this control with a shape, not with path data, and the panel asked for the coordinates first. Import is now the primary action in a drop target you can see is a drop target, and the raw path sits behind a disclosure for anyone who wants it. * feat(cli): let a fruitless catalog search report the gap An agent that searches by meaning and finds nothing worth installing knows something we do not: the name of a move the catalog is missing. There was no way to tell us, so that knowledge was lost at the end of every run. hyperframes feedback --search-miss "<query>" --wanted "<the move>" records it. It carries no rating, so it never lands in the rating metric, and it is a separate deliberate command rather than something catalog --query does on its own: plain search still sends nothing, which is what the CLI promises. --rating stops being required at the arg level, since a miss has no rating to give. The check moved into the run body, where an absent one is now handled rather than crashing on undefined. * feat(cli): carry tuned variable values into the install snippet Someone who tunes a block on its catalog page had no way to keep those values: the install command was the same one everybody gets, and the tuning stayed on the page. hyperframes add <item> --vars '<json>' now prints a mount element carrying data-variable-values, so the values land where the block is used. They ride on the host rather than being written into the installed file. That keeps the composition on disk byte-identical to the registry's, so a later reinstall can still tell an edit from an update, and it lets two mounts of the same block carry different values. * fix(catalog): serve the item's own directory so runtime paths resolve Some compositions assemble their asset URLs at run time — "compositions/components/" + texture + ".png" for the texture masks, a font the compiler pulled into _remote_media — and no scan of the markup can see a string that does not exist until a script concatenates it. Those items either rendered black or were dropped to a video that had never been uploaded. Each item that needs it now has its prepared directory published, and its payload carries a <base> pointing at it, so any relative path the composition invents resolves. caption-texture renders its masks again, and variable-font-flex has a preview at all for the first time: its MP4 and poster are both 403. Both layouts are published, because which one a composition asks for differs per item, and a directory only earns that if it is under 2 MB. The 12 MB texture sheet keeps the recorded video it already had. * fix(catalog): let the variables panel actually drive the composition Every control on the panel was inert. The values reached the composition and nothing repainted, because the payload had already been compiled: compiling inlines a mounted component and resolves its variables into the markup and CSS, so by the time a reader turns a knob there is nothing left to change. An item that declares variables now ships uncompiled, keeping the mount the runtime loads at run time, which is the only state where data-variable-values still means anything. The component travels inline as a data URI rather than a sibling file, because .html is the one type the docs host will not publish. The demo's own pinned values come off, so the reader's choices reach the mount instead of losing to the values the demo picked to show itself off. Measured on the rendered frame rather than the DOM: green rgb(98,207,144), blue rgb(6,6,199), violet rgb(177,147,230), and back to green. docs/public/catalog drops from 48 MB to 35 MB along the way, since an uncompiled payload carries far less than an inlined one. * feat(catalog): keep variable changes in the url A reader who tuned a piece lost it on reload, and had nothing to send anyone. The values now live in the query string, scoped by composition id so two links never read each other,and only the ones that differ from the defaults are written, so changing one knob gives a short URL rather than every variable spelled out. replaceState rather than pushState: dragging a slider should not leave a trail of history entries. An unreadable value is ignored rather than thrown, so a truncated or hand-edited link opens the piece at its defaults. * fix(catalog): only rewrite the url when a value actually changed * refactor(catalog): memoise the declared defaults on their content * feat(catalog): offer an install command carrying the tuned values The Install block is generated before anyone touches a knob, so it can only ever print the plain command. Someone who spent a minute tuning a piece copied it and got the defaults back. The panel now carries its own command in the Snippet tab, with --vars holding exactly the values that differ. An untouched piece still offers the same short command, so nothing gets noisier for the common case. * fix(catalog): a piece with nothing to render is a skip, not a failure caption-blend-difference is a stylesheet and a paragraph of prose — a class you add to your own captions, with no standalone scene to show. The generator treated that as a build failure, so every run ended by reporting something broken when nothing was. It now reports the shape it is and keeps its recorded video, which is the only honest preview such an item has. A genuine render failure still throws. * fix(catalog): restore variables from the url on a cold load A shared link opened at the defaults. The first render happens on the server, where there is no window to read the query string from, and React then hydrates against that markup and never revisits it — so the values only appeared once you touched a control. The URL is read again after mount, which is the first moment it exists. The value is also escaped once now rather than twice: URLSearchParams already decodes on the way out, and decoding a second time turned an SVG path full of percent-escapes into something that no longer parsed, besides doubling the length of every link. * fix(catalog): mount the preview with the values a link carried The frame was built from the declared defaults and the shared values were posted to it afterwards, which is too late for anything the composition reads once at init: a path arrived after the mark had already been drawn from the default one, so a link looked right in the panel and wrong on screen. * feat(catalog): the install command follows the values you tuned Copying the Install line gave the plain command back, because that block is generated before anyone touches a knob and had no way to know what changed. The tuned command only existed in the panel Snippet tab, which is not where anyone looks for it. The line now reads the same query string the panel writes, so the two agree without either component knowing the other exists, and a shared link carries the right command too. replaceState fires no event, so the panel announces its own writes. * fix(catalog): send a text variable to the preview once it is finished Every other control in the explorer reports a whole value on every event: a slider at any position is a position, a swatch is a colour. A text field is not. Typing v3 into a badge posted v first, so the preview remounted and rendered a composition built from half a word. The post now waits while a text field has focus and goes out when the edit is committed, with Enter or by clicking away. The field itself is unchanged and still tracks every keystroke. --------- Co-authored-by: Miguel Angel Simon Sierra <miguelangelsi07@gmail.com> |
||
|
|
536165b6ef |
feat(catalog): play the real composition on catalog pages (#3168)
* feat(catalog): play the real composition on catalog pages 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. * chore: rebuild docs preview * chore: route isolation probe * chore: drop the route isolation probe page * chore: probe which asset types the docs host publishes * refactor(catalog): host preview assets instead of inlining them 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. * refactor(catalog): share preview assets instead of repeating them * style: format the asset module |
||
|
|
79dff20516 |
feat(scripts): typecheck the scripts directory (#3149)
* feat(scripts): typecheck the scripts directory scripts/ was the one TypeScript surface nothing typechecked. Adding a project for it surfaced real errors rather than style: a preview generator passing string | undefined where a string was required, a readdir result indexed without a bound, and two non-null assertions standing in for a filter that could have narrowed the type instead. The two preview generators had also drifted into sharing a capture setup, down to the comment explaining why the capture is opaque. That lifts into scripts/preview-capture.ts, so the reason is written once and both callers own the handles they have to close. @hyperframes/core and @hyperframes/producer become dev dependencies because the scripts import them; without that the project resolves on a machine with a warm node_modules and fails in CI. * fix(scripts): use source imports consistently |
||
|
|
218eff7d36 |
fix(scripts): render template-only blocks in catalog previews (#3098)
* fix(scripts): render template-only blocks in catalog previews The catalog preview renderer treated any file containing `__timelines` as a standalone composition and rendered it as index.html directly. The 12 VS Code snippet blocks register their timeline inside a `<template>`, which stays inert until a host mounts it, so every one of them failed with "Composition has zero duration" and no preview could be produced from the registry at all. Six of the previews on the docs CDN were hand-made from a project still mounting Monokai, so Dark+, High Contrast, High Contrast Light, Solarized Light, Visual Studio Dark and Visual Studio Light all showed Monokai's video. Detect standalone-ness on the document with template content stripped, mount the mirrored install-layout copy so a block's own `../assets/*` references resolve, and capture posters opaque: `format: "png"` is the engine's transparent mode and forces `background-image: none` on every composition root, which erased the desktop backdrop these blocks paint. Publishing gets the missing half too: preview URLs are stable and the objects are uploaded `immutable` with a one-year max-age, so a re-upload alone never reaches a reader. * fix(scripts): install ffmpeg in the preview job and fix the sibling renderer The canary this PR added caught its own regression: the poster transcode shells out to ffmpeg, which ubuntu-latest does not ship and this job never needed, so both canaries failed with `spawnSync ffmpeg ENOENT`. Install it the way every other render job does. `encodeForWeb` has always shelled out to the same binary; the job only got away with it because `--skip-video` skipped that path. generate-template-previews.ts captures posters through the same transparent `format: "png"` mode, so any template painting its own backdrop loses it exactly as the code snippets did. Fixing one renderer and leaving its sibling on the broken call would just move the bug. Also fold the three separate parses of registry-item.json into one read: they had drifted into three different failure behaviours for the same file. |
||
|
|
592301248e |
fix(scripts): drop the catalog poster instead of guarding it, and cut the encode pass down
**The poster guard I added twice was unworkable and I never ran it.** It called existsSync on docs/images/catalog/<name>.png. That directory is gitignored — previews are generated locally, uploaded to the CDN and never committed — so the check is false on every clean checkout and in CI. It would have stripped the poster from all 168 pages, not the 13 with a missing file. It also referenced REPO_ROOT, which does not exist in that file, so the script crashed on the first item. I described this guard in two commit messages without once executing the generator. The poster is now gone entirely, which is the smaller and more honest fix. These previews are autoPlay muted loop, so the poster is visible for a few hundred milliseconds; 13 of the 168 files do not exist and the browser fetches the poster before the video. Removing the attribute kills 13 x 403 and 168 needless image requests, and there is nothing to keep in sync. Also applied a complexity pass to the delivery encode: - hasAudio() deleted. 17 lines and a spawnSync per item to choose between "-c:a aac" and "-an". ffmpeg ignores -c:a when the input has no audio stream; checked, exit 0, output carries no audio track. - The 40-line spawn + Promise wrapper is execFileSync. Everything around it in that script is already synchronous. - The duplicated poster lookup is gone with the poster itself. Net 76 lines lighter. Generator runs clean, emits 168 pages, carry-forward intact. |
||
|
|
4f1ace9f47 |
fix(scripts): actually commit the delivery encode
An earlier commit here said generate-catalog-previews.ts "has a web pass now". It did not. I made that edit in a second checkout while investigating and never brought it onto the branch, so the claim shipped in a commit message while the code stayed as it was. The fix itself is unchanged from what was described: the render output is a master, and publishing it directly is what put 25 Mbps files on the docs CDN. generateVideo now renders to <name>.master.mp4, runs one delivery pass at 1280 wide, CRF 28, faststart, keeping audio only when the source has it, then deletes the master and logs the delivered size. Formatted and linted clean. The Format check failing on this PR is pre-existing: origin/main fails oxfmt --check on the same 30 files, none of which this branch touches. |
||
|
|
bd7ea5d5ce |
fix(scripts): contain registry manifest paths in the preview renderer
Miguel's P1 on #2975, and it is real. `catalog-previews.yml` triggers on `pull_request` for anything under `registry/blocks/**` or `registry/components/**`, so `registry-item.json` arrives from the pull request and is untrusted. `mirrorRegistryTargets` joined `files[].path` and `files[].target` under the temp project and called `cpSync` on the result, and `join()` walks out of its first argument. A `path` of `../../../../etc/passwd` reads an arbitrary runner file into the project — which the job then uploads as an artifact — and a `target` of the same shape writes an arbitrary runner path. Both sides are now resolved and rejected when `relative(projectDir, candidate)` is absolute or starts with `..`. Traversal that lands back inside the project still works, so `nested/../demo.html` is unaffected. Containment lives in `scripts/registry-target-paths.mjs` rather than inline, because the traversal cases have to be testable and importing `generate-catalog-previews.ts` drags in the producer. `existsSync` is injected so the decision cannot depend on whether the target happens to exist on the runner. Eight tests, covering traversal on each field separately, absolute paths on each field, the sibling directory that shares the project's prefix, and traversal that returns inside. Verified end to end on a real tree, not only in unit tests: a manifest asking to read `../secret.txt` and write `../pwned.txt` produces neither file, while the legitimate entry still copies. I introduced the wrapper when I extracted this block for a complexity finding earlier in the stack, and did not look at what it was joining. |
||
|
|
0dcd06ff62 |
refactor(scripts): extract mirrorRegistryTargets from prepareProjectDir
Inlining the registry-target mirroring pushed prepareProjectDir to cyclomatic 24, the one complexity finding fallow attributes to this branch — the other eight are inherited and out of scope here. Same behaviour, expressed as a filter chain in its own named function, so prepareProjectDir returns to what it was and the new helper stays flat. |
||
|
|
79c4056a3f | docs: make Catalog visual and reproducible | ||
|
|
b0d3164ddb | feat(registry): add media treatment overlays | ||
|
|
c9e8dd3862 | fix(runtime): honor render fps when seeking (#1739) | ||
|
|
91f811d4d5 |
fix(scripts): pass rational fps to capture/render in catalog previews
`scripts/generate-catalog-previews.ts` still called `createCaptureSession`
with `fps: 30` and `createRenderJob` with `fps: 24`. Since commit
|
||
|
|
4ae5c0340f |
chore(docs): migrate docs/images/ media to static.heygen.ai CDN (#301)
Move all preview mp4/png/gif assets under docs/images/ out of the repo and serve them from https://static.heygen.ai/hyperframes-oss/docs/images/ (backed by s3://heygen-public/hyperframes-oss/docs/images/, CloudFront). Drops ~49MB from the working tree and, more importantly, ~49MB from every future Mintlify build checkout. Combined with the (already-LFS-tracked) producer snapshots, the remaining bloat in 'npx skills add heygen-com/ hyperframes' (see #300) is LFS smudge during clone — separate fix needed in the skills CLI to pass GIT_LFS_SKIP_SMUDGE=1. Changes: - Delete docs/images/** (103 files, ~49MB). Files are uploaded to S3 already. - Rewrite /images/* references in 44 MDX files, TemplateCard.jsx, and catalog-index.json to absolute CDN URLs. - Update README.md img src to CDN URL (renders correctly on GitHub). - Add docs/images/ to .gitignore so regenerated previews aren't committed. - Add scripts/upload-docs-images.sh to sync docs/images/ → S3 after running the preview generators. - Wire up bun run upload:docs-images and bun run generate:catalog-previews scripts in package.json. - Update generator script docstrings to point at the upload step. External contributors can still regenerate previews locally (mintlify dev reads the CDN URLs, so broken previews appear only for newly added items pending a maintainer upload). Maintainers run: bun run generate:catalog-previews --only <name> bun run upload:docs-images Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9943091247 |
feat(registry): seed transition blocks — 14 shader + 14 CSS showcase (#270)
## What Add 28 transition blocks from the Hyperframe Template Structure catalog, bringing the registry to 53 total items. ### Shader transitions (14 blocks, WebGL, 4s each) `domain-warp-dissolve`, `ridged-burn`, `whip-pan`, `sdf-iris`, `ripple-waves`, `gravitational-lens`, `cinematic-zoom`, `chromatic-radial-split`, `glitch`, `swirl-vortex`, `thermal-distortion`, `flash-through-white`, `cross-warp-morph`, `light-leak` ### CSS transition showcases (14 blocks, various durations) `transitions-3d`, `transitions-blur`, `transitions-cover`, `transitions-destruction`, `transitions-dissolve`, `transitions-distortion`, `transitions-grid`, `transitions-light`, `transitions-mechanical`, `transitions-other`, `transitions-push`, `transitions-radial`, `transitions-scale`, `transitions-shader` ## Why Phase D content accumulation. Transitions are the most-requested category for the catalog. ## How - Shader transitions extracted from `shader-showcase.zip`, each a standalone HTML with WebGL shaders - CSS transitions extracted from `showcase-bundle.zip`, each a standalone showcase page - All tagged with `transition` + `shader` or `showcase` for catalog grouping - Preview thumbnails generated for all 28 blocks - Catalog pages + index regenerated ## Test plan - [x] All 28 blocks produce preview thumbnails - [x] `registry-item.json` validates for all blocks - [x] Catalog pages generated (45 total items in catalog-index.json) - [x] `oxfmt --check` passes |
||
|
|
4bde66f532 |
feat(skills): hyperframes-registry skill (#261)
## What
New skill `hyperframes-registry` that teaches AI coding agents how to install and wire registry blocks and components into HyperFrames compositions.
### Skill structure
```
skills/hyperframes-registry/
SKILL.md — triggers, overview, quick reference
references/
install-locations.md — default paths, hyperframes.json config
wiring-blocks.md — iframe inclusion, data attributes, positioning
wiring-components.md — snippet merging (HTML, CSS, JS, timeline)
discovery.md — manifest reading, item fields, available items table
demo-html-pattern.md — why components ship demo.html, structure conventions
examples/
add-block.md — worked example: data-chart block install + wiring
add-component.md — worked example: shimmer-sweep component install + wiring
```
## Why
Phase B of the catalog plan (PR 10). Without this skill, agents using `hyperframes add` have to guess how to wire installed items into compositions. The skill encodes the iframe/snippet patterns so agents get it right on the first attempt.
## How
- SKILL.md frontmatter triggers on: `hyperframes add`, "block", "component", `hyperframes.json`
- References cover every step: discovery, install, wiring blocks (iframe), wiring components (snippet merge), and the demo.html convention
- Two worked examples walk through complete install-to-preview workflows
- Updated CLAUDE.md skills table + trigger rules, README.md skills table, docs/packages/cli.mdx
## Test plan
- [x] `scripts/lint-skills.ts` passes (checked 4 skill files, no issues)
- [x] `oxfmt --check` passes on all markdown files
- [x] SKILL.md frontmatter has valid `name` and `description`
- [x] All reference links in SKILL.md resolve to existing files
- [x] CLAUDE.md, README.md, and docs CLI page updated with new skill
|
||
|
|
ea6f949922 |
ci: render catalog previews on PR (#262)
## What
CI workflow that auto-renders preview thumbnails for new/changed registry blocks and components on pull requests.
**New files:**
- `scripts/generate-catalog-previews.ts` — catalog preview renderer supporting all three registry item types
- `.github/workflows/catalog-previews.yml` — GitHub Actions workflow triggered on PRs touching `registry/blocks/` or `registry/components/`
## Why
Phase B of the catalog plan (PR 8). After this lands, future block/component PRs don't need to manually generate preview images — CI handles it automatically.
## How
The preview script discovers items from the registry directory structure:
- **Examples**: renders `index.html` (same as the existing `generate-template-previews.ts`)
- **Blocks**: renders the block's standalone HTML file directly (e.g., `data-chart.html`)
- **Components**: renders the component's `demo.html` (the demo.html convention from PR 7)
The CI workflow:
1. Detects which blocks/components changed in the PR via `git diff`
2. Renders thumbnails for only the changed items (not the full catalog)
3. Uploads preview PNGs as artifacts
Output goes to `docs/images/catalog/<type>/<name>.{png,mp4}` (separate from the existing `docs/images/templates/` directory).
Supports CLI flags: `--only <name>`, `--type <example|block|component>`, `--skip-video`.
## Test plan
- [x] Script compiles and passes typecheck (`lefthook pre-commit` ran lint + typecheck + format)
- [x] Workflow YAML is valid (standard GitHub Actions syntax, follows existing ci.yml patterns)
- [ ] Full end-to-end test requires Chrome + FFmpeg (runs in CI, not testable locally without producer deps)
|