Commit Graph
253 Commits
Author SHA1 Message Date
Miguel Ángel 3e16603d37 fix(render): a PNG's colour tags no longer change its pixels in preview, check, snapshot or render (#6453)
* fix(render): a PNG's colour tags no longer change its pixels in preview, check, snapshot or render

* fix(render): the play and present server also serves a PNG without its colour tags

* fix(render): bundled pages inline a PNG without its colour tags too
2026-10-26 17:48:57 +00:00
SophiaHuangCc 2d695a8436 feat(studio): support GIF export with loop count (#5593)
* feat(studio): support GIF export with loop count

* fix(studio): cap GIF exports at 30 fps

* fix(studio): complete GIF export review follow-ups
2026-10-26 14:57:55 +00:00
Miguel Ángel 52b3d32114 fix(studio-server): a failed preview copy reports its causes and what its source is (#6350)
* fix(studio-server): a failed preview copy says why its output could not open

* fix(studio-server): a failed preview copy reports its causes and what its source is

* fix(studio-server): failure reports hide spaced paths and survive a vanished source

* refactor(studio-server): read a probe's result in its own function for the complexity gate

* fix(studio-server): close the source facts function the rebase left open

* fix(studio-server): a failure report reads the source size through one open descriptor
2026-10-26 10:54:09 +00:00
Miguel Ángel 84fd701daa feat(studio): preview copy progress, retry and a remembered quality (#6366)
* feat(studio-server): a copy being made says how far it has got, and can be retried

* feat(core): the preview reports its lighter copies' progress and retries failed ones

* feat(studio): the preview quality and its lighter copies' progress live in the player store

* feat(studio): every host forwards the preview quality, and the choice is kept per computer

* feat(studio): the preview quality knows if a person or the app chose it

* fix(core): the first report of the lighter copies is not held back by the throttle

* test(studio): the preview quality preference test writes under the real key

* fix(core): a quality change reports its copies at once, not after the throttle

* style: format the preview quality files

* feat(core): report how playback is going, and what the preview holds, to the host

* fix(studio): the preview quality levels come from a browser-safe core entry

* style: format the new entry's files

* style: drop two comments the file's ratchet and the citation check refuse

* feat(core): tell which lower levels would give a video a lighter copy

* fix(studio): drop the old level's playback health when the level changes

* feat(studio-server): preview facts carry the probed codec of every video

* refactor(studio): route the preview's reports through a table for the complexity gate

* fix(core): a copy's retry flag is sent on the first ask only, not on every poll

* fix(core): type the health report and restore the player hook's complexity directive

* style: cut four comments the comment ratchet refuses

* fix(studio): keep playerStore.ts within its 600-line cap

* fix(core): a scene mounting does not restart the playback health window

* fix(core): the playback health window restarts only when the display scale changes

* fix(core): a repeated display scale no longer restarts the playback health window

* fix(studio): route the preview's reports by an explicit list of message types
2026-10-26 09:20:07 +00:00
Miguel Ángel 2e23b7e434 fix(studio-server): copies are found by content and made by one process at a time (#6358)
* perf(studio-server): a preview copy is keyed by the file's content, so copied projects reuse it

* fix(studio-server): copies are keyed by content and made by one process at a time

* docs(studio-server): shorten the copy lock comment

* fix(studio-server): find a copy by place and time first, by whole-file content when that misses

* fix(studio-server): hash small sources in one read; wait longer in tests for the hash

* test(studio-server): cache folder listings ignore the copy index files

* style(studio-server): format the copy lookup files

* docs(studio-server): shorten the copy lookup comment

* fix(studio-server): hash a source through one open handle, not a check then a read

* fix(studio-server): hash through one descriptor, keeping the small-file path synchronous

* style(studio-server): format the cache key file
2026-10-26 06:20:03 +00:00
Miguel Ángel 38d9aeb5ea feat(core): lighter preview copies of heavy videos below full quality (#6365)
* feat(core): tell which video files deserve a lighter preview copy, and at which size

* feat(studio-server): serve lighter copies of heavy playable files, a keyframe every second

* feat(core): the preview plays lighter copies of heavy videos below full quality

* style: shorten two comments

* style: shorten a comment

* style: shorten a comment

* fix(core): format the preview quality files and split the copy sizing for the complexity gate

* style: format the studio-server preview files

* fix(core): type the copy job's level as a preview quality

* chore(producer): raise the package size budget for the preview runtime growth
2026-10-26 03:53:21 +00:00
Miguel Ángel badb61aa99 fix(studio-server): take over a history lock whose pid now runs another process (#6386)
* fix(studio-server): take over a history lock whose pid now runs another process

* style(studio-server): drop two comments from the history lock
2026-10-26 03:21:37 +00:00
Miguel Ángel e4bdf8c51c perf(studio-server): copy alpha cut-outs several pieces at a time (#6342) 2026-10-26 02:14:49 +00:00
Miguel Ángel 6f4d46ad34 fix(studio-server): a refused preview frame says so at once instead of timing out (#6074)
* fix(studio-server): a refused preview frame says so at once instead of timing out

A frame the server refuses (project gone, bad variables) loaded a JSON body: no runtime and no error, so Studio waited three 15s budgets and reported 'waiting for runtime'. The refusal now reaches the frame as a page that reports it, like a failing preview script.

* chore(studio-server): remove redundant preview refusal comments
2026-10-25 23:05:51 +00:00
Akshar Patel 6a20ec9e9a fix(cli): preserve clip lengths when duplicating media (#6280)
* fix(cli): prevent overlaps when duplicating natural-duration media

* fix(cli): preserve absolute ends when duplicating clips

* fix(cli): keep rippled clip bounds together
2026-10-25 23:01:27 +00:00
Miguel Ángel b2124b2a56 perf(studio-server): the browser-playable copy of an alpha video encodes on four threads (#6325)
* perf(studio-server): the browser-playable copy of an alpha video encodes on four threads

libvpx runs on one thread unless told, so the alpha copy of a 4K video took several times its length on a small machine.

* docs(studio-server): drop a comment line the name already carries
2026-10-25 17:35:33 +00:00
Miguel Ángel ada9932d4e fix(cli): a long video copy reports progress, resumes in 15 s pieces and stops only when stalled (#6307)
* fix(cli): a long video copy reports progress, resumes in 15 s pieces and stops only when stalled

* perf(core): a video copy's size box follows the stage within a fifth, not a half

* docs(cli): shorten two comments to the line limit

* fix(cli): the copy heartbeat drops a finished copy; size budgets follow the stall and progress code

* chore: size budgets rise by this change's measured bytes
2026-10-25 13:53:03 +00:00
Miguel Ángel 4d154c6a23 test: two flaky queue tests no longer race real time or the first copy's start (#6283) 2026-10-25 07:13:22 +00:00
Miguel Ángel 7bc363c87c fix(studio-server): undoing a video rename brings the file back under its old name (#6285)
* fix(studio-server): undoing a video rename brings the file back under its old name

* fix(studio-server): name the bytes a write's deletes set aside
2026-10-25 05:43:55 +00:00
James RussoandClaude Opus 5.5 7f19f2335a feat(studio): load GSAP from the CLI's bundled copy by default (#6256)
* feat(studio): load GSAP from the CLI's bundled copy by default

Studio preview now loads the GSAP, CustomEase and MotionPathPlugin scripts
it adds from files the CLI ships and serves same-origin at /api/vendor/npm,
instead of jsDelivr. HYPERFRAMES_GSAP_BASE_URL still wins when set, and a
composition on another GSAP version gets its plugin from jsDelivr.

Renders now honor HYPERFRAMES_GSAP_BASE_URL when they replace a missing
local GSAP script, and the setting accepts plain http only for loopback
hosts.

Closes #6205

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

* refactor(studio): keep comment share flat for the GSAP local root

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

* fix(cli): find the installed gsap with node resolution when run from source

The Windows CI lane installs with the hoisted linker, so gsap is not under
packages/core/node_modules there.

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

* fix(producer): load a nested composition's missing GSAP from the mirror

A sub-composition's missing local gsap/dist script was hoisted into the
render unchanged, so HYPERFRAMES_GSAP_BASE_URL (and the jsDelivr fallback)
only reached the entry document. Run the same rewrite on every
sub-composition before it is compiled, resolving its path the way the
inliner will hoist it.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 03:08:42 +00:00
James RussoandClaude Opus 5.5 685cd75a92 fix(studio): load GSAP core when only a GSAP plugin is on the page (#6260)
* fix(studio): load GSAP core when only a GSAP plugin is on the page

The Studio preview checked for GSAP core by matching "gsap" anywhere in a
script src, so the MotionPathPlugin it had just injected (served from
gsap@x/dist/) counted as core. A composition with a motion path and
Studio-edited motion but no GSAP script of its own then got the plugin and
no core, and its animations did not run.

Detect core by file name (gsap.js, gsap.min.js, or a vendored
gsap-<version>.min.js) on any host or path, and inject Studio motion's core
before the MotionPathPlugin so the plugin can register onto it. Apply the
same core-only check to the sub-composition preview fallback and the
missing_gsap_script lint rule.

Refs #6205

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

* refactor(studio): keep GSAP core detection within the comment budget

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

* fix(studio): load GSAP core ahead of head plugins and only on a real core script

Studio motion and sub-composition previews now insert GSAP core before the
first GSAP plugin script in the head, so an authored MotionPathPlugin or
CustomEase registers on core instead of running first. Core detection keys on
a core script (src, inlined marker, license banner or library body), not on
usage like gsap.registerPlugin() or gsap.defaults(). The missing_gsap_script
lint rule drops the same usage-based and plugin-banner shortcuts.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 02:37:08 +00:00
Miguel Ángel 85fb19f1e8 fix(studio-server): undo and restore keep the media they delete or replace (#6215)
* fix(studio-server): undo and restore keep the media they delete or replace

* fix(studio-server): set-aside media is freed only after a whole undo or restore lands

* fix(studio-server): set-aside media does not count toward the history budget

* fix(studio-server): set-aside media keeps its own ledger apart from copied blobs

* fix(studio-server): a set-aside copy waits for every path that counts on it

* fix(studio-server): an unreadable history folder fails the read instead of looking empty

* fix(studio-server): history opens past a temp file a crash left behind

* fix(studio-server): trim a comment so the comment share holds
2026-10-25 01:48:06 +00:00
Abdullah M. Nazif 1c14fbb542 fix(studio): allow a local GSAP dependency mirror (#6247) 2026-10-24 22:13:28 +00:00
Miguel Ángel 0eae4946d3 fix(studio-server): a history restore refuses to remove every tracked file (#6175)
* fix(studio-server): a history restore refuses to remove every tracked file

* test(studio-server): compare the held clip by bytes so the adopting check stays fast

* fix(studio-server): history's own hidden records do not count as files a restore keeps

* refactor(studio-server): name the restore guard's check instead of commenting it

* chore(studio-server): re-baseline the packed size budget for the restore guard
2026-10-24 12:51:30 +00:00
Miguel Ángel 5ebbc8ef6d fix(studio-server): project history no longer copies video, audio or huge files (#6171)
* fix(studio-server): project history no longer copies video, audio or huge files

* fix(studio-server): clean drops old video and audio copies in every history

* fix(studio-server): TypeScript source is not media, so its history is kept

* fix(studio-server): split the media reclaim and point the slow-copy route test at a non-media file

* fix(studio-server): no unused expression when collecting a history's hashes
2026-10-24 04:12:48 +00:00
Abdullah M. Nazif 0aa728b1aa fix(studio-server): decode authored audio attributes for loudness (#5289) 2026-10-24 04:11:25 +00:00
Miguel Ángel 28ac4d76b6 fix(studio): the thumbnail capture page loads media near the playhead like the preview (#6146) 2026-10-23 19:21:51 +00:00
Miguel Ángel 8411956534 fix(cli): long projects check to completion: boxed resumable copies, --prepare-media, idle deadline (#6104)
* fix(cli): check copies hostile videos at the composition size, not the source size

* fix(cli): the check server serves the copy the page names with hf-proxy-box

* feat(cli): check --prepare-media makes the video copies alone, and a stopped copy resumes

A long source is copied in one-minute pieces that stay in the cache, so a copy
that is killed or runs out of time continues from its finished pieces.
check --prepare-media runs only that step: no browser, no report, a per-file
result as JSON, and exit 1 when a copy failed.

* fix(studio-server): keep finished copy pieces while their copy runs

The short limit for dead partial copies would delete the first pieces of a
long copy that is still encoding. Finished pieces now follow their own
one-hour limit and are never swept while their copy is in flight.

* refactor(studio-server): shorten two comments

The comment gate limits a comment line to 120 characters and a run to four lines.

* refactor(studio-server): drop comments the names already carry

The comment ratchet lets a file's comment share only go down.

* fix(cli): the check server pre-warms the copy the page asks for, not the source-size one

The server started a source-size copy of every hostile video next to Chrome on
each check, because its pre-warm passed no box while the page and the up-front
copy use the composition's box. The pre-warm now takes the same box, found by
the one helper the up-front copy uses.

* fix(cli): end a check on no progress, not on a 120 s wall clock

The check ended 120 s after Chrome was ready no matter what it was doing, so a
long project whose sampling and overlap pass each kept working still failed
(the overlap pass was one page call that reported nothing for its whole run).

The deadline is now an idle clock: every phase entered, every sample finished,
every overlap batch of eight times and every operation that settles restarts it,
and the check ends only after 120 s with none. The overlap grid is asked for in
batches so it can report between them.

* refactor(cli): shorten the idle deadline comment

The comment gate allows 120 characters on a comment line.

* test(cli): check's overlap grid runs in page tasks of at most 8 samples

* fix(cli): check test types for the cli lib, studio-server size budget for resumable copies
2026-10-23 16:32:28 +00:00
Miguel Ángel 2ed882ccf2 fix(studio-server): the history lock works on drives without hard links (#6125)
* fix(studio-server): the history lock works on drives without hard links

Taking the owner lock links a prepared file into place. exFAT and some network drives refuse hard links (ENOTSUP, EPERM), so the claim threw and the project history could not open. A refused link now falls back to creating the lock exclusively. A reader treats a just-created empty lock as being taken, and clears one a crashed claimant left behind after two seconds.

* test(studio-server): run the owner lock tests where node:fs can be stubbed

* fix(studio-server): the evict lock also waits out a claimant's empty file

The evict lock takes the same exclusive-create fallback as the owner lock, so it can be empty for a moment too. One deadHolder check now decides a holder is gone at all three sites: owner, evictor and the lock itself.

* chore(studio-server): trim comments on the owner lock

* test(studio-server): an empty lock is taken over once it is old, not while being written

* chore(studio-server): shorten the claim comment
2026-10-23 12:32:47 +00:00
Miguel Ángel 6225271173 fix: preserve motion blur off through cloud render requests (#6087)
* fix: preserve motion blur off through cloud render requests

* fix: keep generated blur docs concise and format examples

* fix: freeze motion blur intent for distributed render workers
2026-10-22 23:54:54 +00:00
Vance IngallsandClaude Sonnet 5.5 1f8ead7bde feat(producer,studio): wire capture motion blur into renders and Studio (#5969)
Carry the runtime's blur through the render pipeline and the Studio render
panel.

- engine: installPageMotionBlur sets the page-side off switch and the render's
  device scale before any author script runs; CaptureOptions.pageMotionBlur
  lets a caller stand the page blur down.
- producer: a composition that asks for data-motion-blur on the root the
  runtime would pick forces screenshot capture, because html-in-canvas needs
  a real paint; the probe session carries the request's pageMotionBlur like
  every other session.
- studio-server and Studio: the render panel sends previewMotionBlur, the
  render route turns "off" into pageMotionBlur once, and the Studio dev server
  and the CLI server both receive the final value.
- mover scan: a large element that is see-through while it moves (a sweep, a
  light leak) is no mover, including a large image or canvas with alpha; the
  scan skips elements moved by a 3D individual transform property.
- cli snapshot injects video frames before the seek settles, matching the
  engine's order.

Folded review fixes: R1, R2, R3 (scan half), R7, R16, R18, T3 (render
adapters).

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 13:32:50 -07:00
Miguel Ángel d67e81c7f1 fix(studio-server): keep transparency in proxies of VP8/VP9 alpha webm (#6025)
* fix(studio-server): keep transparency in proxies of VP8/VP9 alpha webm

A webm whose alpha lives in the VP8/VP9 side channel (alpha_mode=1,
pix_fmt yuv420p) was proxied to opaque H.264, and the VP8 proxy path
decoded it with ffmpeg's native decoder, which drops alpha. The alpha
rule and the alpha-reading decoder now live once in
@hyperframes/parsers/media-alpha, used by engine and studio-server.

* build(parsers): raise the packed size budget for the media-alpha subpath

parsers already sat at its packed budget on main; the shared alpha rule
adds 676 B of JS and 803 B of declarations (190958 B packed in CI).
2026-10-21 13:49:30 +00:00
Miguel Ángel 468467003c fix(studio-server): a history claim on Windows waits out a lock file being deleted (#5923)
* fix(studio-server): a history claim on Windows waits out a lock file being deleted

* style(studio-server): drop the two history-lock comments, names carry them

* style(studio-server): keep the history-lock test comment under 120 columns
2026-10-20 16:13:36 +00:00
Miguel Ángel 6d357ff0d8 fix(cli): check, snapshot and Studio read project text as UTF-8 (#5843)
* fix(cli): check, snapshot and Studio read project text as UTF-8

The preview servers sent HTML, CSS, JS and JSON with no charset, so Chrome
read a page without <meta charset> as windows-1252 and stills showed garbled
non-Latin text while the render, whose file server already sends utf-8,
was correct. The shared MIME table now carries charset=utf-8 for text types,
and the two hard-coded headers use it.

* fix(cli): snapshot shows accented text when the page's meta charset sits outside its head

A <meta charset> past Chrome's first 1024 bytes and outside <head> is ignored, so with no
charset in the header the page decoded as windows-1252. The browser test serves such a page
through the static server snapshot uses. play compares the media type without its charset,
so JSON assets keep their content-hash revalidation.
2026-10-20 13:52:43 +00:00
Miguel Ángel b7fa007bf3 fix(cli): check says it is making video copies instead of going silent for minutes (#5953)
A check on a project whose videos Chrome cannot decode (HEVC, HDR) first transcodes each
of them, which on phone footage takes minutes and printed nothing until it was done.
2026-10-20 09:52:28 +00:00
Miguel Ángel 5583ae4293 perf(cli): commands run without esbuild, puppeteer-core or sharp until they need them (#5930)
* perf(cli): commands run without esbuild, puppeteer-core or sharp until they need them

A host that bundles the CLI can now leave these packages out: each command
loads them only where it uses them, and sharp installs once on first use when
it is missing. The build check fails on any static import of them again.

* fix(cli): use a host's sharp from NODE_PATH and let hosts turn first-use installs off

* fix(cli): capture fails when sharp is missing, and one install serves every caller

A missing sharp now stops the asset download up front instead of counting every
image as unavailable. Concurrent first uses share one install, a failed install
is not retried in the same run, and preview thumbnails report why. Hosts can turn
off installs for a list of packages, e.g. HYPERFRAMES_NO_OPTIONAL_INSTALL=sharp.

* fix(cli): snapshot --describe still works without sharp, resizing only when it is installed

* style(cli): format the sharp loader and its tests

* build(cli): sharp is an optional dependency, so a host can ship the CLI without it

* build(studio-server): keep sharp out of the bundle now that it is optional
2026-10-20 09:12:47 +00:00
Miguel Ángel 93c321285c fix(studio): render scratch stays out of the project and error telemetry counts real failures only (#5902)
* fix(studio): a lost preview server or a superseded edit no longer files as a crash

* fix(studio-server): a render's scratch files no longer stream changes or list as compositions
2026-10-19 19:52:04 +00:00
Miguel Ángel 35c03a509e fix(cli): snapshot --proxy exits once its frames are written, without waiting for copies (#5895)
Closing the page server now stops the media proxy copies it started (pre-warms and the page's own asks). A copy whose last caller leaves is now stopped even after its ffmpeg started; callers without a signal still always get their copy.
2026-10-19 17:24:05 +00:00
Miguel Ángel cadd7af2b5 fix(studio-server): send Studio's history edits to the process that owns the project's history (#5826)
* fix(studio-server): send Studio's history edits to the process that owns the project's history

* fix(studio-server): a forwarded history call gives up after 5 s, so a stopped owner cannot hang
2026-10-18 06:07:33 +00:00
Miguel Ángel 7b1b0bb005 fix(studio-server): history routes say why a project's history cannot open instead of a bare 500 (#5821) 2026-10-18 05:21:15 +00:00
Miguel Ángel 20611541b1 fix(core): font lookup on a Mac no longer waits 5 s on system_profiler (#5810) 2026-10-17 18:04:15 +00:00
Miguel Ángel c38e9f350b fix(core): a composition mounted twice keeps each mount's own timeline (#5729) 2026-10-17 00:47:28 +00:00
Miguel Ángel 49c49504a4 fix(studio-server): an edit aimed at a clip never lands on an audio group sharing its id (#5645)
* fix(studio-server): an edit aimed at a clip never lands on an audio group sharing its id

* refactor(studio-server): one comment line, the rest in names
2026-10-15 08:19:28 +00:00
Miguel Ángel db845d9409 fix(cli): retire old snapshot run folders (#5635)
* fix(cli): retire old snapshot run folders

* chore: satisfy snapshot comment limit

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

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

* fix(studio): verify snapshot leases from open descriptors
2026-10-15 04:59:59 +00:00
Miguel Ángel 916ec37755 fix(studio-server): history copies a changing file without stalling its sweep (#5627)
* fix(studio-server): history copies a changing file without stalling its sweep

* fix(studio-server): a failed history copy leaves no partial file and keeps the mode

* style(studio-server): name the history copy for what it does
2026-10-15 03:16:32 +00:00
Miguel Ángel d65eefad5e feat(cli): init --keep-original copies a clip as is, without transcoding (#5612)
* feat(cli): init --keep-original copies a clip as is, without transcoding

`hyperframes init --keep-original` copies the --video file under its own
name and wires it into the project the way a web-safe clip is wired today:
no transcode and no prompt, interactive or not. Default behaviour is unchanged.

Theora leaves init's web-safe codecs and joins the preview proxy's
browser-hostile codecs, so init converts it and the preview proxy serves an
H.264 copy instead of a blank picture.

* fix(studio-server): keep the codec table's comment share at its base level
2026-10-14 18:19:17 +00:00
Xuanru Li 45825faa5d fix(inspector): grep timeline windows, continuation, candidates and timeline text from the Canary dogfood (#5595)
* fix(inspector): report nested hosts' authored window, one location per source line

The runtime strips data-duration/data-end from nested composition hosts after
boot and keeps them as data-hf-authored-*. Timeline inspection built its
resolver without authored attrs, so a host with data-duration="4" reported
its GSAP timeline length (3.72s) instead.

Timeline text and events[].sources listed the same file:line once per mapped
snippet (creation, caller, dependency), so a one-line script printed one
location up to nine times.

* refactor(inspector): carry the dedupe reason in the helper's name

* fix(inspector): end continuation on a page that fits no snippet; fill budget with candidates

* fix(inspector): timeline groups by component, names attr values, eases, film end

* fix(studio-server): stamp hf ids into a file without reformatting it

* fix(inspector): find an element's tag before its served hf id reaches the source

* fix(inspector): live element map shows authored classes and text, not the playhead's

* fix(inspector): collapse script-built text so the element map ignores the playhead

Class names stay in source order, a runtime-stripped id is shown as id="…"
rather than a selector, and a grouped row names which children it covers.

* fix(inspector): keep paging, stamping, and the live map still on the reported edges

A snippet larger than the page ended the walk, so later snippets could not
be reached. Raw text ended at a longer tag name, which could stamp an id
inside a script. An empty or mixed source element showed live text, and an
unknown duration read as a film that ends at 0s.

* fix(inspector): mint ids for an empty hf id and keep an unquoted class

An empty data-hf-id was treated as already assigned, so stamping left it
blank while the older stamper minted one. An unquoted class was read as no
class, so the live map dropped it.

* fix(inspector): narrow markup before reading a child tag

skipMarkup returns a boolean, so the build still sees Markup where a tag
is required. Narrow on the kind first so the inspector typechecks.

* fix: quiet check false positives and name the ancestor of an id-less target

Endpoint contact, disjoint fromTo pairs, and a negative z-index under a
stacking-context parent were warnings with bad advice. Layout and contrast
blamed a dot grid, a circular avatar's panel, and a visibility override.
A faint texture veil is no longer the click target.

* fix(lint): drop a history sentence and six gsap comments

The comments check treats "load-bearing" as history, and gsap.ts added six
comment lines. The names already carry what those lines said.

* fix(parsers): ignore an empty id written inside another attribute

The empty-id scan matched the first data-hf-id="" in the tag, including one
inside a quoted title. It now skips quoted text, so the real attribute is minted.

* fix(lint): keep real covers and a negative z-index under a card

A gradient scale-down treated every fill like a radial dot, so a panel
that starts transparent stopped counting as a cover. A stacking context
on any ancestor cleared a negative z-index, and a faint wrapper made
images inside it unpickable.

* fix: keep an emoji from shifting a stamped id

The quote mask replaced one emoji with one space, so the id landed before
the closing quote. A radial cover that turns opaque after a clear stop
stays fully painted.

* fix(lint): score a letter-only dot as a dot, not a color name

namesAColor saw #fff and rgba as words, so those dot grids counted as a
full cover. Hex and rgb colors are stripped before that word check.
2026-10-14 09:16:20 +00:00
James RussoandClaude Opus 5.5 ab8a4da53f feat(studio-server): accept crf, videoBitrate, videoFrameFormat and hdrMode on the render route (#5549)
* feat(studio-server): accept a crf override on the render route

The Studio render route only forwarded draft/standard/high, so a Studio or
desktop export could not ask for a specific CRF the way `hyperframes render
--crf` can. Film-grain finishing passes need CRF 12 to keep the grain.

The route now takes an optional integer `crf` (0-51 for mp4, 0-63 for webm),
rejects an out-of-range value with 400 instead of dropping it, ignores it for
mov, and both Studio adapters pass it to the render job.

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

* chore(studio-server): keep the crf change free of new comment lines

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

* feat(studio-server): accept videoBitrate, videoFrameFormat and hdrMode on the render route

Same options `hyperframes render` already takes (--video-bitrate,
--video-frame-format, --sdr/--hdr), so Studio callers are not limited to
the quality presets. crf and videoBitrate are an either/or, matching the CLI;
an invalid value is a 400. Parsing moves to helpers/encoderOptions.ts.

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 20:09:58 +00:00
Xuanru Li 6f76b30675 fix(inspector): stop audio waveform caches from making every live question stale (#5536)
* fix(inspector): stop audio waveform caches from making every live question stale

* fix(inspector): drop the comment and format the test

* refactor(core): share the folders Studio's preview ignores

* fix(core): register the project-folders subpath and bundle it into the inspector
2026-10-13 16:01:05 +00:00
Xuanru Li 23d1fe5b42 feat(cli): save check, snapshot and render artifacts per revision (#5522)
* feat(cli): save check, snapshot and render artifacts per revision

* fix(cli): keep clean --snapshots inside the project

A symlinked .hyperframes or artifacts folder was followed, so the command
deleted snapshot and check files outside the project.

* test(cli): keep check, snapshot, and render successful when recording fails

A held history or a corrupt output registry warns and leaves the command result alone.

* fix(cli): drop snapshot records when a replaced run is not recorded

A reused --output rewrites the files even when history is busy.
Those records are removed so the old revision does not keep the new pixels.
The preview checkpoint stops waiting after five seconds.
A bad PNG header no longer fails the snapshot.

* fix(studio-server): keep the video-state comment share from rising

The forget helper's doc comment pushed the file above its base share.

* fix(cli): post the preview checkpoint and keep cleanup inside artifacts

A preview checkpoint with no body was sent as GET, so Studio never ran it.
Cleanup now requires the real path to stay under the artifacts folder.
A failed follow-up drop does not warn twice.
The studio-server unpacked budget covers the record-dropping helper.

* docs(cli): shorten the snapshot skill notes

The command examples already show range and composition.
The prose only keeps the output path and the flags that are easy to misuse.

* chore: leave package headroom for artifact cleanup

* test(cli): compare artifact paths portably

* style(cli): format portable artifact assertion
2026-10-13 06:20:31 +00:00
Xuanru Li 893b4f6757 feat(studio): record video state, edit targets and artifact cleanup in project history (#5524)
* feat(studio): record video state, edit targets and artifact cleanup

* fix(studio): clear video-state CI gates

* fix(studio-server): pin cleanup reads to file descriptors

* chore: account for cleanup descriptor code in package budget
2026-10-13 05:12:34 +00:00
Xuanru Li 7c73b3caee feat(cli): guard history turns with --exclusive, --who and --if-open (#5518)
* feat(cli): guard history turns with --exclusive, --who and --if-open

* fix(cli): make exclusive history turns atomic

Re-read the open turn only after acquiring history ownership so
concurrent exclusive begins cannot both claim the same project.
Cover the race with a deterministic two-process regression test.

* fix(cli): refuse a second exclusive turn while a preview is open

The preview's begin window now rejects another agent's live turn, and an
idle turn no longer blocks exclusive begin. The two-process test waits on
pids collected from claim drafts instead of seeing both files at once.
2026-10-13 04:22:41 +00:00
Miguel Ángel aa9413b05e fix(studio): splitting a video in an untimed wrapper cuts the video, not the wrapper (#5486)
The split timed both wrapper copies and left both inner videos on the
original window with the same id, so the two halves played together.
2026-10-12 15:39:41 -07:00
Miguel Ángel bf7f6deae9 fix(cli): check warns when a video's browser-playable copy cannot be made (#5441)
* fix(cli): check warns when a video's browser-playable copy cannot be made

* fix(cli): big projects stop failing proxy prep, and a failed copy is reported once

* fix(cli): proxy failures name the real ffmpeg error, and a broken ffmpeg is reported once

* chore(studio-server): shorten the batch proxy resolver doc comment
2026-10-11 02:02:10 +00:00
Miguel Ángel 489036c843 fix(cli): history reads answer while another app has the project open (#5445)
* fix(cli): history reads answer while another app has the project open

* refactor: Fewer comment lines in the history read path

* refactor: Keep a history comment within the line limit
2026-10-11 01:47:31 +00:00