mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-09 20:46:13 +00:00
sync/hyperframes-codegen-ba6ef2a1
462
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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 |
||
|
|
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) |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
4120042ca3 | fix(skills): reject occlusion timeout before verdict (#5491) | ||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
fe38e8fe12 | docs(cli): explain FFmpeg setup without a package manager (#5237) | ||
|
|
320de3973b | fix(cli): preserve voice placement when ducking empty transcripts (#5271) | ||
|
|
29b901dc60 | fix(skills): preserve preset color roles in frame contrast checks (#5195) | ||
|
|
710b7f5bcc | docs(skills): preserve glyphs in text reveal masks (#5241) | ||
|
|
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 |
||
|
|
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 |
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
6f799aaa41 |
docs: align infinite-repeat guidance with bounded repeat: -1 lint (#3795)
Since v0.7.71 (#2763), gsap_infinite_repeat demotes to a warning when the root declares a finite data-duration: deterministic seeking and export clip to that explicit window. Without one it remains a hard error (unbounded timeline duration breaks render planning). Update every doc site that still teaches the old absolute ban — the core determinism contract, the animation adapter notes, and the design-guide tables/checklists — to state the bounded allowance and keep the finite-count formula for loops that must end before the composition does. skills/hyperframes-animation/rules/ambient-glow-bloom.md is intentionally untouched: its shimmer must stop before the composition ends, which is exactly the case where a computed finite count is still required. Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> |
||
|
|
f1b79d8e7b |
fix(media-use): honour HYPERFRAMES_PYTHON in the audio engine (#4614) (#4723)
* fix(media-use): honour HYPERFRAMES_PYTHON in the audio engine (#4614) resolvePythonCommand() only probed python3/python on PATH, so on PEP 668 systems set up with a venv via HYPERFRAMES_PYTHON (the documented setup), doctor reported MusicGen installed while BGM was silently skipped. Check HYPERFRAMES_PYTHON first, as the CLI's findPython() does, and fall through to the PATH probe when the override doesn't run. * fix(media-use): require a Python 3 version from the interpreter probe Address review on #4723: - The probe accepted any executable that exits 0, so a Python 2 HYPERFRAMES_PYTHON (or python on PATH) was taken while the CLI's validatePythonOverride() rejects it. Require "Python 3" in the --version output, reading stderr too since Python 2 prints there. - Regenerate skills-manifest.json for the media-use change. * chore(skills): regenerate manifest without local __pycache__ * chore(media-use): trim python resolver comments to the comment limit * style(media-use): format the python probe test --------- Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com> |
||
|
|
70900216f0 |
feat(studio): audio gain on G, speed in clip names, additive selection and partner clamp (8/8) (#4821)
* feat(studio): show clip speed in the clip name as [150%] or [ramp] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): one fx badge per clip replaces the per-tool and link badges Grey with no effects, white with any. The tooltip lists look, voice, other fx, crop and ducking; a click opens the clip menu. The volume badge stays when volume is off 100%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): premiere-style audio gain dialog on G Set Gain to, Adjust Gain by, Normalize Max Peak to, Normalize All Peaks to and Normalize loudness to -16 LUFS, with the selection's peak amplitude read from the peak map. Writes data-volume on every selected clip with sound as one undo step. The clip menu's Normalize loudness becomes Audio Gain... (G). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): use a theme blue for the audio gain OK button Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep shortcuts out of the audio gain modal; refuse while recording Keys pressed inside an aria-modal dialog no longer reach the global hotkeys, so Delete on the focused OK button cannot delete the clips the dialog edits. The multi-clip write refuses during a recording like the single-clip one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): offer link for any unlinked video and audio pair Link no longer requires the same file and identical timing, matching Premiere. Same-file pairs get a sync origin so a trimmed or moved audio shows its offset; different files link without one. Merge back is disabled until the pair is in sync. Linked moves keep the offset, and a trim only carries to a partner whose edge is at the same time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): shift and cmd-click add timeline clips to the selection A modifier click on a clip replaced the selection, so a video and an audio could never both be selected and Link never appeared. Shift, Cmd and Ctrl now toggle the clip (and its link partners unless Alt) in the selection; Shift no longer turns a clip click into a time-range popover. Right-clicking a lone unlinked video or audio offers Link when exactly one opposite-kind clip from the same file sits on another track. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep audio inside its partner video and make cmd-click add in every host A modifier click passed the toggle as an argument to an optional onSelectElement call, so hosts without that handler (desktop) never ran it. An audio clip linked to a video, or sharing its sync origin, now stays inside that video's span while dragged or trimmed, Alt edits included, unless the video moves or trims in the same gesture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): hold the drag actor at the partner-video clamp The dragged audio's ghost followed the pointer past the video's end while the landing stayed clamped; it now stops where the clip will land. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): preview a look on hover in the clip menu before applying it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(studio): trim comments the clip-link names already carry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): unlinking clears the timeline selection After Unlink both clips stayed selected, so the next edge trim ran the multi-select group resize and the former partner still followed. Unlink from the menu, the shortcut or an Alt edit now leaves nothing selected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk): keep a linked partner's end when a trim changes the grabbed duration Copying the grabbed clip's duration to a partner that starts later pushed the partner's end past the clip both ended on. The partner's duration is now the shared new end minus its own start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep every moving detached audio inside its video, after placement too The containment clamp ran before collision placement and only for the grabbed clip, so a drop pushed past an obstacle, or audio carried along in a multi-selection, could land outside its video. The allowed shift is now the intersection over every moving audio, and a placement outside it is rejected. Link partners are matched within their source file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): carry the normalize refusal and save outcomes into the gain dialog Normalize moved from the clip menu into the Audio Gain dialog here, so the volume-lane refusal and the saved/refused/failed checks go with it: a lane owned clip is refused, and a failed single or multi-clip save rejects instead of reporting success. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(studio): trim comments the gain and multiselect layer added Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(sdk): split the linked partner's duration out of partnerTiming Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk): refuse a linked trim whose new end would cross the partner's start With linked video [2,8] and audio [3,8], setTiming(video, {duration: 0.5}) computed a partner duration of -0.5, skipped it, and committed the video alone, leaving the audio outside its video. The partner edits are now planned before any mutation; a non-positive partner duration throws ("Linked audio would start after the new end — unlink or trim the audio first.") and can() reports E_LINKED_PARTNER_CROSSED, so nothing is written and the undo stack is untouched. In Studio the refusal fails the cutover (never declines into the legacy path) and the timeline resize toast shows the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk): key a linked edit's grabbed baselines by composition scope planLinkedTiming stored each target's timing under its raw data-link id, while linkedPartnerIds scopes link ids to their host composition. One setTiming naming a root video and a nested video that both use lk-1 kept only the nested baseline, so the root audio was skipped and left past its trimmed video. Baselines are now keyed by host scope and link id, the same rule linkedPartnerIds uses, for both the partner trim and the crossing refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(sdk): run the two-scope grabbed-baseline cases on an inline composition too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
45b8e171e2 |
feat(studio): sync origin, out-of-sync repair, linked selection toggle and grouped clip menu (7/8) (#4820)
* feat(studio): order the clip menu into time, sound, picture and delete groups Split and Freeze frame; Normalize, Voice, link items and Duck; Look and Crop; then Delete beside Delete this clip only. Each item file renders its pieces separately, empty groups collapse, and the viewport clamp uses the measured menu height instead of a row-count estimate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): mint freeze-frame right-half links with the shared lk-N helper Freeze frame now calls core relinkSplitHalves, so split halves and freeze halves share one link-id convention instead of L -> L-2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): apply speed edits to every data-link member in one undo step The inspector speed slider, rate lane edits and ramp presets on a linked clip now commit once, writing the selection and, via expandToLinkedMembers, each partner from its own current value. Unlinked clips keep their usual path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(studio): drop the audio parent tick in favour of the data-link chain badge A linked clip now shows one link indicator: the chain badge driven by data-link. The waveform no longer draws its 1px parent tick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): follow late clip-menu rows and target link partners like S2 The menu height follows a ResizeObserver so rows that read the live preview after mount still clamp inside the viewport. Speed fan-out builds partner targets with buildPatchTarget, matching the link edit plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): show the sound strip and audio menu items without studio's shell The audible-video waveform strip drops AudioWaveform's 16px label band, which left a 0px canvas on short lanes. Normalize loudness and Duck under voice fall back to the timeline session's project, the live preview iframe store and a new onNotice edit callback when no StudioShellProvider is mounted. usePreviewIframeStore is exported so a host can publish its preview. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): show duck under voice only when a voice overlaps The item now appears only when an overlapping voice exists or the bed is already ducked. Its check mark moves to the right edge so unchecked rows align with siblings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): name the partner in the clip menu link and unlink items Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): clip menu stops offering unlink once the clips are unlinked The timeline skipped store updates when only data-link changed, so the menu kept reading the stale link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(core): sync origin, offset math and move/slip into sync for linked pairs data-sync-origin survives Unlink; split gives right halves a fresh origin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): split re-mints the sync origin of unlinked pairs too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): detach, link and keep-sound cutout write a sync origin that unlink keeps TimelineElement.syncOrigin is read on both parse paths; merge back drops it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(studio): expect sync-origin on keep-sound cutout write Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): linked selection toggle in the timeline toolbar, on by default Off: a click selects one clip; drag, trim, razor, speed and delete stay on it (delete unlinks the survivor). Alt-click still selects one half when on. Right-click holds the out-of-sync indicator preference. Both persist in UI prefs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): link groups share a label colour; out-of-sync badges with move and slip Red badge on both halves of a sync-origin pair that drifted (frames, s:ff past a second); hidden when rates differ or the preference is off. The whole badge opens Move / Slip into Sync; each edits that clip alone in one undo step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): take link label colours from theme tokens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(sdk): syncOffset, moveIntoSync and slipIntoSync for sync-origin pairs Core gains findSyncPartner, the DOM pairing shared by the SDK and the CLI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cli): timeline rows report syncOffsetFrames and print out-of-sync Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cli): the timeline row fixture carries syncOffsetFrames Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): data-sync-origin in hyperframes-core Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(core,sdk): keep a one-sided cut's sync origin and slip the in-point playback reads relinkSplitHalves reminted the right half's sync origin even when only one member of an unlinked source pair was cut, so the uncut partner kept the old origin and the new half lost its badge. The origin is now reminted only when both the video and the audio were cut. Slip into Sync wrote data-media-start whenever it existed, but playback reads data-playback-start first, so a clip carrying both never moved. The link timing reader now uses playback's precedence and the slip writes the attribute that wins; lint compares the same field. A sync partner is also looked up only in the clip's own composition (core) or source file (Studio). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): flip the out-of-sync menu above the pointer near the bottom edge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: trim comments the sync-origin layer added Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(core): split the scoped candidate lookup out of findSyncPartner Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(core,studio): find sync partners inside the link scope findSyncPartner and Studio's syncPartnerOf now scope by the same rule as link groups: the nearest data-composition-id or data-composition-file ancestor, plus the row's compositionScope in Studio. An origin reused in a file host or an inline composition no longer pairs across it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0559be0b13 |
feat(studio): link a video and its audio with data-link (6/8) (#4818)
* feat(core): add data-link media link helpers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(lint): warn on out-of-sync and orphaned linked clips Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): select, drag and trim linked clips together Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): detach, merge, link and unlink source transforms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): link-aware split, delete, alt-edit unlink and link edit actions Split and delete fan out to link partners. Alt drag/trim edits one member and unlinks it. onLinkEdit runs unlink, link, detach and merge as one undo step each. Single-clip timing edits of a linked clip skip the SDK path so its linked default cannot move the partner behind the store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio-server): give split right halves of a linked pair their own link Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): clip menu detach, unlink, link, merge back and delete-one items Items live in clipMenuLinkItems.tsx with a one-line hook in ClipContextMenu; the same resolver drives the Cmd+L, Opt+Shift+D and Opt+Delete shortcuts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(sdk): setTiming applies to data-link partners by default { linked: false } edits one member and unlinks it, matching Studio's Alt-edit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): document data-link linked clips in hyperframes-core Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(studio): keep comment share at or below base in link plumbing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(studio): audio pill link follows data-link, not a shared file name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): alt-click selects one linked member even inside the pair selection Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(lint): drop the rule-list comment for the linked clip rules Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk,lint): keep a link group inside its own composition Link ids are minted per file, so lk-1 is normal in the root and in a child composition. The SDK's linked-timing query descended into nested hosts and addressed every match with the initiating scope, so moving a root clip moved the child's pair too; lint grouped them into one drifting group. Both now group only members of the same composition scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): match linked media by full asset path, scope links, keep hidden sound silent Merge and Link compared only a lowercased basename, so assets/one/talk.mp4 and assets/two/talk.mp4 enabled a destructive Merge. Clips now compare the src resolved against each clip's own source file, and Merge re-checks the two srcs in the source before it runs. Link membership in the timeline (linked members, gesture expansion, unlink orphans) is scoped to a source file. Detach and the keep-sound cutout carry a hidden video's data-hidden to its audio, and Merge refuses when only one member is hidden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(studio): move the timeline move-edits handler into its adapter App.tsx sat at 600 lines on this layer and 602 once merged with main. useTimelineMoveEditsHandler now lives beside persistTimelineMoveEditsAtomically, same callback and dependencies, bringing App.tsx to 592. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk,studio,lint): keep link groups inside inline compositions A link group now stays inside the nearest data-composition-id or data-composition-file ancestor. The SDK used to recognise only file hosts, and Studio compared only sourceFile, so an inline child composition that reused a link id was edited together with the root group. core/media-link linkScopeOf is the shared rule for the SDK and Studio. Lint cannot import core, so it uses the same two attributes directly. Studio rows record their compositionScope, and link and merge refuse pairs that cross it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk): refuse a link edit whose partner address names another clip A partner addressed in the caller's scope can resolve to a different element when an inline composition reuses a root clip's data-hf-id. setTiming now checks that each partner's address resolves back to that partner and throws before mutating when it does not, instead of editing the root clip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): a remote media url keeps its query when matching an asset Two different videos at one remote path with different queries were treated as the same file, so Merge could delete the audio and use the wrong video's sound. Project-local srcs still drop query and hash. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(studio): keep a remote url's query exactly as written when matching assets Decoding the whole url folded %26 into & so two different media urls matched. Decode only the path; the query is compared as authored. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(studio): compare a remote url as serialized, decode only local paths A remote pathname with an encoded reserved character (%3F) matched the same path with a literal ?, though they can return different media. Remote urls keep origin, path and query exactly; local srcs are still decoded and stripped of query and hash. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
37f30b1d06 |
feat: keep a video's sound on the clip by default (1/8) (#4813)
* feat(cli): one owner for init media placeholders, audio stays on the video Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cli): init keeps a video's sound on the clip; silent files scaffold muted Supersedes the patchVideoSrc changes in #3977 and #3942 and carries their tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: miga-heygen <miga-heygen@users.noreply.github.com> Co-Authored-By: kapelame <kapelame@users.noreply.github.com> * feat(registry): example templates keep the a-roll's sound on the video Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(lint): video audio rules recommend keeping sound on the video Update video_missing_muted and video_audio_double_source message and fixHint text to recommend the new default: keep audio on the video with data-has-audio="true" (not muted). Tests verify the new wording is in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(parsers): a video is either audible (data-has-audio) or muted, never both Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): follow the compiler's audio rule in the audio-track toggle Confirm before background removal drops a video's sound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): hyperframes-core teaches sound on the video by default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): workflows and scaffold guidance keep a video's sound on the clip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: footage with sound keeps it on the video clip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): init from-file a-roll is timed, muted strip is attribute-exact The from-file a-roll video gets data-start="0" so a scaffold lints clean, and the orphaned audio-only slot comment is dropped. setVideoAudio now walks the tag attribute by attribute, so a class value containing the word muted survives. The ffprobe-missing warning says the video is assumed to have sound and how to switch to muted, and DEFAULT_META notes the failure surfaces at render until lint support lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep has-audio in step with the muted toggle, test cutout confirm Muting a video now clears has-audio and unmuting sets it to true, in both the Design and flat media panels, matching the Has audio track toggle. Audio elements are unchanged. Adds coverage for both panels plus the cutout window.confirm guard: declined leaves src alone, accepted swaps src and mutes, a silent video never prompts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(parsers): updateElementInHtml hasAudio only applies to videos, true or false An audio element or an undefined hasAudio no longer picks up a muted attribute; only an explicit false on a video writes muted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: video_missing_muted rule text, silent-footage notes, consistent audible examples lint.mdx and cli.mdx describe the current video_missing_muted rule instead of the retired unmuted-video wording. The editing recipes say how to adapt for silent footage and require the outgoing clip's sound to end at a J-cut. The Remotion media example shows the audible mapping consistently, data-attributes states that has-audio is required unless muted, and the hairline frame note no longer claims the video is muted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): encode init media filenames and probe --audio duration Function replacers stop dollar-sign pattern expansion in filenames; src is percent-encoded. --audio now passes the probed track length as the composition duration in both init paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(lint): audio-state-aware media rules and time-aware double-source check video_missing_muted honors any explicit data-has-audio. video_audio_double_source only considers audible videos and only fires when the same-src clips overlap in time. duplicate_audio_track now covers audible videos. audio_file_without_element no longer claims silence when an audible video carries the sound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): audio-stream check for remotion ports, followable J-cut recipe Remotion Video maps to data-has-audio only when the file has an audio stream. The J/L-cut recipe now shows both shots with muted pictures and separate audio. embedded-captions removes the audio.mp3 scratch file it extracts. skills-manifest.json regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(lint): trim comments to satisfy the comment ratchet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): atomic muted edits, cutout keeps sound on a linked audio Muted and Has audio track commit one batched write (one persist, one undo entry) and remove data-has-audio instead of writing an empty marker. Unmuting a video probes its src: audible -> true, silent -> false, probe failed -> absent. Remove background on an audible video inserts a linked sibling <audio> with the original sound in the same write, replacing the drop-the-sound confirm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(producer): skip hidden videos when auto-adding video audio A video hidden in Studio is dropped by the mixer, so its src never reached existingAudioSrcs and the render re-added it at full volume. The video parser now records the shared hidden predicate and the auto-detect step skips hidden videos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep useDomEditSession under the 600-line cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: miga-heygen <miga-heygen@users.noreply.github.com> Co-authored-by: kapelame <kapelame@users.noreply.github.com> |
||
|
|
2195db5e1d |
fix(media-use): never replace a voice, music or sound file the person put in the project (#4665)
* fix(media-use): record agent-made voice, music and sound effects in the project's media manifest * fix(media-use): record in place only when --source says the agent made the file * test(media-use): a rewritten agent file keeps one manifest record * fix(media-use): leave a person's own sound effect unlabelled and mark locally made music generated * refactor(media-use): record agent audio in-process through the shared manifest library * style(media-use): format the in-process recording and refresh the skills manifest * chore(skills): refresh the skills manifest for the media-use changes * fix(media-use): report a broken ffprobe setting plainly and document the project sfx source * style(skills): format the pipeline manifest assertion * chore(skills): refresh the skills manifest * fix(media-use): never replace a voice, music or sound file the person put in the project * style(media-use): format the keep-person-files change * chore(skills): refresh the skills manifest * fix(media-use): a regenerated file gets a fresh record, and lookups skip the replaced one * test(media-use): a replaced record no longer answers a prompt lookup in the CLI * chore(skills): refresh the skills manifest * chore(skills): refresh the skills manifest from a clean tree * fix(media-use): never give a recorded file the id of a download in flight * chore(skills): refresh the media-use manifest hash * fix(media-use): say when audio lands on a new name, and never let two lines share one * style(media-use): format the keep-files change * chore(skills): refresh the media-use manifest hash * fix(media-use): keep two sound effects off one file when a name is taken * refactor(media-use): drop a set the one-at-a-time downloads never needed * chore(skills): refresh the media-use manifest hash * fix(media-use): keep two sound effects off one recorded file on a rerun * style(media-use): format the rerun test * chore(skills): refresh the media-use manifest hash * fix(media-use): list only a file's newest record in candidates and the media index * revert(media-use): leave promote as it was, nothing calls it * test(media-use): give the index test's two assets their own files * chore(skills): refresh the media-use manifest hash * chore(skills): regenerate the manifest after merging the base branch * fix(core): a figma import lists each media file once, like media-use |