Commit Graph
5333 Commits
Author SHA1 Message Date
Miguel Ángel 9701f0da61 fix(core): keep rebound root timelines clock-driven (#5165)
* fix(core): keep rebound root timelines clock-driven

* test(core): verify transport advances the rebound root
2026-10-07 08:44:52 +00:00
Miguel Ángel da6259896b fix(core): settle slow documents against their own frame cadence (#5168)
* docs(core): map document-relative readiness contract

* fix(core): measure idle against document frame gaps

* fix(core): preserve fast-page readiness while calibrating slow frames

* docs(core): clarify readiness coverage and measurement provenance
2026-10-07 08:40:57 +00:00
Miguel Ángel 87387268a7 fix(core): preserve fresh host-driven timeline updates (#5159)
* perf(core): render a playing frame once when the host also ticks it

* fix(core): reset host tick dedup on playback start

* fix(core): deduplicate host seeks by rendered timeline time

* test(core): advance host tick clocks deterministically
2026-10-07 08:01:27 +00:00
Miguel Ángel e21ab020da fix(cli): preserve transcribed words before speech onset (#5164) 2026-10-07 07:46:58 +00:00
Miguel Ángel 9f09861d2e perf(studio): zooming a long timeline re-decodes and redraws less (#5142)
* perf(studio): reuse filmstrip frames when a zoom changes a strip's width

A zoom that changed a strip's frame count decoded the whole strip again
on the main thread, and showed the clip's single poster frame until it
landed. Frames are now sampled at each slice's left edge, so a strip
twice as long holds every frame of the shorter one; decoded frames are
shared per source and time, so zooming out decodes nothing and zooming in
decodes only the new half. The strip on screen stays until the new width
is ready.

* perf(studio): redraw only the waveforms on or near the screen after a zoom

A zoom resizes every mounted clip, and every mounted waveform redrew in
that same frame, on screen or not. A waveform now draws only while it is
on or near the screen (one shared observer, a screen of margin each side)
and draws when it comes near if a zoom changed it meanwhile.

* perf(studio): never start a beat-analysis stage while the timeline zooms

Analyzing a long music track runs for over a second on the main thread,
and when it started late it landed inside a zoom. The analysis now awaits
an optional pause before each long stage; Studio's waits for the timeline
to rest and the browser to go idle. Results are unchanged. The tempo
detector's stage is one call, so a zoom that starts inside it still waits
for it.

* fix(studio): reuse a filmstrip frame only where its own strip would show it

A shared frame was keyed by the time a strip asked for, not the time it
was decoded at, so a coarse strip's keyframe-early frame could land in a
finer strip and run its frames backwards. A frame now carries its decoded
time and is reused only when the strip would have accepted that keyframe
itself. The frame-count ladder now tops out at a power of two (32), so
its top step shares frames with the one below instead of none.

* feat(studio): show the clip's last frame at the end of every filmstrip

Left-edge sampling never showed a clip's final slice, so an end card or a
fade-out was missing from its strip. Each strip now also decodes the
clip's last frame (at its own time, never an earlier keyframe), shared
across zoom steps like the others, and the last tile shows it.

The strip on screen is now held per clip and range, not per props: a
clip crossing between on and off screen keeps it, and a clip narrowed to
one frame lets it go, so widening again never re-decodes a dropped width.

* docs(studio): keep the filmstrip files at or under main's comment share

The comment ratchet allows no file a higher share of comment lines than on
main; the added explanations move to the PR description.

* fix(studio): keep a held filmstrip leased so a full cache cannot evict it mid-zoom

The hold leased the strip on screen only once a new width was requested,
so for one commit it had no lease; with the cache over budget it was
evicted, the poster showed, and the old width decoded again. The strip on
screen is now leased for as long as it is the clip's, and shown only while
held. A frame that fails to decode takes the frame before it, so every
slot keeps its time and the last tile still shows the end.

* refactor(studio): one owner for which clip a held filmstrip belongs to

The hold checked the clip twice, in its lease and again before showing
it; the lease alone decides now, and the cross-clip test pins it. A test
now also pins that a filled frame is not charged to the cache.

* fix(core): hold the tempo detector until idle after a slow import

* fix(studio): scope shared video frames to timeline sessions

* chore(core): keep beat analysis comments within the ratchet
2026-10-07 07:38:13 +00:00
Miguel Ángel dbb8222c8d fix(studio): catch up the preview after missed file changes (#5126)
* fix(studio): recover previews after missed file changes

* fix(studio): keep queued reloads with the current owner

* fix(studio): keep a queued outside change when switching compositions

* test(cli): the project watcher closes its parent folder's watch too

* fix(studio): keep the project-folder change out of the shared signature check

* fix(studio): keep the edited file's draft and reload every session after a reconnect

A reconnect or replaced folder is a project-wide change: every open SDK session reloads, a failed save is recorded against the file the draft belongs to, and two queued changes to different files merge into one project-wide reload.

* fix(studio): refresh the preview cache when the .hyperframes folder is replaced

* fix(cli): report the files of a folder moved into the project

Replacing a populated folder such as .hyperframes/ now reaches the signature cache through its files, so the shared signature rule stays as on main and Studio's own writes inside .hyperframes/ still invalidate nothing.

* refactor(studio): name the merged file-change payload and share the test sender

* fix(studio): keep a failed draft's recovery copy until that file itself saves

* style(studio): rewrap the merged payload line after the rename

* fix(studio): deliver a held change's full reload when its block clears

A clean save, Use external and Keep Studio now all deliver the merged scope a blocked change owes, and a moved-in .hyperframes folder reports its manifests so a dropped one still clears the cached signature.

* fix(studio): keep restored drafts out of the reload scope and clear the lint gates

* fix(studio): retain held reload scope before React renders
2026-10-07 07:32:16 +00:00
Miguel Ángel 4fcad1ed74 fix(media-use): use a host app's music tools; fail loudly without the heygen CLI (#5154)
* fix(media-use): use a host app's music tools; fail loudly without the heygen CLI

* fix(media-use): name the heygen CLI only for music and sound effects; keep the sfx test offline

* fix(skills): workflow skills use a host app's own music tools first

* fix(skills): say how a host app's music track reaches the narrated workflows

* fix(skills): set a host app's music track after the last audio pass

* fix(skills): a host-music film with no narration still fetches its sound effects

* fix(media-use): keep the stub-failure test off the machine's heygen CLI; sync the skills manifest

* fix(skills): audio passes keep a host app's music and sounds; silent needs no sound cues

* fix(skills): host audio keeps string frames, stops on a broken audio_meta.json, survives a re-run

* fix(skills): report a host audio entry with no path and a broken sidecar instead of crashing

* fix(skills): keep host audio written while an audio pass runs

* test(cli): serve the telemetry capture while the resolve child waits on it

* test(cli): fail the telemetry capture when the resolve child exits non-zero

* style(skills): wrap the host audio comment to the 120-column limit
2026-10-07 07:16:51 +00:00
Miguel Ángel 80c2547eac feat(cli): send missing features and host-app friction as ratingless feedback (#5140)
* docs(skills): ask agents inside a host app to report its friction as feedback

* feat(cli): send missing features and host-app friction as ratingless feedback

* fix(cli): keep the rating error, refuse blank reports and document ratingless feedback

* docs(skills): leave names and figures out of missing-feature reports

* docs(skills): keep anything private out of missing-feature reports
2026-10-07 06:40:37 +00:00
Miguel Ángel 9e0d05d450 fix(engine): auto worker count stays within the heap budget (#5149)
* fix(engine): automatic worker count never exceeds what the V8 heap can feed

A 24 GB Mac exporting a 43 s portrait project with 22 videos picked 5 capture workers against a 4192 MB heap that feeds about 4, and the render died at 62% with JavaScript heap out of memory. The heap budget was only advisory; it now caps the automatic count (bound 'heap'). An explicit --workers N is untouched.

* test(engine): heap-cap test restores its mocks even when an assertion fails

Review findings on #5149: the os and v8 mocks were restored only if every assertion passed, and two comments still called the heap cap advisory.

* docs(engine): heap budget comments say it caps auto sizing
2026-10-07 06:11:30 +00:00
Miguel Ángel 9fbec22596 fix(cli): load telemetry without the core and engine barrels, and stop the history test racing (#5153)
* fix(cli): load telemetry without the core and engine barrels, and stop the history test racing

* fix(cli): load history from its leaf subpath, and fail the history test fast on a crashed CLI
2026-10-07 06:05:41 +00:00
Miguel Ángel 3b89bfe8a7 fix(engine): motion blur works over video by holding each video frame across samples (#5150)
* fix(engine): motion blur works over video by holding each video frame across samples

Each output frame's injected video frame is now held for every sub-frame
sample and probe of that frame, so footage stays as shot and only the
layers over it blur. The refusal for sessions with a before-capture hook
is removed.

Fixes #5144

* fix(engine): trim held-video comments, pin the new test-classification hash

* fix(engine): a blurred frame at a sub-composition cut keeps its video

Each sample now decides which videos show at its own time, like every other
layer, and only a video on screen at the output frame's time keeps that
frame's picture. Before, the visible set was taken at the frame's time while
sub-composition hosts were hidden at the sample's time, so the samples just
before a cut showed no video and the cut frame dipped toward black. The
before-capture hook takes the held time as an optional third argument, and
the early injection is gone: the GPU re-render now always runs at the page's
own time.

* style(engine): one doc comment for the before-capture hook

* fix(engine): a video animating its own style stays in step when blurred

The injector copies a video's own style only when its frame changes, so the
first injection of each blurred frame now runs at the frame's own time
before the samples. A video sliding by its own transform no longer sits a
quarter frame behind with blur on.

* test(producer): a two-line header for the motion blur over video test

* test(producer): reach the cut fixture by path, not a URL fallow reads as an import
2026-10-07 05:46:10 +00:00
Miguel Ángel 2d98d5c15f fix(check): stop false sweep_static and text-overlap alarms on compositions that are fine (#5147)
* fix(check): see color, filter and media time under seek, and name data-no-timeline for stills

* fix(check): warn instead of fail when only audio advances, and judge text overlap on glyphs

* fix(check): judge glyph overlap by the glyph box, and see capitalized words and one-side borders

* fix(check): capitalize past leading punctuation, test outline color, trim a comment

* fix(check): capitalize words the way Chromium does, so digit-led words stay lowercase
2026-10-07 05:38:45 +00:00
James RussoandClaude Opus 5.5 f74f260286 feat(core): bound imported scene timing to source intervals (#5132)
* feat(core): support deterministic async frame sources

* feat(core): bridge preserved film HTML to native capture

* feat(core): bound imported scene timing to source intervals

* test(producer): verify film bridge pixels in real browser

* fix(runtime): retain queued draws and disable unsafe dedup

* fix(runtime): fail promptly on runner reloads and send errors

* fix(runtime): align film draws with export windows

* docs(core): mark frame-source hosts with data-no-timeline

A composition that follows the frame-source contract registers no GSAP
timeline, so lint reported missing_timeline_registry and every render
waited 45 s for timeline registration (17.6 s -> 1 min 47.9 s for the
14 s imported film sample). Document the existing data-no-timeline
opt-out for the host and a timeline-less root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(core): require data-no-timeline on imported film hosts

Imported film wrappers have no GSAP timeline. Without data-no-timeline
on the scene hosts and root, lint fails with missing_timeline_registry
and each render waits 45 s for timeline registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): avoid transport redraws after render seeks

* test: set explicit film fixture canvas background

* test: diagnose film capture timeout phase

* test: foreground film pages before screenshot capture

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 05:25:09 +00:00
Miguel Ángel cffc3aa4e1 feat(render): report encoding and assembling progress (#5137)
* feat(render): report encoding and assembling progress

* fix(render): close every progress stage and fail a render whose progress callback throws

* fix(render): send a repeated progress update once

* fix(render): keep Docker terminal output as before and drop the unneeded callback ending

* fix(render): keep one failure stage code per stage whatever its frame counts
2026-10-07 04:29:35 +00:00
James RussoandClaude Opus 5.5 891a18af76 feat(core): bridge preserved film HTML to native capture (#5131)
* feat(core): support deterministic async frame sources

* feat(core): bridge preserved film HTML to native capture

* fix(runtime): retain queued draws and disable unsafe dedup

* fix(runtime): fail promptly on runner reloads and send errors

* docs(core): mark frame-source hosts with data-no-timeline

A composition that follows the frame-source contract registers no GSAP
timeline, so lint reported missing_timeline_registry and every render
waited 45 s for timeline registration (17.6 s -> 1 min 47.9 s for the
14 s imported film sample). Document the existing data-no-timeline
opt-out for the host and a timeline-less root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(core): require data-no-timeline on imported film hosts

Imported film wrappers have no GSAP timeline. Without data-no-timeline
on the scene hosts and root, lint fails with missing_timeline_registry
and each render waits 45 s for timeline registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): avoid transport redraws after render seeks

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 03:55:42 +00:00
Miguel Ángel f6b3821626 fix(catalog): run block scripts inline in the docs player, and pages for six shader blocks (#5139) 2026-10-07 03:11:49 +00:00
Miguel Ángel 7cf042d74f feat(registry): six WebGPU shader backgrounds from the Shaders library (#5138) 2026-10-07 03:00:53 +00:00
James RussoandClaude Opus 5.5 a17d0bfc00 feat(core): support deterministic async frame sources (#5130)
* feat(core): support deterministic async frame sources

* fix(runtime): retain queued draws and disable unsafe dedup

* docs(core): mark frame-source hosts with data-no-timeline

A composition that follows the frame-source contract registers no GSAP
timeline, so lint reported missing_timeline_registry and every render
waited 45 s for timeline registration (17.6 s -> 1 min 47.9 s for the
14 s imported film sample). Document the existing data-no-timeline
opt-out for the host and a timeline-less root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): avoid transport redraws after render seeks

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:59:55 +00:00
Miguel Ángel 0ca99b30da fix(ci): skills sync publishes hyperframes-creative past its hidden 1.0.13 and checks it went live (#5081)
* fix(ci): skills sync publishes past a version the registry holds unpublished

* fix(ci): skills sync fails unless a republished skill is the registry's latest

* fix(ci): skills sync republishes one patch past a hidden version and checks it as owner

* test(ci): skills sync fails the job on an error and stops after a clean sync

Two mutants survived review: dropping the line that sets the exit code (the job would print its ::error:: lines and still pass) and dropping the clean-sync return. One test runs the script with a failing fake clawhub on PATH and expects exit 1; another pins that a clean sync makes no further call.

* test(ci): the exit-code test can only reach its fake clawhub

The child's PATH was the fake's folder plus the inherited PATH, so on a machine where the fake cannot execute, a real logged-in clawhub further down would run sync for real. PATH is now the fake's folder alone.

* fix(ci): skills sync skips only hyperframes-creative's hidden 1.0.13

Republishing one patch past any taken version would publish past every future hidden version with no one looking. Only the acknowledged case (hyperframes-creative, 1.0.13 taken) is published at 1.0.14; any other taken version fails the sync as before.

* test(ci): the skip allowlist matches the skill, not just the version

A taken 1.0.13 on any other skill must fail the sync too; without this case, accepting 1.0.13 for every skill passed all tests.

* refactor(ci): skills sync picks the republish version in its own helper

The allowlist check took republishTaken to cyclomatic 5, which the Fallow audit scores at its 30.0 CRAP threshold; deciding the version in versionPastSkippable keeps each function under it. Behaviour unchanged.
2026-10-07 01:46:15 +00:00
Miguel Ángel 4cf5cf9b53 feat(engine): render WebGPU compositions on SwiftShader behind an opt-in (#5133)
* feat(engine): render WebGPU compositions on SwiftShader behind an opt-in

PRODUCER_ALLOW_SOFTWARE_WEBGPU=true lets a host without a GPU render a
data-requires-webgpu composition instead of refusing it. SwiftShader can
draw canvas WebGPU only through its Vulkan backend with GPU compositing
on, so an opted-in software launch adds the Vulkan feature and
--use-vulkan=swiftshader, keeps compositing on, and the adapter check
accepts the fallback adapter. Off by default; every other launch is
unchanged.

* test(engine): compare the opted-out launch without a cast

* test(engine): pin which adapter a capture session accepts for WebGPU

* test(engine): assert the adapter check's arguments with the call matcher

* docs(engine): fit the software WebGPU note in three lines
2026-10-07 01:25:10 +00:00
Miguel Ángel 04736006b1 test(producer): unit tests never reach the network (#5135)
* test(producer): unit tests never reach the network

* test(producer): the unit lane refuses sockets to other hosts, not only fetch

* test(producer): refuse outside hosts at the lookup, not by parsing socket calls

* test(producer): keep dns.lookup promisify-compatible under the no-network guard
2026-10-07 00:52:01 +00:00
Miguel Ángel eee0c5101c perf(studio): smooth timeline zoom on long projects (#5109)
* perf(studio): coalesce timeline zoom to one write per frame and zoom to a range

* perf(studio): coalesce timeline zoom to one write per frame and zoom to a range

Every zoom input goes through one owner that writes the store once per frame.

zoomTimelineToRange eases to a time range; the zoom buttons use it.

* feat(studio): zoom-to-range reports done or cancelled and is not a person zoom

* perf(studio): place clips in percent of a one-second layer so zoom restyles nothing

A zoom step rewrites one width per row; clip thumbnails start decoding at rest.

* perf(studio): a zoom step re-renders only clips whose drawing it changes

Clip handlers read the lane callbacks when pressed, so a skipped clip never acts on a stale zoom.

* refactor(studio): move at-rest waveform and thumbnail work to its own change

* perf(studio): draw waveforms and decode thumbnails when the timeline rests

Waveforms draw only the visible span of a clip.

A zoom or scroll step stretches what is drawn until the timeline rests.

* test(studio): fade handles measure the clip only while they show

* test(studio): waveforms draw their visible span and redraw at rest

* feat(studio): export currentTimelineRange, the inverse of zoomTimelineToRange

* perf(studio): zoom gestures scale the drawn timeline and lay it out once at rest

No layout or render per gesture frame; past 0.67x or 2x the preview is laid out and continues.

* perf(studio): rebase a zoom preview only past 0.5x or 4x

Clips stay mounted half a viewport each side, so a zoom-out preview to 0.5x shows no gaps.

* fix(studio): keep the zoom readout, edges and Fit right during a preview

The slider and its percent follow a zoom while it is previewed. A zoom in
from below Fit no longer jumps when it lands: the preview now uses the
width the timeline will really have. A zoom-out lays out as soon as it
would show time with no clips mounted, so edges never show empty strips.
Fit, a press or a scroll on the timeline end a pending zoom first. An
older abort no longer cancels a newer zoom-to-range, and an instant one
lands on the next frame instead of inside its caller. Range selection and
gap tints scale with the preview.

* fix(studio): keep a pending zoom when the timeline re-registers its view

The timeline registers its scroll view again whenever clips are added or
removed, which used to drop a zoom still waiting to be laid out. Now only
a real unmount drops it, and unmounting one timeline leaves a newer
timeline's registration alone.

* refactor(studio): share the zoom geometry helpers the timeline already has

Zoom scales come from getTimelinePixelsPerSecond, the range margins from one
helper, and a zoom step starts from the same playhead anchor every zoom uses.
The clip minimum width is named and shared with the drag ghost.

* fix(studio): keep a zoom preview through scrolls and past the content end

A scroll during a zoom preview, including the one a laid-out zoom makes
itself, used to lay the next preview out at once, so a pinch or eased zoom
fell back to a full layout every frame. A scroll now only rescales what it
mounted. Zooming out also lays out before the preview shows past where the
ruler and beat lines are drawn, not only past the mounted clips.

* fix(studio): keep a moved audio clip's waveform drawn where it shows

A waveform now draws the stretch of its clip that the shared strip tracker
reports near the screen, the tracker the filmstrip already uses. It follows
scrolls, resizes and moves (a drag or ripple that shifts the clip), so a
moved clip no longer shows a blank stretch, and a zoom redraws it once.

* perf(studio): lay a zoom-out step out once, at its start

An eased zoom-out (a Zoom out click, or zooming out to a range) used to
lay out partway, once the shrinking preview reached time with nothing
mounted, and again at the end. It now lays the target out first and eases
by scaling that layout down from the old view, so each click lays out
once. Each eased frame also draws its preview in that frame.

* fix(studio): keep previewing a zoom at the content's end and over new rows

The preview now lays out only once it would show past the window the
timeline draws clips, ruler ticks and beat lines in, with two pixels of
slack, so rounding at the content's end no longer lays out every frame.
Rows mounted into the timeline during a preview are scaled before they
paint.

* fix(studio): redraw a waveform when its clip's width changes or it moves

A waveform drawn whole kept its old bars, stretched, after a zoom changed
its clip's width; it now redraws on any resize. A long clip also marks
its undrawn ends for the strip tracker, so a move without a scroll (a
ripple, a drop, an undo) re-measures it instead of leaving a blank
stretch.

* perf(studio): skip the layout when a zoom preview ends on its own scale

An eased zoom-out lays its target out first, so its preview ends on the
scale already laid out. Landing it now only sets the scroll, instead of
writing the same zoom again and counting the person's zoom twice.

* fix(studio): measure filmstrips and waveforms after a zoom preview ends

The strip tracker measured clip boxes while a zoom preview scaled them,
so after a Zoom out click a long clip's filmstrip or waveform could stay
blank across part of the screen. Reads during a preview now wait, and the
tracker re-measures every strip once the preview ends. A zoom-out lays its
target out in its first frame rather than inside the caller, and only when
the new view holds the old one.

* perf(studio): watch a waveform's undrawn ends only when it has some

A clip drawn whole has no undrawn ends, so it no longer mounts the two
empty markers the strip tracker watches; with hundreds of short audio
clips that saved an observer entry each.

* fix(studio): measure strips each time a zoom preview is laid out

A zoom-out lays its target out partway into the ease, after which the
strip tracker kept the stretch it measured before the zoom, so a long
filmstrip or waveform went partly blank near the end of the ease. Every
layout of a preview now re-measures the strips.

* docs(studio): keep the zoom-to-range doc comment within four lines

* docs(studio): keep the zoom files' comment share at or under main's

The comment ratchet failed on ten files this branch touches. Comments are
cut or folded into one line; no code changes.
2026-10-07 00:46:17 +00:00
Miguel Ángel 1825c37268 test(studio): launch real-Chrome tests like the e2e launcher, bounded by the test budget (#5134)
* test(studio): launch real-Chrome tests like the e2e launcher, bounded by the test budget

* test(studio): share the real-Chrome page setup between the two runtime tests

* test(studio): keep the real-Chrome test helper with the other test helpers
2026-10-07 00:25:18 +00:00
Miguel Ángel 11e684d305 fix: the preview preloads the coming scene's images, and check stops failing on image swaps (#5128)
* fix(core): the preview loads the next scene's images before it starts

* fix(cli): check stops reporting image loads that its own scrubbing cancelled

* fix: preload only a coming clip's own images, and judge aborted images by whether they still decode

* fix(cli): count an aborted image as broken only when its decode fails

* style(cli): keep the aborted-image comment within the line limit

* test(cli): pin that a reload still pending at the cap is not reported

* test(cli): run the cap test on whatever timers are pending, and always restore real timers
2026-10-07 00:10:29 +00:00
Miguel Ángel e6daed3df0 chore: release v0.8.139 (#5136)
* chore: release v0.8.139

* chore: sharpen the v0.8.139 release summary
v0.8.139
2026-10-07 00:06:22 +00:00
Miguel Ángel e8cfd6b73e perf(studio): parse saved HTML once when syncing timeline length (#5092)
* perf(studio): parse the saved file once to sync the composition length after a timeline edit

* perf(studio): keep empty duration updates free of parsing

* test(studio): pin one parse for rootless moves and the delete length readout

* test(studio): check the delete's length readout before its save lands
2026-10-06 23:16:21 +00:00
Miguel Ángel f7bba2288d fix(cli): read the WAV in JS so Built-in transcription runs inside Electron (#5129)
* fix(cli): read the WAV in JS so Built-in transcription runs inside Electron

* fix(cli): read an empty WAV as silence
2026-10-06 23:15:50 +00:00
Miguel Ángel d94708e531 fix(core): keep nested scenes in place during a drag when the root has no timeline (#5115)
* fix(core): keep nested scenes in place during a drag when the root has no timeline

* fix(core): reuse the missing-root composite so a late root timeline still binds

* fix(core): count a nested scene in its composite length so a rebind keeps it at the playhead

* fix(core): rebuild a held composite whose length went stale so the player length stays right

* test(core): reuse the no-root-timeline loader for the stale-length case
2026-10-06 21:58:19 +00:00
Miguel Ángel b50dead1da feat(registry): gooey split block with metaball goo and directional motion blur (#5123)
* feat(registry): gooey split block with metaball goo and directional motion blur

A dot splits into a three-dot menu through a gooey neck, spins into a
swirl mark under shutter motion blur, breaks back into three dots and
merges into one. Timing, spacing and eases are measured off a reference
recording frame by frame.

The motion-blur snippet gains an opt-in `sharp: 0` option that hides the
crisp frame-time copy while an element moves, leaving only the shutter
average. The default is unchanged; the inlined copies carry the same text.

* feat(registry): grow gooey-split blades out of swelling dots

* refactor(registry): tidy gooey-split pose code and document sharp 0 limits

* fix(registry): hide a sharp 0 motion-blur target by marker, not its visibility

Hiding through the element's own visibility fought timelines that
write it (autoAlpha): a target faded in after attach stayed hidden at
rest. A marker attribute and one !important rule now do the hiding, the
default path writes nothing, and a declarative sharp other than 0 or 1
is refused like any other bad option.

* fix(registry): smear a motion-blur target that was hidden when blur attached

Copies froze the target's computed visibility at attach, so a target
entering with autoAlpha never smeared (and vanished entirely under
sharp 0). The group now carries the target's current visibility each
frame, read with the sharp 0 marker lifted, and a copy keeps a
descendant's visibility only where it differs from its parent's.

* test(registry): pin a copy keeping a descendant's own visibility

* fix(registry): keep a child's own hiding in the smear of a target hidden at attach

* refactor(registry): restore a lifted visibility in one call and lift any hidden form

* fix(registry): restore a lifted visibility even when copying styles throws

* fix(registry): let motion-blur copies hide with a clip that has ended

The smear group set an explicit visibility on every moving frame, and
copies used to freeze an explicit visible, so an ancestor clip hiding
after the last update could not hide them: copies from a 0-4 s
sub-composition stayed on screen at 16.4 s. The group now sets a
visibility only where the element's differs from its parent's.
Also regenerates skills-manifest.json for the motion-blur skill doc.
2026-10-06 21:24:38 +00:00
Miguel Ángel 156e87766c feat(cli): stream heard words on the transcribe --json progress stream (#5127)
* feat(cli): stream heard words on the transcribe --json progress stream

* test(cli): share the streaming transcribe helper

* fix(cli): keep install and download stoppable and whisper errors short while streaming
2026-10-06 20:44:45 +00:00
Miguel Ángel 755ab7d165 fix(core): renders keep a fromTo's from-only values from the frame it starts on (#5125)
* fix(core): renders keep a fromTo's from-only values from the frame it starts on

* refactor(core): prime a tween at its start in one helper

* fix(core): a keyframed fromTo also keeps its from-only values at its start

* fix(core): each staggered target keeps its from-only values from the frame it starts on
2026-10-06 20:22:57 +00:00
Miguel Ángel dc244d765c fix(studio): drags after a resize, undo or reload land where they were pressed (#5088)
* test(studio): bench moves pressed right after Add keyframe

* fix(studio): an undo that reloads the preview keeps the selection, so a drag after it moves

* fix(studio): a drag started while a resize saves keeps its animations paused

* fix(parsers): an edit pins no position on a keyframed tween it did not touch

* fix(studio): a move pressed while the preview changes lands on the new preview, at the time pressed

* test(studio): bench waiting presses, a held box resized twice, and reloads at the last playhead

* refactor(studio): tidy the waiting press, bound its wait, and freeze edit stamps after early exits

* test(studio): load the property panel once so its first test stops timing out

* fix(studio): presses queued on a held box keep their release, hover and newest outline

* test(studio): judge bench undo by the history groups Studio claims, not by file writes

* refactor(studio): one waiting-box lookup in the bench and one raf-pause write per release

Behaviour-identical tidy of the waiting-press work: the bench reads the waiting
box through one helper, releaseActiveGesture sets the raf pause once, clearPointerState
writes the false it always computed, and previewGestureStarts calls the opts setters
directly instead of through aliases named like the gesture hook's gated setters.

* fix(studio): a queued press lands on the element it pressed and survives a window switch

* fix(studio): undo cancels a waiting press, a stuck save stops holding presses, scene swaps hold them

* refactor(studio): keep the complexity waiver on clearPointerState itself

* fix(studio): undo of a queued press leaves nothing waiting, and hosts keep their own undo

* fix(studio): a waiting press keeps the box still after a blur, and undo reports what it took back

* style(studio): bring the PR's comments within the comment limits

* fix(studio): bank the four bench cases this fixes and meet the comment, complexity and duplication gates

* test(studio): pin that a waiting press is never a pending edit, and name the new duplication waivers
2026-10-06 20:22:37 +00:00
Miguel Ángel 34552a21d2 fix(cli): let parakeet-mlx detect the language and name the Parakeet install under auto (#5124)
* fix(cli): let parakeet-mlx detect the language and name the Parakeet install under auto

* docs(cli): say what --language does under each engine

* docs(cli): whisper transcribes as the --language, it does not filter

* chore(cli): drop a comment the install-hint condition already says
2026-10-06 19:04:28 +00:00
Miguel Ángel b9e511a768 fix(studio): a timeline jump no longer starts thumbnail loads it is about to cancel (#5085)
* fix(studio): a timeline jump no longer starts thumbnail loads it is about to cancel

* fix(studio): pump inline after the release batch, and pin the synchronous slot free
2026-10-06 18:04:50 +00:00
Miguel Ángel 0ca28db4f8 chore: release v0.8.138 (#5121) v0.8.138 2026-10-06 17:53:18 +00:00
Miguel Ángel 5fad52f21d fix(studio): an offline preview says GSAP could not load instead of an unhandled Event (#5111)
* fix(studio): an offline preview says GSAP could not load instead of an unhandled Event

* fix(studio): the preview error list survives a second capture attach without duplicates

* fix(studio): the console capture keeps listening after a long early error list

* refactor(studio): the console capture owns every reset of its findings
2026-10-06 16:35:11 +00:00
Miguel Ángel b9998dd5a8 test(studio): bundle the real-Chrome undo test with esbuild so its setup fits on Windows (#5080)
* test(studio): bundle the real-Chrome undo test with esbuild so its setup fits on Windows

* test(studio): stub only the Node built-ins the undo test bundle reaches
2026-10-06 15:32:53 +00:00
WaterrrForever 6fe3c2ce94 feat(cli): catch up on what the desktop app did since a project was handed over (#5114)
* feat(cli): catch up on what the desktop app did since a project was handed over

The desktop app records each chat turn in .hyperframes/app-history.jsonl.
`hyperframes catch-up [dir]` prints the turns since the hand-off or the last
catch-up (what the person asked, what Framey did, which files changed) plus
the project files changed since, by the app or by hand, then marks them seen.

`hyperframes open` and Studio's Edit with Framey mark the hand-off moment.
Any command run on a project with unseen turns ends with a line naming
catch-up, so an agent back in the terminal hears about it even when it
forgets. /hyperframes and /hyperframes-cli tell the agent to catch up as
soon as the person writes again.

* feat(cli): hand a Grok terminal session over to the desktop app

Grok gives every tool command its session as GROK_SESSION_ID (grok 1.0.4),
so `hyperframes open` and Edit with Framey now name a Grok session in
.hyperframes/agent-handoff.json, after Claude Code and Codex. The desktop
app reads its turns from Grok's own session files.

* feat(cli): tell a new project's agent to catch up after the desktop app

Scaffolded projects' CLAUDE.md and AGENTS.md now say to run
`npx hyperframes catch-up` before the next change once the project was
opened in the desktop app. Claude Code, Codex and Grok read these files on
their own, so a resumed session catches up even when it never loaded a
HyperFrames skill: a resumed Claude Code session that had not, asked to
change the title, ran catch-up first.

catch-up also prints each reply on one line.

* fix(cli): read and write .hyperframes records without following links, mark seen after showing

Review (Jerrai, Rames):
- One helper (projectRecords.ts) now reads and writes every record in
  .hyperframes/. A linked folder is refused. A read opens once with
  O_NOFOLLOW and checks the open file is a plain file, which clears the
  CodeQL race. A write goes to a temp file renamed over the record, so a
  link is replaced, never written through. agent-handoff.json, the seen
  record and the history all go through it.
- catch-up marks a turn seen only after showing it, and only up to the
  newest turn shown, so a turn written just after the read shows next
  time. Changed files have their own "checked" time.
- Turns show only once the project was handed over from this machine, so
  a history file shipped in a cloned repo is never shown as the person's
  words. The end-of-command check skips reading when the history is
  older than the seen record.
- The skills mention catch-up only when the CLI printed a line naming it,
  so an older CLI never meets an unknown command.
- The notice test uses basename, which fixes Windows.

* feat(cli): add the catch-up line to an older project's instructions on hand-off

A project scaffolded before catch-up existed has no line telling its agent
to catch up, and a resumed session that never loaded a HyperFrames skill
then edits without knowing what happened in the app. When `hyperframes open`
or Edit with Framey hands such a project over, its scaffolded CLAUDE.md and
AGENTS.md (they start "# HyperFrames Composition Project") gain the
template's line, in the template's place. A file of the person's own, a
link, a missing file, or one that has the line is left alone. The file is
rewritten through a temp file with its mode kept. A test keeps the line the
same as the template's.

* fix(cli): keep catch-up's cursor in the person's home, never reset it, read without links on Windows

Review (Rames, Jerrai):
- The seen record moved from the project to ~/.hyperframes/catch-up/,
  keyed by the project's real path. A seen file a cloned repo commits
  can no longer make its history read as the person's words.
- A second hand-off (`open` again, or another Edit with Framey click)
  keeps the cursor, so turns the app recorded meanwhile still show.
- Reads lstat the path, open it once, and refuse a file whose dev/ino
  differs from what lstat saw. That holds where O_NOFOLLOW doesn't
  exist, fixing the Windows test. The note writer reads the same way,
  which clears the new CodeQL alert.
- Writes use core's replaceFileAtomically, which retries a rename that
  Windows briefly refuses.
- catch-up names every changed file, and the cursor is the newest `at`
  shown, not the last line's.
- `open --json` carries `catchUp` after a hand-off.
- The bin scripts are back to mode 644.

* fix(cli): open before checking for CodeQL, never show a turn dated ahead

Review (Rames, Jerrai):
- readPlainFile opens the file first, then requires lstat to find the same
  plain file (dev/ino) at the path, and reads only through the fd. That is
  just as safe, and it clears CodeQL's check-then-use alert.
- catch-up skips turns dated after the moment it reads. So a cloned history
  can't show a far-future turn as the person's words or park the cursor
  past real ones.
- The seen key uses core's realpath (realpathSync.native), so letter case
  can't split a project in two. ~/.hyperframes/catch-up is made 0700, and
  a hand-off that can't note itself says so.
- Tests stub HOME with vi.stubEnv and restore it.
2026-10-06 13:20:27 +00:00
WaterrrForever 32516b5a3d fix(studio-server): keep the desktop app's hand-off and chat history out of the file routes (#5113)
* fix(studio-server): keep the desktop app's hand-off and chat history out of the file routes

The file and preview routes reach any path inside the project. The desktop
app keeps two files there that hold the person's agent session and their
chat: .hyperframes/agent-handoff.json and .hyperframes/app-history.jsonl.
No route now reads, writes, renames onto, duplicates or uploads over them,
by any spelling or through a link. The rest of .hyperframes/ (Studio's
motion file, prepared GIF assets) is served as before.

* fix(studio-server): keep the desktop app's run log out of the file routes too

The app records every chat run, the person's words included, in
.hyperframes/agent-runs.jsonl. Only the app's main process reads it.

* fix(studio-server): refuse everything under .hyperframes/ but Studio's own files

Review (Jerrai, Rames): a list of three names missed the read marks the
desktop app and the CLI also keep there, and renaming the folder reached
them under a new name. The check is now an allow-list: no route reaches
.hyperframes/ or anything in it, by any spelling or through a link, except
Studio's studio-motion.json, studio-manual-edits.json and prepared-assets/.
Studio's own internal files (backups, preview documents, history id) are
read by path inside the server, not through a request path.

The project's file tree leaves out the same paths. The upload test now
sends a file uploads accept elsewhere, so the folder is the only reason
it is refused.

* fix(studio-server): serve the app's request pictures, close the renders and rename paths

Review (Rames, Jerrai):
- Desktop loads each request's picture from .hyperframes/requests/ through
  the preview route, so that folder joins the allow-list. An allowed
  folder's own path is allowed too.
- The render-file route resolves against renders/, so a renders/ linked
  into .hyperframes/ reached the records. It now also checks the file
  against the project.
- A rename's reference update walked every JSON/MD file, records
  included, and could rewrite them. It now skips private paths.
- The allow-list reuses STUDIO_MOTION_PATH and STUDIO_MANUAL_EDITS_PATH.

* chore(studio-server): keep render.ts's comment share at the base

* fix(studio-server): list, view and download only renders the render-file route would serve

Review (Jerrai, Rames): the render list found files in renders/ with join
and statSync, which follow links, and registered them for /render/:id/view
and /download, which read them with no check. A project shipping
renders/leak.mp4 -> ../.hyperframes/app-history.jsonl had its chat served.
The list now keeps an entry only when resolveWithinProject accepts it and
it isn't private, and registers the resolved path. View and download serve
only a plain file, so a link swapped in after the list is refused. This
predates the PR; it's the same shipped-link case.
2026-10-06 13:20:02 +00:00
Miguel Ángel f80614cde4 test(producer): compiler parity test runs without the network (#5087)
* test(producer): compiler parity test runs without the network

* test(producer): parity test counts skipped stylesheets as loaded, without error logs
2026-10-06 11:46:33 +00:00
Vance IngallsandClaude Opus 5.5 518694ac66 fix(studio): clip #root so programmatic scrolls cannot push the header off-screen (#5110)
Dockview keeps inactive "always"-rendered panel overlays in the DOM with
visibility:hidden and no position, so each one falls to its static position
below the dock at full dock height. That made the document ~2x the viewport
tall (1954px in a 997px window). body overflow:hidden only blocks user
scrolling, so the scrollIntoView that runs when a property group opens
scrolled the viewport 124px and cut off the header and panel tabs.

overflow:clip on #root (not hidden, which is itself programmatically
scrollable) removes the overflow: scrollHeight drops to the viewport height
and scrollIntoView on a hidden overlay no longer moves anything.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:03:11 +00:00
Miguel Ángel eff786383d fix(core): preserve SVG icons in loaded compositions (#5096)
* fix(core): namespace SVG ids in runtime-mounted compositions

* fix(core): keep SVG selectors and captured ids working after loaded scenes rename them

* chore(core): generated artifacts carry a plain do-not-edit banner

* test(catalog): record the font subsets the new scene-script installer requests

* test(catalog): keep earlier mirror recordings, as re-records only add
2026-10-06 11:02:51 +00:00
Miguel Ángel 43d07781d8 test(studio): keep the undo socket gate's sockets pinned past Chrome's idle media suspend (#5108)
* test(studio): keep the undo socket gate's sockets pinned past Chrome's idle media suspend

* test(studio): the socket gate retries its video scan and says only what its pins prove
2026-10-06 10:52:58 +00:00
Miguel Ángel 6308727b85 chore: remove committed design notes and ignore docs/contracts (#5107)
* chore: remove committed design notes and ignore docs/contracts

* chore: allow the design note deletions in the main deletion guard
2026-10-06 06:12:45 +00:00
Miguel Ángel 8b5b012e5c fix(studio): dragging a Layers row no longer highlights the row names (#5104) 2026-10-06 06:03:20 +00:00
Miguel Ángel f6e419eb29 fix(cli): use a healthy pinned Sherpa copy beside the CLI (#5095)
* docs(cli): map the pinned Sherpa runtime loading contract

* fix(cli): use a healthy pinned Sherpa copy beside the CLI

* chore(cli): keep runtime loader comments within the ratchet

* docs(cli): wrap the runtime contract palette declarations

* chore(cli): keep the Sherpa contract note out of the repo
2026-10-06 05:43:09 +00:00
Miguel Ángel d09e003b17 Merge pull request #5106 from heygen-com/release/v0.8.137
chore: release v0.8.137
v0.8.137
2026-10-05 22:40:38 -07:00
Miguel Ángel 59b17170a7 fix(studio-server): export the shared upload limit (#5105) 2026-10-06 04:52:55 +00:00
Miguel Ángel e7b69785ea chore: release v0.8.137 2026-10-06 00:39:59 -04:00
Miguel Ángel 4905b4dbca fix(studio): make clip peak warnings plain and hoverable (#5099)
* fix(studio): make clip peak warnings plain and hoverable

* fix(studio): expose peak detail on clip focus above waveforms

* fix(studio): preserve shared badge styling and track test identity
2026-10-06 04:11:06 +00:00