* chore: release v0.8.154
* ci: give the studio test job fifteen minutes
The queue run installs and builds inside the same ten minute cap.
It was still inside the studio tests when that cap cancelled the job.
* fix(inspector): stop audio waveform caches from making every live question stale
* fix(inspector): drop the comment and format the test
* refactor(core): share the folders Studio's preview ignores
* fix(core): register the project-folders subpath and bundle it into the inspector
* fix(cli): bound checks of non-finishing compositions
* fix(cli): account for cold startup in deadline witness
* fix(cli): wait for profile owners before test cleanup
* test(cli): separate portable deadline and Linux process witnesses
* test(cli): preserve per-launch browser profile ownership
* fix(cli): start check deadline after browser readiness
* chore: release v0.8.152
* docs(changelog): correct the v0.8.152 release notes
The summary overstated snapshot retention, history turns, and video state.
It also left out the new artifact paths and nullable keyframe times.
* feat(cli): save check, snapshot and render artifacts per revision
* fix(cli): keep clean --snapshots inside the project
A symlinked .hyperframes or artifacts folder was followed, so the command
deleted snapshot and check files outside the project.
* test(cli): keep check, snapshot, and render successful when recording fails
A held history or a corrupt output registry warns and leaves the command result alone.
* fix(cli): drop snapshot records when a replaced run is not recorded
A reused --output rewrites the files even when history is busy.
Those records are removed so the old revision does not keep the new pixels.
The preview checkpoint stops waiting after five seconds.
A bad PNG header no longer fails the snapshot.
* fix(studio-server): keep the video-state comment share from rising
The forget helper's doc comment pushed the file above its base share.
* fix(cli): post the preview checkpoint and keep cleanup inside artifacts
A preview checkpoint with no body was sent as GET, so Studio never ran it.
Cleanup now requires the real path to stay under the artifacts folder.
A failed follow-up drop does not warn twice.
The studio-server unpacked budget covers the record-dropping helper.
* docs(cli): shorten the snapshot skill notes
The command examples already show range and composition.
The prose only keeps the output path and the flags that are easy to misuse.
* chore: leave package headroom for artifact cleanup
* test(cli): compare artifact paths portably
* style(cli): format portable artifact assertion
* feat(studio): record video state, edit targets and artifact cleanup
* fix(studio): clear video-state CI gates
* fix(studio-server): pin cleanup reads to file descriptors
* chore: account for cleanup descriptor code in package budget
* build(inspector): publish @hyperframes/inspector to npm
* fix(inspector): drop prepack so pnpm pack --json stays parseable
* fix(inspector): add .js to published declaration imports for NodeNext
* fix(inspector): declare @hyperframes/parsers, which the published types import
* chore(fallow): ignore @hyperframes/parsers, which inspector's published types import
* fix(keyframes): report unresolved GSAP timing instead of guessing rest poses
* fix(keyframes): drop later times when a delay cannot be resolved
Boolean(null) treated an unreadable delay as no delay,
so the next cursor-relative tween kept a guessed start.
* fix(keyframes): keep a staggered rest pose in Studio
The parser stops are what Studio holds, so a staggered from keeps its rest
pose there. The CLI reports that pose as unresolved, and an unread position
no longer seeds the next tween at zero.
onnxruntime's 1DS telemetry, on by default in its macOS wheels, can abort the
Python process at exit when an upload response races teardown. execFileSync
then throws for a line whose WAV was already written, and the audio skill drops
it as non-fatal.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): guard history turns with --exclusive, --who and --if-open
* fix(cli): make exclusive history turns atomic
Re-read the open turn only after acquiring history ownership so
concurrent exclusive begins cannot both claim the same project.
Cover the race with a deterministic two-process regression test.
* fix(cli): refuse a second exclusive turn while a preview is open
The preview's begin window now rejects another agent's live turn, and an
idle turn no longer blocks exclusive begin. The two-process test waits on
pids collected from claim drafts instead of seeing both files at once.
A title or body edit fires ci, regression and windows-render again on the
same head commit. When the previous run of that workflow on the commit
passed, Detect changes now reports no changes, so the check jobs skip and
their required checks still pass. Base changes, first runs and runs whose
predecessor failed, was cancelled or is unfinished still run everything.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* fix(core): a render keeps a fromTo's from-values when a frame lands just after its start
The frame redraw steps to 1 ms below the frame and back. When a tween starts
inside that 1 ms, the step down reverts its from-values and the step up does
not reapply them, so a wipe bar whose opacity comes only from its from-vars was
never drawn. Reapply them for every tween whose start the step crossed, as
#5125 does for a tween starting exactly on the frame.
Adds the gsap-redraw-start-and-overlap producer fixture, which also pins that a
slow drift resumes after a short move on the same property, as in playback.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): keep the redraw's comment share and schedule the new fixture
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(cli): HF_TOOL_CONTEXT artifact events and project lock for agent harnesses
* fix(cli): serialize stale project lock takeover
Use a short-lived break lock so only one waiter can remove a dead
holder's lock. Cover the contention path with six real processes.
* fix(cli): hold snapshot lock through finalization
Keep the project lock while contact sheets, artifact events, and frame
descriptions read captured files so another snapshot cannot replace them.
* fix(cli): recover a project lock after the breaker crashes
A killed takeover left project.lock.break in place, so later commands
waited out the timeout even though both holders were dead. Reclaim that
file only when its recorded process is dead, and compare artifact paths
after the platform resolves them.
* fix(cli): accept a killed breaker process with no signal name
Windows ends the process without reporting SIGKILL, so the regression
was failing before it checked that the leftover breaker can be reclaimed.
* fix(cli): leave a killed project-lock breaker in place
Reclaiming a dead breaker unlinks a name another process may just have
created, so two commands can hold the lock. The timeout names that file
and its pid and says to delete it to continue.
* feat(cli): scope check to a time range or a set of components
* fix(cli): keep ranged check gates truthful
Refuse ranged caption and frame gates when none of their seek times
falls inside the requested range. Make ranged motion reporting,
overlap timestamps, filtering tests, and scope documentation match.
A GSAP tween that outlives a later tween on the same target and property by a
moment takes the property back for that tail and leaves its own end value on
screen. A banner word whose drift ended 20 ms after its exit stayed in the
middle of the frame for the rest of the scene, in playback and in renders.
hyperframes check now reads every tween off the live timelines and warns on
that shape (tail up to 0.25 s), with the fix: end the earlier tween first, or
add overwrite: "auto" to the later one. Longer tails, like a slow drift that
resumes after a short move, are left alone.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(inspector): element map, live inspection engine and Node runtime
* fix(inspector): reject preview reload races
Return the live document generation from the same page invocation as each
query and reject mismatches. Rearm browser cleanup for failures before a
page operation starts, and cover reloads across all three live operations.
* feat(inspector): query timelines and inspect the live preview
* fix(inspector): align live timeline contracts
Keep realm-safe target detection through the restack and align
floating-point interval boundaries. Update the live group fixture and
make the private runtime entry build itself before use.
* fix(inspector): declare generated build inputs
* fix(producer): stop JPEG frame capture adding colour noise to graded footage
Standard and high renders captured page frames as JPEG at quality 95 (the
probe session and distributed chunks at 80). Chrome's encoder turns graded
colour and grain into 8x8 chroma blotches at those settings, and they survive
the final encode. Capture now uses quality 100 for standard and high, from one
owner that the in-process render, the probe session and distributed chunks
all read. Draft keeps 80. The disk estimate for JPEG frames doubles to match
the larger files.
* fix(producer): key resume and disk estimate on the capture quality
Segment resume now fingerprints the capture jpeg quality, so a resume
across a quality change re-renders instead of splicing old-quality
segments. The disk estimate allows for larger frames at any jpeg quality
above draft's 80 instead of only at exactly 100.
* fix(producer): keep segment manifest comment share at base
* fix(producer): fit capture quality comment in four lines
A sub-composition that links a shared stylesheet with a plain path like
assets/fonts.css was only looked up next to its own HTML file, so lint
reported font_family_without_font_face for fonts that stylesheet declares.
The ../ spelling is rejected by invalid_parent_traversal_in_asset_path, so
no spelling passed.
Lint still looks next to the sub-composition first and now falls back to
the project root, which is where the preview and the bundler load the link
from. Links that start with / or contain a .. segment keep their old lookup.
A folder with the stylesheet's name is skipped instead of throwing EISDIR.
Fixes#5074
* fix(engine): encode and tag ProRes mov output as BT.709
* test(engine): read the ProRes colour tags from the first frame
* test(engine): end the ProRes tag probe's options with --
Move the keyframe rest-value table into @hyperframes/core/keyframe-defaults
and import it from the SDK backfill and Studio's gsapShared, replacing the two
hand-synced copies.
Closes#5387
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(media-use): transcript-cut video renders without repeated frames or a keyframe warning
transcript-cut wrote each kept range to a temporary .mkv, and ffmpeg
writes Matroska timestamps in whole milliseconds. A 30 fps frame that
belongs at 2/30 s ended up at 0.067, the final copy kept it there, and
the renderer showed the previous frame wherever a timestamp was late.
The temporary files are now .mov, which holds the frame times exactly
and still carries PCM audio, so the audio is encoded once, as before.
The cut also gets a keyframe every second. x264 on its own left them
several seconds apart, and the renderer warned about the footage this
script had just written.
Fixes#5417
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(media-use): transcript-cut ranges start on their first frame with any ffmpeg
ffmpeg 7 and later start a kept range that begins between two source
frames one frame late, and .mov rounds that lead-in to the millisecond,
which put the frames off the grid again. ffmpeg 6 did not do this, so
the tests passed locally and failed in CI.
Each temporary file is now written at a constant frame rate, so the
first frame of a range is at zero whichever ffmpeg runs it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: James <james.russo@heygen.com>
* fix(media-use): keep frame precision on transcript-cut seek boundaries
Fixes#5416.
A kept range whose boundary sits on a frame got rounded to the millisecond
twice on its way to ffmpeg, and at 29.97/30/60 fps that is enough to land just
past the frame: frame 386 at 30 fps is 12.866666 s, and -ss 12.867 starts that
segment on frame 387 instead. ffmpeg's accurate seek drops the frame, so the cut
came out a frame short from that join on and the picture drifted behind the
sound.
cutlist.mjs rounded the emitted range bounds to 3 decimals, so fmt() in
transcript-cut.mjs never saw more precision than that in the first place; fixing
only fmt() leaves the bug in place. Both now keep frame precision -- 6 decimals
in the cut list, full precision for the -ss/-to actually handed to ffmpeg --
while the 3-decimal fmt() is kept for the times that are only printed.
Measured at 30 fps: a cut of 0-1,1.0666666-3 produced 87 frames before and 88
after (frames 0-29 plus frames 32-89), with no timestamp gap at the join. 25 fps
is unaffected, as the issue notes.
* test(media-use): end ffprobe options with -- in transcript-cut test
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: James <james.russo@heygen.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(lint): add a reference page for every lint finding code
docs/reference/lint-rules.mdx lists every code emitted by the linter
(132: the 121 rule codes in packages/lint/src/rules/*.ts plus the 11
project-level probes that run as part of a project lint) with its
severity, what it catches, and how to fix it, grouped by the rule file
the code comes from. Conditional severities (Studio-host structure
checks, GSAP seek-order hazards) say when they escalate.
Link it from docs/packages/lint.mdx and add it to the Composition
reference group in docs.json.
Issue: #5385
* docs(lint): document the color grading codes and correct four entries
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: James <james.russo@heygen.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add the four commands that `hyperframes --help` lists but the CLI reference
page left out, with their options taken from each command's args and
examples checked against a sample project.
Closes#5386
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(media-use): honor telemetryEnabled: false from shared config in optedOut
The CLI's 'hyperframes telemetry disable' writes telemetryEnabled: false to ~/.hyperframes/config.json, but the media-use scripts' optedOut() only checked env vars, so transcribe.mjs, transcript-cut.mjs and audio-duck.mjs kept sending PostHog events for users who had opted out through the CLI.
optedOut() now also returns true when the shared config has telemetryEnabled === false, reusing the readSharedConfig() helper already in the same file. Applied byte-identically to both copies of telemetry.mjs (skills/media-use/scripts/lib and packages/cli/src/media-use/lib, per the copy-parity rule), with tests in both test files.
Closes#5465
* test(media-use): sandbox HOME in the telemetry opt-out test and regenerate the skills manifest
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: James <james.russo@heygen.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The WAAPI and CSS adapters wrote currentTime and then called pause().
On a running animation pause() is pending until the next animation
frame, and Chromium then sets the hold time to the time at that frame,
16.7 ms past the sought time. Pause first: writing currentTime while a
pause is pending completes it with the sought time.
An animation created after the runtime's discover seek is still running
when a render worker makes its first seek, so the first frame of each
worker chunk after the first was drawn 16.7 ms late.
* fix(registry): clamp filmed-screen's curve and vignette
Post-merge review of #5499:
- curve is clamped at 0: a negative value samples outside the frame and
folds over below about -0.32.
- the vignette factor is clamped at 0: above vignette ~1.11 it went
negative in the corners.
- README: a "previous look" line with the pre-#5499 values, noting that
the LED fade-in threshold and the trace of fringing on the focused
element are hard-coded.
The demo renders the same: at the defaults neither clamp is reached.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(registry): point filmed-screen's catalog page at its recorded preview
The catalog builds this item's payload as unsupported (it needs
canvas-draw-element), and without preview.video the page fell back to the
flag notice: "Needs chrome://flags/#canvas-draw-element to render live"
under a "Recorded preview" caption, with no video, whatever the visitor's
flags. The manifest now names the CDN video and poster the way the
liquid-glass blocks do; the regenerated page carries video= and poster=
instead of needsFlag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs(changelog): weekly digest 2026-10-05–2026-10-12
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(changelog): add the Oct 5-12 weekly video
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
New options, with defaults tuned on a white page filmed the way Vox shoots
screens:
- vignette (1.0, was a fixed 0.42) and aberration (2.2x, with a quarter of
it kept on the focused text so it still reads as glass)
- curve (0.15): a barrel bend normalised so the frame's corners stay put
- led (0.45): the LED grid now only fades in once a cell spans ~4 output
px; finer, it aliased into moire on light pages
- tiltTo per shot: the tilt eases between the two, for an orbit
- rack 0.3 (was 0.7), fast enough to follow text as it is typed, and shot
blur 0.45 (was 0.9)
The demo is now a white search homepage for a made-up engine, "lumen": a
query types letter by letter while the camera follows each new letter and
orbits from one side of the screen to the other, all on defaults.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(registry): add filmed-screen component
Films a live-DOM scene like a camera pointed at a monitor: a tilted screen
plane in perspective, depth of field that follows a named element, chromatic
fringing, LED sub-pixels, per-shot palettes and cut tears. The focused element
stays sharp. Ported from ff-tracking's lens.sksl, with each colour channel
weighted by its own sample in the bokeh average.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(registry): land the filmed-screen catalog poster on the title
The catalog poster is captured at min(3 s, 0.6 × duration), which was the
demo's first cut, so it showed a tear over a blurred title. The first cut
moves to 3.8 s and the first shot racks back to the title before it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(registry): filmed-screen glides between shots and racks focus
Shots now glide into each other by default: camera, palette and colour mode
ease over `glide` seconds, and `transition: "cut"` keeps the hard cut with its
tear. Focus racks instead of snapping: the focus box is a Hann-weighted
average of where it was over the last `rack` seconds, so a new target eases in
on an S-curve and every seek order still renders the same frame.
The demo now films three scenes in the style of other compositions, a code
editor, an analytics card and a chat thread, one shot and grade each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(registry): filmed-screen shares the composite slot and films each instance
Review on #5487:
- The composite hooks no longer cycle with data-vfx-chain. Both re-arm every
seek, and core's resolver reads its prior when called, so a stale wrapper
of ours is re-entered through it. Each wrapper now keeps its own prior and
returns at once when re-entered, so the chain stays a constant depth. The
slot is only claimed once a lens is mounted.
- A second instance films its own canvas. mount() takes the snippet's
caller-scoped query result and picks the first canvas not already filmed;
the library's own document belongs to whichever copy loaded first.
- No WebGL2 now steps aside like no HTML-in-Canvas. mount() returns
{ destroy() }, a lens whose canvas leaves the page is dropped, and a lens
whose composition is not on screen skips its capture instead of logging.
- Docs: fadeOut and zIndex, the rack comment, and the description and tags
now describe glides instead of cuts.
filmed-screen.test.mjs runs both arm orders against core's arming pattern,
two mounts over two canvases, and that the demo pastes the snippet's library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(registry): filmed-screen test passes oxlint
The vfx stand-in returned `true || prior`, which oxlint rejects as a
constant-truthiness expression.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(registry): match script tags case-insensitively in the filmed-screen test
CodeQL reads the extraction regexps as HTML filters (js/bad-tag-filter).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(registry): filmed-screen waits for the scene's first paint instead of erroring
In Studio's preview the first draw can run before Chrome has painted the
layoutsubtree scene once (the paint wait gives up after 250 ms on a busy
machine). drawElementImage then throws "No cached paint record", which was
logged with console.error and surfaced as a preview console error. That
error is now a wait: the lens requests a paint and draws again when it
lands. Other capture failures still log.
Render output is unchanged: the demo renders byte-identical to before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs(changelog): weekly digest 2026-09-28-2026-10-05
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(changelog): embed the Sep 28 - Oct 5 weekly video
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>