Restore baseline SVG for map-globe-inset, map-radar-rings, and
map-arrow-fanout. Keep QC_PASS shipping-lanes and border-chase. No new
CEILING_ONLY ports without Miguel OK.
City Water Supply 1941 reverse: topo plate + dam pop + tunnel draw.
Externalize topo plate asset (no base64, no MP4 wrapper). MIA credit and
declared ceilings documented.
US wipe + cascade arrows from MIA oil-industry shot 49 reverse. SVG/GSAP
only; demo mp4 not shipped. NARA/FedFlix PD credit and fidelity ceilings
documented.
Berlin Airlift-style Germany SVG/GSAP from MIA reverse: sectors, divide
glow, thick W-E arrow fan-out + trails, endcard. CDN GSAP only (no local
gsap.min.js, no MP4 wrapper). Document credit and fidelity ceilings.
Self-contained SVG/GSAP multi-hub rings + iris-out from MIA Network
Broadcasting 1934 reverse. Timeline id aligned to map-radar-rings. No MP4
wrapper. Document credit and ceilings (grain, blocky coast, partial borders).
Reverse winner from MIA Our Shrinking World 1946: seek-safe SVG/GSAP
globe rings + continent spin + plane behind disk, then switchboard still.
Stills only (clouds/switchboard); no MP4 wrapper. Document MIA credit and
fidelity ceilings (angular coasts, grain, SVG plane, lighting, still plate).
Replace blob landmass with contiguous US silhouette (us-atlas nation) plus
Great Lakes and staggered east densify / transcontinental / west-spur draws
matching MIA How Our Country Grew grammar. Seek-safe GSAP; Tune vars for
palette and speeds; keep Moving Image Archive inspiration credit. No footage.
Generate docs/public/catalog/blocks/map-*.json payloads and sync gallery
data/nav so check:docs-catalog passes and Mintlify can mount live players
instead of blank stills.
Unblock Catalog Previews by oxfmt-ing the 18 new map compositions.
Replace em dashes / \\u2014 with ASCII hyphens in manifests, HTML notes,
CREDITS, and catalog MDX. Normalize block params to key/text for the
catalog generator.
Five original GSAP/HTML/SVG compositions for classic educational
cartography tropes (route draw-on, territory wipe, city pins, region
zoom, campaign arrows). Inspired by Moving Image Archive Animated Maps
collection aesthetics only — no footage or assets included.
* feat(studio): edit a clip's speed ramp with a rate lane and presets
* fix(studio): keep the live preview attribute in sync with what gets persisted
* fix(cli): snapshot picks the source frame through a rate lane and up to 10x
hyperframes snapshot clamped the constant playback rate at 5 and ignored a
rate lane in data-automation, so a sped-up or ramped clip showed the wrong
frame. It now reads the rate through the runtime's own reader and maps time
with the shared source-time function.
* docs(skills): teach speed ramps as a rate lane in one reference page
Adds the speed-ramps reference, corrects the 0.1 to 5 rate range to 0.1 to 10
and the 'speed ramps are not supported' lines in the skills, and regenerates
the skills manifest.
* docs(skills): say what fixes a ramped clip's length and how the render approximates pitch
* fix(studio): hide the speed preset until the clip has a known duration
A preset on a clip with no data-duration was stretched over a made-up one
second. The panel now offers presets only when the duration is known.
* docs(skills): fix the last stale ramp line in the editing recipes and the slice-cap wording
* feat(studio): every Studio panel is a dock panel and the shell moves onto it
EditorShell, App.tsx, the left and right panel columns, and the
inspector hooks now render through Dock.Panel instead of the old
fixed-width sidebars and split-pane inspector. usePanelLayout becomes
a thin facade over the dock store; fitPanels, the resize divider and
the split-inspector hook are dead and removed.
* refactor(studio): drop dead inspector flags and stale fallow entries after dock migration
Collapse inspectorButtonActive into inspectorPanelActive, remove unread
layers/design active fields, un-export unused prop types, flatten a nested
ternary, and point fallow ignores at the renamed/deleted files.
* fix(studio): keep right-panel behaviours the dock migration dropped
Block params dismiss on selection or when Design is hidden, the slideshow
panel can be closed, the selection outline follows the inspector intent,
caption editing focuses Design, and a URL slideshow tab opens once the
panel exists.
* refactor(studio): name the inspector-open state once in the header
* fix(studio): right-panel behaviours survive selection, restore and reopen after the dock migration
Block params stay open over an element that was already selected, caption mode wins over block params, the slideshow panel waits for the dock, the Renders tab title survives a reopen, each side column remembers the tab it last showed, tab switches are reported only when the tab changes, and the e2e thumbnail selector follows the dock.
* docs(studio): describe the dock layout and point comments at the renamed right panels
* fix(studio): block params survive a preview reload of the selected element, and panel titles are covered
The dismissal compares the selected element by its target key instead of object identity, because a preview reload rebuilds the selection for the same element. A closed and reopened panel keeps its custom title, with a test, and the e2e selector is formatted.
* fix(studio): block params leave with any other tab pick, and a restored slideshow placement survives loading
Block params are now dismissed by the tab setter the way their old tab was, including a re-pick of the same element, instead of by comparing selections. The slideshow panel is only closed after a file that was a slideshow stops being one, so a restored layout is not discarded while the file loads.
* refactor(studio): drop the duplicate inspector-open computation
The rebase onto main left two identical inspectorButtonActive/inspectorOpen
constants after a conflict resolution; only the first was still referenced.
* chore(studio): allow the dock migration deletions in the guard
StudioLeftSidebar.tsx, LeftSidebar.tsx and its storage test,
TimelineResizeDivider.tsx, useInspectorSplitResize.ts and fitPanels
are replaced by the dock; the CI deletion guard needs each named
with its reason.
* style(studio): oxfmt the deletion-guard entries
* fix(studio): rename Timeline's aria-label so it does not collide with its dock panel wrapper
Every dock panel wrapper now carries aria-label=<title>, matching the "Timeline"
title for the timeline panel. Timeline.tsx also set aria-label="Timeline" on its
own content div, so `document.querySelector('[aria-label=\"Timeline\"]')` found
the dock wrapper first and the timeline viewport gate CI check timed out waiting
for an attribute the real content div actually carried. Renamed to "Timeline
track view" and updated every consumer: the production diagnostics selector,
its tests, and the e2e viewport-gate script.
* fix(studio): restore inspectorButtonActive as a real prop after a rebase regression
Rebasing PR2 onto main replayed an older local commit whose diff did not
textually overlap main's newer inspectorButtonActive prop, so git applied
it with no conflict marker and silently reverted the header to computing
the value itself instead of receiving it. StudioHeader.dom.test.tsx already
asserts the prop is respected; restoring it makes that test meaningful
again. App.tsx now computes it once (panelLayout.rightCollapsed is already
the dock's effective collapse state, no separate railed-by-width case) and
passes it down.
* chore(studio): wrap a long doc comment in useRightPanelIntents
* feat(catalog): add the seven 3D-motion pieces as installable registry blocks
Each piece ships as a registry block with a CDN poster and preview, a live
catalog detail page and its source. Scripts travel inside the catalog payload
(shared vendor libraries fetched once, item scripts inline, the one HDR as a
data URI) because the docs host only serves fonts, images and JSON.
NO_ESSAY_COMMENTS_OK: the long comment blocks are in vendored libraries and
the ported frost source under registry/blocks, not hand-written here.
HF_MAX_NONLFS_KB=6000: four hosted texture PNGs under docs/public/catalog/items
are runtime assets the docs host must serve as files.
fallow skipped locally: it audits against origin/main and this branch is
stacked on an older main.
* chore(catalog): regenerate gallery data and nav after rebasing onto main
* fix(catalog): read the install manifest through the bounded project reader
* fix(catalog): a script the inliner missed fails the payload instead of shipping a dead URL
* refactor(catalog): the leftover-script check reuses the reference finder
* fix(catalog): frost and glass textures load from the CDN and the iteration presets are gone
* chore(catalog): regenerate the frost and glass payloads and nav
* fix(catalog): frost and glass textures are hosted through the registry url field, not hardcoded
* chore(catalog): regenerate the frost and glass payloads against the hosted texture URLs
* fix(catalog): the hosted-reference rewrite refuses a bare name it cannot safely replace
* ci(catalog): verify changed catalog payloads in headless Chrome and check the docs catalog
* feat(catalog): every item page follows the shared layout with a Tune panel and tabs
* fix(catalog): let the texture-mask item host its masks so its tile plays live
* chore(catalog): regenerate item pages, gallery data and the texture-mask payload
* chore(catalog): regenerate item pages after merging main
* fix(catalog): declared-only variables get the Tune panel, Code and Docs tabs capped
* feat(catalog): give the blend-difference caption a demo scene so its tile plays live
* chore(catalog): live payload for the blend-difference caption
* perf(catalog): heavy 3d pieces compile and draw before ready, not on the first visible frame
* feat(catalog): the avatar promo card hosts its videos, audio and fonts on the CDN
* feat(catalog): the avatar promo card tile plays live from its CDN-hosted media
* chore(catalog): the avatar promo card plays live, no allowlist left
* chore(catalog): shorten two comment blocks
* chore(catalog): trim first-party comment blocks to the comment-check limits
* chore(catalog): shorten two frost source comments
* fix(catalog): the Docs tab renders as prose again, and the detail page honours reduced motion
Frame-mode pages carry no prose wrapper, and the detail component sat inside
not-prose, so the Docs slot's headings, paragraphs, links and lists had no
styling at all. The slots now carry their own prose classes and the chrome
around them keeps not-prose.
The recorded clip also gets the reduced-motion guard the docs-snippet check
requires, and the generator reads stability from the source metadata type so
the scripts typecheck passes.
* fix(catalog): docs-tab paragraphs render as blocks
Mintlify emits paragraphs as inline spans, so two paragraphs in the Docs slot ran together.
* fix(ci): drop the duplicate docs_catalog job output
git's line-based auto-merge silently duplicated the outputs key when
main and this branch each added it on non-adjacent lines during the
rebase; oxfmt's yaml parser rejects the repeated map key.
* fix(catalog): close four CodeQL alerts in the frost source
* chore(catalog): allowlist the avatar-promo-card CDN asset deletions, format sync-docs-catalog
check-no-main-deletions.mjs was rejecting 11 local mp4/font files this branch
already deleted in favor of CDN hosting; scripts/sync-docs-catalog.mjs had one
line over the 100-col print width from an earlier edit in this branch.
* fix(catalog): detail tabs use docs prose spacing, Install first; previews skip no-WebGPU runners
* fix(catalog): the docs tab scrolls with the page instead of a capped inner box
* refactor(catalog): split the payload asset and verify scripts into single-purpose helpers
* refactor(catalog): split the preview generator into single-purpose helpers
* ci(catalog): fail when the committed catalog differs from generator output
The docs host serves the committed catalog and nothing regenerates it, so a stale
copy shipped silently. The catalog payloads job now generates into a temp dir and
compares it with the committed tree.
* chore(catalog): regenerate docs/public/catalog with the refactored scripts
One full regeneration from an empty output tree with the current scripts. The tree grows
from 58.4 MB to 65.1 MB. Two hosted assets are about 2 MB each (an avatar png and a wav);
the same bytes are already committed under assets/, so HF_MAX_NONLFS_KB is raised for
this commit only.
* fix(catalog): run the catalog script tests under the runner they are written for
The rebase put three node:test files in the vitest half of test:scripts, where they report
"No test suite found". They now sit in the node --test list.
* fix(catalog): a WebGPU item without an adapter shows its recorded clip and says why
* ci(catalog): the drift check runs from a committed fetch mirror with no network
Generation now serves every font and CDN script fetch from scripts/catalog-fetch-mirror and fails
when a fetch is not in it, even if the caller swallowed the error. CATALOG_FETCH_MIRROR=record
refreshes the mirror from the live network. The docs_catalog filter also watches packages/core,
packages/producer and the mirror, and the 13 re-hashed font files are listed in ALLOWED_DELETIONS.
* chore(catalog): clean up the temp font cache and sort the mirror index without a locale
* fix(catalog): hide Tune when the clip stands in, time out the adapter request, share one check
* fix(catalog): the adapter probe survives a sync throw and a hang; test the clip guard
* fix(catalog): keep the adapter probe inside the component, Mintlify evaluates only exports
* fix(catalog): run the detail and page generator tests in CI; no Tune flash while probing WebGPU
* docs(catalog): credit vendored libraries and make each SKILL.md list what the block installs
CREDITS.md names every vendored dependency with its upstream and licence, and flags the two leaf
textures whose origin is still to be confirmed. Each SKILL.md Files table now comes from the block
manifest and its install and render paths point at compositions/<name>/. A test checks that every
path a SKILL.md names is a manifest file that exists.
* fix(catalog): a declared WebGPU item passes verify by its manifest tag, not by console wording
verify-catalog-payloads forgave a missing adapter only by matching the markup and the message, which
library-reported errors and widgets never satisfy. The webgpu tag in the item manifest is now the
owner: a tagged item ignores its page and console errors, an untagged one gets no forgiveness, and a
marker payload with no html is listed instead of crashing the run.
* docs(catalog): correct the credits per block and guard the mount and render paths
CREDITS.md states the three.js version and vendored-or-CDN status per block, adds d3-delaunay,
Clipper on the CDN and the Google Fonts loads, and lists every texture with no known origin.
The SKILL.md test now also checks the mount and render paths against the manifest composition.
* test(catalog): read each block manifest once in the SKILL.md file test
The two checks shared a copied manifest read, which fallow flagged as a clone with a high score.
* fix(catalog): commit the hosted item files a gitignore rule kept out, and check git not the disk
The compositions/ and *-ad/ ignore rules matched folder names under docs/public/catalog/items,
so 33 hosted fonts, textures and marks were generated but never committed and would 404 on
the docs host. The rules now have a negation for that tree and the files are added.
The drift check read the checkout, where git-ignored files still exist, so it passed locally
and failed only in CI. It now unpacks HEAD with git archive and compares that.
* fix(catalog): a tile keeps its poster until the composition has painted
* fix(catalog): a tagged WebGPU item forgives only the adapter-absent error, and test the wiring
The manifest webgpu tag now decides whether an item may lack an adapter, and the error wording
decides which lines are that absence. Any other page or console error on the item, and every
failed request, still fails. The per-item decision is one exported function the tests drive,
marker payloads are parsed by a tested helper, and the SKILL.md variable counts are checked
against their tables (cuboid-carousel said forty-nine for forty).
* fix(catalog): the pinned group's posters load eagerly at high priority
* fix(catalog): type the tile reveal test so the scripts typecheck passes
* fix(catalog): stop the adapter signature matching an unrelated failed adapter request
failed to request .*adapter also matched errors about other adapters. It now matches only a
request for the WebGPU adapter, so a tagged item cannot hide an unrelated failure behind it.
* test(catalog): a tagged item still fails on an error about some other adapter
* feat(catalog): the install command sits above the preview, as on the live catalog
* fix(catalog): find the install slot through Mintlify's child wrapper
* fix(docs): catalog copy buttons keep their size when the label swaps to Copied
* refactor(docs): name the shown copy label once in the catalog copy button
* fix(docs): primary catalog button keeps a readable label on hover and after copy
* fix(docs): inset the catalog tab strip and tab content to match the boxed panels
A new resolver in @hyperframes/parsers decides how long a video, audio or image
occupies the timeline. The core runtime start resolver, clip manifest and clip
tree now read length through it. An image with data-start and no data-duration
gets the dropped-image default of 3 seconds; a bare image stays a static layer.
Video and audio take their length from the file until an authored duration trims it.
The playback rate bound (0.1 to 10) now lives in parsers, and core re-exports it.
A rate lane keeps its own arithmetic in core.
The lint rule that asked images for data-duration is removed, and the docs no
longer say an image requires it.
* fix(cli): install onnxruntime-node and google-genai on first use
Both carry deprecated transitive dependencies (boolean, node-domexception)
that showed up as npm warnings on every npx hyperframes install. They move
to devDependencies (types and the Intel-binding check still need them at
build time; the bundler externalizes them) and load through a new
on-demand loader that installs into the CLI's own cache dir the first
time a feature needs them, with one stderr line, no prompt, and a
manual-install command named in the error if the network is unavailable.
hyperframes doctor lists each as installed (with version) or
not-installed-yet.
* fix(cli): sweep stale install staging and bound network retries
* fix(cli): optional-package install only sweeps staging dirs whose process is dead
Staging dirs now carry a per-call suffix after the pid, and the sweep skips any dir
whose pid is alive, so concurrent first runs no longer delete each other's install.
The install error suggests the network only for network failures, and the manifest
path has one owner. install() takes an injectable npm runner so tests drive the real code.
* fix(cli): optional-package sweep also clears old-format staging dirs and tests a live foreign dir
The concurrent-callers test shared one pid, so it never exercised the sweep rule; a live foreign
staging dir test now does. Old-format dirs of a dead pid are swept, and a failed cleanup no
longer aborts a viable install.
* fix(cli): optional-package sweep names a stale dir it could not remove
A locked leftover is still skipped so the install proceeds, but the warning makes it visible.
* docs(cli): shorten the install docblock to the comment limit
Comment-only change.
* feat(cli): hyperframes timeline prints a project's tracks and clips
* docs(cli): list the timeline command
* feat(cli): timeline command reports unreadable lanes; add skill docs
* fix(cli): timeline stays inside the project, shares one start cache, keeps small volumes
* fix(cli): timeline resolves symlinks before the project containment check
* fix(cli): timeline accepts project folders whose name starts with two dots
* refactor(cli): timeline and compositions share the engine's start resolver
* docs(skills): point agents at the timeline command where they decide how to read a project
* fix(cli): timeline prints absolute main-timeline times and the owning file for nested clips
Nested rows carry absStart/absEnd on the main timeline and their owning
file, alongside existing local start/end. Text prints absolute time first,
local time in parentheses. Adds jq/node one-liners for common queries,
tested against a fixture.
* fix(cli): timeline places nested media at the runtime start and tests its query one-liners
A nested media clip with a negative literal data-start was reported later than it plays,
because the raw start was clamped before the host offset was added. The skill's jq and node
one-liners now run against the fixture in the test suite.
* fix(cli): timeline clamps a negative media start to 0 when its host starts at 0
The runtime skips the host-offset formula for a host at t=0 and clamps the start, so the
timeline command now does the same.
* refactor(core): one media-start rule shared by the runtime and the timeline command
The runtime and the CLI each carried a copy of the rule for when a nested media clip's
start takes the host offset, and the copies had drifted. Both now call
resolveMediaStartSeconds.
* test(cli): timeline fixture for a media start written as an expression
parseNumeric and the runtime's parseStrictFiniteTimingNumber are the same function, so the
two callers cannot disagree on what counts as a literal; this pins the expression case.
attachMotionBlur(el, tl) has an ordering contract: it has to run after every
tween is defined and before the composition registers its timeline. Getting it
wrong makes the blur silently do nothing, which is the worst shape a failure can
take in a renderer nobody watches. An attribute has no order to get wrong.
data-hf-motion-blur on any element is now the contract. Empty means defaults;
anything else is a JSON object of the same options. The element finds its own
timeline: its nearest [data-composition-id] ancestor names the key, and the
snippet reads window.__timelines[key]. attachMotionBlur stays exported for a
target built after the page has settled.
claim(el) is the only thing that attaches, and two triggers call it. An accessor
on window.__timelines wraps the registry in a Proxy whose set trap sweeps on
every per-key registration, which is what attaches before the renderer captures
frame zero. A bounded poll, 32 ms by 250 ticks, reads the registry directly and
needs no cooperation from whoever wrote it. The poll is not belt and braces: a
registry reference captured before the snippet installs bypasses the trap
outright, and a composition that mounts its DOM after registering its timeline
has no marked element to sweep at trap time. It runs its whole budget every time
rather than stopping the first tick nothing is pending, because on an ordinary
page the top-level target is claimed on the registration write, so an early exit
kills the poll one tick in, before any late composition arrives.
Every refusal is by name, once, and never silent: a value that is not JSON, a
value that parses but is not an options object, an option name that is not one
of the four, an option value that is not a finite number, a target inside
another target, and a second timeline registered under one key. Parsing runs
before the timeline lookup, so a malformed value is reported as malformed rather
than as a composition that never registered.
Two properties of the primitive this change made load-bearing:
- Frame rate is resolved from the target's own composition root, not from the
first root in the document. The document-wide lookup was survivable while
every caller could pass fps explicitly; the declarative form has no per-call
argument. Two compositions on one page at different rates now each get their
own shutter window, and one call naming both says it cannot.
- An attach is all-or-nothing. The mark, the copies and the resize observer go
in together and come back out together, so a throw partway through leaves the
element untouched rather than marked blurred with copies in the page and no
tracker driving them. The sweep is also bounded per element, because the write
trap runs it inside the author's own registration statement.
Copies are stripped of data-hf-motion-blur, data-composition-id and data-fps,
on the clone and every descendant: the first would make a copy a target of its
own, and the other two would let a copy win the fps lookup, since copies are
inserted before the original.
skills/hyperframes-animation/references/motion-blur.md is the agent-facing half,
mirroring how colour grading is surfaced. It leads with when NOT to blur,
because the attribute makes blurring everything trivial and a smear on motion
that was never fast enough reads as a soft, cheap render.
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
Co-authored-by: Vance Ingalls <vance@heygen.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
* docs(skills): the agent presents a tailor-made storyboard page instead of a Studio board
Adds the storyboard recipe (decisions first, beat fields, a self-contained
storyboard.html contact sheet, a review round) distilled from the launch
films, and rewrites the review loop, brief contract, storyboard format and
workflow steps that still described the deleted board and comments file.
* docs(skills): reconcile the sketch pass with the beautiful storyboard sheet
Review found: the recipe's storyboard.html cell spec (real fonts, real
colors) contradicted review-loop's own sketch definition (wireframe, no
brand treatment). The sketch is static, not unstyled; only motion is
deferred to the build pass. Also fixes a frame-packet boundary note, a
craft-device citation, and a provenance overclaim.
- HANDOFF section 4a: the single-worker fallback applies on default routing
(non-DE router and the manual opt-in); a DE-router plan keeps its
precomputed fallback. Adds the review's verdicts: three confirmed defects
fixed, one latent hardened, one split accepted, one low left open (a worker
wedged before its first capture is bounded only by player-ready and CDP
timeouts and is not retry-eligible).
- cli.mdx: the parallel-stream paragraph no longer promises a single-worker
retry for every case.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- tests/long-form/README: the two-clip in-flow fixture, the per-frame luma
check over clip B, the 2026-09-18 matrix (branch -w 1 / -w 2 disk / -w 2
streaming / absolute CSS / published 0.8.44 all 0 of 100 black; 0.8.35 and
0.8.36 99 of 100 as the positive control; fixed in 0.8.37 by #3893), and
why the multi-worker trace's captureMode was not the engine's mode.
- HANDOFF: R3 row now reads "hardening built"; new section 4a lists the
guards, the 694 gate result and what the flip still needs; counts refreshed.
- cli.mdx: the parallel-stream paragraph says the opt-in path now retries a
stalled pool or a mid-stream encoder exit on a fresh single-worker session.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three workers produced a byte-identical file to the single-worker segmented
render at 1.6x the speed, which is the property the gate exists to check:
segments finish out of order, so a mismatch would mean the concat list had
picked up completion order instead of the plan.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 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