After SIGKILL the test slept a fixed 5 s, then required every Chrome process
gone. On a loaded Windows runner the watchdog took longer and two processes
were still exiting. It now waits up to 30 s for none to be alive, so a real
orphan still fails.
* feat(registry): echo-smear engine and math tests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(registry): echo-smear demo
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(registry): register echo-smear
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(registry): echo-smear copies inherit visibility and record fast
Final review fixes. Copies were forced visible and escaped a hidden scene.
A selector matching earlier copies re-targeted them, and copies kept data-start and data-duration.
A later attach re-drew every earlier copy at each recording sample.
Tests now run the engine through a fake gsap and timeline, and catch five seam mutations.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(registry): echo-smear skips targets that are not out of flow
Found testing on real blocks: transitions-push and blue-sweater animate static elements.
Their copies stacked in normal flow and pushed the scene 64800px down, rendering black.
The snippet now warns and skips a target that is not absolute or fixed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(registry): echo-smear group keeps the target's z-index
The wrapper group formed a new stacking context at z-index auto,
so a target that sat above a sibling dropped below it (the cursor went under the card).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(registry): echo-smear copies live in a sibling group, like motion-blur
The target is no longer wrapped, so it keeps its parent, blend mode and pointer events.
Static targets smear in place; the group mirrors the target's visibility and z-index.
Copies carry resolved styles, media inside is inert, and an element is attached once.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(registry): echo-smear records resolved transforms and leaves callbacks alone
Poses come from the resolved transform and opacity, so xPercent and any other transform survive.
Travel is measured on the element's own box, so ancestor-only motion draws no copies.
The recording sweep is quiet unless callbacks: true, survives a missing root, and releases its flag.
Copies share one style rule per target and are built to the peak count.
The demo is pinned to the snippet by a test.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(registry): echo-smear copies follow the box, root and look of their target
Copies hidden at a box change never caught up, so a static target drew at the corner.
A shadow-root target threw on every seek; its rules now live in its own root.
Copies show the recorded filter, clip-path and individual transform properties.
No ResizeObserver, so a frame no longer depends on the first frame a worker drew.
Parent perspective, border-box sizing, media children and SVG children are handled.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(registry): echo-smear leaves real GSAP callbacks unfired at attach
Covers the blocker from review: a call or onComplete on the timeline fired during attach.
Runs against real GSAP 3.15 with a removal callback; it fails if the sweep seeks with events on.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(registry): echo-smear frames do not depend on the seek path
Group visibility and the copies' box are recorded in the sweep, not read live in the update.
A backward seek renders the update before the target, so the live read was stale.
Individual translate, rotate and scale are folded into the measured transform.
An element named twice in one call is attached once.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(registry): echo-smear resolves a percentage translate against the box
A computed translate of 150% was read as 150px, so count and copy placement were wrong.
Percentages are now a share of the element's own width and height; a scale percentage is a factor.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(cli): snapshots, compare and check wait for late images the way render does
* fix(cli): check runs its browser pass inside a macOS sandbox that does not name itself
Every CLI Chrome launch has used a pipe since the warm-check change. A pipe
launch that a seatbelt kills fails with only "Protocol error
(Target.setDiscoverTargets): Target closed". The one-process fallback keyed
on Chrome's bootstrap_check_in stderr, which a pipe launch failure never
carries, so it never fired. On macOS a pipe launch that fails at the
protocol level now relaunches as one process over the pipe.
* fix(cli): retry only a pipe launch whose connection closed, and keep puppeteer lazy
* perf(producer): the true-peak correction of long audio encodes in parallel segments
* perf(producer): a cloud plan corrects its audio peak from the mix itself, one lossy pass
* fix(producer): only the PCM-sourced peak correction splits; AAC input stays one encoder
* perf(engine): cloud renders cut a video's audio from its URL instead of downloading the file
* refactor(engine): keep the audio mixer's comment share at the base
* fix(engine): a cloud-held file's audio probe retries once, then fails instead of mixing unprobed
* fix(engine): a cancelled probe is not retried, and the encode test noise is seeded so it repeats
* fix(cli): the browser the CLI launches exits with it, even on kill -9
* fix(build): the root build runs on Windows
* perf(cli): a repeat check runs in a process that already has the CLI and Chrome up
* fix(cli): a warm check delivers all its output before it exits
* fix(cli): the warm check only talks to a server that proves it is the user's
The warm socket now lives in a private, owner-verified directory (XDG_RUNTIME_DIR,
~/.cache or LOCALAPPDATA) next to a 0600 token. Client and server prove the token to
each other over fresh nonces before any request is sent, the request carries an
HMAC of the environment instead of the environment itself, and frames are capped.
* fix(cli): a warm check prints its stdout even after something went to stderr
The client stopped writing frames once any frame had been written, so a check that
printed a notice to stderr first lost its JSON report.
* test(cli): list the warm-check client fixture as a fallow entry instead of an inline ignore
* fix(cli): a check started while a warm one is busy runs cold at once on Windows
The warm server stopped listening when it accepted a client. On Windows the pipe name stays alive through the served connection, so a second client's connect saw a busy pipe and waited 30 s for a free instance before running cold. It now keeps listening while serving (accept already declines anyone else) and stops when the served check finishes.
* test(cli): the orphan test reads the check's own Chrome and watchdog instead of racing a poll
On Windows each process listing is a PowerShell call of about a second, so a check that finishes quickly could start and close Chrome between two polls, and the test failed its own 'Chrome was seen' precondition. A fixture now records the Chrome tree and the watchdog from inside the check when its first page opens.
* fix(aws-lambda): lambda sites create uploads source files over 2 GiB and 5 GB
Site ids are hashed in fixed-size reads instead of whole-file reads, so files
over 2 GiB no longer fail; every existing site keeps its id.
Files over S3's 5 GB single-PUT limit upload in parts that S3 checks one by
one, and the object is created only after the whole file's SHA-256 matches.
Content-store refresh and render-time verification handle those objects too.
* fix(aws-lambda): keep small-site hashing as fast as before
Reads use a 256 KiB buffer: the 8 MiB one made hashing small projects slower,
and big files hash no slower with the smaller buffer.
* perf(aws-lambda): stream big uploads from disk with 8 parts in flight
Each part is hashed from disk and then streamed from disk instead of held in
memory, so a 5 GB upload peaks near 150 MB instead of 500 MB. Eight parts go
up at once, and a part whose connection drops is resent from a fresh read.
The root (and propagated body) background paints the page canvas whatever its
visibility, so an animated page background could make a still scope read as
moving. Scoped captures now clear those backgrounds and keep the resulting
transparency, and the frame comparison reads alpha, so dark paint revealed on
the cleared page still counts.
* test(studio): real-Chrome tests recover from a stuck Chrome launch or close
* test(studio): retry a stuck Chrome launch that Windows reports as already running
* fix(engine): a sharp sound in a film's first moments no longer grows louder with each audio pass
Every AAC encode now starts in digital silence that is dropped afterwards. Opening cold on quiet sound, the native encoder coded the first loud frame three scalefactor steps hot (4.5 dB), and each re-encode (pad, true-peak correction) did it again.
* fix(engine): AAC encodes stay in step with audio that is not 48 kHz before the encoder
* test(producer): regression goldens for audio that now opens without the encoder's cold start
* 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
* fix(cli): check no longer calls films moved by SVG masks, strokes or canvas static
The frozen-sweep guard compared DOM-channel fingerprints only, which never see
SVG mask and clip contents, stroke dashes, or canvas and WebGL pixels. Before
reporting sweep_static, check now compares the rendered frames at the same
sample times; a still verdict stands only when every frame is identical.
* fix(cli): keepsMoving also trusts rendered frames, compared above render noise
keepsMoving read the same DOM liveness channels as the sweep guard, so mask,
stroke and canvas motion failed it as frozen. A span it calls frozen now gets
a frame capture every half maxStaticSec, and spans split where the frames
change. Frames are compared in the page, decoded like the contrast pass, and
count as moved only when at least 16 pixels change by more than 24/255, so
antialiasing and dither noise cannot make a still film pass.
* fix(cli): scoped frame checks see only the scope's own paint
A keepsMoving scope's capture now hides every box outside the scope's subtree,
so a still title over a moving background stays frozen. A span marks only the
frames that still hold its signature, so a span that starts at a cut keeps its
first frame. A scope that matched nothing stays motion_selector_missing. The
sweep counts a frame change only if the first sample time still renders its
first frame, so wall-clock paint is not read as motion under seek.
Color grading paints a video or image into a canvas the runtime inserts right
after it. The layout audit treated that canvas as authored canvas art: the
video's data-layout-* opt-outs never reached it, and canvas_content_at_edge
read full-bleed footage as drawn content cut by the frame. Opt-out lookups now
resolve a grading canvas to its source element, and the edge rule leaves
grading canvases alone, as it does the ungraded video.
* test(player): measure heavy films in real Safari and Chrome on macOS
* test(player): count runtime media seeks and rate writes during the Safari bench
* test(player): compare the Safari bench against native playback of the same media
* test(player): split Safari control playback into per-frame media writes and per-frame transforms
* perf(core): write media volume and mute only when they change
* test(player): presented frame rate and compositor-animation controls in the Safari bench
* perf(core): keep the media volume guard without a new comment line
* test(player): derive presented frames from playback counters and snapshot video state
* test(player): control page plays only listed media, and anchor the Safari process match
* test(player): send the bench as code, and write only validated numbers from the browser
* fix(player): perf host page gives the player a real box, so films are on screen when measured
* fix(player): a player with no height set takes its film's shape instead of collapsing to nothing
The player box had no intrinsic height, so an embed that set only a width (or nothing) was 0 px tall
and showed no picture. An in-flow sizer now carries the composition's aspect ratio; a height the
embed sets still wins, and a height-only embed keeps its full width.
* test(player): label each embed on the player box page
* 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.
The single-worker inversion and the parallel router decide before capture
whether drawElement will run, but the engine's init-time gates (CSS
effects, at-risk timelines, SwiftShader, ...) only ran when the
conditional browser probe did. Most renders skip that probe, so a comp the
engine then refused was still pinned to 1 (or 3) workers and captured by
screenshot on that pinned count.
When either route is eligible and no probe session exists, open the
capture session early and initialize it; a refused comp is not inverted
or routed, calibration runs, and it renders on the resolver's count. An
accepted comp hands the same session to capture. perfSummary carries the
probe's gate reason, since clamped parallel workers report none.
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.
* fix(producer): disk-space error suggests --workers 1 only when one worker would stream
* test(producer): streaming-stage suite mocks the new streaming-gate export
captureStage now asks explainStreamingEncodeGate for the disk-space remedy;
the bun module mock of renderOrchestrator in captureStreamingStage.test.ts
lacked it, so the suite failed at import.
* 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
* test: queue runs on slow windows runners stop failing four timing tests
* test(cli): the keepalive process test budgets for a cold start on a slow runner
* fix(cli): wait for consent navigation before cataloging assets
A consent click can reload the page (some CMPs reload once consent is
stored). The post-click DOM reads then raced the navigation: the overlay
sweep and scrollHeight probe ran against a torn-down or still-parsing
document, scroll screenshots degraded to a handful of shots, and the
asset catalog observed an empty body (0 assets).
Add waitForPageReady: wait for network idle, then poll document.readyState
so a committed-but-still-parsing reload is also waited out — network-idle
alone stops counting a request once its response headers arrive, and
waitForNavigation deadlocks when armed after the commit. The overlay
sweep now runs on the settled document instead of the pre-reload one,
and the same settle guards the asset catalog boundary.
Fixes#4923
* fix(cli): bound consent-click settle by wall clock and skip it on banner-free pages
waitForPageReady now shares one deadline (timeoutMs, capped by the
remaining capture budget) across its network-idle waits and readyState
poll, uses waitForNetworkIdle concurrency: 2 (the networkidle2 semantic
navigation already uses), races each readyState evaluate against the
deadline, and rethrows degradable evaluate timeouts so the stage can
degrade. captureScrollScreenshots only pays the settle when the consent
sweep clicked a control or performance.timeOrigin shows the document was
swapped; the catalog boundary call degrades to a budget-bounded
readyState check (networkIdle: after-load).
* fix(cli): drop added comments to keep comment share at merge-base
* fix(cli): adapt consent-settle tests to ScreenshotCaptureResult and keep comment share flat
* fix(cli): tolerate missing waitForNetworkIdle in consent settle
waitForPageReady called page.waitForNetworkIdle unconditionally; a
Page-shaped double without it throws a synchronous TypeError that
captureScrollScreenshots' catch-all reports as internal-error with
zero screenshots, breaking the partial-budget contract upstream's
coreExtractionPhase.test.ts exercises with a thin mock page. The idle
wait is best-effort, so make the hook optional and swallow its
failures; also only keep the readyState poll alive while the page
affirmatively reports "loading" so a non-boolean result cannot burn
the settle deadline.
* perf(cli): check splits its samples over a second Chrome when a core and memory are spare
* fix(cli): check keeps its result when the second Chrome fails, and counts container memory
* fix(cli): check's helper Chrome code keeps the files' comment share
* fix(cli): check's sampling comment fits the line limit
* fix(producer): render inside an agent's macOS sandbox no longer dies on uptime
The render work dir's owner stamp falls back to os.uptime() for a boot time
when /proc is missing. The seatbelt refuses uptime (uv_uptime EPERM), so every
render died with that error before Chrome started. The fallback is now
optional like the /proc read, and render reaches the existing message that
says to rerun outside the sandbox.
* fix(producer): a render never reclaims a dir whose owner it cannot place
Without a boot identity, or on Linux without the pid namespace, two missing
values matched, so on a shared output folder a same-hostname machine could
delete another machine's live work dir. Such a scope is now unknown, and the
dir is left alone; macOS, which has no pid namespace, still reclaims by boot.
* perf(producer): the plan's true-peak check runs in parallel segments
The plan's -1 dBTP check ran one single-threaded ebur128 pass over the whole
mix. It now scans fixed segments in parallel and reports the same printed
value: each later segment starts one 100 ms block early and drops that block
(the oversampler's mirrored start can invent a peak, and a scan never measures
its last 16 samples); the last segment falls back to one whole-file pass when
its dropped block alone could be the loudest. Segments decode from the file
start, since the AAC decoder carries noise-substitution state.
* fix(producer): a true-peak scan with no summary fails instead of reading 0 dBFS
A run whose stderr has no Peak line threw before the parser moved; it
parsed as 0 dBFS after, which would lower and re-encode audio that was
never measured. It throws again. When one segment scan fails, the others
are stopped, and only 48 kHz audio is split into segments, since ebur128
resamples any other rate.
* docs(producer): shorter true-peak doc comment
* fix(studio): keep an edit's preview waiting while the window draws no frames
An edit that reloads the preview loads it hidden and swaps it in once it
has drawn two frames. Its 45 s wait ran on the wall clock and paused only
when the page reported itself hidden, but a window behind others on some
Linux desktops draws no frames while still reporting visible. The swap
then gave up and the old preview stayed on screen until the next edit.
A wait budget in which no frame was drawn is no longer spent; the next
frame restarts the full budget, as showing a hidden tab already does.
* fix(studio): keep the frame-wait note in the budget comment
* fix(engine): long 4K video exports no longer time out while frames are still coming
* test(engine): the paced extraction test keeps a wide margin on a loaded runner
* fix(engine): long phone-video exports probe each source once instead of once per cut
* fix(engine): a failed source probe is retried, and probes share the extraction slots
* test(engine): synthesize the tone-mapped concurrency fixture with known input colour
* chore: release v0.8.180
* docs(release): describe chunk sub-ranges as groundwork for split retries
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: release v0.8.180 notes list the empty-frame fix
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
* revert(engine): render workers keep the media of FFmpeg-fed videos loaded again
This reverts the behavior change of #5795 (6ed89a1e4). Releasing the media of
videos whose frames come from FFmpeg leaves some worker pages that never paint,
so parallel renders fail intermittently with "Invalid JPEG input at frame N:
empty frame". The shared loopback test fixture it added stays: later tests use it.
* chore(ci): agree the deletion of the reverted release test
* perf(cli): snapshot decodes phone video frames at their on-screen size, several at once
* perf(cli): snapshot decodes phone video frames at their on-screen size, several at once
* test(cli): type the displayed-size snapshot cases instead of splitting strings
* feat(producer): render a distributed chunk as frame sub-ranges
renderChunk and renderChunkV2 take an optional frameRange inside the
chunk's slice, so a slow chunk can be retried as smaller parts rendered in
parallel. A part renders as a chunk of its own: capture, deferred video
frames, GOP size, frame names and the audio packets it carries all follow
the range. Omitting it renders the whole slice as before.
assemble and assembleV2 accept the ordered parts of a chunk in place of
its single output. DISTRIBUTED_RENDER_CAPABILITIES.chunkFrameRange
advertises both.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(producer): keep the chunk sub-range comments within the comment checks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): a paused preview puts a reloaded video back at the playhead
When a video's authoring proxy (or that copy's larger size) loads while the preview is paused, the element restarts at 0 and the paused transport never seeks it again, so a clip stacked with its own cutout showed two moments of the same person. Any media element's loadedmetadata now re-syncs a paused transport.
* fix(core): the reload listener's name carries its reason
* fix(core): keep the paused-video playhead re-sync out of renders
A render drives every video's frames itself. The re-sync now asks the
runtime's one render check (the render config or an injected frame)
instead of only the capture-started pair.
* feat(cli): lambda deploy works from the npm install, no repo checkout
Each release builds the Lambda handler zip on Linux, attaches it to the
GitHub release, and pins its URL and sha256 (plus the SAM template) in
@hyperframes/aws-lambda before npm publish. Outside a checkout, lambda
deploy downloads that zip, refuses it on a sha256 mismatch, and deploys
it with the packaged template. With neither a checkout nor a pinned zip
it fails up front with one message naming every way out.
* fix(cli): lambda deploy test mocks the pinned-handler reader, comments fit the checks
* test(cli): sam tests deploy from a SAM directory, not the repo root
* chore(aws-lambda): raise the package size budget for the pinned handler template
Measured with pnpm@10.17.1 pack on Linux. Release state (prebuilt/ written by pin-handler-zip): 77,307 packed / 285,654 unpacked. CI state (no prebuilt/): 70,104 / 253,390. Packed is the check's 5% headroom over the release size. Unpacked is 292,000 (2.2% over release) because a 5% budget would put the CI pack under the 85% ratchet.
* test(cli): anchor the docs link check in the lambda deploy fallback test
* fix(cli): lambda deploy logs the verified handler sha256 and keeps a presigned query out of errors
* fix(cli): a redirect-limit download error keeps a signed query out of its message
* test(cli): the redirect-limit leak test types its rejection
* docs(aws-lambda): header comments cut to what the code cannot say
* chore(aws-lambda): size budget fits both the CI and the release package
* fix(aws-lambda): old stacks are refused before any upload; stale local copies never re-upload
* docs(aws-lambda): where the CLI keeps a deployed stack's state
* fix(inspector): data-hf-id stamps alone do not make the live preview stale
The source fingerprint hashed html byte for byte, so stamping data-hf-id on disk
(Desktop does at project open and when a run ends) made every live query answer
stale_preview. The preview never reloads for an id-only write, because it already
served those ids, so the error lasted until some other edit. The fingerprint now
reads html without its data-hf-id attributes, the same rule Desktop's change
detection uses.
* fix(inspector): ignore HF IDs only for live previews
* fix(studio): a deselect made while an edit saves stays deselected
A save resyncs the selection it started with once the write returns. It now
does so only while that element is still the one selected, so a deselect or
a different pick made during the save is no longer undone. The preview-reload
sync now routes through the same resync instead of a copy of it.
* refactor(studio): drop two comments the names already say
* ci(repo): the packed consumer check stops failing on npm versions published minutes ago
* fix(repo): a same-day dependency bump still installs in the packed consumer check
* perf(producer): each distributed chunk downloads only its own slice of the plan audio
A v2 plan whose mp4 chunks carry their audio now cuts the mixed track into one
file per chunk (stream copy, the same cut the chunk made itself), and the
manifest sends each chunk only its own file. A chunk muxes its slice with the
timestamps the cut gave it, so its packets match the old in-chunk cut.
* refactor(producer): keep the audio slice comments within the file's comment share
* fix(producer): a sliced chunk muxes the same audio packets on every ffmpeg version
Re-muxing a stream-copied AAC slice changed each chunk's last packet duration
on ffmpeg 6 and 7 (the slice's own edit list trims it), which shortened the
stitched audio timeline by about 2 s on a 30-minute render. The plan now ships
the audio's moov once plus each chunk's packet bytes, and a chunk rebuilds a
sparse copy of the whole file and runs the unchanged cut on it.
* chore(producer): packed size budget to measured x1.05 after main's chunk sizing
* fix(cli): check and snapshot use the pinned Chrome, not an older one another tool cached
* fix(cli): keep the browser-order comment within the line limit
* fix(engine): renders that resolve their own browser follow the same pinned-first rule
* fix(engine): keep the browser rule's reasoning in the PR, not new comments
* fix: a shared-cache shell is used only if it starts, after the pinned build
* fix(engine): import the shared-shell pick, and probe shells with a blank page
* fix(skills): caption scripts launch the browser the CLI chooses
* chore(skills): regenerate the skills manifest for the caption scripts
* 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