Commit Graph
469 Commits
Author SHA1 Message Date
kevin 6dba67a0c3 fix(cli): honour HYPERFRAMES_SKIP_SKILLS in skills update and bare skills (#5743)
skills update (bare or named) and bare skills now exit 0 with a one-line
notice instead of installing or pruning when HYPERFRAMES_SKIP_SKILLS=1, so a
pinned skills install survives the router's trigger-time update. Closes #5695.
2026-10-17 02:34:40 +00:00
Miguel Ángel eab6ca15b1 fix(cli): timeline lists clips nested in a timed element and lint flags ones that never show (#5732)
* fix(cli): timeline lists clips nested in a timed element and lint flags ones that never show

* docs(lint): wrap the nested-clip entries

* docs(skills): format the nested-clip note and refresh the skills manifest

* fix(cli): keep timeline edits from colliding with a clip's own wrapper

* fix(cli): cut clips nested in a wrapper at their sub-composition's in-point

* refactor(lint): split the nested-clip visibility checks to keep the audit green

* style(cli): restore the timeline test's formatting
2026-10-17 02:27:17 +00:00
WaterrrForeverandClaude Opus 5.5 cea8aa8a7d feat(cli): judge text behind a transparent video by its decoded alpha in check (#5657)
* feat(cli): judge text behind a transparent video by its decoded alpha in check

The layout audit treated every <video> as opaque over its whole box, so a
caption between footage and a remove-background cutout always failed
text_occluded and needed data-layout-allow-occlusion, which also hid a word
the subject's head fully covered.

check now draws the video's current (seeked) frame into one reused,
downscaled scratch canvas, once per sample and only for a video a text probe
lands under. A fully opaque frame keeps today's behaviour. A frame with
transparency is judged per rendered word against its alpha: partial cover is
an info finding carrying the most covered word's share, and a word 80% hidden
or two neighbouring words each half hidden is an error. A frame that cannot
be read falls back to opaque with a video_alpha_unread info note.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(cli): serve the video-alpha fixtures from memory, not request paths

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): check finds words under narrow, mirrored or backed cutouts

Review follow-ups on #5657:
- A matte the probe walk passes over, even at clear-alpha points, now triggers
  the per-word check, so a feature between two probe columns (an arm, a mic
  stand) still fails the word it hides.
- Mirrored videos and images map viewport points through the flip; a rotated,
  skewed or 3D-transformed video falls back to opaque with video_alpha_unread.
- A video with an opaque CSS background keeps its whole box occluding.
- cli.mdx lists every unread cause and why a fully transparent frame is one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 18:53:16 +00:00
Xuanru Li 1544b519a9 feat(cli): beats tracks the music with the TypeScript beat tracker, without Chrome (#5634)
* feat(cli): audio answers beats, downbeats, bpm, sections and describe through a provider ladder

Beats and downbeats try api, apple-mu, beat-this, then signal; sections try api, apple-mu,
then signal; describe tries api, then apple-sound. bpm is read off whichever provider placed
the beats. Each provider says whether it can run here and why not, and the ladder warns
when it passes one over.

The signal rung is a TypeScript tracker: log-band spectral flux, autocorrelation tempo with
a log-normal prior, Ellis dynamic-programming beats with a windowed local tempo, a low-band
bar phase, and Foote novelty sections snapped to beats. It reports no steady beat when the
onsets do not repeat.

The api rung posts the audio to HYPERFRAMES_AUDIO_API_URL with HYPERFRAMES_AUDIO_API_KEY and
moves each answered time onto the nearest signal beat within a quarter beat, reporting what
moved and what did not. beat-this runs Beat This! from a configured Python and local weights
and never downloads. apple-sound runs macOS's sound classifier through a Swift helper.
apple-mu reports that Music Understanding needs macOS 27.

words are no longer accepted: they come from hyperframes transcribe.

* docs(cli): audio's beats, downbeats, bpm, sections, describe and their provider ladder

* feat(cli): beats tracks the music with the TypeScript beat tracker, without Chrome

hyperframes beats and Studio's beat detection now share analyzeMusicFromSignal: band energies,
then the spectral-flux tracker in audioRhythm.ts, then each beat's loudness for its strength.
The CLI decodes with the same ffmpeg path as hyperframes audio, so beats no longer launches
headless Chrome. Studio still decodes in the browser and passes every channel.

The old analyzer averaged bpm-detective's tempo with an energy-peak tempo it had only compared
after folding both into one octave, and phased a rigid grid from noisy peaks. On 22 real tracks
with exact script grids it found the tempo 18% of the time with beat F 0.437; this finds it
91% of the time with F 0.887, and refuses the other two rather than guess.

Below a tracker confidence of 0.2 no beats are reported, so ambient or rubato music gets no
invented grid and the CLI still writes nothing. bpm-detective, the energy-peak grid, the
headless analyzer and its prebuilt bundle are gone.

* fix(core): tempo reads 96 BPM off beats on 10 ms frames, not 95.2

The tracker places beats on 10 ms frames, so a 625 ms beat lands 620 or 630 ms apart and the
median interval reported 95.2 or 96.8 BPM. Tempo now averages the intervals within 10% of the
median: as robust to a skipped or doubled beat, without the frame-sized step. Both
hyperframes audio and hyperframes beats read it.

* fix(cli): remove redundant audio context comment

* fix(core): remove redundant tempo interval comment

* fix(ci): record beat analyzer removals and browser evidence

* fix(core): keep flat audio novelty in one section

* chore: keep PR capture evidence out of the source tree

* fix(cli): honor BPM provider pins before analysis
2026-10-15 06:23:03 +00:00
Miguel Ángel d4c8d76fff fix(lint): warn on unbounded cosine powers in shaders (#5640) 2026-10-15 05:40:38 +00:00
Miguel Ángel db845d9409 fix(cli): retire old snapshot run folders (#5635)
* fix(cli): retire old snapshot run folders

* chore: satisfy snapshot comment limit

* fix(history): read and release snapshot leases through verified handles

* test(history): run lease races in Node and check released markers

* fix(studio): verify snapshot leases from open descriptors
2026-10-15 04:59:59 +00:00
Xuanru LiandClaude Opus 5.5 150e1c94d0 fix(cli): report spend usage for Claude plans without rate windows (#5629)
* fix(cli): report spend usage for Claude plans without rate windows

Spend-metered Claude plans (for example Enterprise) return null five_hour
and seven_day windows from the OAuth usage endpoint and put their allowance
in spend / extra_usage instead. The parser rejected every such response as
invalid_usage_response, so `hyperframes usage` always reported unknown.

- Read spend.percent (falling back to extra_usage.utilization) into a new
  `spend` window on known results; over-limit spend caps at fully used.
- Report `no_usage_windows` instead of `invalid_usage_response` when a
  well-formed response simply has no window.
- Document the `spend` field in the hyperframes-cli skill.

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

* fix(cli): satisfy usage budget comment ratchet

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:04:55 +00:00
Miguel Ángel 6f90ca15a7 chore(skills): storyboards are a board of stills with one page format (#5599)
* chore(skills): storyboards are a board of stills with one page format

* chore(skills): count audio_meta.json as a project file in comment citations

* chore(skills): point every board mention at the storyboard recipe

* chore(skills): board redos replace only their own stills, from the mounted composition

* chore(skills): board stills come from the host's snapshot tool when one is offered
2026-10-14 13:56:58 +00:00
Xuanru Li 23d1fe5b42 feat(cli): save check, snapshot and render artifacts per revision (#5522)
* 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
2026-10-13 06:20:31 +00:00
Märten Tropp 63f32fefa9 docs(skills): fix frame-worker contract, lint codes and stale references (#5515)
* docs(skills): fix frame-worker contract, lint codes and stale references (#5467)

* docs(skills): use placeholder paths so comment citations resolve (#5467)
2026-10-13 04:12:09 +00:00
1d2aa5240d fix(media-use): transcript-cut video renders without repeated frames or a keyframe warning (#5463)
* 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>
2026-10-13 01:23:05 +01:00
b71c9f4e1b fix(media-use): keep frame precision on transcript-cut seek boundaries (#5426)
* 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>
2026-10-13 01:08:20 +01:00
1577060dc1 fix(media-use): honor telemetryEnabled: false from shared config in optedOut (#5490)
* 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>
2026-10-12 23:56:46 +01:00
DanteandJames ea7a4e433f fix(media): read ElevenLabs input as UTF-8 (#5469)
* fix(media): read ElevenLabs input as UTF-8

* fix(media): regenerate skill manifest after UTF-8 correction

---------

Co-authored-by: James <james.russo@heygen.com>
2026-10-12 22:56:24 +01:00
路从今夜白 73d89b81c7 fix(media-use): clean up temporary TTS text directories (#5477)
* fix(media-use): clean up temporary TTS text files

* chore(skills): refresh manifest for TTS cleanup
2026-10-12 21:08:50 +00:00
xjh 4120042ca3 fix(skills): reject occlusion timeout before verdict (#5491) 2026-10-12 20:58:52 +00:00
Jerrai e1925d4317 feat(audio): composition master bus (data-fx-chain on the root) (#5420)
* feat(audio): composition master bus (data-fx-chain on the root)

A clip can belong to only one audio group, so there was no way to put one
effect, such as a limiter, across the whole mix. The data-fx-chain on the
top-level composition root now runs once over the sum of every clip and every
group, after each group's own chain and fader. No new attribute: a sub-composition
root's data-fx-chain was never applied to anything and still is not.

Render: when a master chain exists, mixAudioTracks writes a float WAV instead
of AAC, applyAudioFxChain runs the chain over it, and ffmpeg encodes AAC last,
so the chain reads unclipped samples. An unreadable chain is a mix failure, as
for a group. Preview: the transport splices the chain between the clips' sum and
the monitor, re-anchored per play generation and watched for edits.

Only the top-level root is read. No master automation: the root's data-automation
is not read as the bus's. No lint rule: data-fx-chain is valid on clips, groups
and roots, and the one place it does nothing (a sub-composition root) has always
been silent.

— Jerrai

* fix(core): preview routes every audible video through the master bus

A root data-fx-chain is attached only inside the Web Audio graph, but a plainly
audible <video> joined that graph only for its own chain, group, automation or a
volume over 1. So preview limited the music and left the talking head dry, and a
composition whose only audio is video got no master chain at all, while export
sums every audible element into the master. A master chain on the top-level
composition root is now a reason to join, the same as a video's own chain.
Elements under a root with no chain keep native output. Sub-composition roots
are still ignored.

— Jerrai

* fix(audio): master bus review round 2 (temp dir, tests, docs, Studio meter)

- audioMixer: a fatal FX error in the master pass or a group bus left the
  work directory (the full float sum, about 1.4 GB an hour) on disk. The
  final mix now runs in try/finally and the group rethrow removes it first.
- Tests: the "once over the sum" case now uses a limiter then a gain, so a
  per-clip chain (-10.4) and a doubled one (-18) both fail it; a new case
  proves the sum stays float until the chain reads it; the fader case uses
  a group volume of 0.5 so the fader's position is exercised; the work-dir
  removal is covered for both paths.
- Docs: "Three rules exist" (the master-bus lint rule is gone), a bad master
  chain stops the render with audio_processing_failed, a sub-composition
  file opened on its own has its root chain as the master bus, and a seek
  clears a group or master limiter.
- Studio: TimelineElement.masterChain carries the root's data-fx-chain, so
  the program meter shows for a video-only composition whose only audio
  routing is the master chain, as preview now plays it.
- Comment blocks over four lines cut; webAudioTransport.setRate split so
  its CRAP score is under the Fallow threshold.

— Jerrai

* fix(audio): master chain reaches tracks the render adds; honest plain-limiter copy

- Master chain: it was carried on each parsed AudioElement, so a track the
  producer adds itself (appendAutoDetectedVideoAudio, the browser-discovered
  audio in probeStage) never had it, and a composition whose only audio was
  such a track exported with no master chain, no ceiling and no warning. It is
  now a composition property: parseMasterFxChain(html) at compile, carried on
  CompiledComposition and CompositionMetadata, passed to processCompositionAudio
  by runAudioStage (in-process and distributed plan). AudioElement.masterFxChain
  is removed so there is one source. Each forward (compile stage, local
  orchestrator, distributed plan) has a test that fails when it is dropped.
- Plain limiter: it follows the level with no lookahead and Level after is
  applied past the ceiling, so it cannot promise "nothing gets past" or "never
  clips". Studio copy (description, panel text, knob label, summary) and the
  hyperframes-audio skill now say a fast or inter-sample peak can pass it and
  point to the true-peak limiter for a delivery limit. A copy test guards it.

— Jerrai

* fix(producer): keep master bus comments within the ratchet; run the plan test with the ffmpeg tests

Drop three one-line field comments that raised their files' comment share,
and list plan.masterBus.test.ts with the integration tests because plan()
reads the ffmpeg version.

— Jerrai

* chore(ci): repin the producer test-classification source in the reachability map

The reachability map pins the hash of test-classification.mjs, which now
lists one more integration test. The map's producer lane command already
covers every test under src, in both the unit and integration matrix lanes.

— Jerrai
2026-10-11 19:07:00 +00:00
Miguel Ángel 44e5a47e09 fix(cli): media-treatment applies one treatment to every matched image or video (#5443)
* fix(cli): media-treatment applies one treatment to every matched image or video

* fix(cli): media-treatment reports its action from every target and documents the per-element merge

* fix(cli): media-treatment patches each match by its stable id so one write cannot redirect another

* fix(cli): media-treatment sets every match in one parse instead of re-finding elements

* fix(cli): media-treatment refuses media that is not an HTML element, as before

* chore(skills): refresh the media-use manifest hash for the treatment reference edit
2026-10-11 03:45:37 +00:00
Jerrai 3746703ea7 feat(core): truepeak, a lookahead true-peak limiter FX (#5419)
* feat(core): add a lookahead true-peak limiter FX (truepeak)

The existing limiter is an envelope follower with no lookahead and no
inter-sample detection, so a -14 dB ceiling can leave the true peak several dB
over. truepeak estimates the true peak on a 4x polyphase interpolator, takes the
minimum required gain over a lookahead window and smooths it with a boxcar of
the same length, so the gain is already down when the peak leaves the delay
line. Release is exponential and can only lower the gain further; channels
share one gain.

The offline render lengthens the OfflineAudioContext by the chain's latency and
drops the lead-in, so a limited clip stays sample-aligned with its source.
Preview plays the lookahead (about 3.3 ms at the default).

— Jerrai

* docs(audio): truepeak ceiling is a 4x estimate; measure it with an ideal interpolator

The unit test's meter was a 96-tap Kaiser sinc, which rolls off near Nyquist and
reads full-band noise about 0.5 dB low, so the PR reported a 0.3 dB broadband
overshoot. Against an ideal 16x interpolation (zero-padded FFT, cross-checked with
a 16x soxr resample) hot full-band white noise ends 1.2 to 1.7 dB over the ceiling
(median 1.4 over 28 runs of 10 to 30 s). ffmpeg's ebur128 true peak is also a 4x
meter and reads the same files 0.1 dB over.

- The test meter is now the FFT interpolation. Tolerances are what it measures on
  each fixed signal: tones 0.01 dB (they land on the ceiling), the dense program
  0.5 dB (reads 0.445, the old 0.4 passed only because the meter read low), and a
  new full-band noise case at -1, -6 and -14 dBTP with 1.35 dB (reads 1.09 to 1.31).
- audio-effects.mdx and the hyperframes-audio skill say the ceiling is a 4x
  estimate, give the measured overshoot and recommend a 2 dB margin under a hard
  delivery limit.
- skills-manifest.json regenerated.

— Jerrai

* fix(core): truepeak UI copy no longer promises a hard ceiling

The registry description and the rack summary said the true peak is never above
the ceiling. The limiter holds a 4x estimate, and full-band noise can land up to
about 1.7 dB above it, as the docs now say. The summary reads "held near", the
description names the estimate and the overshoot, and the rack blurb says "holds
... to a ceiling". No skill file changed, so the skills manifest is unchanged.

— Jerrai

* fix(core): Studio truepeak copy states the 4x-estimate margin

The ceiling control was labelled "Never exceed" and the module said the mix
"must not clip", so a Studio user never saw that dense full-band content can end
up to about 1.7 dB above the ceiling. The label is now "Target ceiling" with a
hint that names the 4x estimate and the headroom to leave, the blurb says
"toward a target ceiling", the rack summary says peaks are pulled down toward the
value, and the registry hint no longer says "highest true peak allowed". The
audio skill's "hard dBTP ceiling" wording is gone and the skills manifest is
regenerated. A copy test keeps the promise out.

— Jerrai

* fix(core): truepeak clears on a seek, and the review nits (fallow, comments)

- Unexport TRUE_PEAK_OVERSAMPLE, TRUE_PEAK_DETECTOR_DELAY and
  truePeakLookaheadSamples: nothing imports them (Fallow audit).
- A persistent group bus keeps its chain across a seek, so the limiter's
  delay line and held gain carried over. The worklet takes __hfReset, the
  graph exposes FxChainHandle.reset(), and reanchor() calls it.
- The processor-loading harness is shared by the truepeak and worklet tests
  (it was a 12-line clone); the test FFT no longer repeats audioCarve's
  bit-reversal; two test bodies are under the CRAP threshold.
- Comment blocks cut to four lines or fewer (Comments check).
- Docs state the preview delay in ms.

— Jerrai
2026-10-10 18:28:21 +00:00
Miguel Ángel f46033d048 fix(skills): skill scripts run through a symlinked skills dir and agents keep long renders alive (#5396)
* fix(skills): skill scripts run when the skills dir is a symlink

* test(skills): plugin launcher fixture ships the shared main-module helper like an install

* fix(skills): the CLI's cube validator runs through a symlink too, and the guard check is stricter

* docs(skills): long agent-started renders run detached in their own process group

* docs(cli): detached renders start in their own process group, on macOS too

* docs(cli): the detached render recipe never waits on terminal input
2026-10-10 04:54:41 +00:00
Miguel Ángel b20f3f7ad9 fix(cli): check warns instead of failing on a still title card (#5369)
* fix(cli): check passes a still title card whose timeline follows the seek

* fix(cli): check warns on a still frame and fails only when an animation is stuck

* fix(cli): count a seek clock once per sample and point the error at the registered timeline

* fix(cli): judge a finished animation by its own completion state, not its end time

* test(cli): pin a Web Animation paused at its end as finished

* fix(cli): judge a reversed animation's end in its own direction, and read plain timelines

* test(cli): pin a plain timeline held at its end as finished

* test(cli): keep an unfinished plain timeline beside the finished one

* fix(cli): skip a timeline whose time cannot be read instead of calling it stuck

* fix(cli): skip an animation whose time is not a plain number, such as a scroll-driven one

* fix(cli): a partial timeline with plain values no longer stops the browser checks
2026-10-10 00:38:09 +00:00
Abdullah M. Nazif 9e69821b8b docs: clarify async font measurement and timeline registration (#5203)
* docs: clarify async font measurement and timeline registration

* chore: align skill manifest with main before regeneration

* chore: regenerate animation skill manifest
2026-10-09 17:56:30 +00:00
Abdullah M. Nazif fe38e8fe12 docs(cli): explain FFmpeg setup without a package manager (#5237) 2026-10-08 20:58:47 +00:00
Abdullah M. Nazif 320de3973b fix(cli): preserve voice placement when ducking empty transcripts (#5271) 2026-10-08 20:09:19 +00:00
Abdullah M. Nazif 29b901dc60 fix(skills): preserve preset color roles in frame contrast checks (#5195) 2026-10-08 19:32:24 +00:00
Abdullah M. Nazif 710b7f5bcc docs(skills): preserve glyphs in text reveal masks (#5241) 2026-10-08 19:31:56 +00:00
Miguel Ángel 8dae5ef89b fix(core): make grading writes previewable and honor computed properties (#5157)
* fix(core): write only authored hue curves, so a single hue curve passes lint

* fix(cli): apply over a grading saved with empty hue curves instead of refusing it

* fix(core): read grading properties from computed style, and stop advising an inline start

* fix(cli): preserve validation during legacy hue repair

* fix(cli): preview grading writes without apply
2026-10-08 15:21:47 +00:00
Miguel Ángel cf1b04bb71 feat(cli): accept host-managed HeyGen access tokens (#5177)
* fix(auth): accept host-managed HeyGen access tokens

* fix(auth): share environment header validation

* docs(auth): condense credential resolver guidance

* fix(auth): report the account a browser login just signed in

Identify the account from the tokens the login issued, not a fresh credential
lookup that an environment token or API key outranks.

* docs(auth): list the host access token in the CLI credential order

* fix(cli): name a rejected host access token on publish

Publish told a host-token user to log in again, which cannot help while the token outranks the login.

* docs(auth): show the media credential order main resolves

* fix(cli): warn at logout that a host access token still signs commands

One map of environment credential variables, owned by the resolver, now feeds the logout warning and both publish messages.

* docs(auth): scope the media credential order to the audio scripts

* refactor(cli): read every env credential name from the resolver's map
2026-10-08 10:33:57 +00:00
Wenbo ZhuandClaude Opus 5.5 21d14b25b3 feat(media-use): reach HeyGen through a host app's gateway (HEYGEN_API_BASE) (#5181)
* feat(media-use): reach HeyGen through a host app's gateway (HEYGEN_API_BASE)

- The audio engine sends every HeyGen call to $HEYGEN_API_BASE when set, as the heygen CLI does; plain HTTP needs
  $HEYGEN_ALLOW_HTTP=1, the CLI's own rule. A host gateway (that base with its own key) wins over a host OAuth token.
- The skill says: inside a host that gives HeyGen access, no CLI install or sign-in, prefer the host's own HeyGen
  tools, relay a refused call's message as written and do not switch providers on your own.

HyperFrames Desktop sets these variables when a HeyGen API key is saved in its Settings, so media-use's TTS, catalog
search and avatar calls are paid by that key.

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

* chore(media-use): format the edited tables and regenerate the skills manifest

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

* fix(media-use): host access applies only when HEYGEN_API_BASE is set; HeyGen credentials stay on HeyGen hosts

- The "use the host's own tools first / no CLI sign-in" guidance is now conditional on HEYGEN_API_BASE alone, so an
  agent outside such a host (Claude Code with a HeyGen MCP connector, say) keeps today's media-use journey. The
  compressed copies in README, CLAUDE.md, the prompting overview, general-video and the capability menu are back to
  main's wording.
- A base outside heygen.com gets no stored or host OAuth credential, only a key set for it (review on #5181).

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

* fix(media-use): a project's .env cannot set the HeyGen base or allow HTTP

loadEnvFromDir copied HEYGEN_API_BASE from a project file into process.env, and the host gateway path then sent the
person's shell HEYGEN_API_KEY to that host. A host app sets HEYGEN_API_BASE and HEYGEN_ALLOW_HTTP in the environment
it spawns, never in a project file, so the .env loader skips both.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:30:56 +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 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
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 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
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
miga-heygenandMiguel Ángel 6c353d89c8 docs(skills): teach shared cut constants and fill lights for Three.js scenes (#5077)
The three adapter skill had no guidance for camera cuts made inside one
canvas. A visibility window written as its own literal near a cut shows
the next shot's objects for the last frames of the previous shot, a
two-frame pop-in that a still per shot does not reveal. The skill now
says to declare each cut once, read it in both the shot selector and
every visibility window, and snapshot the last frame before each cut.

It also explains why a MeshStandardMaterial lit by a single key light
renders as a flat black cutout when the camera faces that light, and to
add a fill such as a HemisphereLight driven at a fixed ratio to the key.

Signed: Miga

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-05 16:59:59 +00:00
miga-heygenandMiguel Ángel 0c3e244269 docs(skills): fix wrong facts and broken paths from the doctrine audit (#5026)
Section 2 of #5025, each checked against current main:
- studio: sub-compositions.md lives in /hyperframes-core
- music-to-video: bgm.md path is audio/references/bgm.md in /media-use
- remotion-to-hyperframes: when in doubt, return to /hyperframes
- registry: the search-miss report passes the tier that answered
- talking-head-recut: layout files carry bounds only, the storyboard records
  card.zone; the field is contentHints; style varies only inside the chosen
  group
- embedded-captions: name the real transcription fallback
- motion-graphics: drop the nonexistent resolve.mjs block, list maps
- animation, creative, figma, slideshow: resolve bundled paths from the
  installed skill, not the repo root

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-04 12:27:35 -07:00
miga-heygenandMiguel Ángel 173103dfde docs(skills): resolve cross-skill contradictions from the doctrine audit (#5028)
* docs(skills): resolve cross-skill contradictions from the doctrine audit

Section 1 of #5025, each direction taken from the code or the shared
contract:
- async timeline build is supported (runtime rebinds an async-registered
  timeline; lint only flags registering before the build finishes), so
  animation and keyframes now say register after the build completes
- data-track-index is a Studio row, display only; layering is z-index
- the final gate is `check`, which reruns lint
- music-to-video keeps render approval as the one autonomous question,
  per the brief contract
- faceless-explainer and product-launch-video pre-install registry blocks
  before dispatch, like pr-to-video
- talking-head-recut layout files cover landscape and portrait; 4:5 is
  derived
- embedded-captions Cinematic authors cinematic.json; make-cinematic.cjs
  derives plan.json
- audio carve has no static mode; remove the stale static and dynamic text

Left open: the figma usage beacon and the skills-update consent rule.

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

* chore(skills): refresh manifest; treat cinematic.json and safe-zones.json as project files

Both are written into the user's project by the embedded-captions workflow,
so the comment-citation check should not look for them in the repo.

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

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-04 12:14:30 -07:00
Miguel Ángel 2497402b27 feat(media-use): clone, list and delete HeyGen voices, and accept a host OAuth token (#4992)
* feat(media-use): clone, list and delete HeyGen voices, and accept a host OAuth token

heygen-voice.mjs clones a voice from an mp3 or wav file, lists cloned voices by name prefix, and deletes one, printing HeyGen's own error message on any refusal. HEYGEN_ACCESS_TOKEN is now the first credential source and is sent as an OAuth Bearer token.

* fix(media-use): heygen-tts prints HeyGen's own refusal instead of a generic line

* chore(media-use): regenerate the skills manifest for the voice scripts

* fix(media-use): voice list stops on a repeated page, delete accepts an empty reply, plain refusals

* test(media-use): a voice list that pages forever fails at once instead of hanging

* docs(media-use): keep heygen-voice's header to three lines, usage lives in tts.md
2026-10-04 15:07:30 +00:00
WaterrrForever 69a2169bde feat(skills): open Studio before every final render, and pitch the desktop app as the chat way to edit (#4952)
* feat(skills): tell the user about HyperFrames Studio at the final preview and after delivery

/hyperframes gains § 6: mention the preview header's Edit with Framey button when the final
Studio preview opens, and after the delivery render pass on the render's own desktop-app line
in plain words — offer to run `hyperframes open` when the app is installed, else give the
download link. Say nothing when the render prints no such line (a draft, a batch row, a run
inside the app). Builds on the CLI lines added in #4951.

* feat(skills): open studio before every final render, pitch the app as the chat way to edit

Users who only ask for a video never saw Studio: autonomous runs asked "preview
first, or render?" and a "you decide" answer rendered. The final look now opens the
Studio preview in every mode, then asks "render now, or what changes?" — opening it
asks nothing, so it also holds when the user said not to ask.

Section 6 says what is true: the Studio preview already edits and saves; the desktop
app adds editing by chat with Framey. The agent names it "the HyperFrames Studio
desktop app" so it is not mistaken for the preview. general-video, the review loop
and the brief contract carry the same final look.

* fix(skills): call the app the hyperframes desktop app, never studio

* fix(skills): pitch edit with framey only when preview shows it, keep direct renders direct

* fix(skills): mention the button only after the line that ships with it
2026-10-04 12:55:56 +00:00
Miguel Ángel 981bcf4553 fix(media-use): say to fix an unreadable credentials path, not to log in again (#4998)
* fix(media-use): say to fix an unreadable credentials path, not to log in again

* fix(media-use): provider=heygen gives the same fix hint as the auth headers

* test(media-use): restore env in the pickProvider test with a plain if
2026-10-04 10:25:47 +00:00
Miguel Ángel 18a922f433 fix(media-use): a slow download no longer leaves an empty asset that undo brings back (#4989)
* fix(media-use): a slow download no longer leaves an empty asset that undo brings back

* docs(media-use): tell the person an OAuth sign-in makes voice and avatar video free

* docs(media-use): the OAuth free usage line names the monthly allowance

* docs(media-use): the OAuth line says a free allowance
2026-10-04 08:40:46 +00:00
Miguel Ángel 32d2bc6fe0 fix(media-use): a credentials folder means no HeyGen credential, not a crash (#4995)
* fix(media-use): a credentials folder means no HeyGen credential, not a crash

* fix(media-use): keep heygenCredential never-throws for any unreadable credentials path
2026-10-04 08:04:07 +00:00
Miguel Ángel 5f3191bdc5 fix(media-use): skip a .env folder when loading HeyGen credentials (#4988)
* fix(media-use): skip a .env folder when loading HeyGen credentials

heygen-tts.mjs and audio.mjs crashed with EISDIR when a parent folder held
a .env directory instead of a file.

* fix(media-use): still load a .env that is a pipe or a locked folder's file

* fix(media-use): read a .env without checking it first, so it cannot change in between
2026-10-04 07:29:46 +00:00
Miguel Ángel 9031a02cb5 fix(skills): load shared creative guides for motion graphics (#4970)
* fix(skills): load shared creative guides for motion graphics

* fix(skills): separate shared guide context from project outputs
2026-10-03 15:38:45 -07:00
miga-heygenandMiguel Ángel 835e0c16ec fix(media-use): forward TTS speed to Kokoro (#4930)
* fix(media-use): forward TTS speed to Kokoro

synthesizeOne built the `npx hyperframes tts` argv without --speed, so a
request speed (audio_request.json `speed` or audio.mjs --speed) was ignored
on the Kokoro path and every line rendered at 1.0. Forward it when it is not
the default.

Kokoro now enforces the CLI's speed range, so audio.mjs rejects a speed
outside (0, 3] up front instead of dropping every line with an opaque
subprocess exit.

Fixes #4926

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

* fix(media-use): reject zero and non-numeric TTS speed

`Number(x) || 1.0` turned 0 and non-numeric speeds into 1 before the range
check, so they passed silently. The engine now parses the raw request or
--speed value once, with no fallback, and the faceless-explainer,
pr-to-video and product-launch-video adapters forward the raw flag instead
of applying their own copy of the fallback.

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

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-03 03:35:20 +00:00
Miguel Ángel 7348933111 feat(cli): expose harness subscription usage to agents (#4919)
* feat(cli): plan video creation around remaining harness usage

* feat(cli): read Codex and Grok subscription allowances

* test(cli): load usage fixtures through portable file URLs

* test(cli): keep fixture writes on filesystem paths

* refactor(cli): separate usage window and login decisions

* fix(cli): guard parsed credential array entries

* refactor(cli): report harness usage without workflow policy

* fix(cli): preserve usage when subscription tier is null

* test(cli): separate usage fixture code from inputs

* style(cli): format usage transport fixture

* refactor(cli): isolate usage fixture credential checks
2026-10-03 00:25:43 +00:00
miga-heygenandMiguel Angel b31fb02c7e fix(cli): distinguish a skipped browser check from a genuinely clean one (#3901)
* fix(cli): distinguish a skipped browser check from a genuinely clean one

`hyperframes check`'s runtime/layout/motion/contrast JSON sections reported
the exact same ok:true/zero-findings shape whether the browser session
actually ran and found nothing, or never ran at all (a blocking lint error,
a lint-step crash, or an exception thrown before the audit could start all
took the same "empty result" fallback). Only the top-level ok field and an
undocumented layout.duration === 0 tell separated the two cases apart.

Add a skipped boolean to CheckBrowserResult, set at the only two places one
is constructed (false in the real audit result, true in the empty-result
fallback), and mirror it to a new top-level browserSkipped field on
CheckReport. The human-readable report now prints a warning when the
browser never ran; the JSON envelope carries the same signal for callers
that only check individual sections.

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

* docs(cli): document browserSkipped in the check --json envelope

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

* refactor(cli): trim browserSkipped docs and cover the linter-crash skip path

* chore(skills): refresh the hyperframes-cli manifest hash

* refactor(cli): drop the browserSkipped type comments to keep the comment share flat

---------

Co-authored-by: Miguel Angel <miguel.sierra@heygen.com>
2026-10-02 07:32:48 -07:00
miga-heygenandMiguel Ángel 94650480e2 docs(skills): fix unresolved cross-references in skill docs (#3951)
* docs(skills): fix unresolved cross-references in skill docs

Skill reference docs that live one directory below a skill root
(`references/`, `sub-agents/`) pointed at sibling skills with `../`,
which resolves inside the referencing skill instead of `skills/`.
Rewrite those to `../../<skill>/...` so every explicitly relative
path resolves from the file that contains it.

- faceless-explainer, pr-to-video, product-launch-video: story-design,
  visual-design, motion-language, cut-catalog, sub-agents/frame-worker
  now point at ../../hyperframes, ../../hyperframes-animation and
  ../../hyperframes-creative.
- music-to-video/references/planning.md: same fix for
  hyperframes-creative design-spec and frame-presets.
- media-use/audio/references/{bgm,tts}.md: `../SKILL.md` ->
  `../../SKILL.md` (the media-use skill root).
- hyperframes/references/frame-worker-core.md: the examples pointer now
  reads ../../hyperframes-animation/examples/; the cut-catalog pointer
  is written as skill-root shorthand (`references/cut-catalog.md`),
  matching the other paths in that file, since the target is the
  consuming workflow's own copy and has no single file-relative path.
- visual-design.md tables re-padded by oxfmt after the cell widened.

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

* docs(skills): fix the two skill doc cross-references added since the first pass

* chore(skills): regenerate the skills manifest after the rebase

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-02 07:29:25 -07:00
jbernard077andMiguel Ángel 0a04f80bbe feat(skills): motion-blur-streak — per-frame-driven form for elements riding a baked track (#3781)
* feat(skills): motion-blur-streak — per-frame-driven form for elements riding a baked track

Extends rules/motion-blur-streak.md with a Per-Frame-Driven Carve-In: when an element is driven
from a baked track (tracked insert, shake-matched title) there is no tween window to share, so the
envelope becomes a per-frame function of the track delta — velocity-gated text-free silhouette
ghosts (Path B, the default for driven subtrees) or a rotate/blur/counter-rotate filter sandwich
(Path A), streak axis from atan2 of the delta. Adds the Values rows, the constraint, and updates
the rules-index row.

* fix(skills): keep motion-blur-streak trail opacity independent of seek order

The driven ghost trail read the lead opacity written by another tween, so a frame could get a different trail opacity depending on the previous seek. Ghosts now sit inside the positioned, faded track wrapper and take the fade as group opacity. The snippet also names which element owns position, rotation and counter-rotation so the transform writes cannot clobber each other.

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-02 07:16:51 -07:00