Commit Graph
235 Commits
Author SHA1 Message Date
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
Miguel Ángel 9825614d91 fix(studio): Studio's own text reads without em or en dashes (#5414)
* fix(studio): text in Studio reads without em or en dashes

* fix(studio): the dash check also catches HTML entities, and peak reads Unavailable

* test(studio): the dash check test covers every entity form

* fix(studio): messages Studio shows from lint, sdk, core and the renderer drop dashes too

* fix(producer): the gate check stops early on the engine's new fallback wording
2026-10-10 19:08:30 +00:00
Miguel Ángel 165be85132 fix(cli): timeline split keeps a sub-composition's place in both halves (#5410) 2026-10-10 08:05:14 +00:00
Miguel Ángel e158c73a9e fix(studio-server): the public proxy cache path names boxed preview copies too (#5407)
* fix(studio-server): the public proxy cache path names boxed preview copies too

* fix(studio-server): keep the proxy cache path docs within the comment budget
2026-10-10 05:22:52 +00:00
Miguel Ángel 91f9c9eb04 feat(studio): export can encode on the GPU, like render --gpu (#5397)
* feat(studio): export can encode on the GPU, like render --gpu

* test(studio): the 4K export assertion carries the GPU flag

* refactor(studio): the GPU flag needs no comment beyond its name

* fix(studio-server): only MP4 exports encode on the GPU, and render errors report it

* fix(studio): the disabled GPU option dims like the other export controls
2026-10-10 04:45:05 +00:00
Miguel Ángel 063c2f8d61 fix(studio-server): a failed save reports its real error when a later file was never written (#5364)
* fix(studio-server): a failed save reports its real error when a later file was never written

* refactor(studio-server): share the file writer type and harden rollback test cleanup
2026-10-10 00:49:37 +00:00
Miguel Ángel 5ec2dd2e12 fix(lint): check passes valid sub-compositions and scene-relative asset paths (#5337)
* fix(lint): check passes valid sub-compositions and scene-relative asset paths

* fix(lint): keep the browser lint build free of node modules

* refactor(lint): tidy sub-composition lint helpers and guard bare node imports

* fix(lint): resolve stylesheet asset paths from the stylesheet's own folder

* fix(lint): catch every written form of a path that climbs out of the project

* fix(parsers): sub-composition asset paths written with backslashes load in preview

* fix(parsers): an encoded backslash stays in the file name and a leading backslash stays absolute

* fix(lint): an encoded %2f counts as a separator when checking for a climb out of the project
2026-10-09 19:54:58 +00:00
Miguel Ángel 5eb2a96fc2 fix(studio-server): the preview's proxy copy jumps queued thumbnails, and abandoned copies drop (#5340)
* fix(studio-server): the preview's proxy copy jumps queued thumbnails, and abandoned copies drop

* fix(studio-server): a re-ask right after a dropped proxy copy gets a fresh copy

* fix(studio-server): leave Studio's preview route on the normal proxy queue

* docs(studio-server): shorten the proxy transcoder's detachment note

* fix(studio-server): a refused proxy copy still invalidates thumbnail captures
2026-10-09 19:17:46 +00:00
Abdullah M. Nazif 1449d0d2a1 fix(cli): normalize the audio passage the clip plays (#5222) 2026-10-08 21:29:17 +00:00
Miguel Ángel 621ddd4693 fix(studio): stop file names with %, apostrophes or $ from breaking Studio (#5156)
* fix(studio): serve files whose name has a bare percent sign

A browser sends src="100%.png" with the % unescaped, and decoding the whole path threw, so Studio answered 500. Only well-formed escapes are decoded now.

* fix(cli): serve files with a bare percent sign from the check and snapshot server

One shared decode rule for request paths; the image thumbnail route's malformed-escape 400 could no longer fire, so a name that is not a file is a 404.

* fix(cli): name a failed file with a bare percent sign in check

* fix(studio): keep apostrophes and dollar signs in file names when editing attributes

An attribute value is matched by its own quote kind, so src="it's.png" is replaced instead of duplicated, and every insert uses a function replacer, so $1 or $& in a file name is written as is.

* fix(studio): rename references spelled with %20 or &amp; and read quoted media sources

WIP: not yet tested; see the resume note.

* fix(studio): match whole attribute names when editing media sources

* fix(studio): compare decoded names and escape replacement references

* fix(studio): preserve file references across URL and string consumers

* docs: clarify raw file references and URL fields

* fix(studio): keep raw path identity distinct from URL spelling

* fix: share attribute grammar and decode media URLs at their readers

* fix: preserve local filename identity in HTML URL output

* fix: decode entity references before rename candidate matching

* style: format rename entity handling and regressions

* fix: preserve platform file identity when renaming references

* fix(core): decode CSS escapes before rebasing asset URLs

* fix(core): consume CSS continuations before URL encoding

* fix(core): preserve CSS URL identity and escape rebased suffixes

* fix(core): express CSS suffix controls with Unicode categories

* fix(core): preserve URL backslash identity while rebasing

* test(core): respect platform backslash filename semantics

* fix(studio): a dropped file with %, # or ? in its name loads, and an encoded reference renames

* fix(studio): read an authored src as a URL so dropped files with spaces, %, # or ? load

* fix(studio): media routes find files named with ? or #, and the probe reads a src as a URL

* fix(studio-server): one rule for request file paths, so loudness finds files named with %, # or ?

* chore: drop the design note from the change

* fix(core): rename replaced clips whose src is URL-encoded
2026-10-08 16:37:49 +00:00
Kenny 4d61c9ff2f fix(studio): delete renders after job cache expiry (#4825) 2026-10-08 09:04:07 -07:00
Miguel Ángel de4b31a262 fix(studio): update unreferenced generated media names on replace (#5169)
* fix(core): derive generated media labels from current source

* fix(core): decode generated media filenames

* fix(core): share filename decoding across URL forms

* fix(core): strip filename suffixes without unsafe indexing

* fix(studio): rename unreferenced generated media ids on replace

* fix(core): register media ID exports and trim names in linear time
2026-10-08 10:38:12 +00:00
Miguel Ángel b66e8a195e perf(studio): preview decodes hard-to-play videos at the size they are shown (#5184)
* perf(studio): preview decodes hard-to-play videos at the size they are shown

Studio's copy of an HEVC or ProRes clip was made at the source's full size, so a 4K clip was decoded at 4K inside a preview a few hundred pixels wide, and back-to-back 4K clips flashed the background at their cuts. The player now reports how large it shows the composition, the runtime asks for a copy sized to the shown video (on a ladder of sizes, never above the source), and Studio stops pre-warming source-size copies. CLI play, static serving, publish and renders keep the source-size copy.

* fix(studio): sized preview copies follow zoom, hover previews and the newest size

Studio reports the preview's on-screen scale when a zoom settles and for composition hover previews, which no player measures. The runtime loads only the newest size asked for, regrows a first copy made while the frame grew, and never replaces a copy with one smaller on either side. The player's scale is measured against the frame's own layout width.

* fix(studio): keep comment shares and file sizes within the repo ratchets

* test(studio-server): end the sizing test's ffprobe options before its input

* fix(studio): hover previews report their size once their runtime is ready
2026-10-08 00:26:08 +00:00
Miguel Ángel dbb8222c8d fix(studio): catch up the preview after missed file changes (#5126)
* fix(studio): recover previews after missed file changes

* fix(studio): keep queued reloads with the current owner

* fix(studio): keep a queued outside change when switching compositions

* test(cli): the project watcher closes its parent folder's watch too

* fix(studio): keep the project-folder change out of the shared signature check

* fix(studio): keep the edited file's draft and reload every session after a reconnect

A reconnect or replaced folder is a project-wide change: every open SDK session reloads, a failed save is recorded against the file the draft belongs to, and two queued changes to different files merge into one project-wide reload.

* fix(studio): refresh the preview cache when the .hyperframes folder is replaced

* fix(cli): report the files of a folder moved into the project

Replacing a populated folder such as .hyperframes/ now reaches the signature cache through its files, so the shared signature rule stays as on main and Studio's own writes inside .hyperframes/ still invalidate nothing.

* refactor(studio): name the merged file-change payload and share the test sender

* fix(studio): keep a failed draft's recovery copy until that file itself saves

* style(studio): rewrap the merged payload line after the rename

* fix(studio): deliver a held change's full reload when its block clears

A clean save, Use external and Keep Studio now all deliver the merged scope a blocked change owes, and a moved-in .hyperframes folder reports its manifests so a dropped one still clears the cached signature.

* fix(studio): keep restored drafts out of the reload scope and clear the lint gates

* fix(studio): retain held reload scope before React renders
2026-10-07 07:32:16 +00:00
Miguel Ángel dc244d765c fix(studio): drags after a resize, undo or reload land where they were pressed (#5088)
* test(studio): bench moves pressed right after Add keyframe

* fix(studio): an undo that reloads the preview keeps the selection, so a drag after it moves

* fix(studio): a drag started while a resize saves keeps its animations paused

* fix(parsers): an edit pins no position on a keyframed tween it did not touch

* fix(studio): a move pressed while the preview changes lands on the new preview, at the time pressed

* test(studio): bench waiting presses, a held box resized twice, and reloads at the last playhead

* refactor(studio): tidy the waiting press, bound its wait, and freeze edit stamps after early exits

* test(studio): load the property panel once so its first test stops timing out

* fix(studio): presses queued on a held box keep their release, hover and newest outline

* test(studio): judge bench undo by the history groups Studio claims, not by file writes

* refactor(studio): one waiting-box lookup in the bench and one raf-pause write per release

Behaviour-identical tidy of the waiting-press work: the bench reads the waiting
box through one helper, releaseActiveGesture sets the raf pause once, clearPointerState
writes the false it always computed, and previewGestureStarts calls the opts setters
directly instead of through aliases named like the gesture hook's gated setters.

* fix(studio): a queued press lands on the element it pressed and survives a window switch

* fix(studio): undo cancels a waiting press, a stuck save stops holding presses, scene swaps hold them

* refactor(studio): keep the complexity waiver on clearPointerState itself

* fix(studio): undo of a queued press leaves nothing waiting, and hosts keep their own undo

* fix(studio): a waiting press keeps the box still after a blur, and undo reports what it took back

* style(studio): bring the PR's comments within the comment limits

* fix(studio): bank the four bench cases this fixes and meet the comment, complexity and duplication gates

* test(studio): pin that a waiting press is never a pending edit, and name the new duplication waivers
2026-10-06 20:22:37 +00:00
Miguel Ángel 5fad52f21d fix(studio): an offline preview says GSAP could not load instead of an unhandled Event (#5111)
* fix(studio): an offline preview says GSAP could not load instead of an unhandled Event

* fix(studio): the preview error list survives a second capture attach without duplicates

* fix(studio): the console capture keeps listening after a long early error list

* refactor(studio): the console capture owns every reset of its findings
2026-10-06 16:35:11 +00:00
WaterrrForever 32516b5a3d fix(studio-server): keep the desktop app's hand-off and chat history out of the file routes (#5113)
* fix(studio-server): keep the desktop app's hand-off and chat history out of the file routes

The file and preview routes reach any path inside the project. The desktop
app keeps two files there that hold the person's agent session and their
chat: .hyperframes/agent-handoff.json and .hyperframes/app-history.jsonl.
No route now reads, writes, renames onto, duplicates or uploads over them,
by any spelling or through a link. The rest of .hyperframes/ (Studio's
motion file, prepared GIF assets) is served as before.

* fix(studio-server): keep the desktop app's run log out of the file routes too

The app records every chat run, the person's words included, in
.hyperframes/agent-runs.jsonl. Only the app's main process reads it.

* fix(studio-server): refuse everything under .hyperframes/ but Studio's own files

Review (Jerrai, Rames): a list of three names missed the read marks the
desktop app and the CLI also keep there, and renaming the folder reached
them under a new name. The check is now an allow-list: no route reaches
.hyperframes/ or anything in it, by any spelling or through a link, except
Studio's studio-motion.json, studio-manual-edits.json and prepared-assets/.
Studio's own internal files (backups, preview documents, history id) are
read by path inside the server, not through a request path.

The project's file tree leaves out the same paths. The upload test now
sends a file uploads accept elsewhere, so the folder is the only reason
it is refused.

* fix(studio-server): serve the app's request pictures, close the renders and rename paths

Review (Rames, Jerrai):
- Desktop loads each request's picture from .hyperframes/requests/ through
  the preview route, so that folder joins the allow-list. An allowed
  folder's own path is allowed too.
- The render-file route resolves against renders/, so a renders/ linked
  into .hyperframes/ reached the records. It now also checks the file
  against the project.
- A rename's reference update walked every JSON/MD file, records
  included, and could rewrite them. It now skips private paths.
- The allow-list reuses STUDIO_MOTION_PATH and STUDIO_MANUAL_EDITS_PATH.

* chore(studio-server): keep render.ts's comment share at the base

* fix(studio-server): list, view and download only renders the render-file route would serve

Review (Jerrai, Rames): the render list found files in renders/ with join
and statSync, which follow links, and registered them for /render/:id/view
and /download, which read them with no check. A project shipping
renders/leak.mp4 -> ../.hyperframes/app-history.jsonl had its chat served.
The list now keeps an entry only when resolveWithinProject accepts it and
it isn't private, and registers the resolved path. View and download serve
only a plain file, so a link swapped in after the list is refused. This
predates the PR; it's the same shipped-link case.
2026-10-06 13:20:02 +00:00
Miguel Ángel 59b17170a7 fix(studio-server): export the shared upload limit (#5105) 2026-10-06 04:52:55 +00:00
Miguel Ángel 0221dc1701 fix(studio-server): bound entity-quoted preview references (#5100) 2026-10-05 18:47:04 -07:00