mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-03 20:37:30 +00:00
sync/hyperframes-codegen-75e380df
1365
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
821e8c8ad0 |
fix(cli): hide console windows on Windows for background/detached spawns (#3996)
* fix(cli): hide console windows on Windows for background/detached spawns Add windowsHide: true to 8 spawn/execFile call sites that were missing it: ffprobe/ffmpeg probes in the producer and engine packages, the telemetry exit-flush and studio dev-server spawns, and the browser-open and background-preview detached launches in the CLI. previewLifecycle.ts's spawnDetachedPreview redirects stdout/stderr to a raw log-file descriptor; libuv only adds Windows' CREATE_NO_WINDOW flag when no stdio slot inherits a raw handle, so windowsHide alone does not fully suppress a console flash there (documented inline) — it's still added for consistency and because it correctly sets the process's show-window state regardless. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): correct windowsHide flash-risk claims and add missing dev-server spawn tests - The previewLifecycle.ts comment (and its mirrored test comment) overstated the residual console-flash risk on the detached background preview spawn: DETACHED_PROCESS already leaves that child without a console regardless of whether CREATE_NO_WINDOW gets applied. - Add the two missing regression tests for preview.ts's runDevMode and runLocalStudioMode spawns, exporting both for test access. - Drop an unused export in audioPadTrim.ts flagged by the repo's dead-code gate; the constant is only ever used within its own file. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(engine): hide console window for the drawElement self-verify PSNR comparison psnr.ts's execFile call runs in-process inside the Studio server's render path (parallelCoordinator.ts's drawElement disk self-verify), so a render started from Studio's UI under a detached preview hits this call from a console-less parent — the same flash case the other windowsHide sites in this PR already cover. psnrFilterAvailability.ts's probe is on the same in-process render path and already had the flag. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): keep only the spawn sites main still misses and leave browser windows visible windowsHide maps to libuv UV_PROCESS_WINDOWS_HIDE, which also starts GUI apps with SW_HIDE, so the detached browser launch must not set it. The background preview child already has no console (DETACHED_PROCESS with inherited log fds), so its flag was a no-op. The telemetry, psnr and audioPadTrim sites shipped on main via #3839 and #4604. * fix(cli): stop the studio dev server when the terminal closes With windowsHide the dev server runs in its own hidden console on Windows, so closing the terminal only signals the wrapper (SIGHUP). Handle it like SIGINT and SIGTERM and kill the process tree. * fix(studio-server): hide the console window for loudness measurement The background preview has no console, so its ffmpeg loudness run opened a visible window on Windows. * refactor(cli): drop the comment on the studio shutdown signals --------- Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> |
||
|
|
f16e509832 |
fix(cli): stop browser --force from deleting chrome under running renders (#4901)
* fix(cli): stop browser --force from deleting chrome under running renders * style(cli): format browser manager test * fix(cli): sweep leftovers of killed browser installs and on browser clear * docs(cli): state what browser clear returns without history wording |
||
|
|
d865b5a7cb |
fix(skills): stop mirroring skills into Codex and remove old mirror links (#4105)
* fix(skills): avoid duplicate Codex skill mirrors * fix(skills): remove Codex mirror links left by earlier skill updates * fix(skills): guard the Codex mirror cleanup and keep links without a universal copy --------- Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> |
||
|
|
b31fb02c7e |
fix(cli): distinguish a skipped browser check from a genuinely clean one (#3901)
* fix(cli): distinguish a skipped browser check from a genuinely clean one `hyperframes check`'s runtime/layout/motion/contrast JSON sections reported the exact same ok:true/zero-findings shape whether the browser session actually ran and found nothing, or never ran at all (a blocking lint error, a lint-step crash, or an exception thrown before the audit could start all took the same "empty result" fallback). Only the top-level ok field and an undocumented layout.duration === 0 tell separated the two cases apart. Add a skipped boolean to CheckBrowserResult, set at the only two places one is constructed (false in the real audit result, true in the empty-result fallback), and mirror it to a new top-level browserSkipped field on CheckReport. The human-readable report now prints a warning when the browser never ran; the JSON envelope carries the same signal for callers that only check individual sections. Co-Authored-By: Miguel Angel <miguel.sierra@heygen.com> * docs(cli): document browserSkipped in the check --json envelope Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * refactor(cli): trim browserSkipped docs and cover the linter-crash skip path * chore(skills): refresh the hyperframes-cli manifest hash * refactor(cli): drop the browserSkipped type comments to keep the comment share flat --------- Co-authored-by: Miguel Angel <miguel.sierra@heygen.com> |
||
|
|
a7e95c0e37 |
fix(cli): stop lambda deploy from pointing at a nonexistent --sam-template flag (#3879)
* fix(cli): stop lambda deploy/destroy from pointing at a nonexistent --sam-template flag locateSamTemplate()'s error message and doc comment both promised a --sam-template CLI flag as an installed-package fallback, but no such flag was ever wired up anywhere in lambda.ts/deploy.ts/destroy.ts, and examples/aws-lambda/template.yaml isn't shipped in any published package either -- a dead-end escape hatch that just wasted users' time. Replace it with the honest requirement (matches repoRoot()'s own message): these commands need a HyperFrames monorepo checkout, or HYPERFRAMES_REPO_ROOT pointed at one. * fix(cli): scope the missing sam template error to lambda deploy --------- Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com> |
||
|
|
bad0bc7ce2 |
fix(cli): recapture snapshot composites after video injection (#4767)
* fix(cli): recapture snapshot composites after video injection * test(cli): guard snapshot composite recapture ordering |
||
|
|
ded5fa8661 |
fix(cli): reject extra positional arguments passed to add (#3891)
* fix(cli): reject extra positional arguments passed to add `add <a> <b> <c>` silently installed only `<a>` and exited 0, discarding `<b>` and `<c>` with no warning. citty binds only the first positional token to `name`; every token after it still lands in `args._`, but nothing read it. `add` now rejects the extra arguments up front, before touching the registry, naming exactly what was dropped. Co-Authored-By: Miguel Angel <miguel.sierra@heygen.com> * refactor(cli): inline the add extra-argument message and test it through citty parsing --------- Co-authored-by: Miguel Angel <miguel.sierra@heygen.com> |
||
|
|
e8659cb653 |
fix(cli): recognize textual motion in sweep checks (#3707)
* fix(cli): fingerprint textual sweep state * fix(cli): share one motion classifier between sweep guard and liveness The frozen-sweep guard (sweep_static) and keepsMoving liveness (motion_frozen) each owned their own definition of "what counts as motion": the sweep fingerprint in layout-audit.browser.js read nine channels while motion-sample.browser.js compared box + opacity only. They meet in one decision in checkPipeline.ts, so a fixed-width attr() countdown was accepted by the sweep guard and still reported frozen by liveness. Move the classifier into motion-signature.browser.js, injected before both samplers, and make it the single owner: - Per-element channels (box, opacity, font-variation axes, clip-path, direct text, form-control state, ::before/::after content, media pixels) as a reader list so further channels (e.g. SVG dash) plug in. - Liveness consumes the same signature with 2px / 0.08 bucketing; the sweep guard consumes it exact. motion-sample throws at install if the shared script is missing. - Counter state is a composition-level channel over box-generating elements and root ancestors. display:none subtrees generate no boxes and are excluded, and only declarations naming a counter that some painted generated content (::before/::after/::marker on a non-ignored, paintable host) consumes are folded in, so a decoy hidden counter cannot make a frozen composition read as live. - clip-path is a channel, replacing the fingerprint's former reliance on the clipped-away visibility probe, so a wipe over a static box still counts. - data-layout-ignore layers are excluded from the signature but not from the per-selector `visible` bit reported for explicit motion assertions. - Root resolution for the "*" liveness scope now matches the sweep guard and the root element is part of its own signature. Tests: motion-signature.browser.test.ts (happy-dom, per-element fake computed style so ancestor handling comes from the classifier) and motion-signature.browser.chromium.test.ts (real Chromium via puppeteer-core; skips without a system browser) pin every branch: attr() substitution, display:none decoys, named-counter gating, ::marker, zero-box hosts, ancestor counters, textarea/indeterminate, root text, clip-path, ignore layers, and sweep/liveness agreement on the countdown fixture. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): prune unrendered content from the motion signature and measure asserted scopes The shared motion classifier pruned only display:none subtrees, so text, pseudo content, media pixels, and counter decoys inside skipped content-visibility contents changed the frozen-sweep and liveness signatures although nothing painted. Pruning is now platform-owned where checkVisibility exists (display:none, skipped hidden/auto contents; a display:contents host keeps its painting pseudo-elements), with the computed-style fallback for happy-dom. A content-visibility:hidden host signs its own box, opacity, clip-path, font axes, and widget state, but not its text, pseudo content, control value, or media pixels; hosts where size containment does not apply (non-atomic inline, display:contents, table and internal table boxes, ruby) skip nothing. isVisibleElement regains the checkVisibility floor layout-audit uses on its opacity-floor path, so an asserted selector inside skipped contents reports hidden. The layout-audit opt-out no longer applies to the element passed as the signature root: a keepsMoving scope that is itself data-layout-ignore is measured (an assertion naming an element outranks the opt-out) instead of producing an empty signature that motionAudit reports as a missing selector. Opt-outs inside the scope still apply. motion-sample defers the missing-classifier error to the first call so the driver's evaluate surfaces it instead of an opaque "is not a function" page error; round() is shared. The real-Chromium suite is opt-in on Windows (HYPERFRAMES_BROWSER_TESTS=1): in the shared Windows package lane the runner's Chrome did not launch within the package hookTimeout, and the suite already runs on Linux CI where the system Chrome is found. The launch hook has its own 120s ceiling. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): prune display:contents in skipped contents; table-cell skips, caption paints A display:contents child of a content-visibility:hidden host has no box of its own, so the platform check could not tell it apart from an ordinary display:contents owner and it stayed a counter consumer: a change of the owner's counter-reset altered the sweep signature although nothing paints. The parent's skipsContents verdict now prunes it; boxed descendants keep being decided by the platform. NOT_CONTAINABLE_DISPLAY had table-cell and table-caption inverted versus Chromium 152, where a table-cell host skips its contents and a table-caption host paints them. Four Chromium pins: the display:contents-in-skipped-contents counter, a table-cell host's own text and pseudo content (frozen), a table-caption host's own text and pseudo content (motion), and a counter reachable only through a skipped host's hidden ::marker / ::after (frozen; also asserts the skipped child is not visible for motion-sample). Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * chore(cli): trim sweep motion comments to the comment ratchet * chore(cli): shorten sweep motion comments to the merge-hook limit --------- Co-authored-by: miga-heygen <miguel.sierra_miga@heygen.com> |
||
|
|
1a619785d5 |
fix(cli): keep check --json stdout pure by sending proxy pre-resolve diagnostics to stderr (#3836)
* fix(cli): keep check --json stdout pure by sending proxy pre-resolve diagnostics to stderr preResolveHostileMediaProxies() logged its summary and scan-failure lines via console.info, which writes to stdout. With autoProxy on (the default), any project containing HEVC/ProRes/AV1 media emitted that line ahead of the JSON envelope from `check --json`, so JSON.parse(stdout) failed for consumers of the machine-readable output. Route both diagnostics through console.error (stderr), matching how the check command already reports its own failures. Add regression tests asserting the summary and scan-failure lines land on stderr and that stdout stays untouched. * refactor(cli): shorten the pre-resolve stderr comments * refactor(cli): drop the pre-resolve doc line the comment ratchet rejects --------- Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com> |
||
|
|
5aa3c8f72f |
fix(cli): connector dash gate honours pathLength (#3954)
* fix(cli): connector dash gate honours pathLength `shaftDashHidden` compared the stroke's dash window against `getTotalLength()`, but a `pathLength` attribute rescales every distance along the path, so the CSS draw-on idiom (`<path pathLength="1">` with `stroke-dasharray: 1; stroke-dashoffset: 1`) — which renders zero ink — still fired `connector_detached`. The window is now judged in the author's units: `pathLength` when set (`SVGAnimatedNumber.baseVal`, 0 when unset), the real length otherwise. `dashLength`/`dashArrayLengths` stay the only owners of parsing and units. Two adjacent changes to the gate's arithmetic: - The `total > period` shortcut is removed so the 10% rule applies uniformly. A window that spans a whole period without enclosing a whole dash (`2 97` on a 100-long path at offset 0.5 paints 3%) is hidden, like any other stroke painting under 10%; any pattern that does enclose a whole dash is still caught by the in-loop check. - The dash-offset wrap adds the period only to negative remainders. The previous `((offset % period) + period) % period` is off by one ulp for fractional offsets (`0.9 % 2` → 0.8999…), which flipped the exact 10% boundary once dashes are expressed in `pathLength` units. Tests: rows for `pathLength="1"` (`1/1` hidden, `1/0.9` on the boundary hidden, `1/0.5` painted, `0.1 0.9` round dot painted), a negative offset that only the wrap gets right (`100` at -150), a percentage row (`10%` resolves against the viewport, not as a bare number), and the `2 97` full-period row. `installConnectorGeometry` mirrors the DOM's `pathLength` for happy-dom, which has no SVGGeometryElement. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): treat a zero pathLength as a solid stroke in the dash gate A present `pathLength` that resolves to 0 (`"0"` or an unparseable value) zeroes the browser's dash scale factor, so the stroke paints solid. The gate saw `baseVal` 0, fell back to the real length, judged the window inside a gap and dropped the shaft, suppressing a real `connector_detached` finding. Return painted for that case. Add a `pathLength=0` row to the dash table and make the happy-dom `pathLength` mirror parse like the DOM (finite SVG number only; `Infinity`, hex, `1.` read as 0). Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): check a zero pathLength before the butt-cap dots shortcut A pathLength resolving to 0 paints the stroke solid, so a butt-capped `0 4` pattern on it is visible; the dots shortcut used to call it hidden first. Also trims the dash-table comment. * test(cli): keep the pathLength helper apart from the geometry installer comment --------- Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> |
||
|
|
d2cdad418a |
fix(cli): refuse capture folders that resolve outside the output root (#4789)
* fix(cli): refuse capture directories that resolve outside the output root writeCaptureFileSync guards the final path entry, but a directory in the path that is itself a planted symlink (assets -> ~/somewhere) still carried the staging directory and the rename into wherever it pointed, with a filename the captured page chose. Every capture directory is now created through ensureCaptureDirSync(root, dir), which walks from the output root one level at a time and refuses any directory whose real path is not the one it has under the root, before creating anything inside it. The root itself stays trusted as given, so a symlinked root like /tmp on macOS keeps working. Fixes #4304 * fix(cli): vet assets/svgs before the SVG contact sheet writes into it Under --skip-assets, downloadAssets never runs, so nothing checked assets/svgs before createSvgContactSheet wrote its thumbnails and contact-sheet.jpg into it; a planted assets/svgs link received them. The sheet now runs ensureCaptureDirSync on the directory first, and a refusal just skips the sheet. A dangling or looping directory link is now refused with the same message instead of a raw ENOENT/ELOOP, and a comment records why the check uses the JS realpathSync rather than realpathSync.native. Refs #4304 * fix(cli): vet capture --video's download folder Video mode created assets/videos (or capture/assets/videos in a W2H project) with a plain mkdirSync, so a planted assets or capture link carried the downloaded clip outside the project. It now goes through ensureCaptureDirSync with the project folder as the trusted root, and a refused folder is reported before anything is downloaded. * fix(cli): warn when a best-effort capture phase refuses a folder Folder refusals now throw CaptureDirRefusedError. The contact sheets and the video manifest still skip on any error, but a refusal adds a warning so a planted link doesn't go unnoticed. saveLottieAnimations now vets assets/lottie itself, taking the output root like renderLottiePreviews, and every folder check in the capture pipeline is pinned by a test. * style(cli): trim the ensureCaptureDirSync doc comment to two lines --------- Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com> |
||
|
|
8aefd91bf8 |
fix(cli): capture snapshot --at at the exact requested instant (#4827)
snapshot --at captures the frame at the exact requested instant instead of the nearest seek step. |
||
|
|
efdabb758d |
fix(cli): connector_detached now skips fully dash-hidden connector shafts (#3912)
* fix(cli): connector_detached now skips dash-hidden connector shafts connector_detached fired on SVG connector paths that were 100% hidden via stroke-dashoffset/stroke-dasharray draw-on entrances, evaluating rendered/user-space geometry unconditionally. The sibling connector_orphan check already gates on shaftDashHidden(path); add the same gate here, in the same position. Co-Authored-By: Miguel Angel <miguel.sierra@heygen.com> * fix(cli): connector dash gate reads the whole stroke-dasharray; one owner for connector enumeration `shaftDashHidden` read only the first `stroke-dasharray` entry, so any pattern starting with 0 (`0 4` dotted) or a long first dash counted as hidden and suppressed both connector findings on a visible stroke. It now resolves the full computed list (comma/space separated, units, percentages against the SVG viewport diagonal, odd lists repeated; `none`, all-zero and negative lists render solid) and treats the shaft as hidden only when the window `[dashoffset, dashoffset + length]` sits in a single gap, with up to 10% of the length overlapping a neighbouring dash. Zero-length dashes paint only under `stroke-linecap: round | square`; under the default `butt` they render nothing, which also covers the `0px, 999999px` state a finished draw-off tween leaves behind. `connector_detached` and `connector_orphan` each walked SVG paths with their own copy of the candidate/gate logic, which had already drifted. A single `connectorShafts(root)` generator now owns enumeration, the dash gate and endpoint resolution, and yields the `painted` verdict; both findings consume it. The path length is queried once per shaft (`pathUserEndpoints` returns it) instead of twice, and the attach threshold formula has one owner. Tests pin `0 4` (butt and round), `0, 4`, `4 0`, `0`, `none`, px units, negative offsets, a draw-off end state, and both sides of the 10% boundary. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * refactor(cli): shorten the connector and dash-window comments * test(cli): shorten the dash-gate table comment --------- Co-authored-by: Miguel Angel <miguel.sierra@heygen.com> |
||
|
|
0eba8d6a06 |
fix(cli): route FFmpeg exec-failure hint through getFFmpegInstallHint() (#3877)
* fix(cli): route FFmpeg exec-failure hint through getFFmpegInstallHint() checkFFmpeg()'s "cannot start" branch (binary found but fails to run -version) hardcoded a Windows/DLL-flavored install hint on every platform instead of delegating to the already platform-aware getFFmpegInstallHint() used by every other branch in this file. * test(cli): pin the ffmpeg cannot-start hint through the async probe mock * fix(cli): keep the runtime-DLL cannot-start hint on Windows --------- Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com> |
||
|
|
0b88cd0537 |
chore(deps): update node.js (#4245)
* chore(deps): update node.js * fix(deps): keep the Node 22 engines floor at >=22 --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> |
||
|
|
45b8e171e2 |
feat(studio): sync origin, out-of-sync repair, linked selection toggle and grouped clip menu (7/8) (#4820)
* feat(studio): order the clip menu into time, sound, picture and delete groups Split and Freeze frame; Normalize, Voice, link items and Duck; Look and Crop; then Delete beside Delete this clip only. Each item file renders its pieces separately, empty groups collapse, and the viewport clamp uses the measured menu height instead of a row-count estimate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): mint freeze-frame right-half links with the shared lk-N helper Freeze frame now calls core relinkSplitHalves, so split halves and freeze halves share one link-id convention instead of L -> L-2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): apply speed edits to every data-link member in one undo step The inspector speed slider, rate lane edits and ramp presets on a linked clip now commit once, writing the selection and, via expandToLinkedMembers, each partner from its own current value. Unlinked clips keep their usual path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(studio): drop the audio parent tick in favour of the data-link chain badge A linked clip now shows one link indicator: the chain badge driven by data-link. The waveform no longer draws its 1px parent tick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): follow late clip-menu rows and target link partners like S2 The menu height follows a ResizeObserver so rows that read the live preview after mount still clamp inside the viewport. Speed fan-out builds partner targets with buildPatchTarget, matching the link edit plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): show the sound strip and audio menu items without studio's shell The audible-video waveform strip drops AudioWaveform's 16px label band, which left a 0px canvas on short lanes. Normalize loudness and Duck under voice fall back to the timeline session's project, the live preview iframe store and a new onNotice edit callback when no StudioShellProvider is mounted. usePreviewIframeStore is exported so a host can publish its preview. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): show duck under voice only when a voice overlaps The item now appears only when an overlapping voice exists or the bed is already ducked. Its check mark moves to the right edge so unchecked rows align with siblings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): name the partner in the clip menu link and unlink items Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): clip menu stops offering unlink once the clips are unlinked The timeline skipped store updates when only data-link changed, so the menu kept reading the stale link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(core): sync origin, offset math and move/slip into sync for linked pairs data-sync-origin survives Unlink; split gives right halves a fresh origin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): split re-mints the sync origin of unlinked pairs too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): detach, link and keep-sound cutout write a sync origin that unlink keeps TimelineElement.syncOrigin is read on both parse paths; merge back drops it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(studio): expect sync-origin on keep-sound cutout write Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): linked selection toggle in the timeline toolbar, on by default Off: a click selects one clip; drag, trim, razor, speed and delete stay on it (delete unlinks the survivor). Alt-click still selects one half when on. Right-click holds the out-of-sync indicator preference. Both persist in UI prefs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): link groups share a label colour; out-of-sync badges with move and slip Red badge on both halves of a sync-origin pair that drifted (frames, s:ff past a second); hidden when rates differ or the preference is off. The whole badge opens Move / Slip into Sync; each edits that clip alone in one undo step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): take link label colours from theme tokens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(sdk): syncOffset, moveIntoSync and slipIntoSync for sync-origin pairs Core gains findSyncPartner, the DOM pairing shared by the SDK and the CLI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cli): timeline rows report syncOffsetFrames and print out-of-sync Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cli): the timeline row fixture carries syncOffsetFrames Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): data-sync-origin in hyperframes-core Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(core,sdk): keep a one-sided cut's sync origin and slip the in-point playback reads relinkSplitHalves reminted the right half's sync origin even when only one member of an unlinked source pair was cut, so the uncut partner kept the old origin and the new half lost its badge. The origin is now reminted only when both the video and the audio were cut. Slip into Sync wrote data-media-start whenever it existed, but playback reads data-playback-start first, so a clip carrying both never moved. The link timing reader now uses playback's precedence and the slip writes the attribute that wins; lint compares the same field. A sync partner is also looked up only in the clip's own composition (core) or source file (Studio). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): flip the out-of-sync menu above the pointer near the bottom edge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: trim comments the sync-origin layer added Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(core): split the scoped candidate lookup out of findSyncPartner Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(core,studio): find sync partners inside the link scope findSyncPartner and Studio's syncPartnerOf now scope by the same rule as link groups: the nearest data-composition-id or data-composition-file ancestor, plus the row's compositionScope in Studio. An origin reused in a file host or an inline composition no longer pairs across it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
824e4100c6 |
feat(studio): waveform strip, clip light, peak marks and video beat source (5/8) (#4817)
* feat(studio): draw a waveform strip under video clips that carry sound An audible video keeps its thumbnails and gains the audio waveform along the bottom 38% of the clip (also when thumbnails are off); muted and silent video are unchanged. The waveform route already decodes video audio; a test now proves it on an mp4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): latch a CLIP light on the master meter at the export ceiling The light turns red when the preview master sample peak reaches -1 dBFS (the export true-peak ceiling) and stays lit until clicked. Mutation-tested threshold and latch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): paint the CLIP light from theme colour tokens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): mark where each clip redlines on its waveform studio-server computes an absolute per-50ms sample-peak map once per media file (native channels, cached by size+mtime next to the waveform cache); the timeline paints red marks where the source at the clip's own volume reaches -1 dBFS and names the loudest point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): let a video with sound be the beat source Studio's isMusicSourceElement accepts an audible, unmuted video for the music track and the beat fallback (lane zoning unchanged); core findMusicAudioSrc accepts <video data-has-audio="true"> without muted; the beats CLI message mentions video. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): decode each peak map once and never offer Duck on a carve source Concurrent peak requests for one file share a single ffmpeg decode; Duck under voice is withheld on a clip another bed already carves against. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): decode the same audio stream the peak map probes The channel probe read stream a:0 while the decode left stream choice to ffmpeg, which picks a later stereo stream over a mono first one; every frame was then read at the wrong width, doubling the bins and taking the other stream's level. Both commands now select a:0, and the cache key moves to peaks-v2 so maps built from the wrong stream are rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): terminate the peak map's ffprobe options before the input Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): reject a peak map whose decode exits nonzero A decode that emits some samples and then fails no longer resolves as a complete map, so /peaks cannot cache a truncated clipping map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9244c47328 |
feat(studio): normalize loudness and duck under voice, with a limiter report (4/8) (#4816)
* feat(studio-server): share loudness measurement with an absolute target and video support normalize-audio moves its measure/plan code into @hyperframes/studio-server/loudness, gains an absolute mode (--lufs, default -16, clamped at +12 dB and -1.5 dBTP) and accepts <video> clips with sound. Adds the @hyperframes/core/audible-video subpath. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio-server): add a measure-only loudness normalize endpoint POST /projects/:id/loudness/normalize measures a clip's played window (audio or video) and answers the -16 LUFS data-volume plan; Studio writes the attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: register the loudness and audible-video subpaths in their descriptors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: format the loudness and audible-video subpath descriptors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(studio): add Normalize loudness and Duck under voice to the clip menu Normalize measures the clip on studio-server and writes data-volume as one undoable edit; Duck toggles a carve against every overlapping voice (audio or video with sound) at the default strength. Both live in clipMenuAudioItems with one hook-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(studio): tighten carveOutput docs to the package comment share Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(producer): report how far the true-peak limiter lowered the export The AAC true-peak pass now returns its attenuation; the render logs "Audio lowered by X.X dB to stay under -1 dBTP" and sets RenderJob.audioLoweredDb, which Studio servers carry in the complete SSE event and the /renders record. Also exports useAudioMetersVisible from @hyperframes/studio for hosts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio-server): scan HTML start tags without a backtracking regex The start-tag regex backtracked quadratically on '<' followed by many spaces (CodeQL js/polynomial-redos). A linear character scan reads the same name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): refuse to normalize a volume-automated clip and honour save outcomes A volume lane owns the clip's gain in preview and render, so writing data-volume changed nothing while the toast claimed success; Normalize now refuses with a clear message. Normalize and Duck also check each quiet-save outcome, stop at the first failed or refused write and report it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(core): drop the duplicate audible-video subpath descriptor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
37f30b1d06 |
feat: keep a video's sound on the clip by default (1/8) (#4813)
* feat(cli): one owner for init media placeholders, audio stays on the video Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cli): init keeps a video's sound on the clip; silent files scaffold muted Supersedes the patchVideoSrc changes in #3977 and #3942 and carries their tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: miga-heygen <miga-heygen@users.noreply.github.com> Co-Authored-By: kapelame <kapelame@users.noreply.github.com> * feat(registry): example templates keep the a-roll's sound on the video Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(lint): video audio rules recommend keeping sound on the video Update video_missing_muted and video_audio_double_source message and fixHint text to recommend the new default: keep audio on the video with data-has-audio="true" (not muted). Tests verify the new wording is in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(parsers): a video is either audible (data-has-audio) or muted, never both Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): follow the compiler's audio rule in the audio-track toggle Confirm before background removal drops a video's sound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): hyperframes-core teaches sound on the video by default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): workflows and scaffold guidance keep a video's sound on the clip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: footage with sound keeps it on the video clip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): init from-file a-roll is timed, muted strip is attribute-exact The from-file a-roll video gets data-start="0" so a scaffold lints clean, and the orphaned audio-only slot comment is dropped. setVideoAudio now walks the tag attribute by attribute, so a class value containing the word muted survives. The ffprobe-missing warning says the video is assumed to have sound and how to switch to muted, and DEFAULT_META notes the failure surfaces at render until lint support lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep has-audio in step with the muted toggle, test cutout confirm Muting a video now clears has-audio and unmuting sets it to true, in both the Design and flat media panels, matching the Has audio track toggle. Audio elements are unchanged. Adds coverage for both panels plus the cutout window.confirm guard: declined leaves src alone, accepted swaps src and mutes, a silent video never prompts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(parsers): updateElementInHtml hasAudio only applies to videos, true or false An audio element or an undefined hasAudio no longer picks up a muted attribute; only an explicit false on a video writes muted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: video_missing_muted rule text, silent-footage notes, consistent audible examples lint.mdx and cli.mdx describe the current video_missing_muted rule instead of the retired unmuted-video wording. The editing recipes say how to adapt for silent footage and require the outgoing clip's sound to end at a J-cut. The Remotion media example shows the audible mapping consistently, data-attributes states that has-audio is required unless muted, and the hairline frame note no longer claims the video is muted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): encode init media filenames and probe --audio duration Function replacers stop dollar-sign pattern expansion in filenames; src is percent-encoded. --audio now passes the probed track length as the composition duration in both init paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(lint): audio-state-aware media rules and time-aware double-source check video_missing_muted honors any explicit data-has-audio. video_audio_double_source only considers audible videos and only fires when the same-src clips overlap in time. duplicate_audio_track now covers audible videos. audio_file_without_element no longer claims silence when an audible video carries the sound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(skills): audio-stream check for remotion ports, followable J-cut recipe Remotion Video maps to data-has-audio only when the file has an audio stream. The J/L-cut recipe now shows both shots with muted pictures and separate audio. embedded-captions removes the audio.mp3 scratch file it extracts. skills-manifest.json regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(lint): trim comments to satisfy the comment ratchet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): atomic muted edits, cutout keeps sound on a linked audio Muted and Has audio track commit one batched write (one persist, one undo entry) and remove data-has-audio instead of writing an empty marker. Unmuting a video probes its src: audible -> true, silent -> false, probe failed -> absent. Remove background on an audible video inserts a linked sibling <audio> with the original sound in the same write, replacing the drop-the-sound confirm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(producer): skip hidden videos when auto-adding video audio A video hidden in Studio is dropped by the mixer, so its src never reached existingAudioSrcs and the render re-added it at full volume. The video parser now records the shared hidden predicate and the auto-detect step skips hidden videos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(studio): keep useDomEditSession under the 600-line cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: miga-heygen <miga-heygen@users.noreply.github.com> Co-authored-by: kapelame <kapelame@users.noreply.github.com> |
||
|
|
c72b735a8c |
fix: a save's temp file no longer reaches Studio as an outside change (#4857)
* fix: a save's temp file no longer reaches Studio as an outside change * refactor: the project file walk and its tests carry no new duplication or complexity * test(studio): the edit accuracy gate judges the two nudge sequences again |
||
|
|
567525a8c2 |
fix: readers never see an empty or half-written project file (#4777)
* fix: readers never see an empty or half-written project file * test: pin the create routes and block install to whole-file writes * fix(sdk): a write through a link to a missing file creates that file * fix: saves through a linked folder reach the real file, atomic writes handle long names * test(sdk): check for the stray file, not the folder listing * fix(core): creates work on Windows FAT drives, and links through folders resolve as the system does * fix: agree the helper move in the deletion check, and match temp paths without a path regex * test: check POSIX file modes only off Windows |
||
|
|
481dae1ed9 |
fix: render names a folder composition source instead of crashing (#4613)
* 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 |
||
|
|
f361ee4bc4 | codegen: resync cloud client from EF d1d64f05 (#4716) | ||
|
|
9e1dbca7a2 |
fix(studio): undo and redo update the preview in place instead of reloading it (#4718)
* 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 |
||
|
|
2195db5e1d |
fix(media-use): never replace a voice, music or sound file the person put in the project (#4665)
* 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 |
||
|
|
663b02297d |
feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk (#4701)
* feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk `hyperframes clean` lists leftover render work dirs, debug renders, the history of projects that no longer exist, idle video proxies and extracted video frames, with sizes, and removes them. `--dry-run` removes nothing, `--json` is for agents, `--snapshots` also removes snapshot folders (otherwise listed as reclaimable). It never touches outputs, sources or anything a running render or preview still owns. * docs(cli): say that clean does not search a project's subfolders * fix(cli): clean test compares folder names on Windows and the skills manifest is regenerated * fix(studio-server): the proxy cache sweep removes only files the transcoder named It removed any idle .mp4 or .webm in .transcode-cache, so a user file placed there, or reached through a symlinked cache folder, could go. Proxies are always <sha256>.<ext> and their temp files .tmp-<uuid>-<proxy name>; nothing else is swept. * style(producer): keep a renderDirOwner doc comment within 120 columns |
||
|
|
e07ea1fc70 |
perf(cli): load each command's code only when that command runs (#4656)
* perf(cli): load each command's code only when that command runs * fix(cli): honest bench percentiles, and no unused registry entry in the split build * fix(cli): the startup bench is a package script and stops on a failed run * style(cli): format the bench's failed-run check * fix(cli): a notice module that fails to load cannot fail the command |
||
|
|
e25bc3a806 |
fix(media-use): record agent-made voice, music and sound effects in the media manifest (#4648)
* fix(media-use): record agent-made voice, music and sound effects in the project's media manifest * fix(media-use): record in place only when --source says the agent made the file * test(media-use): a rewritten agent file keeps one manifest record * fix(media-use): leave a person's own sound effect unlabelled and mark locally made music generated * refactor(media-use): record agent audio in-process through the shared manifest library * style(media-use): format the in-process recording and refresh the skills manifest * chore(skills): refresh the skills manifest for the media-use changes * fix(media-use): report a broken ffprobe setting plainly and document the project sfx source * style(skills): format the pipeline manifest assertion * chore(skills): refresh the skills manifest * fix(media-use): a regenerated file gets a fresh record, and lookups skip the replaced one * test(media-use): a replaced record no longer answers a prompt lookup in the CLI * chore(skills): refresh the skills manifest * chore(skills): refresh the skills manifest from a clean tree * fix(media-use): never give a recorded file the id of a download in flight * chore(skills): refresh the media-use manifest hash * fix(media-use): list only a file's newest record in candidates and the media index * revert(media-use): leave promote as it was, nothing calls it * test(media-use): give the index test's two assets their own files * chore(skills): refresh the media-use manifest hash * fix(core): a figma import lists each media file once, like media-use |
||
|
|
590c192498 |
feat(catalog): add 3D objects, cursors, snow, starfield and paper transitions (#4349)
* feat(catalog): shelve 3D objects and cursors in their own catalog sections Carries the generator grouping rules from #4310 and #4308. * feat(registry): add a starfield ambient overlay From #4313. * feat(registry): add a falling snow ambient overlay From #4331. * feat(registry): add a torn-paper-rip transition From #4321. * feat(registry): add a paper-collage-reveal transition From #4322. * feat(registry): add a curtain-reveal transition From #4323. * feat(registry): add 3D objects: product turntable, glass hero, exploded stack, extruded logo From #4310. * feat(registry): add eight cursor animations and the dart cursor From #4308. * fix(catalog): find items by everyday editor words and accept a bare search word From #4311. * chore(registry): remove the simulated-cursor component The Cursors section's new pointer animations replace it (from #4308). * fix(registry): take cursor-zoom-follow's screenshot from runtime variables only The declared default is empty, so page markup never becomes an image URL. Clears the code-scanning alert raised on #4308. * docs(cli): show a catalog search example that finds items "smoke" matches nothing in the catalog; "crossfade" finds fade-through. * chore(ci): allow the simulated-cursor removal in the deletion guard * fix(ci): skip tracked files missing from disk in the player CDN pin check Catalog regeneration removes a deleted item's pages before the publish PR untracks them, so the check crashed on every catalog deletion PR. * chore(cli): drop comments that narrate catalog search history * fix(registry): drag-drop keeps the family arrow and shows the drop slot only while hovering * fix(registry): agent cursor labels its steps in an accent tag, no glow or sparkles, in 4.8 s * fix(registry): annotate cursor draws a tapered marker stroke and clears the circled value * fix(registry): show real file thumbnails and end the context menu demo sooner * fix(registry): give marquee select real photos, a Finder-blue marquee and a tighter loop * fix(registry): centre the text-select toolbar on the selection and tighten its timing * fix(registry): selection cursors default to the macOS system blue * style(registry): format the agent and annotate cursor sources * fix(registry): dart-cursor stops covering the choice it makes and shows the export finishing * fix(registry): frame the product turntable fully and light it with two studio softboxes * fix(registry): touch-indicator taps like a real phone and the post it taps gets liked * fix(registry): press-ripple confirms with a check and small click rings at the cursor * fix(registry): exploded stack separates at once and marks each layer in blue as it is labelled * fix(registry): extruded logo settles facing the camera, set in Geist Bold with a neutral rim * fix(registry): magnetic cursor docks its toolbar on the selection and ends after the press * style(registry): format the exploded stack source * fix(registry): glyph trail becomes a tutorial cursor path with numbered steps on a settings screen * fix(registry): make glass-hero a clear lens that shows the headline through it * fix(registry): snow reads as real snowfall with bokeh flakes over a winter title card * fix(registry): oversized cursor is a 3.5x arrow that clicks a real editor toggle on * fix(registry): starfield reads as a drifting night sky over a real title card * style(registry): format the glass hero source * fix(registry): make the paper tear show a white fibrous edge and lift the torn sheet away * fix(registry): curtain reveal draws velvet folds that gather onto a title card * fix(registry): make every zoom-follow click change the screen and read at zoom * fix(registry): paper collage pieces stay opaque, tear with deckled grain and lift away on an arc * refactor(registry): drop dead code and correct stale comments in reworked items * fix(registry): agent cursor scrubs cleanly, docs match press-ripple, fictional demo names * docs(registry): catalog notes say oversized-cursor exits and how it differs from press-ripple * docs(registry): plain lowercase comments in oversized-cursor and touch-indicator * docs(registry): oversized-cursor names CLICK_AT as the click cue |
||
|
|
f7ef18dca8 |
fix(producer): renders leave no captured frames behind when interrupted (#4694)
Ctrl+C during a render left its whole work dir of captured frames (and a staging dir) in the renders folder. Puppeteer's default SIGINT handler kills Chrome and exits the process before the render's own cancel path can remove them. The engine keeps that default unless the host owns Ctrl+C; the CLI's render cancellation scope claims it while a render runs, so the render aborts and cleans up after itself. A render killed outright (SIGKILL, crash) cannot clean up, so each work dir and staging dir now records its owner process (pid, hostname, boot, and on Linux the pid namespace), and the next render into the same folder removes the ones whose owner has exited. Cancelling a render while its browsers start no longer leaves anything running that kept a batch render alive after Ctrl+C: the BeginFrame warm-up loop, the probe browser and file server, or a capture browser whose launch finished after the cancel (now closed before the worker settles, since Puppeteer's exit hook skips every other live browser). |
||
|
|
46eedd4dfb |
fix(fonts): resolve declared family names with missing spaces (#4670)
* fix(producer): a misspelled font-family resolves to the family the document declares "Saira ExtraCondensed" in CSS next to an @import of family=Saira+Extra+Condensed was unresolved (Google Fonts answers the typo with HTTP 400), so fail-closed compiles threw FontFetchError. Once every existing path fails, a family whose spelling (case, spaces, -, _, +) uniquely matches a family the document declares in a Google Fonts @import/<link> URL or its own @font-face resolves to it. Faces are emitted under the authored name, so every authored usage matches them. Zero or ambiguous matches keep today's behavior. FontFetchError gains a structured unresolvedFamilies list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): font localization failures name the unresolved font families The failure line keeps its prefix and appends the sanitized names from FontFetchError.unresolvedFamilies, e.g. font localization failed (FontFetchError): unresolved font families: "Foo Bar" The error message, URLs and HTML are still never printed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(producer): keep conditional font declarations out of alias fallback * refactor(producer): bound declared font collection complexity * test(producer): model multi-family Google font responses * test(producer): preserve optional font policy through alias fallback --------- Co-authored-by: terence.cho_tai2 <terence.cho_tai2@heygen.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
26bfd38136 |
fix(studio-server): a save never recreates a project folder renamed away (#4602)
* fix(studio-server): a save never recreates a project folder renamed away * style(cli): wrap imports after merging main * fix(studio-server): proxy and upload answer that the project folder is gone instead of 502 or an empty 201 * test(studio-server): rename the project folder while an upload is being read * fix(studio-server): a proxy or backup answers that a vanished project folder is gone * test(studio-server): clean up the renamed folder after the proxy test * refactor(studio-server): drop an unused re-export and share the folder-gone test setup * fix(core): no folder outside a project is created once the project folder is gone * test(studio-server): a refused render leaves no outside renders folder behind * docs(core): wrap the mkdirWithinProject comment under the line limit * test(studio-server): pass the project folder to the preview document store |
||
|
|
f39b179030 |
fix(cli): an update never replaces the CLI's files while a hyperframes command is running (#4637)
* 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 |
||
|
|
bee6a8e17d |
fix(studio-server): remove the history of a project that no longer exists (#4634)
* 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 |
||
|
|
27f1cfd23e |
fix(lint): Studio's lint panel and the CLI label a finding by the composition it names (#4618)
* 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 |
||
|
|
7fedc689b8 |
fix: run the configured ffmpeg and ffprobe everywhere and say when an upload was not checked (#4611)
* 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 |
||
|
|
fa044a89e5 |
feat(cli): tag telemetry and feedback with the app that launched the CLI (#4632)
* 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 |
||
|
|
a380036903 |
docs(cli): render help says when page-side compositing is actually used (#4572)
* 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 |
||
|
|
7eb00137a2 |
fix(lint): a missing image, audio or mask file names the composition that references it (#4616)
* 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 |
||
|
|
693c385508 |
fix(cli): commands exit right away even when the network or DNS is broken (#4604)
* 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 |
||
|
|
09ab947e1a |
fix(layout): flag text a clipping card cuts off (#4577)
* 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 |
||
|
|
03e3755aaa |
fix(studio-server): background removal writes only where it checked, even through planted links (#4540)
* 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 |
||
|
|
5cbef29d3a |
feat(cli): transcribe with Parakeet on macOS, Linux and Windows after an opt-in install (#4583)
* 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 |
||
|
|
0a5e3c7fc4 |
feat(plugins): ship portable agent bundles and installation guides (#4608)
* 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 |
||
|
|
ff6e2103d7 |
fix(skills): keep plugin workflows on their installed release (#4607)
* fix(skills): keep plugin workflows on their installed release * docs(skills): clarify plugin manifest discovery * fix(plugins): suppress stale notices and isolate plugin context |
||
|
|
3db3da7bb2 |
fix(studio-server): history saved by 0.8.78 survives the update with every entry (#4601)
* 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 |
||
|
|
627e9476af |
fix(cli): a failed command sends one error report, naming its real cause (#4586)
* 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 |
||
|
|
59e3850f6a |
fix(studio-server): a write through a link lands on the file the system resolves (#4589)
* 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 |
||
|
|
2b1bb8b731 |
test(cli): CLI tests run with a home folder of their own, never the user's (#4587)
* 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 |
||
|
|
e00baefb5c |
fix(studio): reload the preview only when a file it loaded changes (#4570)
* 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 |