* fix(producer): render names a data-composition-src that points at a folder instead of crashing
* fix(lint): name a data-composition-src that points at a folder instead of crashing
* fix(cli): compositions, keyframes and Studio insertion survive a folder composition source
* fix(lint): tell a folder composition source to point at the HTML file inside it
* fix: read a composition source through one file handle so its type check and read cannot race
* fix(parsers): report a named pipe as a composition source instead of blocking on it
* revert(parsers): open composition sources read-only again, which CodeQL reads as no file creation
* fix(parsers): open composition sources with a private mode, skip pipes, and treat bad paths as missing
* fix(studio): undo and redo swap the restored scene in place instead of reloading
* fix(studio-server): a preview asked for right after an undo shows the undone state
* fix(studio): an undo that restores a page outside every scene still reloads in full
* fix(studio-server): a preview built across a write is not cached under the old signature
* test(studio): a soft undo keeps the scene hashes so the next edit still swaps
* fix(media-use): record agent-made voice, music and sound effects in the project's media manifest
* fix(media-use): record in place only when --source says the agent made the file
* test(media-use): a rewritten agent file keeps one manifest record
* fix(media-use): leave a person's own sound effect unlabelled and mark locally made music generated
* refactor(media-use): record agent audio in-process through the shared manifest library
* style(media-use): format the in-process recording and refresh the skills manifest
* chore(skills): refresh the skills manifest for the media-use changes
* fix(media-use): report a broken ffprobe setting plainly and document the project sfx source
* style(skills): format the pipeline manifest assertion
* chore(skills): refresh the skills manifest
* fix(media-use): never replace a voice, music or sound file the person put in the project
* style(media-use): format the keep-person-files change
* chore(skills): refresh the skills manifest
* fix(media-use): a regenerated file gets a fresh record, and lookups skip the replaced one
* test(media-use): a replaced record no longer answers a prompt lookup in the CLI
* chore(skills): refresh the skills manifest
* chore(skills): refresh the skills manifest from a clean tree
* fix(media-use): never give a recorded file the id of a download in flight
* chore(skills): refresh the media-use manifest hash
* fix(media-use): say when audio lands on a new name, and never let two lines share one
* style(media-use): format the keep-files change
* chore(skills): refresh the media-use manifest hash
* fix(media-use): keep two sound effects off one file when a name is taken
* refactor(media-use): drop a set the one-at-a-time downloads never needed
* chore(skills): refresh the media-use manifest hash
* fix(media-use): keep two sound effects off one recorded file on a rerun
* style(media-use): format the rerun test
* chore(skills): refresh the media-use manifest hash
* fix(media-use): list only a file's newest record in candidates and the media index
* revert(media-use): leave promote as it was, nothing calls it
* test(media-use): give the index test's two assets their own files
* chore(skills): refresh the media-use manifest hash
* chore(skills): regenerate the manifest after merging the base branch
* fix(core): a figma import lists each media file once, like media-use
* feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk
`hyperframes clean` lists leftover render work dirs, debug renders, the history of projects
that no longer exist, idle video proxies and extracted video frames, with sizes, and removes
them. `--dry-run` removes nothing, `--json` is for agents, `--snapshots` also removes
snapshot folders (otherwise listed as reclaimable). It never touches outputs, sources or
anything a running render or preview still owns.
* docs(cli): say that clean does not search a project's subfolders
* fix(cli): clean test compares folder names on Windows and the skills manifest is regenerated
* fix(studio-server): the proxy cache sweep removes only files the transcoder named
It removed any idle .mp4 or .webm in .transcode-cache, so a user file placed there, or reached through
a symlinked cache folder, could go. Proxies are always <sha256>.<ext> and their temp files
.tmp-<uuid>-<proxy name>; nothing else is swept.
* style(producer): keep a renderDirOwner doc comment within 120 columns
* perf(cli): load each command's code only when that command runs
* fix(cli): honest bench percentiles, and no unused registry entry in the split build
* fix(cli): the startup bench is a package script and stops on a failed run
* style(cli): format the bench's failed-run check
* fix(cli): a notice module that fails to load cannot fail the command
* fix(media-use): record agent-made voice, music and sound effects in the project's media manifest
* fix(media-use): record in place only when --source says the agent made the file
* test(media-use): a rewritten agent file keeps one manifest record
* fix(media-use): leave a person's own sound effect unlabelled and mark locally made music generated
* refactor(media-use): record agent audio in-process through the shared manifest library
* style(media-use): format the in-process recording and refresh the skills manifest
* chore(skills): refresh the skills manifest for the media-use changes
* fix(media-use): report a broken ffprobe setting plainly and document the project sfx source
* style(skills): format the pipeline manifest assertion
* chore(skills): refresh the skills manifest
* fix(media-use): a regenerated file gets a fresh record, and lookups skip the replaced one
* test(media-use): a replaced record no longer answers a prompt lookup in the CLI
* chore(skills): refresh the skills manifest
* chore(skills): refresh the skills manifest from a clean tree
* fix(media-use): never give a recorded file the id of a download in flight
* chore(skills): refresh the media-use manifest hash
* fix(media-use): list only a file's newest record in candidates and the media index
* revert(media-use): leave promote as it was, nothing calls it
* test(media-use): give the index test's two assets their own files
* chore(skills): refresh the media-use manifest hash
* fix(core): a figma import lists each media file once, like media-use
* feat(catalog): shelve 3D objects and cursors in their own catalog sections
Carries the generator grouping rules from #4310 and #4308.
* feat(registry): add a starfield ambient overlay
From #4313.
* feat(registry): add a falling snow ambient overlay
From #4331.
* feat(registry): add a torn-paper-rip transition
From #4321.
* feat(registry): add a paper-collage-reveal transition
From #4322.
* feat(registry): add a curtain-reveal transition
From #4323.
* feat(registry): add 3D objects: product turntable, glass hero, exploded stack, extruded logo
From #4310.
* feat(registry): add eight cursor animations and the dart cursor
From #4308.
* fix(catalog): find items by everyday editor words and accept a bare search word
From #4311.
* chore(registry): remove the simulated-cursor component
The Cursors section's new pointer animations replace it (from #4308).
* fix(registry): take cursor-zoom-follow's screenshot from runtime variables only
The declared default is empty, so page markup never becomes an image URL.
Clears the code-scanning alert raised on #4308.
* docs(cli): show a catalog search example that finds items
"smoke" matches nothing in the catalog; "crossfade" finds fade-through.
* chore(ci): allow the simulated-cursor removal in the deletion guard
* fix(ci): skip tracked files missing from disk in the player CDN pin check
Catalog regeneration removes a deleted item's pages before the publish PR untracks them,
so the check crashed on every catalog deletion PR.
* chore(cli): drop comments that narrate catalog search history
* fix(registry): drag-drop keeps the family arrow and shows the drop slot only while hovering
* fix(registry): agent cursor labels its steps in an accent tag, no glow or sparkles, in 4.8 s
* fix(registry): annotate cursor draws a tapered marker stroke and clears the circled value
* fix(registry): show real file thumbnails and end the context menu demo sooner
* fix(registry): give marquee select real photos, a Finder-blue marquee and a tighter loop
* fix(registry): centre the text-select toolbar on the selection and tighten its timing
* fix(registry): selection cursors default to the macOS system blue
* style(registry): format the agent and annotate cursor sources
* fix(registry): dart-cursor stops covering the choice it makes and shows the export finishing
* fix(registry): frame the product turntable fully and light it with two studio softboxes
* fix(registry): touch-indicator taps like a real phone and the post it taps gets liked
* fix(registry): press-ripple confirms with a check and small click rings at the cursor
* fix(registry): exploded stack separates at once and marks each layer in blue as it is labelled
* fix(registry): extruded logo settles facing the camera, set in Geist Bold with a neutral rim
* fix(registry): magnetic cursor docks its toolbar on the selection and ends after the press
* style(registry): format the exploded stack source
* fix(registry): glyph trail becomes a tutorial cursor path with numbered steps on a settings screen
* fix(registry): make glass-hero a clear lens that shows the headline through it
* fix(registry): snow reads as real snowfall with bokeh flakes over a winter title card
* fix(registry): oversized cursor is a 3.5x arrow that clicks a real editor toggle on
* fix(registry): starfield reads as a drifting night sky over a real title card
* style(registry): format the glass hero source
* fix(registry): make the paper tear show a white fibrous edge and lift the torn sheet away
* fix(registry): curtain reveal draws velvet folds that gather onto a title card
* fix(registry): make every zoom-follow click change the screen and read at zoom
* fix(registry): paper collage pieces stay opaque, tear with deckled grain and lift away on an arc
* refactor(registry): drop dead code and correct stale comments in reworked items
* fix(registry): agent cursor scrubs cleanly, docs match press-ripple, fictional demo names
* docs(registry): catalog notes say oversized-cursor exits and how it differs from press-ripple
* docs(registry): plain lowercase comments in oversized-cursor and touch-indicator
* docs(registry): oversized-cursor names CLICK_AT as the click cue
Ctrl+C during a render left its whole work dir of captured frames (and a
staging dir) in the renders folder. Puppeteer's default SIGINT handler kills
Chrome and exits the process before the render's own cancel path can remove
them. The engine keeps that default unless the host owns Ctrl+C; the CLI's
render cancellation scope claims it while a render runs, so the render aborts
and cleans up after itself.
A render killed outright (SIGKILL, crash) cannot clean up, so each work dir and
staging dir now records its owner process (pid, hostname, boot, and on Linux
the pid namespace), and the next render into the same folder removes
the ones whose owner has exited.
Cancelling a render while its browsers start no longer leaves anything
running that kept a batch render alive after Ctrl+C: the BeginFrame warm-up
loop, the probe browser and file server, or a capture browser whose launch
finished after the cancel (now closed before the worker settles, since
Puppeteer's exit hook skips every other live browser).
* fix(producer): a misspelled font-family resolves to the family the document declares
"Saira ExtraCondensed" in CSS next to an @import of family=Saira+Extra+Condensed
was unresolved (Google Fonts answers the typo with HTTP 400), so fail-closed
compiles threw FontFetchError. Once every existing path fails, a family whose
spelling (case, spaces, -, _, +) uniquely matches a family the document declares
in a Google Fonts @import/<link> URL or its own @font-face resolves to it. Faces
are emitted under the authored name, so every authored usage matches them. Zero
or ambiguous matches keep today's behavior.
FontFetchError gains a structured unresolvedFamilies list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): font localization failures name the unresolved font families
The failure line keeps its prefix and appends the sanitized names from
FontFetchError.unresolvedFamilies, e.g.
font localization failed (FontFetchError): unresolved font families: "Foo Bar"
The error message, URLs and HTML are still never printed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(producer): keep conditional font declarations out of alias fallback
* refactor(producer): bound declared font collection complexity
* test(producer): model multi-family Google font responses
* test(producer): preserve optional font policy through alias fallback
---------
Co-authored-by: terence.cho_tai2 <terence.cho_tai2@heygen.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(studio-server): a save never recreates a project folder renamed away
* style(cli): wrap imports after merging main
* fix(studio-server): proxy and upload answer that the project folder is gone instead of 502 or an empty 201
* test(studio-server): rename the project folder while an upload is being read
* fix(studio-server): a proxy or backup answers that a vanished project folder is gone
* test(studio-server): clean up the renamed folder after the proxy test
* refactor(studio-server): drop an unused re-export and share the folder-gone test setup
* fix(core): no folder outside a project is created once the project folder is gone
* test(studio-server): a refused render leaves no outside renders folder behind
* docs(core): wrap the mkdirWithinProject comment under the line limit
* test(studio-server): pass the project folder to the preview document store
* fix(cli): an update never replaces the CLI's files while a hyperframes command is running
* fix(cli): a killed command's leftover pid file cannot hold updates back
* fix(cli): judge a stale pid file by the installer's own clock, so sleep cannot drop a live command
* fix(cli): after a sleep the installer watches every command's file afresh
* docs(cli): shorten the installer script comment
* docs(cli): keep the new files within their comment share
* refactor(cli): split the upgrade eligibility check out of the installer scheduler
* docs(cli): keep the scheduler's doc on the scheduler
* fix(studio-server): remove the history of a project that no longer exists
* fix(studio-server): keep a project's history while its drive is disconnected
* fix(studio-server): keep an older history whose drive may only be unmounted
* fix(lint): a missing image, audio or mask file names the composition that references it
* test(cli): a missing file is reported once per composition that references it
* fix(lint): Studio's lint panel and the CLI label a finding by the composition it names
* refactor(lint): drop a comment and unused first-entry lines from the grouping change
* fix(studio-server): ffmpeg and ffprobe run from the configured binary; unchecked uploads say so
* fix(studio-server): the upload result type lists unchecked files
* fix(media-use): spawn the configured ffmpeg and ffprobe, and name a missing configured ffprobe
* style: format the ffprobe path test, doctor spawns and upload check
* fix(studio): show the not-checked upload notice as info, not an error
* fix: a configured ffmpeg or ffprobe path that cannot run fails loudly
* fix: refuse a folder as the ffprobe path, and report broken paths through --json
* test: reuse the file manager test setup and split the upload stream check
* fix(media-use): a configured ffmpeg or ffprobe must run -version, and a relative one runs itself
* fix(media-use): a configured ffprobe path must name ffprobe, not another tool
* feat(cli): tag telemetry and feedback with the app that launched the CLI
* fix(cli): keep the client tag to a short slug and correct the telemetry docs
* test(cli): read the delivered batch through the shared helper
* refactor(cli): let the tag pattern alone bound the client tag's length
* docs(cli): describe the client tag generically as the launching app's tag
* style(cli): format the client tag test cases
* test(cli): use a neutral example value for the client tag
* chore(skills): regenerate the skills manifest for the telemetry reference
* docs(cli): render help says when page-side compositing is actually used
* docs(cli): page-side compositing help names webm and mov and the fast-capture flag
* docs(cli): say plainly that gif keeps page-side compositing
* fix(lint): a missing image, audio or mask file names the composition that references it
* test(cli): a missing file is reported once per composition that references it
* fix(cli): commands exit without waiting on update checks or telemetry
The update and skills checks run in a detached child that refreshes the
caches for the next run; the parent only reads the caches. finalizeCli no
longer awaits the telemetry flush: the exit handler flushSync() delivers
what is queued. The events beacon, feedback and figma error paths stop
awaiting flush() too.
* fix(cli): retry a failed background check at most once an hour
Before starting the background worker, the CLI stamps a per-check attempt
time and skips a check it attempted within the hour, so a dead network no
longer starts a worker on every command. The stale-pin notice writes onto a
fresh read, detached spawns set windowsHide, and a test ties the worker path
to its build entry.
* fix(cli): a failed-check stamp dated in the future no longer silences update checks
* fix(layout): flag text a clipping card cuts off
A short card with overflow hidden cut the words off, and the check
dropped text_box_overflow because that parent clips.
The text element's own box does not clip, so clipped_text stayed quiet.
A deliberate mask still clears with data-layout-allow-overflow.
* fix(layout): ignore a text line parked fully outside its box
A reel row that never enters the window was failing with the cut-off card.
Only a line that still meets the box and sticks out of it is an overflow.
* fix(layout): report visible text outside non-clipping cards
* fix(layout): distinguish line boxes from parked font extents
* test(layout): type browser audit test interfaces
* fix(studio-server): background removal writes only where it checked, even through planted links
* test(cli): read the cutout through a no-follow handle instead of checking then reading
* feat(cli): download Parakeet with models install parakeet and transcribe with it
`hyperframes models install parakeet` installs the sherpa-onnx runtime from
npm and the Parakeet TDT 0.6B v3 int8 model from Hugging Face, pinned to a
revision. Every file is checked against its pinned size and sha256 under a
temp name before it is renamed into place. Re-running it is a no-op. On
Linux with glibc older than 2.32 it refuses up front.
`transcribe --engine auto|parakeet` now prefers that engine when it is
installed, then an installed parakeet-mlx runner, then whisper. Audio is cut
into about 60 s windows at the quietest 100 ms near each cut, and decoding
runs in a child process. If the child cannot load the runtime or crashes,
transcribe falls back with one stderr line saying why. The JSON output and
transcript.json shape are unchanged.
* fix(skills): report the real word count and engine from transcribe fallbacks
The media-use transcribe script read `words` from the CLI's transcript,
which is a flat array, so its whisper path always reported an undefined
word count. The embedded-captions script labelled every CLI transcript
whisper.cpp even when `--engine auto` ran Parakeet; it now uses the engine
the CLI reports.
* fix(cli): pin the sherpa-onnx native package to the runtime's exact version
* test(cli): type the sherpa install test doubles
* fix(cli): transcribe audio with no speech as 0 words instead of failing
Parakeet writes an empty word list for a music bed, a silent intro or a
very short clip, and loading it failed with "Unrecognized JSON transcript
format". An empty list is now read as a transcript with no words, as
whisper's empty output already is. A non-empty list still has to look like
words.
* fix(cli): re-decode a Parakeet window that skipped the start of its speech
At some window lengths the model silently drops the first clause: on a
looped speech clip, 4 of 167 lengths between 55 and 60 s, and a 61.91 s
window, lost their first 9 words. A window with a token gap of at least 1 s
that is as loud as speech is decoded again with 0.5 s of leading silence,
and the result with more tokens wins. Clean windows are unchanged.
Decoding with 1.5 s of overlap before each cut was measured too and was
worse on 573 s of audio (2.62% WER against 0.98%), so it is not used.
Empty audio no longer makes a zero-length window, which aborted the
decoder.
* fix(cli): stop Parakeet on Ctrl-C and name the repair when its install breaks
- Ctrl-C during a Parakeet transcribe now stops with exit 130 instead of
starting whisper, and a SIGTERM to the CLI also stops its decode child.
- Ctrl-C during models install parakeet aborts the download and the npm
install, keeps no partial file, and exits 130. A staging dir left by a
killed install is swept by the next run.
- When Parakeet fails under --engine auto, the warning leads with the
Parakeet error and names hyperframes models install parakeet. If the
fallback fails too, the Parakeet error is reported rather than
whisper_unavailable, so --optional no longer hides a broken install.
- An explicit --engine parakeet no longer falls back: it fails with the
Parakeet error and the repair command.
- The install refuses up front on systems with no native package
(naming the six supported ones) and on musl, instead of a raw npm error.
- 24-bit and float WAV go through ffmpeg, since sherpa-onnx reads only
16-bit PCM. The decode timeout scales with the audio length.
* docs(cli): point Parakeet setup at models install parakeet
The transcribe docs and the media-use references named parakeet-mlx as the
only way to get Parakeet. They now name hyperframes models install
parakeet, and the CLI reference gets a models section.
* test(cli): cover the npm cancel signal and decode timeout, and fix a doubled period
The Parakeet failure line printed "failed.. To repair" when the decoder's
own message ended in a period. Tests now also pin that Ctrl-C reaches the
npm install and that the decode timeout scales with the audio length.
* fix(cli): report unreadable audio as an input error, not a Parakeet failure
Prepare the WAV once before the Parakeet fallback and hand the same file to whisper,
so ffmpeg runs once. Ctrl-C while ffmpeg runs now ends as a cancel (exit 130)
instead of starting whisper.
* fix(cli): recover a clause Parakeet drops after a pause by decoding the gap alone
Each loud gap without tokens is decoded on its own with 0.5 s either side, and only
its new tokens are spliced in. The padded whole-window retry stays as the fallback.
A window with no tokens at all is no longer decoded twice.
* perf(cli): size the decode timeout from the WAV header instead of reading the file
* docs(cli): say a broken Parakeet install still fails transcribe --optional
* fix(cli): treat Ctrl-C during audio preparation as a cancel and drop the partial WAV
ffmpeg traps SIGINT and exits 255 rather than dying by the signal, so that status now
counts as a cancel unless the timeout fired. A failed or stopped ffmpeg removes the
temp WAV it was writing. The decode timeout now sizes a prepared WAV from its file
size, so a large header can no longer hide its length.
* fix(cli): splice a re-decoded gap by whole words so its neighbours are never doubled
The gap decode splits edge words into other tokens than the window decode did. Match
neighbours as whole words within 0.3 s of each gap edge, add only whole words that start
inside the gap, and decode the gap behind 0.5 s of silence like the padded retry.
* fix(cli): count a cancel only when ffmpeg says it received a signal
ffmpeg 7 and later also exit 255 when a file is not permitted (EPERM), so exit 255
alone read a permission error as a cancel. ffmpeg's stderr is now captured, a cancel
needs its received-signal line, and any other failure reports ffmpeg's last line.
The output path is passed to the shared runner explicitly.
* fix(cli): keep a spliced gap word that has no leading space
The filter dropped such a word; it is now kept and given its word boundary.
* refactor(cli): trim three long comments in the Parakeet decoder and installer
* fix(cli): repair a Parakeet runtime missing its native binary, and pick Parakeet by language
The runtime dir is per platform and arch. Installed now needs the runtime
manifest and the native sherpa-onnx.node; a half-installed dir is removed
before npm runs, since install() keeps any dir holding the runtime manifest.
The decode worker flattens a multi-line loader error onto its prefixed line
and stops between windows once orphaned. transcribe opens its cancellation
scope before audio prep, so Ctrl-C under --json cleans up. auto uses
Parakeet only for its 25 languages and only where the host can run it;
--engine parakeet with another language fails.
* fix(cli): sweep reused-pid staging, name failed model downloads, keep ffmpeg's cause
sweepStaleStaging also removes a staging dir older than 6 hours whose pid
is alive, since containers reuse pids. A failed model download names the
file, the host and a retry. runFfmpeg keeps ffmpeg's stderr on its error,
so transcribe shows "Output file does not contain any stream" for a video
without audio, and reads a cancel only from ffmpeg's own signal line.
* fix(skills): make media-use --engine whisper run whisper and report the engine that ran
The whisper path now passes --engine whisper when asked, or --engine auto,
and reads the engine from the CLI's --json line, so a CLI Parakeet run is
no longer labelled whisper. Docs state the language rule, the real repair
behaviour and the platforms Parakeet runs on.
* test(cli): pin that a long Parakeet decoder error is kept whole up to a bound
* fix(cli): report a broken Parakeet runtime with the repair instead of skipping it
* refactor(cli): name the staging age limit instead of commenting it
* fix(skills): let media-use --engine parakeet use the CLI Parakeet when parakeet-mlx is absent
* fix(cli): repair a Parakeet runtime that fails to load, and read Ctrl-C after whisper
models install parakeet now loads the runtime in a child process, the way
the decode worker does, and reinstalls it when that fails, so a missing
shared library, a corrupt binary or missing runtime JS is repaired. It
reports changed: false when the runtime loads. transcribe keeps its cheap
manifest check. After a failure, transcribe lets the event loop turn once
before reading the Ctrl-C scope, since a signal that landed during the
synchronous whisper fallback is only handled then.
* fix(cli): read Ctrl-C from the whisper child, and re-check the Parakeet runtime after a repair
A whisper fallback killed by SIGINT or SIGTERM is a cancel (exit 130), the
same rule decode and runFfmpeg use; the loop-turn wait is gone. The
runtime probe treats a child stopped by Ctrl-C as a cancel and keeps the
runtime; a reinstall is probed again and fails with the loader's error if
it still does not load. media-use keeps the CLI's last lines, stdout
included, when hyperframes transcribe fails.
* fix(cli): give the user-stop rule one owner, SIGHUP included, and end a cancelled install at 130
stoppedByCancelSignal in renderCancellation.ts uses the scope's own signal
list (SIGINT, SIGTERM, SIGHUP) and is false whenever Node stopped the child
(a code, killed, or a spawn error). decode, runFfmpeg, the runtime probe and
the whisper fallback all route through it, so a probe killed by SIGHUP keeps
the runtime. isWhisperTimeoutError now keys on ETIMEDOUT only, which Node
sets on execFileSync's own timeout, so a SIGTERM reaches the cancel rule.
A cancelled models install sets exit 130 and returns instead of failing, and
the probe says what went wrong (timed out, crashed, no output).
* refactor(cli): let the stop-rule helper name carry its meaning
* feat(plugins): ship portable agent bundles and installation guides
* fix(plugins): align new manifests with current release
* test(plugins): split caption fixture setup and cleanup
* test(plugins): pass render fixture path as an argument
* fix(studio-server): history saved by 0.8.78 opens with every entry, and an unreadable id is never overwritten
* test(studio-server): a malformed history id is refused and kept, not replaced
* refactor(studio-server): name the 0.8.78 record check instead of commenting it
* docs(studio-server): hosts keep project histories in historyCache, which reopens a replaced folder
* fix(studio-server): an unreadable 0.8.78 record keeps its history, and another path to the folder is not a copy
* style(studio-server): format the 0.8.78 history tests
* fix(studio-server): name the history id error, and a recorded path that cannot be read is not a copy
* style(studio-server): format the 0.8.78 history fixture
* fix(cli): a failed command sends one cli_error, naming its real cause
* chore(cli): drop a comment the parameter name already says
* fix(cli): a failure caught by its own command is still reported, and only once
* test(cli): normalize-audio test passes the flags it requires
* style(cli): format the failure tracking test
* fix(cli): one function unwraps and reports a command failure, once
* docs(cli): shorten the command failure reporter's comment
* fix(studio-server): a write through a link lands on the file the system resolves
* fix(cli): resolve catalog install paths as the system does, matching the install root
* fix(cli): list installed files relative to the real project folder
* test(cli): CLI tests run with a home folder of their own, never the user's
* style(cli): format the test home check
* test(cli): the skills test lane also runs with a home folder of its own
* style(cli): format the test home check
* ci: the test reachability map follows the skills lane's new command
* fix(studio): reload the preview only when a file it loaded changes
* test(studio): read the owner id from the new change-owner shape
* refactor(studio): drop a comment the parser name already says
* fix(studio): keep reloading until the preview is built, and count what its document names
* style(studio): format the reference pattern and name the kept reload
* fix(studio-server): scan preview references in linear time and refresh all thumbnails on a merged reload
* fix(studio): keep a waiting film change when a file the preview never loaded arrives behind it
* test(studio): name the preview ownership test for what it checks
* chore(ci): allow the watchIgnore removals and trim the new comments
* fix(studio-server): reload the preview when a folder holding a file it loaded moves in or out
* perf(studio-server): check a changed folder in one lookup and cover every reference form
* fix(studio-server): record read folders for a project at the filesystem root
* fix(studio-server): reload the preview when the file a symlinked asset points at changes
* fix(studio-server): resolve symlinks on every preview read and key projects by their real folder
* fix(studio-server): record every link a preview read passes through, resolved as the system does
* fix(cli): Studio's catalog install goes through add, so it records the item and honours paths
* fix(cli): Studio's catalog lists the project's registry and install paths survive symlinks
* refactor(cli): name the Studio catalog's registry option plainly
* feat(cli): registry_item_added says whether add or Studio installed the item
* fix(cli): dropping the same catalog block in Studio twice installs it again
* chore(cli): drop a comment that restates add's source option
* fix(cli): Studio's re-record finds lock entries by their resolved path
* test(cli): the symlinked-folder install test checks the resize happened
* fix(cli): Studio mounts the installed item's own file by the path add recorded
* fix(cli): Studio's install never hands back a file the project kept for its own edit
* test: cli and studio-server tests remove the temp folders they create
* test(cli): each temp folder is removed where it is made, even when a test fails
* test(cli): the one-liner helper relies on its project's own cleanup
* fix(studio): avoid preview reloads for temporary and excluded files
* docs: capture public preview reload reproduction
* fix(studio-server): normalize watch paths in linear time
* fix(studio): register dynamically opened preview projects
* fix(cli): skip background checks in unattended runs and never hold exit on a silent DNS
* fix(cli): probe DNS only on background checks so foreground upgrade and skills commands still wait
* fix(cli): keep update checks for agents and skip them only for CI, dev and the opt-out
* refactor(cli): give the update-check opt-outs one owner shared with auto-install
* docs(cli): stop the auto-install notes claiming runs without a terminal are skipped
* fix(cli): run telemetry flushes one at a time so an overlapping flush never resends an event
* fix(cli): let a telemetry flush run even when the one before it threw
* fix(cli): cap exit wait at one telemetry attempt when an overlapping flush already failed