Commit Graph
815 Commits
Author SHA1 Message Date
Vance IngallsandClaude Opus 5 bc2a8b5e0d docs(cli): document the parallel-stream router and its capture-mode split
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 20:52:58 -07:00
Vance Ingalls 2b2b999e62 Merge pull request #4109 from heygen-com/lfr/02-phase0-duration-cap
feat(producer): stream long single-worker renders by default; duration cap behind a flag (Phase 0)
2026-09-18 20:50:36 -07:00
Miguel Ángel a25da94b77 refactor(studio): remove the storyboard view and its Storyboard/Preview switch (#4140)
* refactor(studio): remove the storyboard view and its Storyboard/Preview switch

* refactor(studio): fold the thumbnail output branches left after the storyboard mode went

* docs(studio): drop leftover storyboard references after the view was removed

* ci: allow the storyboard view deletions in the no-deletions guard

* docs(studio): wrap an over-long comment line in the caption overlay helper

* style(studio): reindent App.tsx after the view-mode wrapper was removed
2026-09-18 23:47:12 -04:00
Vance Ingalls 42f4afa367 chore: release v0.8.50 2026-09-18 19:41:06 -07:00
Miguel Ángel cb9ca4f0af chore: release v0.8.49 (#4122) 2026-09-18 17:44:18 -04:00
Miguel Ángel 9bb4f5cd57 feat(catalog): browse the catalog as a live grid, not a static index (#4051)
* feat(catalog): browse the catalog as a live grid, not a static index

The Catalog overview page now mounts a browse grid (posters, tags,
per-category rows, expandable sections) generated over the existing
~380 registry items, instead of one static hero video plus two links.
docs/snippets/catalog-gallery.jsx and docs/catalog-gallery.css are
ported from a reference implementation of this same UX; the CSS keeps
its original comments as-is (NO_ESSAY_COMMENTS_OK: vendored file).

scripts/sync-docs-catalog.mjs and build-docs-gallery-nav.mjs are new,
generating the gallery's data and the Catalog tab's nav directly from
this repo's registry/*/registry-item.json and docs.json, so no sparse
clone or snapshot of another project is needed. scripts/check-docs-catalog.mjs
proves every catalog page from before this change still exists, on
disk and in the sidebar, with an identical page list.

No catalog page or snippet is removed; the previous overview content
(catalog-overview-player.jsx) is unused on this page but stays in the
repo.

* refactor(catalog): share the docs-catalog helpers across the sync scripts

Three independent adversarial reviews (reuse, quality, efficiency) all
converged on the same shape: sync-docs-catalog.mjs, build-docs-gallery-nav.mjs
and check-docs-catalog.mjs each hand-wrote the same docs-root resolution,
the same catalog-gallery-data.mdx parse, and the same Catalog-tab lookup.
Extracted into scripts/docs-catalog-shared.mjs.

sync-docs-catalog.mjs also ran build-docs-gallery-nav.mjs as a dynamic
import purely for its side effect, forcing a same-process re-read and
re-parse of the data file it had just written. build-docs-gallery-nav.mjs
now exports buildNav(docs), called directly with the path already
resolved; its CLI entry is guarded for standalone use.

slug() dropped the /^-+|-+$/ trim in favor of the character-scan trim
packages/core/src/tokenSlug.ts already uses for the same reason (CodeQL
flags that regex as polynomial ReDoS, js/polynomial-redos); not imported
directly since tokenSlug.ts is compiled TS with a different domain
(CSS variable naming) and no existing scripts/*.mjs imports packages/core
source at plain node runtime.

Fixed one bug found while re-verifying after the refactor: the recursive
nav walk wasn't idempotent against its own generated output — re-running
sync-docs-catalog.mjs a second time saw the "Catalog" wrapper buildNav()
adds and misattributed every flat, single-section group under it, 9
groups collapsing to 7. walk() now treats "Catalog" as transparent, the
same way it already treated "Overview". Verified: three consecutive runs
from a fresh origin/main checkout now produce identical 9 groups / 400
items each time, and check-docs-catalog.mjs still passes.

* chore(catalog): scrub an internal decision code from a code comment

* fix(catalog): stop losing a section's label when its nav is flattened

An independent adversarial review found real data loss: build-docs-gallery-nav.mjs
collapsed a group with exactly one section into a flat page list (a clean sidebar
for the common case), but sync-docs-catalog.mjs's walk() derives an item's section
from nav depth, so a second sync run over that flattened nav could no longer
recover the section name and silently fell back to the group's own label. Data &
charts / Data was the one real casualty in this catalog; Carousels and Blocks
happened to already have a section named the same as their group, so the loss
was invisible there.

Fix: only flatten when the section label equals the group label, so nothing
non-recoverable is thrown away. Verified with a deliberate mutation (reverted the
condition, reproduced the exact bug from a clean origin/main checkout, confirmed
the new check-docs-catalog.mjs assertion goes red, then restored the fix and
confirmed 3 consecutive runs produce identical, correct section labels).

check-docs-catalog.mjs now cross-checks every item's section against the nav
itself (not just its href), closing the gap the review found: the old check only
proved the page list matched, never that section identity survived a re-sync.

Also deletes docs/snippets/catalog-overview-player.jsx: unreferenced anywhere in
the repo now that catalog/index.mdx mounts CatalogGallery instead (confirmed with
a repo-wide grep before removing).

* chore(catalog): allowlist the deliberate catalog-overview-player.jsx deletion

* feat(catalog): live-hover preview, fuller sidebar, landing order

Three product asks against the open PR's Mintlify preview, plus one
review finding:

- Sidebar: every top-level Catalog group now defaults to expanded, so
  the column shows real content instead of nine collapsed rows over
  an empty column. Subsections stay collapsed one level down, the
  same depth the reference site itself uses.
- Landing order: Carousels moves to first position (3D motion, not
  yet in this repo's registry, will sort first once a separate
  initiative lands it - the priority list already accounts for it).
  Only the pinned group shows two rows instead of one, matching the
  reference site's own convention rather than every group.
- Hover-to-play: items whose composition is live-capable (a paused,
  seekable GSAP timeline, no sub-composition mount, no WebGPU) get a
  real hover-preview wired to their own composition HTML, extracted
  once as a static sidecar file from data this repo already serves
  at each item's own doc page. Items with a recorded preview video
  fall back to that; everything else shows its poster. Verified no
  WebGL/three.js item is ever marked live (238 live, 30 video, 132
  poster-only of 400).
- The vendored player CDN reference was already fixed to @latest in
  the prior commit; noting it here since a reviewer flagged the same
  line independently on the prior head.

docs/public/catalog/gallery-live/**/*.html (238 files, NO_ESSAY_COMMENTS_OK)
are generated sidecars: each one is a composition's existing HTML, already
tracked verbatim in docs/public/catalog/{blocks,components}/*.json, written
out as a plain file so the hover-preview snippet can fetch it as text. Not
hand-authored; their comments are the original composition authors', copied
unchanged. Regenerated by scripts/sync-docs-catalog.mjs on every run.

* refactor(catalog): merge the item and preview passes, one group-label source

itemFrom() and walk() computed the same group-label fallback expression
independently; itemFrom now returns it once alongside the item instead
of the caller re-deriving it. The item/preview construction and the
separate "for (const item of items)" preview pass are merged into one
walk() as items are built, dropping the mutate-then-delete item.live
scratch field. Byte-identical generated output, confirmed by diff.

* chore(catalog): scrub the private upstream repo name from code comments

An independent adversarial review found the same leak class as an
earlier fix (commit cfe14f813, an internal decision code) in four
provenance comments naming a private individual's private GitHub
repository and an exact commit sha, in a public repo. Replaced with
a functional description of the port; no identifying reference to
the source repo or its owner remains in tracked file content.

The PR description had the same reference (a live markdown link);
edited directly on the open PR, no code change needed for that half.

* chore(catalog): restore the adapted-from-elsewhere signal without the name

* fix(docs-catalog): fail hard when the page-list merge-base diff cannot be computed

* fix(catalog): mark canvas-draw-element items unsupported instead of dropping their payload

* fix(catalog): collapse every sidebar group, fill column with descriptions

Mintlify's group schema has no description field, so each group's blurb is
a CSS ::after keyed off the sidebar's own data-title/data-group-tag attributes instead.

NO_ESSAY_COMMENTS_OK: false positive.
The real /* */ block is 3 lines. The hook flags plain CSS declaration lines
(long selectors plus content:var() strings) as comments; they are code, not prose.

* feat(catalog): fold the 3D-motion group into the browse grid

Regenerated after merging feat/ad93-3d-motion-catalog: 407 items, 10

groups, 3D motion leading and pinned on the landing order.

* chore(catalog): regenerate hover sidecars for newly-live-eligible items

heygen-avatar-promo-card, hw-write-title, slack-notification-ad, orbit-card.

NO_ESSAY_COMMENTS_OK: generated composition HTML, not hand-authored.

* fix(docs-catalog): the page-list check should catch loss, not additions

It was written for a pure refactor (this PR's original scope: same pages,

different nav shape) so identical-set was the right proof. Stacking a

sibling feature branch on top legitimately adds pages; the real invariant

is nothing lost and no duplicates, so only that half stays an assertion.

* fix(catalog): mount the player from each item's own payload, no CDN needed

Root cause of the grey Carousels/3D-motion tiles: those registry items

never had a CDN poster/video, and the .html hover sidecar 404s on the

deployed docs host (Mintlify drops .html/.js/.css from docs/public with

no error). Cards now fetch the same repo-served .json payload the detail

page already uses, mount a player, seek to 3s and rest there, play on

hover -- viewport-gated, capped per tier (DOM vs WebGL) so a heavy row

can't blow the page's GPU budget. Also fixes a real regression: the

canvas-draw-element marker file was making detail pages embed a broken

player (srcdoc=undefined) instead of falling back correctly; it now

renders an honest tile naming the required flag. Deletes the now-dead

.html sidecar mechanism and its vendored gating helper.

* fix(catalog): move player-mount consts inside the component scope

Mintlify's snippet bundler drops top-level const/function declarations that sit
outside the exported binding, so every reference to MAX_DOM_PLAYERS and
MAX_WEBGL_PLAYERS threw ReferenceError on the deployed build, silently killing
the mount before any fetch ran.

Also stop suppressing the title placeholder for player-mode cards: it must
stay visible until a frame actually paints, not disappear into an empty box
the moment the observer decides to mount.

* fix(catalog): make mount failures loud and add a bundler-scope check

Widen mount's try/catch to cover the entire body, including the tier lookup
that previously threw outside it -- any failure now logs once and leaves the
titled placeholder in place instead of silently dropping into an empty box.

Add a check-docs-catalog.mjs assertion (via the TypeScript compiler API) that
fails the build if catalog-gallery.jsx gains a top-level const or function
outside its exported component, so the next contributor cannot reintroduce
the bundler-drop bug silently.

* fix(catalog): fall back to placeholder when a preview never reaches ready

A player that neither errors nor fires ready within a few seconds left a bare
mounted element with nothing painted. Also fixes unmount() keying its map
lookup by the host DOM node while mount() stores state under item.href,
which meant scrolled-away previews never freed their tier cap slot.

* docs(catalog): trim an essay-length comment in docs-catalog-shared.mjs

* fix(catalog): drop the WebGPU tile message, keep the headline mounted

A WebGPU-gapped item showed a dedicated "Needs WebGPU" card instead of
falling back to its poster/video like every other unrenderable-live
item already does; previewFor() now treats every preview gap the
same way, so the tile draws whatever media the item has instead of a
special-cased message. Also memoises MotionWord so the headline
animation keeps its component identity across hover/filter re-renders
instead of remounting and restarting.

* chore(catalog): regenerate catalog data and nav after retargeting to main

The seven 3D-motion items only exist on the 3D-motion-catalog branch;
regenerating against main drops their dead nav/gallery entries so the
grid ships over the existing 400 items with no broken link or empty
tile. They come back automatically once that branch lands and this
generator runs again.

* fix(docs): clear format, lint and complexity gates on the catalog PR

Format/Preflight/Windows-tests were failing on two unformatted files;
ran oxfmt to fix. Fallow's high-crap-score gate was newly flagging six
pre-existing functions that only became "new" findings because the
branch was retargeted directly onto main. Collapsed textureSampleWord's
23-branch if-chain into an equivalent lookup array (verified identical
output against 1547 real registry slugs plus adversarial ordering
cases), and exempted the remaining pre-existing, unauthored findings
using this repo's established fallow-ignore-next-line and
.fallowrc.jsonc health.ignore conventions.

* fix(catalog): drop the stale index prose, give the new item a CDN preview

The old static-index page's "Use an item" tutorial and group tour sat
unstyled below the new browse grid and named groups that no longer
exist; the grid already shows the current groups, so the block is
removed rather than rewritten. Kept the required Related topics
section.

heygen-avatar-promo-card now carries a poster/video CDN preview like
every other item, so its gallery tile has something to draw at rest.

* fix(catalog): correct an inaccurate fallow-exemption justification

A reviewer caught that the generate-catalog-payloads.ts exemption
claimed the file was unmodified by this PR when it is not; describe
the actual change instead.

* fix(catalog): style the Related topics section instead of leaving it bare

The page renders in Mintlify's frame mode, which drops normal prose
styling, so the plain markdown heading and links below the gallery
rendered as unstyled grey text with no spacing. Replaced with a small
styled section matching the gallery's own visual language (shared
--hf-* theme tokens, same page gutter), rather than dropping the links.

* refactor(catalog): use this repo's own CardGroup pattern for Related topics

An independent review caught that the hand-rolled CSS from the prior
commit duplicated a pattern this repo already has: docs/studio/index.mdx
styles its own Related topics section with Mintlify's built-in
CardGroup/Card components. Switched to that instead and dropped the
custom CSS. Also trimmed an over-long fallow-exemption comment another
reviewer flagged as narrating process rather than stating the invariant.

* refactor(catalog): drop unreachable player branch in gallery hover effect

* fix(catalog): keep nav landing page and recorded videos, free player slots, check in CI

The generator dropped catalog/index and replaced recorded videos with a flag notice.
A card scrolled away before ready released its player slot twice.
check-docs-catalog now runs in CI.

* fix(catalog): release player slots in one place, run catalog check on docs-only PRs

Unmount, error, timeout and catch now share one idempotent release.
The Lint job also runs when only docs or registry files change.

* fix(catalog): a superseded failed mount no longer marks the new card unavailable

* revert(catalog): move the CI wiring out of this PR

The CI change ships separately as chore(ci).

* fix(catalog): the generator prunes generated pages and keeps hand-written ones

Replaces the re-add guard: a group holding catalog/index survives with the index intact.
2026-09-18 15:10:35 -04:00
Miguel Ángel a0e7c43921 fix(catalog): fall back to recorded video when directory is over budget (#4084)
* fix(catalog): fall back to recorded video when directory is over budget

hostItemDirectory() returned "" for both "not needed" and "over the
2MB host budget", so an item that needed its own hosted directory but
exceeded the cap (texture-mask-text: 5.6MB of mask PNGs referenced via
runtime JS template strings, invisible to the static asset scanner)
silently shipped a payload with no <base href> and dead relative
image references, 404ing on the docs site. It now returns a tagged
result so the caller can fall back to the recorded video on
over-budget, same as the existing unresolved-assets fallback.

* test(catalog): cover the over-budget skip path in the payload generator

* chore(catalog): allowlist the deliberate over-budget payload deletion

* chore(catalog): dedupe hosted-directory assertions, note pre-existing complexity

Fallow's new-only gate flagged two problems this branch introduced: five
near-identical hostItemDirectory assertions in the file-race test collapse
into a shared expectHosted() helper, and the over-budget branch added to
buildPayload pushed it one branch past the complexity threshold on top of
already-high pre-existing debt (CRAP 240 before this branch, unrelated to
the fix). Suppressed with the repo's existing fallow-ignore-next-line
convention rather than refactoring an unrelated 115-line function inside
a bugfix PR.
2026-09-18 13:06:09 -04:00
Miguel Ángel 7b4f3a22b0 chore: release v0.8.48 (#4085) 2026-09-18 11:11:40 -04:00
Vance Ingalls 5b7ff519a5 chore: release v0.8.47 2026-09-17 23:55:00 -07:00
Vance IngallsandClaude Opus 5 c038b087e1 docs(cli): the streaming duration cap is off by default; document the flag
Explains which routes still write raw RGBA frames to disk and what the
preflight error is telling the user, and records that
PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS (including its 0 = "never
stream" setting) is inert unless the new enable flag is set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 17:14:41 -07:00
miga-heygenandMiguel Ángel 82f49262ec feat(registry): blur every transform driver, and ship the reference case as shutter-slam (#4038)
The motion-blur snippet integrated translation only. Its output stage was one
SVG filter per target whose copies were feOffset, and feOffset only translates,
so a beat that only scaled or only turned sampled a displacement of zero and
rendered sharp. No SVG filter primitive can give a copy its own affine
transform, so the sampling loop was never the problem: the output stage was.

Copies are now DOM clones of the element in an isolation:isolate group, each at
1/N opacity with mix-blend-mode plus-lighter, each carrying the element's
resolved transform at its sample time. plus-lighter adds premultiplied colour,
so N + 1 copies at 1/N sum to the average the shutter integral asks for; the
isolation is load-bearing rather than tidy, because without a transparent
backdrop of its own the first copy adds onto the page and blows a light
background out to white. Reading the resolved matrix instead of a list of named
GSAP properties is what makes every driver work at once: translation, scale,
rotation, 3D rotation and skew arrive in the same one value.

Measured against the After Effects export the shutter model was fitted to, per
beat, previous stage to this one: scale +0.62 dB, rotate X +0.43, rotate Y
+0.84, rotate Z +1.15, translate X -0.18, translate Y -0.04. The two small
translate regressions are inherent and not tunable: the filter offset one
rasterisation so every copy was pixel-identical, where each copy is now
rasterised independently at its own fractional transform with its own subpixel
antialiasing.

Three consequences of the new stage, each carrying its own fix:

- mix-blend-mode flattens preserve-3d, so a copy cannot inherit the parent's 3D
  context. Each copy carries the parent's perspective as its own first transform
  function instead.
- A copy is styled by nothing that selected the original, since dropping the id
  also drops every #id rule that gave it size, colour and font. Each copy
  carries its own resolved style inline, read once and replayed. Those values
  are px, so a container-relative element needs them again when its box changes:
  a ResizeObserver re-snapshots rather than paying a subtree walk every frame.
- A beat that moved and faded at once kept a full-strength smear behind a
  vanishing element. The element's opacity now rides the group; the 1/N weight
  stays on the copies.

The axis option is gone. It selected which of x and y to sample, a question the
resolved matrix does not have.

shutter-slam is the new component: one word through six single-property beats,
two translate slams, a scale punch, then a full turn about each axis, which
exercises every driver the model covers in one mount. Easing is power2.inOut
because it measured best, not because it was the default. A bezier fitted to the
reference's own bbox centres looks like the better curve and renders worse: a
blurred bbox centre is the shutter window's average rather than the instant, so
on an accelerating segment the fit measures its own blur.

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-17 16:03:14 -04:00
Miguel Ángel a8a9fdb0fe chore: prepare v0.8.46 (#4033)
* chore: prepare v0.8.46

Draft only, not tagged: bun run release:prepare 0.8.46 --no-tag.
Changelog rewritten in plain words for the one change since v0.8.45
(#4031, adaptive per-frame motion-blur sample count). Cutting the
release is a separate step on Miguel's word.

* docs(release): fix false render-time savings claim in v0.8.46 notes

Adaptive sampling never drops below the old fixed-16 default; it only
adds cost on frames that measure real spatial motion. Per Miga's review
on #4033.
2026-09-17 01:48:54 -04:00
miga-heygenandMiguel Ángel 484a06fb65 chore: release v0.8.45 (#4027)
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-16 22:56:40 -04:00
Aditya Poudyal e4b1832fad feat(cli): add hls output format (#4022) 2026-09-16 21:09:14 -04:00
miga-heygenandMiguel Ángel 9aa60a844f fix(registry): make motion-blur match After Effects shutter-based blur (#4008)
* fix(registry): make motion-blur match After Effects shutter-based blur

The motion-blur catalog snippet estimated speed from the x/y delta
between consecutive timeline updates and drew three one-sided ghost
copies with decaying alpha plus a Gaussian on top. After Effects does
temporal supersampling: each frame integrates the layer over a shutter
window (shutter angle, shutter phase, samples per frame), producing a
symmetric box smear of length speed x shutter time.

The snippet now does the same inside the page. After every timeline
update a deferred pass seeks the GSAP timeline to N sub-frame times
across the shutter window (events suppressed), reads each target's x/y,
restores the frame time, and drives an SVG filter that averages N
feOffset copies with equal weight (feComposite arithmetic, 1/N, sRGB).
Defaults follow AE: shutterAngle 180, shutterPhase -90, samplesPerFrame
16; fps comes from the render config, then the root data-fps, then 30.
Displacements are mapped through the inverse of the element's own
scale/rotation; percent x/y values are resolved to px; SVG targets use
their bbox; an existing inline filter is kept and the blur appended to
it. The pass runs in a microtask rather than inside the tracker tween's
onUpdate, because seeking GSAP from within its own render reads stale
tween state and suppresses the composition's callbacks on forward
seeks. The heuristic options blurScale/blurMax/stretchScale/stretchMax
are removed.

Reference: an AE-algorithm reconstruction of the same composition
rendered without the component at an effective 960 fps and averaged
over the 16 sub-frames of the 180 degree window per output frame.
Against it (1920x1080, 30 fps, solid + text translating linear then
power2.inOut): old mean PSNR 53.2 dB (min 26.7), SSIM 0.9930; new mean
PSNR 72.4 dB (min 52.6), SSIM 0.9998. The 10-90 edge ramp at 66.7
px/frame is 26.8 px in the reference, 27.5 px new, 14.5/6.3 px
(one-sided) old; at the ease peak 79.3 vs 79.5 vs 7.9/17.7 px, with
smear centres matching to 0.1 px.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* fix(registry): drop render-config fps probe from motion-blur and refresh its docs

The snippet resolved fps by reconstructing the renderer's private seek
config key so the compiler's inline-script guard would not strip it. A
public catalog snippet must not depend on that key, so fps is now the
explicit option, then the root data-fps, then 30; renders with an fps
override pass fps explicitly. The catalog page's Usage, How it works and
Options sections still described the old velocity heuristic and are
rewritten for the shutter model. The 1/N weight keeps full precision.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* fix(registry): match the After Effects reference export's shutter window

The shutter model landed on this branch was built against a reconstruction
of After Effects' algorithm, not against After Effects. A real 1920x1080 /
30 fps export of translating text disagrees with it on three points, all
measurable off the export itself.

The window is two frames wide, not half a frame. On the measured frame the
outermost duplicate on the trailing side sits 128 px back while the previous
frame is 125.8 px back, and the outermost leading duplicate sits 189 px ahead
while the next frame is 184.9 px ahead: the window ends land on the
neighbouring frames. Defaults become shutterAngle 720, shutterPhase -360.

Duplicates sit on the sub-interval boundaries, both ends included, not at
sub-interval centres. The staircase across a stroke steps every 15.6 px on a
125.8 px displacement and every 23.0 px on a 184.9 px one, i.e. an eighth of
a frame, with the run closing exactly on the neighbouring frame's position.
samplesPerFrame now means sub-intervals of the window, so there are N+1
copies at k/N rather than N copies at (k+0.5)/N.

The frame-time instance is drawn sharp over the smear. At full ink the core
bounding box is the whole word at every frame and total ink rises to 1.44x
the unblurred frame's, neither of which an average can produce. A final
feComposite over of SourceGraphic reproduces it. Each duplicate stays at
1/N: the staircase plateaus step by 0.063 +- 0.002 of the sharp text
intensity, flat, with no taper toward the window edges, and triangle
weighting scores 1.6 dB worse against the export.

Verified by replaying the export's translating section as a composition
whose sprite is the export's own rest-frame ink and whose eight legs follow
its own measured per-frame positions, rendered at 1920x1080 / 30 fps and
compared frame by frame over the text band. Mean PSNR against the export
rises from 24.19 dB (min 17.07) to 32.51 dB (min 27.57) across 72 frames,
and from 19.83 dB (min 17.07) to 32.03 dB (min 27.57) across the 26 frames
that actually move. Ghost extents match to a few px and the rendered
staircase plateaus match the export's to two decimal places. The export's
later scale, skew, flip and 3D rotation sections are out of reach here:
integrating anything but translation inside the shutter is engine work
tracked in issue #4010.

The filter region now clamps its bounds through zero. It was derived from
the duplicate offsets alone, which was correct while the output was nothing
but duplicates; the sharp instance draws at offset zero, so a window sitting
entirely on one side of the frame time - any shutterPhase above 0 or below
-shutterAngle - produced a region starting inside the element's own box and
cut the instance off. The defaults straddle the frame time and were never
affected, so the measured numbers above are unchanged.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-17 00:51:03 +00:00
miga-heygenandMiguel Ángel 3c9462feea chore: release v0.8.44 (#4016)
- Lint no longer reports root_dimensions_mismatch for sub-compositions, the
  false positive v0.8.43 shipped with; a top-level mismatch is still reported
  and a viewport-meta-only mismatch gets its own wording (#4012)
- The streaming encoder accepts lockGopForChunkConcat and gopSize, emitting the
  same arguments as the disk encoder. Off by default, so an existing render
  produces an unchanged FFmpeg arg list (#3973)

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-16 19:14:51 -04:00
miga-heygenandMiguel Ángel d6fe66707f chore: release v0.8.43 (#4007)
- Portrait compositions render the full frame when the project was
  scaffolded at a different size: html/body are sized to the composition
  root at runtime; new lint warning root_dimensions_mismatch (#4005,
  fixes #4001)
- render_complete telemetry adds composition_element_tags,
  aroll_video_count, heygen_video_count; render_error adds error_name and
  failed_stage_code (#4004)
- Catalog docs "Source" accordion titles are plain strings, so docs
  search no longer shows [object Object] (#4006)

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-16 14:05:29 -04:00
miga-heygenandMiguel Ángel 7b5fdb1ad2 fix(docs): emit catalog source accordion titles as plain strings (#4006)
The generated catalog pages rendered the "Source" accordion title as a
JSX expression, `<Accordion title={`name.html`}>`. Mintlify's search
indexer stringifies expression attribute nodes rather than evaluating
them, so every one of the 399 catalog pages surfaced its Source section
in search results as "[object Object]" (visible on the Scroll Feed page,
among others).

The template literal carried no escaping benefit: every title is a
single-segment `name.html` path, and a double-quoted JSX string handles
it directly. Introduce `mdxStringAttribute`, which emits a plain
`name="value"` attribute and escapes `& " < { }` as HTML character
references (MDX decodes references inside quoted attribute values, and
unlike JSON.stringify it does not rely on backslash escapes, which JSX
strings do not honour). Use it for both the Source accordion title and
the preview iframe title, and regenerate the catalog pages.

Regenerated with `npx tsx scripts/generate-catalog-pages.ts`; the only
change across the 399 pages is the one Accordion title line each.

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-16 11:55:00 -04:00
miga-heygenandMiguel Ángel 71b274bbfd chore: release v0.8.42 (#3988)
* chore: release v0.8.42

- Studio preview works for projects whose path has non-Latin-1 characters (#3982)
- Studio render downloads use RFC 6266 headers for non-Latin-1 filenames (#3984)
- check --json reports HDR auto-promotion; render prints one warning (#3985)
- Forced --sdr renders tone-map HDR sources before extraction (#3981)
- Render/preview workers no longer survive as orphans after Ctrl-C or parent exit;
  cancellation reaches final artifact promotion; HYPERFRAMES_RENDER_DETACHED=1 for
  intentionally detached renders (#3974)
- HDR preflight test coverage for held tails (#3963)

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* chore: release v0.8.42 notes — name the #3982 symptom and scope

- #3982 entry: a Studio preview server crash for compositions whose file path
  has non-Latin-1 characters (the project directory is not involved)

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* chore: release v0.8.42 notes — name the #3982 symptom

- #3982 entry: the uncaught ETag TypeError took the preview server down, so
  call it a Studio preview server crash rather than a preview failure

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-16 09:40:06 -04:00
terencecho edf6e4b373 chore: release v0.8.41 (#3966) 2026-09-15 17:24:09 -04:00
James Russo 14ad0c08a3 docs(changelog): weekly digest 2026-09-07–2026-09-14 (#3939)
* docs(changelog): weekly digest 2026-09-07–2026-09-14

Rewrites the generated draft into themed sections. 113 new commits across v0.8.32 through v0.8.40.

Signed-off by Rames.

* docs(weekly-updates): embed Sep 07-14 changelog video

Adds the weekly changelog video to the Week of September 7 digest entry,
matching the DocsVideo embed convention used by the prior weeks.

Video: 57.0s, square 1080, h264 + aac. Verified through CloudFront -
content-length and SHA-256 both match the local render exactly.

- Rames Jusso
2026-09-14 11:56:50 -07:00
Miguel Ángel cfe5dcfad3 chore: release v0.8.40 (#3938) 2026-09-14 12:41:47 -04:00
Miguel Ángel d13a89b670 chore: release v0.8.39 (#3936) 2026-09-14 11:13:53 -04:00
Miguel Ángel 10e8447ac0 chore: release v0.8.38 (#3929) 2026-09-13 22:02:19 -04:00
Miguel Ángel f059a6eb53 feat(cli): default BeginFrame, agent blank, and looks quality (#3927)
* feat(cli): render summary names the capture path, gpu mode and stage timings

* fix(lint): drop gsap_exit_missing_hard_kill, its hazard does not reproduce on cold workers

* fix(cli): render slow-path hint fires only when the gpu was auto-probed

* test(cli): isolate agent hint suite from ambient agent env keys

The suite runs inside agent sessions. Ambient env keys fill the 16-key
hint cap and fail assertions that a plain shell would pass.

* docs(music-to-video): drop leftover hard-kill mandate after lint rule removal

Frame workers still required a tl.set hard-kill that the dropped lint
rule no longer enforces. Align the self-check with montage.md.

* refactor(cli): share hint-key pattern and collapse render summary args

Export HINT_KEY_PATTERN for the test isolation strip. Read duration and
frame count from the perf summary instead of passing them beside it.

* test(cli): lock capture-mode preference and 16-key hint isolation

Session mode, including drawelement, must print over observability.
The hint suite now fails if ambient keys already fill the 16-key cap.

* feat(cli): default BeginFrame, agent blank, and looks quality

Local auto render opts out of the software-GPU screenshot clamp so
BeginFrame can run. init --agent scaffolds a centered Inter blank with
no prompts. --quality looks (now the default) is CRF 16; delivery is high.

* fix(cli): honor screenshot env and teach agent pitfalls on the happy path

Local auto still requests BeginFrame unless PRODUCER_FORCE_SCREENSHOT=true.
The agent blank fills its canvas so --resolution can resize it. Skills now
name the four pitfalls and tell agents to read the render summary line.

* feat(cli): default init to the centered blank

Bare init scaffolds the centered Inter stage. --agent is a hidden alias.

* fix(cli): pack from-file and use it for video init

TTY --video with no --example selects from-file. Pack copies that template.

* fix(cli): wire --video onto from-file even with --example blank

One resolver picks the scaffold. Spawned init --video asserts a-roll src.

* fix(cli): narrow looks CRF on the quality alias

Only looks carries crf. Typecheck failed on the union without a guard.

* style(cli): shorten render-pipeline summary comment
2026-09-13 21:44:19 -04:00
Miguel Angel Simon Sierra 7a5d2bf769 chore: release v0.8.37 2026-09-13 12:51:02 -04:00
Miguel Ángel f86aae655a chore: release v0.8.36 (#3911) 2026-09-12 13:03:01 -04:00
Miguel Ángel 51a88b9566 docs(catalog): put the install command above the preview (#3888)
The install command is the one line a reader comes to a catalog page to copy,
and it sat below the whole variables explorer, under its Customize panel, a
screen or more down on long items. Move the Install section above the preview
and regenerate all 399 pages.
2026-09-11 18:15:00 -04:00
Vance IngallsandClaude Fable 5.1 4ba8396c27 chore: release v0.8.35
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 14:30:23 -07:00
miga-heygen bafc7b4e00 fix(cli): stop promising a universal per-time filmstrip for --layout strip (#3867)
## What

`keyframes --shot --layout strip`'s help text, its type's inline doc comment, and the CLI reference docs all described `strip` as an unqualified "filmstrip by time." The tool doesn't actually do that for the overwhelmingly common case.

## Why

A real per-time pixel filmstrip is only produced when the sampled selector is an SVG element (gated by an internal shape check — `typeof element.getBBox === "function" && typeof element.getScreenCTM === "function"`). Any other selector — including every nested sub-composition host, which is always a `<div data-composition-src>` — silently falls back to one live screenshot plus vector position markers instead.

This isn't a capture bug: for a non-SVG selector, real per-time pixel compositing was never implemented, only 3D bbox/marker sampling. But the documented behavior over-promised what the tool does, so a user following the docs on the common case (a DOM/sub-composition selector) sees root captions and empty image boxes where they expected the nested composition's actual content to move across frames — the diagnostic strip is misleading, even though the real render is correct.

## How

Reworded all three descriptions (CLI help text, `ShotOptions.layout` TSDoc, and the reference docs table) to state the SVG-only condition and the DOM/sub-composition fallback explicitly. No behavior changed — this is a documentation-accuracy fix, per the ticket's own framing that a doc-only fix fully resolves the reported symptom (a silent, misleading omission) for a P3.

## Testing

Added a test asserting the CLI help text no longer makes the unqualified "filmstrip by time" claim and does disclose the SVG-only condition — guards against a future regression back to the misleading wording. Verified RED (fails against the pre-fix string) and GREEN (passes after the fix) via a local before/after comparison.

- `bunx vitest run src/commands/keyframes.test.ts src/commands/motionShotLayout.test.ts` — 46/46 passing
- `bunx tsc --noEmit` in `packages/cli` — clean
- `bunx oxlint` / `bunx oxfmt --write` on changed files — clean
- Full CLI suite (excluding known-broken-in-sandbox browser-launch tests, unrelated to this change): 217 test files / 3025 tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-09-10 23:22:14 -04:00
James Russo 8820a01090 fix(skills): exclude repo-native skills from public installs (#3856) 2026-09-10 12:35:29 -07:00
Miguel Ángel 59def1b66d chore(release): v0.8.34 (#3854) 2026-09-10 14:45:17 -04:00
Miguel Ángel 0f8eb892e3 fix(skills): search the component catalog before hand-building a look (#3829)
* fix(motion-graphics): make the catalog search fire before hand-authoring

The workflow's only reuse instruction pointed at catalog-map.md, a
hand-maintained snapshot of ~60 registry items, and no file in the skill
ever named `hyperframes catalog --query`. An agent asked mid-build for
CRT scanlines and a glitch effect had no instruction to search, so it
hand-authored both while caption-glitch-rgb ("RGB chromatic aberration
with CRT scanline overlay") ranks first for that query on either tier.

The search reads the hosted registry and needs nothing installed, from
any directory with no project, so "the components were not installed"
was never the cause. Say that where the reader is, since the wrong
diagnosis is the intuitive one.

Director Part 2 and the Builder now run the search before naming a
block, and catalog-map.md is labelled a partial snapshot whose misses
prove nothing. Pinned by a content test in coreSkillContent.test.ts.

* fix(skills): search the component catalog before hand-building a look

Authoring workflows never told the agent to search the component library,
so agents rebuilt effects the registry already shipped. A user reported
building an effect from scratch that the registry already contained; the
search that would have found it needs nothing installed, which is why the
usual self-diagnosis ("I forgot to install the components") is wrong.

All ten workflow skills carried zero mentions of `hyperframes catalog`.
The instruction lived only in hyperframes-cli and hyperframes-registry,
both loaded on demand, and the registry skill's own trigger named the
command rather than the symptom - circular, because an agent that never
thought to search could not reach the doc telling it to search.

- Eight workflows now run the search at the point they decide what to
  build, before authoring. The two that compile through a closed
  authoring vocabulary (embedded-captions, talking-head-recut) document
  why they deliberately do not.
- hyperframes-registry triggers on the symptom (a named look, effect,
  treatment or transition) instead of the command name; the router table
  and the catalog surfaces carry the same framing.
- Fixes hand-maintained lists that had drifted: bar-chart-race was listed
  as a hand-author gap in two files while shipping in the registry;
  stat-motion was named as an installable block and is not one; the
  caption-* family count was one high; the registry discovery tables
  claimed to be the block list while covering 97 of 180.
- bun run lint:skills now fails when a doc marked as a registry snapshot
  names an item the registry does not have.

* refactor(scripts): reuse native recursive readdir and the shared registry type

Simplify pass on the new registry-snapshot check, behaviour identical:
- collectMarkdownFiles uses readdirSync({ recursive: true }) instead of
  hand-rolled recursion, matching scripts/generate-template-previews.ts.
- registryItemNames types registry.json with the exported RegistryManifest
  instead of an ad hoc inline shape, matching scripts/catalog/build-local-vectors.ts.
  The runtime guard stays: a cast describes the file, it does not validate it.
- One report() helper replaces the duplicated print-and-count block in both
  lint passes.

* refactor(scripts): name the registry check's blind spots and stop self-arming

Applies the review findings on the new check, behaviour identical except
where noted:
- lintRegistryItemRefs returns null for an unmarked file instead of an
  empty array, so "not a snapshot" and "a clean snapshot" have one owner
  and the marker is matched once rather than twice.
- Marker detection ignores fenced blocks, so a doc that documents the
  marker syntax in an example no longer arms the check on itself. The id
  scan still reads full content, so fenced examples stay covered.
- The header comment and two tests now pin both known false negatives:
  identifiers outside backticks, and single-word item names. Measured on
  the six marked files, dropping the hyphen requirement would monitor 3
  more items and force 46 allow= entries for ordinary prose words, so the
  requirement stays and the gap is stated instead of silent.

* chore(skills): regenerate skills manifest after catalog-search edits
2026-09-10 03:21:33 +00:00
James Russo 4233b5c6bf fix(core): contain generated HTML CSS and script contexts (#3800)
* fix(core): escape generator metadata attributes

* fix(parsers): retain decoded metadata while assigning ids

* fix(parsers): preserve runtime html parser semantics

* fix(parsers): canonicalize HTML attribute names for stable IDs

* fix(parsers): normalize SVG attribute hashes across HTML parsers

* fix(core): escape public resolution attribute values

* fix(core): contain generated HTML CSS and script contexts

* fix(core): preserve empty captions and document authored code trust
2026-09-09 03:56:10 -04:00
Vance Ingalls 78e84a9ca8 docs(render): explain alpha-aware WebM inspection 2026-09-09 00:00:24 -07:00
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>
2026-09-09 00:47:31 -04:00
miga-heygenandMiguel Ángel 6e3308be4f chore: release v0.8.33 (#3796)
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-08 22:12:51 -04:00
miga-heygenandMiguel Ángel 662f96b3f4 chore: release v0.8.32 (#3788)
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-08 19:40:29 -04:00
Miguel Ángel 01744f2b0c fix(media): unify nested start coordinate mapping (#3732) 2026-09-08 18:10:51 +00:00
Miguel Ángel 2f7b7a9529 fix(render): reject unsupported ProRes rate controls (#3702) 2026-09-08 18:09:19 +00:00
Kakumanu Ashok ReddyandashokDevs 250afc313b docs: fix broken Claude Design guide link (#3584)
Co-authored-by: ashokDevs <bhargavcodes3@gmail.com>
2026-09-08 17:56:48 +00:00
James Russo bb89f7507d docs(changelog): weekly digest 2026-08-31–2026-09-07 (#3749)
* docs(changelog): weekly digest 2026-08-31–2026-09-07

Rewritten from the 111 non-merge commits in the window, grouped by theme.
The week was mostly hardening: file reads pinned to checked descriptors,
bounded parser scans, and isolated concurrent writes. Studio agent control
and site capture continued.

Every commit sha and PR number was resolved and verified against the repo,
not hand-typed.

— Rames

* docs(changelog): add weekly changelog video for 2026-08-31–2026-09-07

Embeds the rendered 49.5s digest video. Uploaded to
static.heygen.ai/hyperframes/changelog-videos/weekly-changelog-aug31-sep07.mp4
and verified live before committing the src (200, content-length matches the
object, cache miss).

— Rames Jusso
2026-09-07 15:56:02 -04:00
Miguel Ángel 30d6f43bdb chore: release v0.8.31 (#3747)
* chore: release v0.8.31

* docs(release): describe the range fix on its own terms
2026-09-07 12:12:35 -04:00
Miguel Ángel 3874990449 chore: release v0.8.30 (#3733) 2026-09-05 23:14:12 -04:00
miga-heygen ae3d80c30f chore: release v0.8.29 (#3690) 2026-09-04 21:50:46 -04:00
Miguel Ángel 64ce9fdf1f chore: release v0.8.28 (#3689) 2026-09-04 21:01:08 -04:00
James RussoandKamal Kannan 7b772465e3 docs(adopters): add PandaStudio (#3663)
Co-authored-by: Kamal Kannan <kamalkannan.sankarraj@gmail.com>
2026-09-04 18:51:06 -04:00
James Russoanderen-commits f7cac7287a docs(adopters): add Mini Course Generator (#3662)
* Add Mini Course Generator to adopters list

Added a new card for the Mini Course Generator showcasing its AI-powered course generation capabilities.

* docs(adopters): add Mini Course Generator

---------

Co-authored-by: eren-commits <eren@minicoursegenerator.com>
2026-09-04 18:40:10 -04:00
James RussoandKiyeon Jeon 29efd5dfa8 docs(registry): document installer metadata (#3658)
Co-authored-by: Kiyeon Jeon <kiyeon.jeon.21@gmail.com>
2026-09-04 18:39:41 -04:00
WaterrrForever c8300c69a1 fix(cli): install the published skill set, not every SKILL.md in the repo (#3636)
Bare `hyperframes skills` passed `--skill '*'` to the upstream installer, which discovers every SKILL.md in the clone, so a full install put 26 skills on disk against the 20 the manifest publishes (the six repo-internal ones under .claude/skills / .agents/skills). It now resolves the canonical manifest and installs its names, the same path `skills update` uses; offline it warns and degrades to the pinned core set instead of the wildcard. Docs point the full set at `npx hyperframes skills` and say what `skills add --all` really pulls.
2026-09-04 23:25:43 +08:00