mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-04 04:46:59 +00:00
sync/hyperframes-codegen-5739f5dc
235
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5c7f11ab6d |
fix(studio): plain elements keep their place and size when keyframed (#5045)
* test(studio): bench a plain element animated through auto-record and Add keyframe * fix(studio): seed a first tween from the offset a plain move left in CSS translate * test(studio): bench resize then drag on a GSAP tween, root and nested * fix(studio): a resize under auto-record keys size at the playhead, held at all times * fix(studio): keyframe tweens Studio writes end on their last key, so a seek matches playback * test(studio): keep the plain-to-keyframes moves inside the nested frame * fix(studio): a resize moves its anchor from the drag stamp it read at release * fix(studio): an undo shown in place is no longer replaced by an older reload * refactor: split the trailing keyframe trim and drop an unused GSAP reader * test(studio): settle the resize-then-drag bench steps so a two-save resize is one undo * fix(studio): a preview reload waits for an undo shown before its write lands * fix(studio): Add keyframe reads a subtracted calc() translate with its sign * refactor: tidy the keyframe hold, trim and resize code after review * fix(parsers): the keyframe trim leaves looping, chained and relatively placed tweens alone * fix(parsers): a size hold pins only a lone key, so an edit never changes frames left alone * fix(studio): every undo applied in place counts as a preview change, script-only ones too * fix(studio): a resize keys size only when a move or size is keyframed, not a fade * test(studio): a bench step whose save never lands fails the case * fix(parsers): holds and trims change only the tweens an edit touches * test(studio): bank the plain-to-keyframes and resize-then-drag bench cases |
||
|
|
c6478e404c |
fix(studio): undo takes back the first edit of a film whose media is still being copied (#5043)
* fix(studio): undo takes back the first edit of a film whose media is still being copied * fix(studio): the first undo of a film repaints the preview in place * fix(studio): background media copies keep the media ledger and survive a history prune * fix(studio): a busy project history never makes writes wait for its lock again * refactor(studio): name the restore stamping instead of commenting it * perf(studio): undo stamps only a restore side that has no stamps * fix(studio): closing a project history finishes its media copy first * fix(studio): media copies stay resumable until recorded and skip a replaced folder * fix(studio): the history lock wait comes back after a successful open |
||
|
|
db12b3e022 |
fix(studio): place GSAP step lists and duration-less tweens where GSAP plays them (#5024)
* fix(studio): place GSAP step lists and duration-less tweens where GSAP plays them * refactor(studio): drop narrated comments and unnest the extend clamp * fix(studio): match keyframes in time, resolve partial step timing, start readout from prior tween * fix(studio): time step-list keyframes from the parts' last end and format * refactor(studio): trim comments to the package's comment share * fix(parsers): apply timeline defaults as GSAP does to step lists and motion paths * fix(studio): keep GSAP's ease for motion paths with keyframes and read finished tweens * refactor(studio): read a live motion path in its own step * refactor(parsers): leave step-list timing to its one owner and skip timeline defaults there * refactor(studio): move the live motion-path read into its own module * test(parsers): pin an unresolved length for a step list stretched by a variable duration * fix(studio): time keyframes linearly under an outer ease that overshoots or steps * fix(studio): time a converted expression-duration tween from its live tween, and pin no rewrite |
||
|
|
cd82ce044d |
perf(studio): drag and resize any number of selected clips, saved in one write per file (#4937)
* feat(studio): hand-move and hand-resize any number of selected clips * test(studio): bench dragging and resizing every clip, frame cost and drop time * test(studio): give the bench browser launch the run timeout * test(studio): bench request timeline offsets * fix(studio): joining or leaving a multi-clip drag restyles a clip instead of remounting it * fix(studio): restore the linked-selection import and mark the drag bench complex * test(studio): find a clip's track row through the per-clip wrapper * test(studio): resolve track rows through the per-clip wrapper in the remaining cases * perf(studio): write a group edit's GSAP changes as one request per file * test(studio): cover the batched GSAP write and keep the owned-chain cases * test(studio): bench profile of the drag's pointer moves * perf(studio): sync a group edit's root duration once per file, not once per clip * feat(studio): a timeline move registers a still-saving revert so Cmd+Z right after it need not wait * test(studio): the reverted move stays reverted when its save lands * test(studio): bench prints the heaviest call chain on request * perf(studio): preflight a many-clip selection against one index of the live tweens * test(studio): share one timeline across the indexed lookups * test(studio): bench failure output skips resource 404 noise * fix(studio): the live-tween index skips timeline entries that are not objects * fix(studio): the live-tween index skips a null timeline entry * test(studio): bench reports the first drag frame apart from the steady ones * test(studio): bench lists inclusive time per function on request * test(studio): bench reports when the last write of an action landed * test(studio): bench reports medians of save, frame and first-frame times * refactor(studio): move the live-tween index out of gsapRuntimeKeyframes to keep it under the line cap * fix(studio): wire the live-tween index imports and drop an unused test binding * style(studio): format the files CI flagged * fix(studio): keep the bench and the scale helper out of fallow's findings * refactor(studio): one exact tween-index lookup, finish a batch file once, reuse the duration sync * fix(studio): the tween index lists a tween once per key * fix(studio): restore the batch-finish and named-parameter helpers * fix(studio): undo reverts an edit still saving before painting an older predicted step * style(studio): format the batch duration sync * chore(studio): keep the new comments within the block and share limits * test(studio): settle the pending edit even when the undo-order test fails * perf(studio-server): retime every clip in a GSAP batch with one parse and one hold sync * test(studio-server): a batch of clip retimes writes what single requests in turn write * style: format the retime fold and its tests * perf(parsers): clip matchers in one retime share a DOM query cache * perf(parsers): look up every clip id in one DOM walk when retiming * perf(studio): re-resolving the same selected node keeps its source check * perf(studio): a drop re-renders only the selection that actually changed * test(studio): report main-thread task time of a bulk drop * perf(studio): a group drop updates the timeline store once, not once per clip * fix(studio): re-reading index.html keeps sub-composition keyframes * refactor(studio): one owner for clip-retime no-op rules and selection options * fix(studio): reuse a source check only for the same target, and re-check on a new cache version * fix(studio): undo confirms a reverted save only once its history claim counted * refactor: split a retime's application out of the one-parse fold; share test fixtures * perf(studio): drop the per-clip wrapper node so timeline scrolling stays within budget * refactor(studio): name the drag passenger style after the passenger it styles * fix(studio): undo keeps a move undone when its history claim landed before the key * fix(studio): undo a timeline move and its tween shift in one step * refactor(studio): one owner of a timeline gesture's undo window * refactor(studio): move single-clip move and resize into their own hook * fix(studio): a reload or second tab never joins another page's undo step * fix(studio): a delete and its ripple stay one undo step however late the ripple lands * fix(studio-server): date a held undo step by its last write, not by the edit that ends it |
||
|
|
f70dce70ed |
test(studio-server): pin how an older history log takes in the media ledger (#5021)
* test(studio-server): pin how an older history log takes in the media ledger Four guards in the take-in had no test that failed without them: the start already naming the ledger, a change that made it, setting the keepsLedger mark, and saving it. * test(studio-server): put the undoNewest note back on undoNewest |
||
|
|
16871a1796 |
fix: preview and bundled scripts run where and when the author put them (#5014)
* fix(core): bundled local scripts keep their own place, defer and async The bundler joined every local <script src> into one inline script at the first one's position. That dropped defer and async (a deferred main.js ran before a deferred CDN gsap), moved later files ahead of CDN scripts between them, and let one file's error stop the rest. Each local script is now inlined where it stands with its attributes; a deferred or async one keeps its tag with its file as a data: URL. Inline-script merging also no longer folds in nomodule scripts, which dropped the attribute. * fix: preview scripts run after the runtime when the bundler fails, and inlined files are escaped - When the bundler returns nothing or throws, the preview places the runtime where the bundled page does (end of head) instead of after the composition's scripts or not at all; core's placement moves to insertRuntimeTag. - Every HTML-spec JavaScript MIME type counts as a classic script for merging and the web-font gate. - Inlined local files and merged runs are escaped with escapeInlineScriptSource. * fix: deferred local scripts run late without data: URLs, and authored runtime reads survive - Local defer/async scripts are inlined in their own tag with the attribute kept; the font-gate runner (and its old-runtime fallback) runs an inline classic defer script after the classic scripts, in document order, so a page policy that allows inline scripts but not data: still runs them. - stripEmbeddedRuntimeScripts matches the bootstrap attribute and runtime file names on the tag, not runtime words in an authored script's text. - The preview applies its runtime on every path, so an adapter page without one still gets it; the inline escaper writes <!-- as \x3C!--, valid in a unicode regex. * fix: a deferred inline script waits for the deferred CDN script before it to load * fix: an authored script that mentions the runtime marker no longer stops the runtime being added * fix: only inlined deferred files run late, head ones included, and renders skip a CDN runtime link * fix: deferred head files run as before, and one selector owns the deferred-file rule * fix: a local file's legacy <!-- comment no longer breaks its merged run, and escapes keep case * fix: a local file inlined on its own keeps working with a legacy <!-- comment * test(core): a local non-JavaScript file keeps its text as written |
||
|
|
a2f4160899 |
fix(studio-server): undoing a change takes the media ledger back with the film (#5016)
* fix(studio-server): undoing a change takes the media ledger back with the film * fix(studio-server): a log 0.8.123 wrote takes the media ledger in, so the first Undo undoes the edit * fix(studio-server): only a log from before the ledger fix takes it in, once * fix(studio-server): an older log takes in the media ledger only as the sweep would see it A ledger behind a link was read straight into the baseline, then filed as a deletion by the first sweep. The take-in now asks the same scan the sweep uses. The cutout test also pins that other .media files and other manifest.jsonl names stay out of history. |
||
|
|
5cee25efcb |
fix(studio-server): motion paths animate when gsap has a query string or loads deferred (#5007)
* fix(studio-server): motion paths animate when gsap has a query string or loads deferred Studio puts MotionPathPlugin right after the composition gsap tag. A gsap URL with a query string or hash was not recognised, so the plugin went to the head and ran before gsap; a deferred gsap kept the plugin undeferred, with the same result. The tag is now found with a query or hash, and the plugin copies its type and defer, read with the HTML parser the server already uses. * fix(studio-server): finding the gsap tag stays fast on any preview HTML The gsap tag pattern from the previous commit could backtrack polynomially on long runs of gsap-like src text. The preview now walks script tags with index searches, decides gsap core from the URL path, reads attributes case-insensitively, and skips tags inside comments and templates. * refactor(studio-server): the gsap tag scan is small single-purpose steps with no whitespace regexes Splits the scan so each step has one job (walk tags, read one script tag, find its closing tag, skip a comment or template), and skips whitespace by character instead of with \s* patterns, which code scanning reads as polynomial. * fix(studio-server): the gsap tag search reuses the core HTML scanner, so style blocks and odd comments cannot hide gsap The hand-written walk missed raw-text bodies, empty comments and a comment opener inside an attribute value; any of those before the gsap tag sent the plugin to the head again. findStartTags already skips all of them and is what lazyPreviewImages pairs with the parsed document; the preview now does the same. |
||
|
|
399f49104e |
fix(studio): a first animation in a full-document sub-composition plays (#5008)
* fix(studio): a first animation in a full-document sub-composition plays * fix(studio): keep the comment share of the two touched files at their base * fix(studio): a first animation also plays when <html> carries the composition id * fix(studio): a root whose id sits on <html> keeps its first animation in <body> * fix(studio): a composition id on the template tag keeps the first animation inside it |
||
|
|
cad3db47d2 |
fix(studio-server): history never files a hidden file as a change (#4966)
* fix(studio-server): history never files a hidden file as a change * fix(studio-server): an older log never makes history write a hidden file * fix(studio-server): an older log's hidden-file entry no longer stalls Undo * fix(studio-server): an older log's emptied Undo still keeps its target undone * fix(studio-server): an older log keeps every entry, and undoing an emptied one still lands * refactor(studio-server): history's hidden-path rule says itself in names, not comments |
||
|
|
44b5836250 |
fix(core): composition scripts start after web fonts are ready, so renders repeat (#4980)
* fix(core): composition scripts start after web fonts are ready A scene script that measures text (offsetHeight, getBoundingClientRect) ran while the page parsed, often before its web font loaded, so it laid the scene out with fallback metrics. Renders of the same film differed run to run, and preview could differ from export. The compilers now give every body script a type the browser does not run (text/hf-after-fonts, or +module), through one helper in scriptRuns.ts called at the end of the bundler, the producer compile and the sub-composition preview. The Studio motion script is emitted the same way so it still runs after them. The runtime entry owns the wait: on DOMContentLoaded it forces a layout, waits for document.fonts.ready or FONT_WAIT_TIMEOUT_MS (5 s), then re-creates each script in its own place in parser order (classic in order, awaiting src loads; then module and defer scripts in insertion order), boots as before, and calls the DOMContentLoaded and load listeners the scripts added. On timeout the scripts run anyway and runtime_font_wait_timeout names the families still loading. Scene swaps and the composition loader use the same wait. * fix(core): a page whose runtime predates the font gate still runs its scripts A bundle can be served with a runtime loaded from HYPERFRAME_RUNTIME_URL. If that runtime is older than the font gate, nothing would run the deferred composition scripts and the film would be blank. Whenever the marking helper defers scripts, it also puts one small inline script first in the head. At DOMContentLoaded it runs the deferred scripts in parser order itself, without waiting for fonts and with a console warning, unless a runtime has claimed them. The gate's runtime claims them when it loads, before DOMContentLoaded, so the scripts run exactly once either way. * fix(studio-server): motion paths animate in Studio preview when gsap loads in the body The MotionPathPlugin tag Studio inserts after a body gsap script now carries that script's type, so the runtime runs it right after gsap once fonts are ready, instead of before gsap exists. * fix(cli): layout, motion-shot and validate wait for the composition scripts to run The three commands waited on the timeline registry, which exists as soon as the runtime file loads, or on a flat sleep. They now wait for the runtime's render-ready flag through one shared helper, keeping each command's timeout and proceeding as before when it passes. * fix(core): a script that wraps addEventListener keeps its wrapper after its scripts run While composition scripts run, the runtime patches addEventListener to hold load events that already passed. Putting the original back now happens only if the patch is still in place, so a library wrapper installed meanwhile survives. * docs: comments on script timing match scripts that wait for web fonts The early stub's flush and the player's iframe-load handler described page scripts as done by DOMContentLoaded and the runtime as ready before load. Composition scripts now wait for web fonts, so both can come later. Comment-only. * test(core): pin script order after an external script and the scene swap's font wait An inline composition script that follows an external one sees its global, and a scene swap runs the new scene's script only once web fonts are ready. * docs(producer): the early stub comment names what drains late font-gated work * fix(studio): motion paths add without a flash when gsap loads after web fonts Studio preloads MotionPathPlugin on iframe load. A body gsap now runs after web fonts, often after that load, so the preload retries once the runtime reports ready. * test(cli): pin the runtime wait before each command first samples the page * fix(studio): the motion path preload stops waiting once the preview runtime has booted A composition with no global gsap posts ready before the preload runs; the retry listener then never detached and kept each retired preview document alive. It now waits only while the runtime has not booted, and the tests pin the cleanup and the ready filter. * fix(studio): a preview frame that never boots is not kept alive by the motion path preload Only the latest preview waits for the ready message of its runtime; a newer load drops the older wait, so a crashed or missing composition frame can be collected. * style: comments added by the font gate move into names |
||
|
|
48308e3708 |
fix(studio): the server picks an added effect's element id; one client for patch and gsap writes (#4962)
* refactor(studio): the auto-id write posts through the one patch-element client * test(studio): pin the auto-id write's saves, refusals and unsafe targets * test(studio): pin the auto-id write's plain refusal and missing changed flag * test(studio): the auto-id write needs a toast handler and sends the write token * refactor(studio): one request for every GSAP mutation write * fix(studio): a refused id write no longer leaves the id on the previewed element * style(studio): drop the two comments the names already carry * fix(studio): keep minting the id on the element so overlapping adds never share one * fix(studio): an id the file already holds counts as saved, and no project leaves no id * fix(studio): the server picks an added effect's element id, so ids never repeat The browser minted the id from the live preview and rolled it back when the write failed. A reply lost after the id was saved left the file holding an id the preview had dropped, so the next add on a sibling wrote the same id again; and a refused add could strip the id an overlapping saved add had just given the same element. A new patch-element op, ensure-id, keeps the element's id or makes the proposed one unique in the file, and every patch-element reply names the element's id. Studio sets the id on the preview only from that reply, so there is nothing to roll back. nextUniqueId moves to sourceMutation as the one owner (split and duplicate already used it). * test(studio): pin the gsap write token and the refused-write toast * refactor(studio): one unique-id helper on the server, and shorter id comments * style(studio): keep the id helper doc under the line limit and the test-utils header true * fix(studio): an added effect's id stays unique across every composition file * style(studio): keep the files route's comment share at the base * fix(studio): count an id written right after a quote or slash as taken * fix(studio): a quote-ended string no longer hides another file's id |
||
|
|
4e118824b3 |
fix(studio-server): freeze still appears whole, so a slow extraction cannot break its undo (#4982)
* fix(studio-server): freeze still appears whole, so a slow extraction cannot break its undo The freeze route reserved the still's final name with an empty file before ffmpeg ran. When extraction outlasted the history's quiet time, history committed that empty file as an outside change, and Cmd+Z then restored a 0-byte PNG. ffmpeg now writes under an atomic temp name the history skips, and the still is published with createFileAtomically (EEXIST still refuses a taken name). * fix(core): a direct exclusive write that fails midway leaves no partial file Without hard links, createFileAtomically writes the destination directly. A full disk then left a partial file behind (a corrupt upload or freeze still); it is now removed, and a name another writer holds is never touched. Freeze tests: the slow-extraction test leaves a half-written temp file while history commits, and concurrent extractions meet at a barrier instead of a real timer. * fix(core): a quota reported only at close still removes the partial file * test(core): the direct write closes once and keeps its write error when cleanup fails * test(core): a failed direct write closes its descriptor exactly once * refactor(studio-server): freeze names carry what their comments said |
||
|
|
cd1a34ceca |
fix(studio): one owner for gsap CDN URLs, so MotionPathPlugin matches the composition's gsap (#4976)
* fix(studio): load MotionPathPlugin at the composition's own gsap version * fix(core): list the gsap-cdn subpath where core's exports are generated from * fix(core): every default gsap URL comes from the one gsap-cdn owner * fix(core): the gsap a new composition or first tween gets comes from the gsap-cdn owner |
||
|
|
237a984bb8 |
fix(studio): undoing a freeze frame removes the still it made (#4971)
* fix(studio): undoing a freeze frame removes the still it made * fix(studio-server): a failed frame extraction leaves no partial still * fix(studio-server): a freeze claims its still's name before extracting * fix(studio-server): a freeze past the media's end fails instead of saving an empty still * refactor(studio-server): freeze route names say what its comments said |
||
|
|
7dea1e581f |
fix(studio): playhead keyframe edits keep their ease, undo names its edit and never waits on videos (#4889)
* fix(studio): an edit on a GSAP-animated layer writes its value at the playhead into the file's tween
* fix(studio): undo pressed while an edit saves undoes that edit, not one started after the key
* fix(studio): an edit stays saved when the history reply after it cannot be read
* feat(studio): export ColorField and GradientField; a session style commit takes a map
* refactor(studio): name the map form instead of commenting it
* feat(studio): a host can move layers by exact film px, one undo per call
handleDomGroupMoveBy(moves) on useDomEditSession and DomEditValue moves each
selection by its delta in composition px, for Desktop's align and distribute.
It reuses the group drag's own path: a drag member per element (press-time
route, measured offset map), the delta carried to screen px through the
preview frames, then handleGsapAwareGroupPathOffsetCommit, so one shared
coalesce key gives one undo entry and GSAP members take the GSAP write.
A member that can't move refuses the call before anything is written; the
refusal rejects with its reason and shows no Studio toast (the group commit
takes refusalToast: false; telemetry stays). Zero deltas are skipped.
Tests: a pre-offset element and one under a half-scale parent move by exactly
{10,0} and {0,-5} under one group key; a half-scale parent with a 30 deg
rotated ancestor lands within the 0.001 px a translate is written with; a
GSAP member goes through the GSAP write; three refusal kinds write nothing,
leave the boxes where they were, show no toast and leave no gesture mark,
while a canvas group drag still toasts; the session hands the call to the
group commit with refusalToast: false. Each mutation (no frame mapping, no
zero skip, no restore, no teardown, no capability check, toast on refusal)
fails at least one of them.
* fix(studio): an outside change during a drag waits for the drop to show
While a gesture mark is live in the preview, refreshPlayer takes the shadow load instead of
swapping scenes in place. A swap replaced the node under the pointer mid-drag; the shadow load
is then held by the existing gesture hold and, if the drop saved, reloaded fresh by the stale
rule, so this adds no second "released" signal.
Test: an outside change during a live drag on a preview that can swap scenes leaves the dragged
node in place, shows nothing under the pointer, and after the drop loads fresh with the dropped
position. It fails on the parent without the condition ("swapped under the pointer").
Cost case, measured in a real headless preview on the edit-accuracy bench's per-frame teleport
check. Every GSAP instant patch was forced to miss and fall back to reloadPreview during its own
commit; move and rotate of a held element (gsap.set and tl.set holds), px and xPercent
placement, r0 and r30, root and nested, at 100%:
- The gesture mark was live when the fallback asked for the reload, but had always ended by the
time refreshPlayer ran (refreshKey is consumed in an effect after the commit settles). So the
new condition never fired there: root previews offer no scene swap and load shadow on both
builds, and nested previews swap on both builds.
- Worst post-release frame offset, 32 cases on this build and 32 on the parent: 0.00 px over
76-106 frames each (release to settle). No frame was off by more than 0.5 px.
- Sensitivity: a 3 px nudge injected 50 ms after the fallback reads back as 3.00 px.
* fix(studio): an edit no longer reopens the live-updates stream, and thumbnails ask one at a time
* fix(studio): an undo no longer waits behind paused preview videos for a connection
* feat(studio): a host whose API needs same-origin cookies can have Studio send them
* docs(studio-server): a host on same-origin cookies sets the Studio credentials opt-out
* test(studio): an undo is not held up by the preview's paused videos
* fix(studio): a resize inside one gesture writes a mixed tween once, in place, never beside it
* chore(studio): keep the carried fixes inside the comment ratchet; telemetry posts through studioApiFetch
* test(studio): a drag on a mixed tween is one write under the gesture key
* fix(studio): freeze-frame, loudness and peak-map requests go through studioApiFetch too
* fix(studio): an undo with no project open reloads in full instead of skipping nested files
* refactor(studio): drop the soft reload's reparse seeding, the held-element sweep covers it
* test(studio): keep the undo-restore browser test off the network and inside its timeout
* test(studio): nested composition reads expect the cookie-free request studioApiFetch sends
* fix(studio): an edit at the playhead rewrites its tween in place, so later tweens keep their times
* fix(studio): a rotate on a tween that turns another axis holds its angle and never turns early
* fix(studio): plan every group member before the first write so one refusal moves none
A group drag or nudge preflighted each member against the move refusal rule only. The value-at-playhead planner ran later, in the commit loop, after earlier members were already flushed to disk, so a tween with extras, eased keyframes or an unknown ease left the group half moved.
The group preflight now runs the commit's own route and keyframe plan for every member with its real offset, so any refusal refuses the whole group with nothing written and one toast attributed to the refusing member. The plan is not reused in the commit: earlier members' writes change the file under it, so each member re-plans against the fresh tween, from the same planValueEdit.
* chore(studio): the fetch lint rule also catches window.fetch, globalThis.fetch and self.fetch
* docs(studio-server): set the credentials flag before Studio loads; a mounting host injects it
* test(studio): the undo socket test needs six pinned videos under Vite and checks each undo restores
* fix(studio): Studio leaves cookies out of its requests only when served from loopback
* fix(studio): undo takes back the first edit, and the first save never waits on media
* fix(studio): undo of a move whose save failed stays undone and steps back no older edit
* fix(studio): a style set by its JavaScript name saves under its CSS name
* test(studio): the undo socket test holds round 0 to a full restore too
* fix(studio): the host revert keeps returning what shows the edit again
* fix(studio): an entrance rewritten at the playhead still shows its start state before it starts
* fix(studio): undo takes back the edit pending at the key even after a later edit claims
* chore(studio): lift the late-write quarantine now that undo never waits on videos; format
* test(studio): pin an entrance's start render on the written tween, not a happy-dom GSAP run
* revert(studio): leave entrances as on main, immediateRender does not render a keyframed start
This reverts commit
|
||
|
|
70dde41b5c |
perf(studio): keep timeline bulk actions under a second, hand-move at most 3 clips (#4932)
* perf(studio): keep timeline bulk actions under a second, hand-move at most 3 clips Dragging or keyboard-moving a selection of 4 or more clips is refused with a toast pointing at the prompt box; 1 to 3 clips move as before. Root causes of the multi-second waits on large projects: - every selected clip fired its own source-existence probe; they now leave as one request per file - a timeline delete sent one request per clip, each re-parsing the file; it is now one request that parses once - each click on a clip from another file re-fetched GSAP data for every composition; covered files are kept and unchanged files answer 304 - any root index.html write re-captured every composition thumbnail; only a root head edit does now, since a scene page borrows nothing else from it Adds timeline-bulk-actions.mjs, a browser benchmark of select-all, move 3 and delete-all on a real project. * chore(studio): trim the bulk benchmark header to the comment limit * refactor(studio): tidy the bulk-action fixes after review Root head memo is keyed by the root file's content so every subscriber of one write gets one answer, and a thumbnail request can no longer overwrite it. The GSAP read reuses the shared content version as its ETag. * fix(studio): keep a head edit's thumbnail refresh when a later write replaces it File changes coalesce to the newest while a drain is in flight, so the affected-composition lists are merged instead of replaced. A failed GSAP read stays unloaded and is retried, and the GSAP read's ETag is salted with the parser version. * feat(studio): cap hand-resizing a multi-selection at 3 clips like moving Dragging a resize edge with 4 or more clips selected is refused with the same toast as a move; the rule is one constant and one helper shared by pointer move, pointer resize and keyboard pickup. * fix(studio): keep a head edit's thumbnail refresh through a replaced conflict A newer blocked change replaces the held one, so it now inherits the thumbnails the held one owed. * fix(studio): keep a held head edit's thumbnail refresh when a later write saves cleanly The clean-save path and the failed-cleanup path now merge the held blocked change's affected compositions, and the blocked-state merge runs inside the state update so two conflicts in one tick cannot lose the first. * chore(studio): keep useTimelineClipDrag under the 600-line cap * feat(studio): cap by the clips an edit would change, and cut the added comments The 3-clip rule counts the clips a move or trim would write to: a locked clip swept into a move stays put, and a trim some member cannot take edits only the grabbed clip. Comment share is back at or under the base in every changed file. * fix(studio): clear fallow audit and comment length on the multi-select cap * fix(studio): keep bench script comments under the block limit * fix(studio): keep the failed file's payload when another file's snapshot delete fails |
||
|
|
9123628d94 |
fix(studio-server): time history idle limits on one clock so a slow machine cannot flip them (#4924)
* test(cli): time history turns on one injected clock instead of real waits * fix(studio-server): end an idle history window by its own clock, not a bare timer * fix(studio-server): cap a history window's idle timer at Node's timer range |
||
|
|
ada4e0d289 |
fix(studio): show each timeline row its own layer instead of a flat bar (#4917)
* fix(studio): show each timeline row its own layer instead of a grey bar A row got a frame only if it spanned under 92% of the film and its id did not look like a backdrop; every other row drew a plain grey bar. Every element row now gets its frame, and the capture fades the row element's siblings first, so the frame shows that layer and not the whole film. Studio's dev server and the CLI share one capture helper; Studio's own copy is gone. * fix(studio): read timeline rows from any realm so each keeps its selector * refactor(studio-server): always isolate the element in a thumbnail clip * fix(cli): undo a thumbnail's isolation before its page takes the next capture * chore(core): generate the dom-realm export and trim thumbnail clip comments * fix(studio-server): restore sibling opacity after a clip, hash selectors in cache keys * fix(studio-server): keep faded siblings in a page-owned list so restore is exact * feat(studio): draw a text layer row as its own live text instead of a captured picture * refactor(studio): move timeline text reading into its own module * fix(studio): keep text rows readable and current, and leave painted layers as pictures * fix(studio): refresh a canvas-edited text row by its own identity and source file * fix(studio): refresh a canvas-edited text row known only by its data-hf-id |
||
|
|
852782bbb0 |
feat(studio): track completed preview editing gestures (#4916)
* feat(studio): track completed preview editing gestures * fix(studio): preserve gesture receipts and suppress duplicate usage * fix(studio): count semantic keyframe writes once per gesture * fix(studio): isolate recording state during pending saves * fix(studio): count marquee selection changes from owner receipts * fix(studio): retain accurate empty marquee receipts * refactor(studio): simplify recording transitions and selection tests * refactor(studio): share recording timing and marquee fixtures * fix(studio): preserve recording targets and batch attribution * fix(studio): retain per-member telemetry suppression * style(studio): format batch receipt attribution * test(studio): match writer receipts and animation metadata * docs(studio): clarify paused position save rejection * fix(studio): replace telemetry route names with route identity * style(studio): format route identity regression * Use random tab-local route identities in Studio telemetry * Preserve route identities inherited through session storage * Separate route storage validation from token creation |
||
|
|
1168bb740d |
fix(studio-server): a rename rewrites only references that name the renamed path (#4915)
* fix(studio-server): a rename rewrites only references that name the renamed path * fix(studio-server): rename keeps root leads and reads escaped separators * fix(studio-server): rename matches whole references only and cannot backtrack * fix(studio-server): rename leaves paths that exist alone, whatever their names hold * fix(studio-server): rename checks every existing path once and reads escaped spellings * fix(studio-server): rename finds existing longer paths by lookup, on normalised text * fix(studio-server): rename inventories every path and indexes lengths per suffix * fix(studio-server): rename inventory follows linked folders * fix(studio-server): rename classifies its source before moving it |
||
|
|
821e8c8ad0 |
fix(cli): hide console windows on Windows for background/detached spawns (#3996)
* fix(cli): hide console windows on Windows for background/detached spawns Add windowsHide: true to 8 spawn/execFile call sites that were missing it: ffprobe/ffmpeg probes in the producer and engine packages, the telemetry exit-flush and studio dev-server spawns, and the browser-open and background-preview detached launches in the CLI. previewLifecycle.ts's spawnDetachedPreview redirects stdout/stderr to a raw log-file descriptor; libuv only adds Windows' CREATE_NO_WINDOW flag when no stdio slot inherits a raw handle, so windowsHide alone does not fully suppress a console flash there (documented inline) — it's still added for consistency and because it correctly sets the process's show-window state regardless. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): correct windowsHide flash-risk claims and add missing dev-server spawn tests - The previewLifecycle.ts comment (and its mirrored test comment) overstated the residual console-flash risk on the detached background preview spawn: DETACHED_PROCESS already leaves that child without a console regardless of whether CREATE_NO_WINDOW gets applied. - Add the two missing regression tests for preview.ts's runDevMode and runLocalStudioMode spawns, exporting both for test access. - Drop an unused export in audioPadTrim.ts flagged by the repo's dead-code gate; the constant is only ever used within its own file. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(engine): hide console window for the drawElement self-verify PSNR comparison psnr.ts's execFile call runs in-process inside the Studio server's render path (parallelCoordinator.ts's drawElement disk self-verify), so a render started from Studio's UI under a detached preview hits this call from a console-less parent — the same flash case the other windowsHide sites in this PR already cover. psnrFilterAvailability.ts's probe is on the same in-process render path and already had the flag. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): keep only the spawn sites main still misses and leave browser windows visible windowsHide maps to libuv UV_PROCESS_WINDOWS_HIDE, which also starts GUI apps with SW_HIDE, so the detached browser launch must not set it. The background preview child already has no console (DETACHED_PROCESS with inherited log fds), so its flag was a no-op. The telemetry, psnr and audioPadTrim sites shipped on main via #3839 and #4604. * fix(cli): stop the studio dev server when the terminal closes With windowsHide the dev server runs in its own hidden console on Windows, so closing the terminal only signals the wrapper (SIGHUP). Handle it like SIGINT and SIGTERM and kill the process tree. * fix(studio-server): hide the console window for loudness measurement The background preview has no console, so its ffmpeg loudness run opened a visible window on Windows. * refactor(cli): drop the comment on the studio shutdown signals --------- Co-authored-by: Miguel Ángel <miguel.sierra@heygen.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> |
||
|
|
e205207583 |
fix(studio): a text edited in place shows its caret in a scaled preview (#4875)
* fix(studio): a text edited in place shows its caret in a scaled preview * fix(studio): the drawn caret follows a resized preview and stands right at bold words and empty lines * fix(studio-server): a write receipt's replaced version comes only from the bytes it overwrote * fix(studio): the drawn caret stands on the selection inside a transformed canvas * test(studio): unmount the caret tests' roots before clearing the page * test(studio): the caret gate waits for each key to land and retries a text that does not open * test(studio): the caret gate checks what the DOM can tell at a soft wrap, and shows a text that would not open * test(studio): the caret gate opens a text with a double press when click and Enter do not * fix(studio): the drawn caret follows a pan or zoom of the preview * fix(studio): the inline text style bar stands by the selected text inside a transformed canvas * fix(studio): after End at a soft wrap the drawn caret stays at the line's end, as the system caret does * fix(studio): only End keeps the drawn caret at a wrapped line's end, as Chrome's own caret * refactor(studio): share the test mount helper and split the caret's placement for the code gates * docs(studio): trim the drawn caret's comments to the package's share * fix(studio): open inline edit on styled text whose nodes come from another window * refactor(studio): name the realm-safe html check instead of commenting it * docs(studio): shorten the caret fixture comment to the 120-character limit |
||
|
|
824e4100c6 |
feat(studio): waveform strip, clip light, peak marks and video beat source (5/8) (#4817)
* feat(studio): draw a waveform strip under video clips that carry sound An audible video keeps its thumbnails and gains the audio waveform along the bottom 38% of the clip (also when thumbnails are off); muted and silent video are unchanged. The waveform route already decodes video audio; a test now proves it on an mp4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): latch a CLIP light on the master meter at the export ceiling The light turns red when the preview master sample peak reaches -1 dBFS (the export true-peak ceiling) and stays lit until clicked. Mutation-tested threshold and latch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): paint the CLIP light from theme colour tokens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): mark where each clip redlines on its waveform studio-server computes an absolute per-50ms sample-peak map once per media file (native channels, cached by size+mtime next to the waveform cache); the timeline paints red marks where the source at the clip's own volume reaches -1 dBFS and names the loudest point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): let a video with sound be the beat source Studio's isMusicSourceElement accepts an audible, unmuted video for the music track and the beat fallback (lane zoning unchanged); core findMusicAudioSrc accepts <video data-has-audio="true"> without muted; the beats CLI message mentions video. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): decode each peak map once and never offer Duck on a carve source Concurrent peak requests for one file share a single ffmpeg decode; Duck under voice is withheld on a clip another bed already carves against. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): decode the same audio stream the peak map probes The channel probe read stream a:0 while the decode left stream choice to ffmpeg, which picks a later stereo stream over a mono first one; every frame was then read at the wrong width, doubling the bins and taking the other stream's level. Both commands now select a:0, and the cache key moves to peaks-v2 so maps built from the wrong stream are rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): terminate the peak map's ffprobe options before the input Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): reject a peak map whose decode exits nonzero A decode that emits some samples and then fails no longer resolves as a complete map, so /peaks cannot cache a truncated clipping map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9244c47328 |
feat(studio): normalize loudness and duck under voice, with a limiter report (4/8) (#4816)
* feat(studio-server): share loudness measurement with an absolute target and video support normalize-audio moves its measure/plan code into @hyperframes/studio-server/loudness, gains an absolute mode (--lufs, default -16, clamped at +12 dB and -1.5 dBTP) and accepts <video> clips with sound. Adds the @hyperframes/core/audible-video subpath. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio-server): add a measure-only loudness normalize endpoint POST /projects/:id/loudness/normalize measures a clip's played window (audio or video) and answers the -16 LUFS data-volume plan; Studio writes the attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: register the loudness and audible-video subpaths in their descriptors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: format the loudness and audible-video subpath descriptors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): add Normalize loudness and Duck under voice to the clip menu Normalize measures the clip on studio-server and writes data-volume as one undoable edit; Duck toggles a carve against every overlapping voice (audio or video with sound) at the default strength. Both live in clipMenuAudioItems with one hook-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(studio): tighten carveOutput docs to the package comment share Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(producer): report how far the true-peak limiter lowered the export The AAC true-peak pass now returns its attenuation; the render logs "Audio lowered by X.X dB to stay under -1 dBTP" and sets RenderJob.audioLoweredDb, which Studio servers carry in the complete SSE event and the /renders record. Also exports useAudioMetersVisible from @hyperframes/studio for hosts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): scan HTML start tags without a backtracking regex The start-tag regex backtracked quadratically on '<' followed by many spaces (CodeQL js/polynomial-redos). A linear character scan reads the same name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): refuse to normalize a volume-automated clip and honour save outcomes A volume lane owns the clip's gain in preview and render, so writing data-volume changed nothing while the toast claimed success; Normalize now refuses with a clear message. Normalize and Duck also check each quiet-save outcome, stop at the first failed or refused write and report it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(core): drop the duplicate audible-video subpath descriptor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
000ccd426a |
feat(studio): speed presets, voice, look, crop presets and freeze frame in the clip menu (3/8) (#4815)
* feat(core): add gentle ramp-in, ramp-out and slow-mo-middle speed presets Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): add voice, look and crop clip-menu items and clip badges Voice and Look are single-choice submenus writing data-fx-chain and data-color-grading; None clears. Crop opens the canvas crop preset bar. ClipBadges reads the live preview node and follows attribute edits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): drop the unstyled badge classes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): add crop aspect presets to the canvas crop handles Free, 16:9, 9:16, 1:1, 4:5, Reset and Done float above the clip opened from the clip menu. Presets commit a centred inset clip-path; Reset removes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(studio): read the committed crop from the inline clip-path Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio-server): add freeze-frame file mutation Extracts the frame under the playhead with ffmpeg (media time honours data-media-start, playback rate and a rate lane) into assets/freeze/, then in one write splits the video, inserts a 2 s <img> still with the video's box styles, shifts the rest of the track and splits a data-link partner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): import replaceFileAtomically from core atomic-file Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): add freeze frame to the clip menu Freeze frame on a video under the playhead posts the studio-server freeze-frame mutation and records its before/after as one undo step. onFreezeFrame is threaded through the timeline edit callbacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): keep the freeze still inside assets/freeze The clip id from the project HTML went straight into ffmpeg's -y output path, so an id with ../ segments wrote outside the project. The id is now reduced to one safe filename component, the output is pinned inside assets/freeze before extraction, and the still's src is built from that name rather than from a filesystem path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): give every freeze still its own immutable name Two clip ids that sanitise alike (a.b / a_b), ids sharing an 80-character prefix, or a second freeze of one clip at the same playhead all produced the same still path, and ffmpeg's -y replaced the first still's pixels. The name now carries a hash of the raw id and a per-extraction token, the route refuses a name that already exists, and ffmpeg runs with -n so it can never overwrite. The freeze media time reads data-playback-start before data-media-start, as playback does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d4756f597c |
fix(studio): edits on GSAP-animated layers keep their keyframes and file positions (#4869)
* fix(studio): resizing an element GSAP only positions saves its CSS width and height * fix(studio): a resize never saves GSAP's scale mask as the element's CSS * fix(studio): keep the resize size writer inside the file and comment budgets * fix(studio): a GSAP-positioned resize saves its CSS size before the GSAP write renders * fix(studio): undoing a crop leaves a GSAP-animated layer where its file puts it * test(studio): an undo on a GSAP-parsed layer in a two-script file reloads the preview * fix(studio): a move keeps an authored xPercent centring set in the saved file * fix(studio): a drag on a centred element no longer flashes off-centre on release * fix(studio): a font pick saves the edit and its @font-face in one write * fix(studio): a focused combobox, switch or plain-text editor counts as an unsaved edit * test(scripts): pin that a slash after i-- before a quoted slash is division * fix(studio): an inspector edit saved while a preview reload loads stays on screen * fix(studio-server): a font save accepts exactly one @font-face rule and nothing after it * fix(studio-server): a font name or path with braces in quotes still saves its @font-face * fix(studio-server): a CR or form feed in a quoted font name cannot end the @font-face rule * test(studio): bank the 99 edit accuracy cases the GSAP fixes now pass |
||
|
|
30c55694cb | fix(studio): a first animation in a sub-composition file runs from inside its template (#4806) | ||
|
|
c72b735a8c |
fix: a save's temp file no longer reaches Studio as an outside change (#4857)
* fix: a save's temp file no longer reaches Studio as an outside change * refactor: the project file walk and its tests carry no new duplication or complexity * test(studio): the edit accuracy gate judges the two nudge sequences again |
||
|
|
59bbe7f098 |
feat(studio-server): a write receipt names the version the write replaced (#4864)
* feat(studio-server): a write receipt names the version the write replaced * test(studio-server): route receipts carry the version each write replaced * refactor(studio-server): the receipt's from field needs no comment |
||
|
|
a9929b60ee |
fix(studio): the preview serves a same-size edit written right after the last one (#4792)
* fix(studio): the preview serves a same-size edit written right after the last one * chore(studio): keep the comment share and complexity gates green * refactor(studio): keep the project signature doc comment on the function it describes * fix(studio): the preview re-reads files written in the last two seconds, whoever wrote them |
||
|
|
b6c879c4e9 |
fix(studio-server): a folder rename past an unreadable folder or file answers ok (#4860)
* fix(studio-server): a folder rename past an unreadable folder or file answers ok * fix(studio-server): a rename skips only folders and files it may not read * fix(studio-server): the rename's skip helpers say it by name * test(studio-server): the rename tests share one request |
||
|
|
a369708fa5 |
fix(studio): open projects named with a colon and keep preview assets for names with # (#4829)
* fix(studio): open projects whose folder name has a colon * fix(studio): open projects named with a colon or '#' in Studio and its preview * fix(studio): refuse a Windows stream id reached through a session alias One adapter rule, isServableProjectId, now guards the requested id, a session's projectId and the folder listing. The bundler-failure preview places its <base> inside an attributed <head>, and the media metadata cache keys on the encoded URL so ids with ':' can't collide. |
||
|
|
299670e210 |
fix: stop two merge-queue flakes in the history prune and timeline tests (#4786)
* test(studio-server): history prune tests allow a file time a moment past Date.now() * ci: temporary probe for the history prune test flake * fix(studio): timeline scroll-end timer no longer fires after unmount * ci: remove the temporary history prune probe * test(studio): check the virtualizer teardown with a spy and a 200 ms wait |
||
|
|
f1c1f1d69c |
fix(parsers): moving a clip on the timeline moves its inner animations too (#4761)
* fix(parsers): moving a clip on the timeline moves its inner animations too * fix(parsers): a timeline move keeps chained and partly known tweens where they belong * fix(parsers): moving a clip leaves chained outside tweens and template aliases in place * fix(studio): a clip timing edit syncs its animations once, not twice * fix(studio): a timing edit falls back to the server when the SDK wrote no animation change * fix(studio): the SDK sync check reads scripts inside a template too * fix(studio): the SDK sync check finds template scripts through the DOM walker * fix(core): a clip retime writes only its own timed tweens, never outside ones * style(core): format the clip tween import |
||
|
|
567525a8c2 |
fix: readers never see an empty or half-written project file (#4777)
* fix: readers never see an empty or half-written project file * test: pin the create routes and block install to whole-file writes * fix(sdk): a write through a link to a missing file creates that file * fix: saves through a linked folder reach the real file, atomic writes handle long names * test(sdk): check for the stray file, not the folder listing * fix(core): creates work on Windows FAT drives, and links through folders resolve as the system does * fix: agree the helper move in the deletion check, and match temp paths without a path regex * test: check POSIX file modes only off Windows |
||
|
|
481dae1ed9 |
fix: render names a folder composition source instead of crashing (#4613)
* fix(producer): render names a data-composition-src that points at a folder instead of crashing * fix(lint): name a data-composition-src that points at a folder instead of crashing * fix(cli): compositions, keyframes and Studio insertion survive a folder composition source * fix(lint): tell a folder composition source to point at the HTML file inside it * fix: read a composition source through one file handle so its type check and read cannot race * fix(parsers): report a named pipe as a composition source instead of blocking on it * revert(parsers): open composition sources read-only again, which CodeQL reads as no file creation * fix(parsers): open composition sources with a private mode, skip pipes, and treat bad paths as missing |
||
|
|
9e1dbca7a2 |
fix(studio): undo and redo update the preview in place instead of reloading it (#4718)
* fix(studio): undo and redo swap the restored scene in place instead of reloading * fix(studio-server): a preview asked for right after an undo shows the undone state * fix(studio): an undo that restores a page outside every scene still reloads in full * fix(studio-server): a preview built across a write is not cached under the old signature * test(studio): a soft undo keeps the scene hashes so the next edit still swaps |
||
|
|
663b02297d |
feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk (#4701)
* feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk `hyperframes clean` lists leftover render work dirs, debug renders, the history of projects that no longer exist, idle video proxies and extracted video frames, with sizes, and removes them. `--dry-run` removes nothing, `--json` is for agents, `--snapshots` also removes snapshot folders (otherwise listed as reclaimable). It never touches outputs, sources or anything a running render or preview still owns. * docs(cli): say that clean does not search a project's subfolders * fix(cli): clean test compares folder names on Windows and the skills manifest is regenerated * fix(studio-server): the proxy cache sweep removes only files the transcoder named It removed any idle .mp4 or .webm in .transcode-cache, so a user file placed there, or reached through a symlinked cache folder, could go. Proxies are always <sha256>.<ext> and their temp files .tmp-<uuid>-<proxy name>; nothing else is swept. * style(producer): keep a renderDirOwner doc comment within 120 columns |
||
|
|
498643bea4 |
fix(studio-server): the server loads where sharp's native binary is missing (#4654)
* fix(studio-server): the server loads where sharp's native binary is missing * fix(studio-server): a missing sharp warns once and is checked before the image is read |
||
|
|
381fb0fd9a |
fix(studio-server): undo keeps an outside change even when Studio's history claim arrives late (#4631)
* test(studio-server): a write landing between Studio's read and its patch survives undo * test(studio-server): the outside write also survives a claim that arrives after receipts expire * fix(studio-server): undo keeps an outside change even when Studio's history claim arrives late * fix(studio-server): history forgets replaced bytes on delete and frees unused bytes past budget Also recreates the history folder before storing replaced bytes, hears writes inside the queued start, and pins the agent-turn claim that arrives after the receipt expired. |
||
|
|
52154bddb5 |
fix(studio-server): answer 413/415 instead of reading huge or binary files as text (#4677)
* fix(studio-server): answer 413/415 instead of reading huge or binary files as text * test(studio-server): prove the 413 path never reads the file via peak RSS |
||
|
|
e251d4c1de |
perf(studio-server): a host can import id stamping, file walking and history without the server (#4644)
* perf(studio-server): light entries for safe paths, hf-id stamping and project history * docs(studio-server): list the light entries a host can import |
||
|
|
cbcadbfbd0 |
fix(studio-server): uncopied HEVC clips no longer stall Studio while their preview copies are made (#4580)
* fix(studio-server): a cold proxy copy answers 202 at once and the preview retries until it lands * fix(studio): timeline filmstrips wait for a clip's proxy copy instead of failing on the 202 * fix(studio-server): a copy that cannot be made reports its failure instead of answering 202 forever * test(studio-server): pin that a full queue is not remembered and a reset probe stops waiting * test(studio-server): pin that a failure outside ffmpeg is remembered too * style(studio-server): keep comment share at base in the transcoder and decoder * fix(core): a clip's proxy URL goes on the video only once the copy is served, so every reader of it works * fix(player): the parent media mirror follows a clip's src, so it plays the preview copy once it lands * perf(player): the mirror re-points a clip only when the parent takes over playback * style(core): shorten the sync-state comment in the proxy swap * fix(studio): the timeline follows a clip re-pointed at its preview copy * fix(core): a failed readiness check is retried instead of leaving a clip on its original * fix(studio-server): a composition card rendered while preview copies are made is not cached * fix(core): frame captures wait for a pending preview copy as long as for a loading video * fix(studio-server): the preview-copy check uses the shared realpath from main * fix(player): re-pointing a clip onto another clip's file no longer plays that file twice * fix(player): a clip that replaces another on the same file keeps its sound |
||
|
|
5257dd581f | fix(studio): tone-map HDR preview proxies with the render tone map (#4606) | ||
|
|
addc72d761 |
fix(studio-server): a request that fails after its project folder vanished says the folder is gone (#4617)
* fix(studio-server): a save never recreates a project folder renamed away * style(cli): wrap imports after merging main * fix(studio-server): proxy and upload answer that the project folder is gone instead of 502 or an empty 201 * test(studio-server): rename the project folder while an upload is being read * fix(studio-server): a proxy or backup answers that a vanished project folder is gone * test(studio-server): clean up the renamed folder after the proxy test * refactor(studio-server): drop an unused re-export and share the folder-gone test setup * fix(studio-server): any request failing after its project folder vanished says the folder is gone * fix(studio-server): the folder-gone answer works on every host and drops the failed route's headers * fix(studio-server): the folder-gone 404 keeps the host's own headers * fix(core): no folder outside a project is created once the project folder is gone * test(studio-server): a rename that makes the codec probe fail answers that the folder is gone * test(studio-server): a refused render leaves no outside renders folder behind * docs(core): wrap the mkdirWithinProject comment under the line limit * refactor(studio-server): let the folder-gone middleware's names carry what its comments said * fix(studio-server): say the folder is gone when a render names a composition * fix(studio-server): check renders against the folder path and share one folder-gone answer * test(studio-server): pass the project folder to the preview document store |
||
|
|
26bfd38136 |
fix(studio-server): a save never recreates a project folder renamed away (#4602)
* fix(studio-server): a save never recreates a project folder renamed away * style(cli): wrap imports after merging main * fix(studio-server): proxy and upload answer that the project folder is gone instead of 502 or an empty 201 * test(studio-server): rename the project folder while an upload is being read * fix(studio-server): a proxy or backup answers that a vanished project folder is gone * test(studio-server): clean up the renamed folder after the proxy test * refactor(studio-server): drop an unused re-export and share the folder-gone test setup * fix(core): no folder outside a project is created once the project folder is gone * test(studio-server): a refused render leaves no outside renders folder behind * docs(core): wrap the mkdirWithinProject comment under the line limit * test(studio-server): pass the project folder to the preview document store |
||
|
|
34a66744f2 | fix(studio-server): a cached-only thumbnail miss answers 204, so the page logs no error (#4652) |