Commit Graph
4935 Commits
Author SHA1 Message Date
Miguel Ángel 6b59004de2 feat(studio): one timeline tool picker with select leftward and rightward (#4714)
* feat(studio): one timeline tool picker with select leftward and rightward

The Selection and Razor buttons become one button showing the active tool, with a menu:
Select (V), Split (B), Select leftward ([) and Select rightward (]). Leftward and rightward
select every clip, on every track, that starts before or at/after the playhead.

* test(studio): toolbar order and label follow the tool picker

* fix(studio): select leftward and rightward use the live playhead and the usual selection resets

* style(studio): keep playerStore's comment share at its base
2026-09-29 01:21:14 +00:00
Miguel Ángel 00a907d6b3 feat(studio): a host can hide the timeline's keyframe lanes and diamonds (#4715)
* feat(studio): a host can hide the timeline's keyframe lanes and diamonds

* refactor(studio): read the timeline's keyframe data through one gate

* fix(studio): hidden keyframes keep the track names in the timeline header

* test(studio): check diamonds on a collapsed clip and lanes on a keyframed one

* style(studio): format the keyframes switch test
2026-09-29 01:21:05 +00:00
Miguel Ángel 82ae73a031 chore: release v0.8.87 (#4717) v0.8.87 2026-09-28 18:02:10 -07:00
Miguel Ángel 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
2026-09-29 00:43:42 +00:00
Miguel Ángel cc792d4a8a feat(studio): export the canvas edit overlay so a host can mount its handles (#4708) 2026-09-29 00:43:32 +00:00
Miguel Ángel e07ea1fc70 perf(cli): load each command's code only when that command runs (#4656)
* perf(cli): load each command's code only when that command runs

* fix(cli): honest bench percentiles, and no unused registry entry in the split build

* fix(cli): the startup bench is a package script and stops on a failed run

* style(cli): format the bench's failed-run check

* fix(cli): a notice module that fails to load cannot fail the command
2026-09-29 00:07:28 +00:00
Miguel Ángel e25bc3a806 fix(media-use): record agent-made voice, music and sound effects in the media manifest (#4648)
* 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): 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): 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

* fix(core): a figma import lists each media file once, like media-use
2026-09-29 00:07:16 +00:00
Miguel Ángel 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
2026-09-28 23:48:24 +00:00
Miguel Ángel 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.
2026-09-28 23:48:13 +00:00
Miguel Ángel 7e0711505a fix(studio): hide handles Studio would refuse to commit (#4666)
* fix(studio): hide handles Studio would refuse to commit

Studio showed move, resize and rotate handles on animated elements whose
motion it cannot write back (a helper or loop, a runtime-computed value, a
live tween with no matching source). The drag ran, the commit was refused
at release, the element snapped back and a toast explained why.

The commit's refusal rules now also run when a selection resolves:
- move uses the existing drag preflight, now exported
- rotation and resize rules are extracted into preflight functions that the
  commit paths call first, so both use the same decision code
- the session narrows canApplyManualOffset / Size / Rotation (with the
  refusal's message as reasonIfDisabled) in one place, and the chrome,
  nudge, group drag and design panel read that unchanged
- until an element's first check lands, its manual flags stay closed

A press on a box that cannot move now shows the reason at once instead of
after 4px of travel.

* refactor(studio): read the rotation preflight selector once and derive the scale flag

* fix(studio): make the handle check read what the commit reads, and never stall

- Match tweens to an element the same way everywhere: the animation fetch
  used by the handle check and by group moves now also matches through the
  live element, so an element a class tween animates keeps its handles.
- The handle check reads each file's parse once per edit version. A file with
  no tweens is an answer, not a cold parse to retry, so plain elements get
  their handles after one read and later selections in that file at once.
- A failed read is not remembered: the next selection asks again.
- While the check is still running a press stays silent, and only the primary
  button explains a blocked press. A group press with a blocked member now
  says why instead of doing nothing.

* fix(studio): check handles per project, keep answers on failed reads, refuse shared group tweens

- The handle check's parse cache now uses the same project and file key as
  the parse request, so switching projects never judges one project's file
  by another's parse.
- A failed re-read after a save or undo keeps the last good answer and asks
  again on the next selection, instead of closing the handles.
- A group move stays refused when a member's motion comes from one tween
  shared with other targets, as before: each member would write into the
  same tween. The group check and the group commit use the same rule; a
  single selection still edits the shared tween.
- The element-to-tween matcher moves to its own module so the move rule can
  use it without an import cycle.

* docs(studio): the group move rule comment says which tweens it excludes
2026-09-28 23:48:01 +00:00
Miguel Ángel 840f657028 feat(player): play and loop a range of a film with range-start and range-end (#4688)
* feat(player): play and loop a range of the film with range-start and range-end

The runtime takes a set-play-range control and stops on the range end frame itself.
The player wraps to range-start with loop, fires ended without it, and reports a
range cut to the film with rangeclamped. Video and __timelines players check it per tick.

* fix(player): hold a range on its last frame and let the runtime own range jumps

A runtime with the play-range capability jumps to range-start itself when a new range leaves
the playhead outside, so a stale state no longer ends the film; older runtimes keep the
player fallback, guarded until a state lands inside. Every range stop shows the last frame
before range-end; a paused playhead re-parks only when it falls outside the new range.

* fix(player): stop a range a check early and enforce its end on a video in a background tab

* test(player): share the range video setup

* fix(player): a slow frame no longer cuts a range short; a video without a range is unchanged

* fix(player): a range still stops early where video time moves once per frame

* refactor(player): cut the range look-ahead comment to one line

* fix(player): a per-frame video time rounded down still stops a range early

* test(player): pin no rounding tolerance at a film's own end

* test(studio): expect the play-range capability on control messages
2026-09-28 23:38:32 +00:00
Miguel Ángel dc61ce105b feat(studio): a host can hide the timeline's audio effects controls (#4704)
* feat(studio): a host can hide the timeline's audio effects controls

* test(studio): share the whole-timeline mount setup between timeline tests

* fix(studio): hidden audio effects also take the effect-rack shortcut off lane names

* test(studio): share the gain-automated group fixture

* test(studio): use the shared group fixture in every automated-group test

* fix(studio): a disabled group lane name does not light up on hover

* refactor(studio): keep the timeline provider and lanes under the file size limit

* refactor(studio): format the lanes row indexes within the size limit
2026-09-28 23:38:21 +00:00
Miguel Ángel 0af671c6df feat(studio): a host can save style edits on a selection through the Studio style commit (#4698)
* feat(studio): a host can save style edits on a selection through Studio's own style commit

* fix(studio): drop an import the moved writer no longer needs

* fix(studio): host style saves recover after a failure and never revert a later edit
2026-09-28 23:05:18 +00:00
Miguel Ángel a519927436 fix(studio): an escape cancels a preview marquee band and stops there (#4703)
* fix(studio): an escape cancels a preview marquee band and stops there

* fix(studio): name the band escape handler instead of commenting it

* test(studio): check the band is gone before the pointer is released
2026-09-28 22:24:32 +00:00
Miguel Ángel 0c2c4248e6 docs(skills): studio agents answer questions before editing and plan new films like the launch films (#4699)
* docs(skills): studio agents answer questions before editing and plan new films like the launch films

* docs(skills): format the new studio sections and regenerate the manifest

* docs(skills): realign the storyboard table and regenerate the manifest

* docs(skills): defer studio film planning to the owning guides and read polite requests as changes

* docs(skills): format the studio sections and regenerate the manifest

* docs(skills): route felt notes and new films in a project through the studio skill

* docs(skills): keep the interview's opening line and regenerate the manifest

* docs(skills): fit the new entry row to the table

* docs(skills): realign the entry table and regenerate the manifest

* docs(skills): keep only follow-on films in an existing project

* docs(skills): realign the studio tables and regenerate the manifest
2026-09-28 22:07:32 +00:00
Miguel Ángel 590c192498 feat(catalog): add 3D objects, cursors, snow, starfield and paper transitions (#4349)
* feat(catalog): shelve 3D objects and cursors in their own catalog sections

Carries the generator grouping rules from #4310 and #4308.

* feat(registry): add a starfield ambient overlay

From #4313.

* feat(registry): add a falling snow ambient overlay

From #4331.

* feat(registry): add a torn-paper-rip transition

From #4321.

* feat(registry): add a paper-collage-reveal transition

From #4322.

* feat(registry): add a curtain-reveal transition

From #4323.

* feat(registry): add 3D objects: product turntable, glass hero, exploded stack, extruded logo

From #4310.

* feat(registry): add eight cursor animations and the dart cursor

From #4308.

* fix(catalog): find items by everyday editor words and accept a bare search word

From #4311.

* chore(registry): remove the simulated-cursor component

The Cursors section's new pointer animations replace it (from #4308).

* fix(registry): take cursor-zoom-follow's screenshot from runtime variables only

The declared default is empty, so page markup never becomes an image URL.

Clears the code-scanning alert raised on #4308.

* docs(cli): show a catalog search example that finds items

"smoke" matches nothing in the catalog; "crossfade" finds fade-through.

* chore(ci): allow the simulated-cursor removal in the deletion guard

* fix(ci): skip tracked files missing from disk in the player CDN pin check

Catalog regeneration removes a deleted item's pages before the publish PR untracks them,

so the check crashed on every catalog deletion PR.

* chore(cli): drop comments that narrate catalog search history

* fix(registry): drag-drop keeps the family arrow and shows the drop slot only while hovering

* fix(registry): agent cursor labels its steps in an accent tag, no glow or sparkles, in 4.8 s

* fix(registry): annotate cursor draws a tapered marker stroke and clears the circled value

* fix(registry): show real file thumbnails and end the context menu demo sooner

* fix(registry): give marquee select real photos, a Finder-blue marquee and a tighter loop

* fix(registry): centre the text-select toolbar on the selection and tighten its timing

* fix(registry): selection cursors default to the macOS system blue

* style(registry): format the agent and annotate cursor sources

* fix(registry): dart-cursor stops covering the choice it makes and shows the export finishing

* fix(registry): frame the product turntable fully and light it with two studio softboxes

* fix(registry): touch-indicator taps like a real phone and the post it taps gets liked

* fix(registry): press-ripple confirms with a check and small click rings at the cursor

* fix(registry): exploded stack separates at once and marks each layer in blue as it is labelled

* fix(registry): extruded logo settles facing the camera, set in Geist Bold with a neutral rim

* fix(registry): magnetic cursor docks its toolbar on the selection and ends after the press

* style(registry): format the exploded stack source

* fix(registry): glyph trail becomes a tutorial cursor path with numbered steps on a settings screen

* fix(registry): make glass-hero a clear lens that shows the headline through it

* fix(registry): snow reads as real snowfall with bokeh flakes over a winter title card

* fix(registry): oversized cursor is a 3.5x arrow that clicks a real editor toggle on

* fix(registry): starfield reads as a drifting night sky over a real title card

* style(registry): format the glass hero source

* fix(registry): make the paper tear show a white fibrous edge and lift the torn sheet away

* fix(registry): curtain reveal draws velvet folds that gather onto a title card

* fix(registry): make every zoom-follow click change the screen and read at zoom

* fix(registry): paper collage pieces stay opaque, tear with deckled grain and lift away on an arc

* refactor(registry): drop dead code and correct stale comments in reworked items

* fix(registry): agent cursor scrubs cleanly, docs match press-ripple, fictional demo names

* docs(registry): catalog notes say oversized-cursor exits and how it differs from press-ripple

* docs(registry): plain lowercase comments in oversized-cursor and touch-indicator

* docs(registry): oversized-cursor names CLICK_AT as the click cue
2026-09-28 22:03:59 +00:00
Miguel Ángel f7207247de fix(studio): an escape that cancels a timeline band stops there (#4700)
* fix(studio): an escape that cancels a timeline band stops there

* test(studio): an escape that cancels a timeline band never reaches the host
2026-09-28 21:58:51 +00:00
Miguel Ángel f7ef18dca8 fix(producer): renders leave no captured frames behind when interrupted (#4694)
Ctrl+C during a render left its whole work dir of captured frames (and a
staging dir) in the renders folder. Puppeteer's default SIGINT handler kills
Chrome and exits the process before the render's own cancel path can remove
them. The engine keeps that default unless the host owns Ctrl+C; the CLI's
render cancellation scope claims it while a render runs, so the render aborts
and cleans up after itself.

A render killed outright (SIGKILL, crash) cannot clean up, so each work dir and
staging dir now records its owner process (pid, hostname, boot, and on Linux
the pid namespace), and the next render into the same folder removes
the ones whose owner has exited.

Cancelling a render while its browsers start no longer leaves anything
running that kept a batch render alive after Ctrl+C: the BeginFrame warm-up
loop, the probe browser and file server, or a capture browser whose launch
finished after the cancel (now closed before the worker settles, since
Puppeteer's exit hook skips every other live browser).
2026-09-28 21:58:06 +00:00
Miguel Ángel e0b940aa30 fix(core): a sound effect starts on its time instead of a frame late (#4696)
* fix(core): a sound effect starts on its time instead of a frame late

* style(core): format the early-start change

* test(core): a clip past its end is never started early

* fix(core): a seek stops a clip started early, and the lead never passes 40 ms of real time

* test(core): drive the early-start tests the way the runtime receives a seek and a speed

* fix(core): the early-start lead never passes 40 ms of film time at fast speeds

* fix(core): keep a sped-up sound effect inside strict sync when it starts early

* test(core): pin the early-start cap at fast speed
2026-09-28 21:53:17 +00:00
Miguel Ángel a0c72ad64a fix(studio): drilling in opens the composition's full project path (#4689)
* fix(studio): drilling in opens the composition's full project path

* fix(studio): keep the shared source-path helper as is and trim ./ only for the drill path

* fix(studio): a project folder named preview keeps its path
2026-09-28 21:04:10 +00:00
Miguel Ángel 303c8e922f perf(studio): swap an edited scene into the live preview instead of reloading (#4499)
* perf(studio): swap an edited scene into the live preview instead of reloading

On a preview reload, Studio fetches the rebuilt preview document and asks the preview to swap
in only the scenes that changed. When the preview cannot or will not swap (it predates the
swap, a full reload is already running, or the document changed outside its scenes), Studio
does the full shadow reload as before. A document from an older reload is never swapped in.

* fix(studio): re-sync selection and layers after a scene swap

A swap replaces scene DOM without a load event, so selection, manual
edits, the Layers panel and the edit callout kept pointing at the
replaced nodes. They now listen for either a load or a swap.

* fix(studio): drop a stale scene swap once the preview is replaced

A swap still pending when the preview reloaded or switched composition
could fall back to reloading the old one. Color grading and its compare
view are also re-sent after a swap.

* docs(studio): say exactly when the preview generation changes

* refactor(studio): cut scene swap comments to what names and types do not say

* fix(studio): fall back to a full reload when a scene swap download stalls

* fix(studio): give up on a scene swap that stalls at any step, not only the download

* fix(runtime): a scene swap refuses when an animation outside the scene moves its elements

* test(runtime): pin the scene swap volume re-probe, host CSS variables and diagnostics-free swap

* fix(runtime): a scene swap keeps a video its scene script animates instead of reloading it

* fix(runtime): a scene swap stops every animation the old scene script started

* fix(runtime): record scene script animations only on pages a scene swap can act on

* docs(core): reword the scene animation recording comment

* fix(runtime): watch media as the page parses only on pages a scene swap can act on

* perf(runtime): a scene swap checks for outside tweens in one pass over the film's tweens

* test(runtime): pin the single revert and the cleared animation record of a scene swap

* test(studio): pin that a playing film keeps playing through a scene swap

* fix(runtime): a scene swap rebuilds media when the edit changes the scene script

* fix(runtime): a scene swap undoes the sets a scene script made

* fix(runtime): a scene swap rechecks outside tweens and runs new scripts after every host

* test(runtime): pin that a tween started during the caption wait leaves the page untouched

* fix(runtime): a scene swap refuses when its script set a value on an element the swap keeps

* docs(core): say the scene recording drops a finished tween that the timeline would keep when paused

* fix(runtime): a scene swap refuses when two of its animations write to an element it keeps

* fix(runtime): a scene swap resets kept media and refuses animations outside the scene

* fix(runtime): a scene swap keeps a moved video in place and swaps tweens on plain objects

* chore(core): regenerate the inlined position-edits script for the kept-video move reapply

* fix(runtime): a scene swap checks the timeline registry as it is after the caption wait

* fix(runtime): a scene swap refuses when a scene changed during the wait or a revert re-registers

* fix(runtime): a scene swap stops every animation a revert chain registers and rechecks its hosts

* fix(runtime): a scene swap refuses a moved host or a cancelled swap and stops at a shortened end

* fix(studio): a scene swap that times out is cancelled before the reload starts

* fix(runtime): a scene swap refuses when a scene leaves its place or the film during the wait

* fix(studio): a newer edit cancels an older scene swap still waiting

* fix(runtime): a scene swap checks only its host's place, so a later body append does not refuse it

* fix(studio): a scene swap cancelled before it starts does not run

* fix(runtime): a scene swap refuses when the film's scene layout changed during the wait

* fix(runtime): a scene swap refuses when the order around a scene changed during the wait

* test(runtime): pin that a scene swap checks the film root order when every scene is wrapped

* fix(runtime): a scene swap gives kept media the mute, rate and loop a fresh load gives it

* fix(runtime): a scene swap rebuilds media a script wrote attributes on, keeping the rest

* fix(runtime): a scene swap keeps media carrying the runtime classes or reordered attributes

* fix(runtime): a scene swap resets a kept video's volume as a load does, not through the volume probe
2026-09-28 20:44:30 +00:00
Miguel Ángel 1004b69829 chore: release v0.8.86 (#4695) v0.8.86 2026-09-28 13:37:47 -07:00
Miguel Ángel a3b6eec97e fix(studio): timeline Fit fills the width with the film, and a host can hide keyframe controls (#4680)
* fix(studio): timeline Fit fills the width with the film, and a host can hide keyframe controls

* fix(studio): keyframe tests read the claimed K press, and the fit span stays private

* fix(studio): an edit that changes the film's length keeps the timeline scale, and Fit is an icon

* fix(studio): manual zoom keeps its scale whenever only the film's length changes

* fix(studio): drop the width check the kept scale never needed

* fix(studio): a composition switch refits, and the kept scale clamps against the new fit

* test(studio): a project reset refits the manual zoom
2026-09-28 20:03:33 +00:00
Miguel Ángel 73d26a5187 fix(studio): back to the master after a nested drill restores the master playhead (#4678)
* fix(studio): back to the master after a nested drill restores the master playhead

* fix(studio): every way back to the master restores its playhead, 0 included

* fix(studio): the timeline playhead follows the restored master time

The timeline canvas unmounts while a composition level loads and remounts
with its playhead drawn at 0. The playhead effect only re-ran on a time or
zoom change, so a level switch that kept the time left the needle at 0 while
the readout showed the real time. It now also re-runs when the canvas remounts.

Drilling in during playback saved the store time, which playback does not
update per frame; the save now reads the live playhead.
2026-09-28 19:47:55 +00:00
Miguel Ángel 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
2026-09-28 19:24:05 +00:00
46eedd4dfb fix(fonts): resolve declared family names with missing spaces (#4670)
* fix(producer): a misspelled font-family resolves to the family the document declares

"Saira ExtraCondensed" in CSS next to an @import of family=Saira+Extra+Condensed
was unresolved (Google Fonts answers the typo with HTTP 400), so fail-closed
compiles threw FontFetchError. Once every existing path fails, a family whose
spelling (case, spaces, -, _, +) uniquely matches a family the document declares
in a Google Fonts @import/<link> URL or its own @font-face resolves to it. Faces
are emitted under the authored name, so every authored usage matches them. Zero
or ambiguous matches keep today's behavior.

FontFetchError gains a structured unresolvedFamilies list.

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

* fix(cli): font localization failures name the unresolved font families

The failure line keeps its prefix and appends the sanitized names from
FontFetchError.unresolvedFamilies, e.g.
font localization failed (FontFetchError): unresolved font families: "Foo Bar"
The error message, URLs and HTML are still never printed.

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

* fix(producer): keep conditional font declarations out of alias fallback

* refactor(producer): bound declared font collection complexity

* test(producer): model multi-family Google font responses

* test(producer): preserve optional font policy through alias fallback

---------

Co-authored-by: terence.cho_tai2 <terence.cho_tai2@heygen.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:09:21 +00:00
Miguel Ángel fdbc5dcaf7 fix(core): the playhead no longer jumps back when an audio clip starts (#4679)
* fix(core): the playhead no longer jumps back when an audio clip starts

While no Web Audio buffer plays, the transport clock follows an audio element, and each frame it re-picked the first in-window <audio> in page order. A sound effect that started during a voice line took the clock over, and since a clip starts late (seeked, asked to play, not yet producing sound) the playhead jumped back by that delay, 30 to 450 ms in real films, dragging every other track with it. The followed clip now keeps the clock while it plays. And audio may pull the playhead forward or hold it while it catches up (starting, seeking, buffering), but never push it back; a jump back past a hard sync, such as a native loop wrap, is ignored. The clock never runs ahead of its source, so media sync never chases it. Also: a playing element with a media error no longer leads the clock, and a speed change sets the clock's rate before the elements'.

* style(core): trim the handover fix's comments to what the code cannot say

* style(core): name the clock's no-rewind rule instead of commenting it

* style(core): format the clock

* test(core): pin the clock's hold while seeking and its skip of a failed clip

* fix(core): the playhead keeps its place on a speed change, a short loop or a failed clip

* fix(core): a late looping bed holds the playhead, and a clip with no source never leads it

* refactor(core): split the clock's behind-the-playhead rule into its own method

* fix(core): an audio clip with no source never leads or freezes the playhead
2026-09-28 18:58:29 +00:00
Miguel Ángel a31738f115 fix(docs): restore examples with the HyperFrames Player (#4675)
* fix(docs): restore visible example video cards

* fix(docs): use catalog player for example films

* fix(docs): fit player posters before playback

* fix(docs): preserve the original examples grid
2026-09-28 11:33:54 -07:00
Miguel Ángel 326bd0c9a1 feat(player): play video files with type="video/..." (#4673)
* feat(player): play video files with type="video/..."

A src with a video type now plays in a <video playsinline> behind the same
API and events as a composition: play/pause/seek, currentTime, duration,
ready with the video's size, timeupdate, ended, loop, durationchange and
resize. A load failure fires error at once with the MediaError code, and a
blocked play fires playbackerror and leaves the player paused.

The video is driven through the direct-timeline adapter and clock, and every
src load goes through one _loadSrc that picks the video or the iframe.

* fix(player): keep video playback steady across option changes, pauses and errors

* fix(player): keep video playback in step with the video after seek, pause and srcdoc

* fix(player): limit video-mode changes to video players and pause on srcdoc switches

* refactor(player): cut the video-source header comment to two lines
2026-09-28 18:04:13 +00:00
Miguel Ángel a46095f1cc chore: release v0.8.85 (#4676) v0.8.85 2026-09-28 10:52:18 -07:00
Miguel Ángel af7ff3b0a1 feat(studio): enter a sub-composition from the keyboard and host its navigation (#4672)
* feat(studio): enter a sub-composition from the keyboard and host its navigation

* fix(studio): drop a comment the element field already says
2026-09-28 17:19:03 +00:00
James RussoandClaude Opus 5.5 5f7c6bc5b2 docs(changelog): weekly digest 2026-09-21-2026-09-28 (#4669)
* docs(changelog): weekly digest 2026-09-21-2026-09-28

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

* docs(changelog): embed the Sep 21-28 weekly video

Adds the 50.4s square changelog video for the Sep 21-28 digest,
served from the static CDN.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 17:17:56 +00:00
Miguel Ángel 52f849e2b4 chore: release v0.8.84 (#4671) v0.8.84 2026-09-28 10:00:31 -07:00
Miguel Ángel 42f6a3a27e fix(studio): unmounting mid-edit saves the typed words, as blur does (#4651)
* fix(studio): unmounting mid-edit saves the typed words, as blur does

* fix(studio): in-place text is not saved once the preview turns read-only
2026-09-28 16:16:02 +00:00
Miguel Ángel 523685985b fix(studio): the timeline reads clip speed with the same limits as playback (#4667) 2026-09-28 16:15:49 +00:00
Miguel Ángel 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
2026-09-28 16:14:43 +00:00
Miguel Ángel 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
2026-09-28 16:14:29 +00:00
Miguel Ángel 8fd5fd5a32 chore: release v0.8.83 (#4668) v0.8.83 2026-09-28 09:27:18 -07:00
Miguel Ángel 5257dd581f fix(studio): tone-map HDR preview proxies with the render tone map (#4606) 2026-09-28 15:48:58 +00:00
Miguel Ángel 16a010404c fix(studio): the shortcut list shows only keyframe keys that work (#4657)
* fix(studio): the shortcut list shows only keyframe keys that work

* docs(studio): animation guide drops the H and U keys and says what K does
2026-09-28 15:48:42 +00:00
Miguel Ángel 7fae35f36e fix(studio): carve and duck follow each clip's trim and speed (#4661)
* fix(studio): read and write a clip's in-point the way playback does

* fix(studio): a group head trim keeps the minimum clip length off the grid

* fix(studio): a group head trim stops where a single-clip trim does and never goes below zero

* fix(studio): group members on the grid move together when a trim stops at its limit

* fix(studio): level-out measures and places its lift on the clip clock at any speed

* fix(studio): round level-out lane times and tighten the half-speed test

* fix(studio): carve and duck follow each clip's trim and speed

* fix(studio): carve ignores speech after the bed stops playing

* test(studio): carve ignores speech past a bed that ducks and has no duration

* fix(studio): carve cuts speech past the bed's end at every strength
2026-09-28 15:48:26 +00:00
Miguel Ángel 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
2026-09-28 15:47:13 +00:00
Miguel Ángel d2f0bc7f34 perf(studio): a host's first screen can import Studio's UI and Player without the editor (#4650)
* perf(studio): light ui and player entries for a host's first screen

* perf(studio): the ui entry's logo no longer loads the studio header

* test(studio): refuse only packages studio itself depends on

* test(studio): the light entries also refuse the hf-ids parser
2026-09-28 15:15:00 +00:00
Miguel Ángel 2251e5fad0 feat(studio): export DOM editing for a host outside EditorShell (#4640)
* feat(studio): export DOM editing for a host outside EditorShell

* fix(studio): host hears only single-box clicks, and a handle release no longer eats the next one

* refactor(studio): cut the sibling z-order comment to one line
2026-09-28 15:14:32 +00:00
Miguel Ángel 93ab2899f1 feat(studio): let a host app keep paint order in step with timeline rows (#4647)
* feat(studio): let a host app keep paint order in step with timeline rows

* docs(studio): say each stacking sync prop overrides separately
2026-09-28 09:50:09 +00:00
Miguel Ángel 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
2026-09-28 09:48:22 +00:00
Miguel Ángel 6cb95d844d test(core): findProjects stop tests fit Windows' time budget and pin a stopped walk ending (#4653)
* test(core): findProjects stop tests build two projects, not four hundred files

* test(core): stop tests keep a folder queued at the stop, with room for Windows setup
2026-09-28 09:29:44 +00:00
Miguel Ángel 34a66744f2 fix(studio-server): a cached-only thumbnail miss answers 204, so the page logs no error (#4652) 2026-09-28 09:29:32 +00:00
Miguel Ángel 5eec513281 fix(studio): the timeline takes m4v video and flac audio (#4649)
* fix(studio): the timeline takes m4v video and flac audio

* fix(studio-server): check and label flac uploads as audio
2026-09-28 08:49:57 +00:00
Miguel Ángel 2119b188a2 fix(studio): a clip drag released outside the window is cancelled (#4646)
* fix(studio): releasing a clip drag outside the window cancels it

Pointer capture delivered the release to the timeline even outside the window,
so the move or trim committed wherever its preview was last drawn. A release
outside the window now cancels the gesture, the same as Escape.

* test(studio): pin a release exactly on the window's edge as outside

* fix(studio): releasing a fade-handle drag outside the window cancels it

Fade handles run their own gesture, so they now use the same outside-window
check as clip moves and trims, moved into one shared module.
2026-09-28 08:49:42 +00:00