mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-15 13:58:20 +00:00
cbc5e7e1342cf5bd45c623f1b8883498302b989f
257
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6fe3c2ce94 |
feat(cli): catch up on what the desktop app did since a project was handed over (#5114)
* feat(cli): catch up on what the desktop app did since a project was handed over The desktop app records each chat turn in .hyperframes/app-history.jsonl. `hyperframes catch-up [dir]` prints the turns since the hand-off or the last catch-up (what the person asked, what Framey did, which files changed) plus the project files changed since, by the app or by hand, then marks them seen. `hyperframes open` and Studio's Edit with Framey mark the hand-off moment. Any command run on a project with unseen turns ends with a line naming catch-up, so an agent back in the terminal hears about it even when it forgets. /hyperframes and /hyperframes-cli tell the agent to catch up as soon as the person writes again. * feat(cli): hand a Grok terminal session over to the desktop app Grok gives every tool command its session as GROK_SESSION_ID (grok 1.0.4), so `hyperframes open` and Edit with Framey now name a Grok session in .hyperframes/agent-handoff.json, after Claude Code and Codex. The desktop app reads its turns from Grok's own session files. * feat(cli): tell a new project's agent to catch up after the desktop app Scaffolded projects' CLAUDE.md and AGENTS.md now say to run `npx hyperframes catch-up` before the next change once the project was opened in the desktop app. Claude Code, Codex and Grok read these files on their own, so a resumed session catches up even when it never loaded a HyperFrames skill: a resumed Claude Code session that had not, asked to change the title, ran catch-up first. catch-up also prints each reply on one line. * fix(cli): read and write .hyperframes records without following links, mark seen after showing Review (Jerrai, Rames): - One helper (projectRecords.ts) now reads and writes every record in .hyperframes/. A linked folder is refused. A read opens once with O_NOFOLLOW and checks the open file is a plain file, which clears the CodeQL race. A write goes to a temp file renamed over the record, so a link is replaced, never written through. agent-handoff.json, the seen record and the history all go through it. - catch-up marks a turn seen only after showing it, and only up to the newest turn shown, so a turn written just after the read shows next time. Changed files have their own "checked" time. - Turns show only once the project was handed over from this machine, so a history file shipped in a cloned repo is never shown as the person's words. The end-of-command check skips reading when the history is older than the seen record. - The skills mention catch-up only when the CLI printed a line naming it, so an older CLI never meets an unknown command. - The notice test uses basename, which fixes Windows. * feat(cli): add the catch-up line to an older project's instructions on hand-off A project scaffolded before catch-up existed has no line telling its agent to catch up, and a resumed session that never loaded a HyperFrames skill then edits without knowing what happened in the app. When `hyperframes open` or Edit with Framey hands such a project over, its scaffolded CLAUDE.md and AGENTS.md (they start "# HyperFrames Composition Project") gain the template's line, in the template's place. A file of the person's own, a link, a missing file, or one that has the line is left alone. The file is rewritten through a temp file with its mode kept. A test keeps the line the same as the template's. * fix(cli): keep catch-up's cursor in the person's home, never reset it, read without links on Windows Review (Rames, Jerrai): - The seen record moved from the project to ~/.hyperframes/catch-up/, keyed by the project's real path. A seen file a cloned repo commits can no longer make its history read as the person's words. - A second hand-off (`open` again, or another Edit with Framey click) keeps the cursor, so turns the app recorded meanwhile still show. - Reads lstat the path, open it once, and refuse a file whose dev/ino differs from what lstat saw. That holds where O_NOFOLLOW doesn't exist, fixing the Windows test. The note writer reads the same way, which clears the new CodeQL alert. - Writes use core's replaceFileAtomically, which retries a rename that Windows briefly refuses. - catch-up names every changed file, and the cursor is the newest `at` shown, not the last line's. - `open --json` carries `catchUp` after a hand-off. - The bin scripts are back to mode 644. * fix(cli): open before checking for CodeQL, never show a turn dated ahead Review (Rames, Jerrai): - readPlainFile opens the file first, then requires lstat to find the same plain file (dev/ino) at the path, and reads only through the fd. That is just as safe, and it clears CodeQL's check-then-use alert. - catch-up skips turns dated after the moment it reads. So a cloned history can't show a far-future turn as the person's words or park the cursor past real ones. - The seen key uses core's realpath (realpathSync.native), so letter case can't split a project in two. ~/.hyperframes/catch-up is made 0700, and a hand-off that can't note itself says so. - Tests stub HOME with vi.stubEnv and restore it. |
||
|
|
f6e419eb29 |
fix(cli): use a healthy pinned Sherpa copy beside the CLI (#5095)
* docs(cli): map the pinned Sherpa runtime loading contract * fix(cli): use a healthy pinned Sherpa copy beside the CLI * chore(cli): keep runtime loader comments within the ratchet * docs(cli): wrap the runtime contract palette declarations * chore(cli): keep the Sherpa contract note out of the repo |
||
|
|
1db44c6595 |
fix(cli): report transcription language and progress (#5103)
* docs(cli): record transcription output contract * fix(cli): report transcription language and progress |
||
|
|
dcc721062b |
fix(cli): a project .env cannot set the feedback email in another letter case (#5018)
* fix(cli): a project .env cannot set the feedback email in another letter case * fix(cli): capture loads a project .env through the same rules as the CLI |
||
|
|
6a27a34f21 |
feat(cli,studio): hand off on macOS now, and open the app on Windows and Linux (#4999)
* feat(studio,cli): add an Edit with Framey button and `hyperframes open` for HyperFrames Studio
Point people from the CLI and the Studio preview to HyperFrames Studio, the desktop app.
- Studio header: an "Edit with Framey" button with Framey, the app's cursor mascot (the eye
follows the pointer, a wiggle on hover, honours reduced motion). Until the app opens
handed-over projects (HANDOFF_READY), a press shows a "Meet Framey" card with the download.
The button shows only where the preview server answers GET /api/open-in-desktop on macOS,
so the desktop app's own embedded Studio never shows it.
- `hyperframes open [dir]`: hands the project folder to the app with `open -b` (released app,
then Canary) and points to the download when neither is installed. When run by Claude Code
or Codex it leaves .hyperframes/agent-handoff.json (CLAUDE_CODE_SESSION_ID / CODEX_THREAD_ID)
so the app's first chat can pick up that conversation.
- render and preview print one line about the app: the download until HANDOFF_READY, then
`hyperframes open` when the app is installed. Nothing for a draft render, a batch row, or a
run inside the app (HYPERFRAMES_DESKTOP_PROJECT).
* fix(studio,cli): gate `hyperframes open` too, guard the open-in-desktop POST, keep one gate
Review of #4951 at
|
||
|
|
bd0dc8c3ae |
feat(studio,cli): add an Edit with Framey button and hyperframes open for HyperFrames Studio (#4951)
* feat(studio,cli): add an Edit with Framey button and `hyperframes open` for HyperFrames Studio
Point people from the CLI and the Studio preview to HyperFrames Studio, the desktop app.
- Studio header: an "Edit with Framey" button with Framey, the app's cursor mascot (the eye
follows the pointer, a wiggle on hover, honours reduced motion). Until the app opens
handed-over projects (HANDOFF_READY), a press shows a "Meet Framey" card with the download.
The button shows only where the preview server answers GET /api/open-in-desktop on macOS,
so the desktop app's own embedded Studio never shows it.
- `hyperframes open [dir]`: hands the project folder to the app with `open -b` (released app,
then Canary) and points to the download when neither is installed. When run by Claude Code
or Codex it leaves .hyperframes/agent-handoff.json (CLAUDE_CODE_SESSION_ID / CODEX_THREAD_ID)
so the app's first chat can pick up that conversation.
- render and preview print one line about the app: the download until HANDOFF_READY, then
`hyperframes open` when the app is installed. Nothing for a draft render, a batch row, or a
run inside the app (HYPERFRAMES_DESKTOP_PROJECT).
* fix(studio,cli): gate `hyperframes open` too, guard the open-in-desktop POST, keep one gate
Review of #4951 at
|
||
|
|
0ca69472d9 |
feat(cli): feedback says whether a person or an agent wrote it, with the email an app attaches (#4984)
* feat(cli): feedback says whether a person or an agent wrote it, with the email an app attaches * refactor(cli): split the .env loader so each part stays simple * fix(cli): a broken .env loader fails loudly; only a missing .env is ignored |
||
|
|
de9bf487d4 |
fix(cli): publish with a refused login refreshes or asks you to log in again (#4964)
* fix(cli): publish with an expired login stops and asks you to log in again The server now answers 401 to a rejected credential instead of publishing anonymously, so the CLI says so and drops the warning for the old fallback. * fix(cli): publish refreshes an expired login first, and names a rejected API key An expired access token with a refresh token is refreshed (the cloud commands already do) instead of being sent and refused. Only the requests that carry the credential map a 401; an environment API key is named, since logging in would not replace it. * fix(cli): an expired login without a refresh token asks you to log in again A stored login past expiry with no refresh token now resolves as LOGIN_EXPIRED instead of "not signed in", so publish stops with the re-login message before sending anything. A signed-in publish that the server answers with an unclaimed project fails the same way and links nothing. * fix(cli): keep the resolver comment share at its base * fix(cli): an API-key publish still succeeds, and only an owned publish is linked * fix(cli): --update and --space refuse an API key instead of being ignored * fix(cli): publish uploads with the credential it checked, so --update and --space are never dropped * fix(cli): a login that expires during the bake is refreshed, not reported as expired * fix(cli): an expired login that cannot refresh stops publish before anything is sent * fix(cli): a token refresh never writes over a login that replaced it * fix(cli): a refresh that cannot read the saved login reports that, not a changed login |
||
|
|
9123628d94 |
fix(studio-server): time history idle limits on one clock so a slow machine cannot flip them (#4924)
* test(cli): time history turns on one injected clock instead of real waits * fix(studio-server): end an idle history window by its own clock, not a bare timer * fix(studio-server): cap a history window's idle timer at Node's timer range |
||
|
|
7348933111 |
feat(cli): expose harness subscription usage to agents (#4919)
* feat(cli): plan video creation around remaining harness usage * feat(cli): read Codex and Grok subscription allowances * test(cli): load usage fixtures through portable file URLs * test(cli): keep fixture writes on filesystem paths * refactor(cli): separate usage window and login decisions * fix(cli): guard parsed credential array entries * refactor(cli): report harness usage without workflow policy * fix(cli): preserve usage when subscription tier is null * test(cli): separate usage fixture code from inputs * style(cli): format usage transport fixture * refactor(cli): isolate usage fixture credential checks |
||
|
|
4178a8f95d |
fix(cli): reject extra positional arguments on every command (#4905)
* fix(cli): reject extra positional arguments on every command * fix(cli): read the command name without assuming a resolved meta * fix(cli): join free-text words for catalog and tts, reject a bad repo root * test(cli): load the listed commands through static imports * fix(cli): keep a tts file path out of the word join and read the parsed json flag * fix(cli): apply the file exception only to a tts .txt path * fix(cli): keep usageLine private and test the json fallback for undeclared json * fix(cli): keep extra arguments and the repo root value out of telemetry * test(cli): type the skills update lookup * fix(cli): read json mode from the parsed flag, reject tts words with --text-file, restore add's hint * fix(cli): check tts words with --text-file before --list and test --json=false |
||
|
|
fcc637068c |
fix(cli): honor an explicit --port for background preview reuse and launch (#3915)
* fix: honor explicit --port when reusing a background preview server startBackgroundPreview never compared a reuse candidate's actual bound port against the caller's explicitly requested --port before returning it, so a second `preview --port <N>` call could silently return an existing server on a different port instead of honoring the request. Reuses the existing PreviewServerPortMismatchError (already used by findPreviewServerForProject for the same discipline) instead of adding a new error type: an explicit --port that doesn't match the reuse candidate now throws a clear conflict rather than substituting the wrong port silently. A bare launch with no --port keeps reusing any project-matching server, unchanged. Deliberately out of scope: the interactive/embedded launch path (runEmbeddedMode -> findPortAndServe) has the same silent-substitution shape when a same-project server is found mid-scan at a port other than the one requested. That path's output isn't the JSON lifecycle schema this bug was reported against, and fixing it would mean touching the interactive dev-server bind/scan loop rather than a simple pre-return check, so it's left as a follow-up candidate. Co-Authored-By: Miguel Angel <miguel.sierra@heygen.com> * fix: honor explicit --port on every background preview exit path `startBackgroundPreview` already refused to reuse a same-project server on the wrong port when --port was explicit, but the fresh-launch path still trusted whatever port the detached child bound. The child scans upward from --port and takes the first free port, so `--background --port N` with N busy reported `{ type: "started", port: N+1 }` and recorded ownership of it. - After the child comes up, compare its port with the explicit --port. On a mismatch, reap the wrapper, wait until the substitute stops answering, and throw `PreviewPortUnavailableError` (JSON code `preview-port-unavailable`). If the substitute never stops, fail loudly instead of reporting it. - Share one `unmetPreferredPort` predicate between the reuse and launch paths, and one `awaitServerGone` wait between the reap path and `stopBackgroundPreview`. - When several same-project servers are running, prefer the one on the explicit --port as the reuse candidate instead of the lowest port. - Map launch failures to JSON codes in one place (`backgroundStartFailureCode`). Tests: preferred port bound → reported as-is; preferred port taken → reaped and rejected with both ports, no session record; substitute that keeps serving → distinct error; bare launch keeps the next free port; explicit port picks the matching sibling. Deleting the launch-path check, the candidate ordering, or the post-kill wait each turns its test red. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(cli): reuse the same-project server on the explicit --port `startBackgroundPreview` picked its reuse candidate before honouring `--port`, so with an owned server on 3002 and an unmanaged same-project server on 3003, `--port 3003` rejected with "No Studio preview server … on port 3003. Matching server port: 3002" even though 3003 was running. - A policy-matching same-project server already on the explicit port is now the reuse candidate, owned or not; the owned server stays authoritative otherwise, and an explicit GPU-policy change on the requested port still replaces it. - `PreviewServerPortMismatchError` receives every policy-matching same-project server, so the ports it reports are the ones running. - `policyMatchingServers` is the single owner of the same-project + GPU-policy predicate; `matchingServer` derives from it. - `BackgroundPreviewResult` names the launch result union once; `PREVIEW_PORT_MISMATCH_CODE` replaces three literal error codes. Tests: unmanaged sibling on the explicit port is reused without touching the owned server; mismatch error lists every same-project port and no foreign-project port; owned server on the explicit port is replaced on a GPU-policy change. Each new check fails when its guard is removed. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * refactor(cli): keep the --port honour comments short * fix(cli): drop the scan binding stopBackgroundPreview no longer reads * refactor(cli): keep comment share at base in the preview lifecycle files * fix(cli): reuse a same-project server on an explicit --port beyond the scan window * test(cli): pin the JSON codes for a background start that misses --port * refactor(cli): probe an out-of-window --port in its own helper --------- Co-authored-by: Miguel Angel <miguel.sierra@heygen.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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). |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
950cccdac0 |
test: cli and studio-server tests remove the temp folders they create (#4566)
* 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 |
||
|
|
31c00d2f0e |
fix(studio): avoid preview reloads for temporary and excluded files (#4553)
* 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 |
||
|
|
16dc05e9fb |
fix(cli): stop background update and skills checks from holding a finished command open (#4556)
* 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 |
||
|
|
d290c05b09 |
fix(cli): --yes in an unattended run no longer overrides a no saved by another process (#4547)
* fix(cli): --yes in an unattended run no longer overrides a no saved by another process * fix(cli): every settings writer takes the lock, and --yes needs a real terminal to reverse a no * fix(cli): the thin-results download offer also needs a real terminal * fix(cli): one owner-checked settings lock, and an unreadable settings file never counts as a yes * fix(cli): settings lock removals go through a reaper, and a non-boolean answer counts as a no * fix(cli): a settings lock that cannot be released no longer fails a write that landed * fix(cli): a leftover settings lock is never taken over, and doctor names it * fix(cli): a background update that cannot be recorded is not launched again every run * fix(cli): every on-device install re-reads the saved answer first, and an unsaved yes installs nothing * fix(cli): the thin-results offer stops when a yes cannot be saved * fix(cli): only the consent-checked step installs the on-device runtime, and a leftover lock warns once * fix(cli): the leftover-lock warning follows the attended-terminal rule |
||
|
|
7eb6430e4e | fix(studio-server): history refuses once closed and reopens when a new project takes its folder (#4537) | ||
|
|
7c017a08de |
feat(studio-server): history can copy the files before or after any change into a folder (#4493)
* feat(studio-server): history can copy the files before or after any change into a folder ProjectHistory.checkout(entryId, side, emptyDir) writes the project as entry entryId found it (before) or left it (after) into an empty folder, leaving the project and its history untouched, so a host can play a change's before and after side by side. The entry's own files come from its recorded change and the rest from the entries logged before it, so a change stays exact when entries were logged out of write order. * fix(studio-server): checkout refuses an unreadable folder and every replay names its side * fix(studio-server): checkout lays out only trees that existed and never writes into the project An entry's tree now includes later-logged entries whose writes it continues (an outside change that turned a file into a folder before an agent turn edited inside it), each file replayed in the order its versions chain. Checkout refuses a folder inside the project (symlinks included), refuses a tree with a path that is both a file and a folder before writing anything, and empties the folder again if a copy fails. manifestAt is back to main's replay. * fix(studio-server): history logs every change in the order it was written A window now settles pending writes, the held claim and the outside group before it opens, and commits a held claim before its own entry when it closes, so the log order is the write order and every replay (checkout, restore, peek, reopen) sees files as they were. Removes the dependency replay added for checkout. Visible change: an outside burst that spans an agent turn is two entries, and a drag held across a turn ends at the turn. * fix(studio-server): checkout recognises its project folder whatever letter case the path uses * fix(studio-server): overlapping turns, operations and claims log every pending change in write order settle() (a window opening, an undo, restore or step) and every claim commit the held claim, the outside group and each open window's pending changes as a part, oldest first; the windows stay open. A held drag ends when another write is pending. One commitPending owner replaces the claimed-paths-only cut. * feat(studio-server): undo is per author, and a turn part undoes alone keeping later edits * test(studio-server): pin per-author redo and name a person's open window on Cmd+Z * refactor(studio-server): split Cmd+Z target lookup and claim steps under the audit's thresholds * fix(studio-server): a file replaced by a folder logs its removal before the folder's files * fix(studio-server): undo across a file/folder swap works, and a later swap stays the person's * fix(studio-server): an undo blocked by a file added since refuses before touching any file * fix(studio-server): an undo never writes through or replaces a link standing in its way * fix(studio-server): undo and redo across a letter-case file/folder swap work in every direction * fix(studio-server): a letter-case file/folder swap after an idle turn stays one checkable entry * fix(studio-server): decide letter-case folding from the project's own disk * docs(studio-server): keep three history comments under the line limit * fix(studio-server): checkout checks and fills one resolved folder, so it never overwrites a nonempty one |
||
|
|
2be82f0099 |
fix(cli): use onnxruntime-node installed beside the CLI when it is the pinned version (#4507)
* fix(cli): use onnxruntime-node installed beside the CLI when it is the pinned version An optional package (onnxruntime-node, @google/genai) installed next to the CLI at the pinned version is now loaded from there, before the ~/.cache/hyperframes/optional copy and without an npm install. Any other version is ignored and the cache path runs as before. doctor reports that copy's version. * refactor(cli): name the beside-the-CLI lookup instead of commenting it * fix(cli): a bad optional package copy beside the CLI no longer breaks doctor The lookup reads the first copy Node would load and treats an unreadable manifest as not the pinned copy, so doctor and the loader fall back to the cache instead of throwing or checking a different copy. * fix(cli): let Node pick the optional package copy before checking its version The beside-the-CLI lookup now asks require.resolve which copy it would load and checks that copy's manifest, so an empty folder or a same-named file earlier on the path can no longer make the check and the loader disagree. * test(cli): cover a symlinked optional package copy beside the CLI * fix(cli): find a symlinked optional package when Node preserves symlinks Under --preserve-symlinks require.resolve returns the symlink path while the candidate folder was compared by its real path, so a pinned copy Node loads was rejected. Both sides are now real paths. A test runs the lookup in a Node subprocess with --preserve-symlinks. |
||
|
|
2734ee2c02 |
feat(cli): hyperframes history lists, undoes and restores a project's recorded changes (#4449)
* feat(cli): hyperframes history lists, undoes and restores a project's recorded changes * fix(cli): an agent's history turn ends after 10 idle minutes on every path A turn left open no longer takes the person's later edits: the engine files a CLI turn's writes by the same idle rule as a preview window. Studio claims take their paths back from an agent's window; refusals, --json shapes, --limit and time parsing follow the review. * refactor(cli): the project-history opener lives in its own module The history command and other commands open a project's history the same way: through a running preview, or directly under the engine's lock. * docs(skills): the history loop lines sit in one marked trial block per skill * fix(cli): a turn marker without its last write time has ended Idle-limit tests set file times instead of racing the clock, and the restore refusal test picks a ref no entry id starts with. * docs(cli): the history entry shape comment names no other app * refactor(cli): history reuses the busy error class and its who helper * fix(cli): undo --who reverts the agent's whole last turn and nothing older When the open turn recorded nothing, undo fell back to the agent's earlier turn; when a mid-turn command split the turn, it reverted only the newest part. The turn now keeps the entries it filed, and undo --who reverts exactly those or refuses. Engine tests pin the write timing rules, and a date that rolls over (Feb 31) is refused. * fix(cli): an undo --who that meets a conflict leaves the whole turn as it was It redoes the parts it already undid, lists the newer entries as they are now, and offers --just-this and --back-to-before for the whole turn. The last turn is recorded atomically before its marker is cleared. * chore(cli): history keeps each file's comment share at or under main's * fix(cli): history keeps log order past a turn's idle limit and undoes a turn's cut parts * fix(cli): history drops unused imports and splits undo and reopen steps |
||
|
|
f873734cc4 |
perf(studio): open a large film ready to play in about 4 s instead of 32 s (#4401)
* perf(studio): keep lint off the preview open path The bundler linted every build only to print a StaticGuard warning; a preview bundle now skips it through a staticGuard option, since Studio lints on its own route. That route now lints in the CLI child-process worker the render path already uses, so seconds of parsing no longer stall the preview and every other Studio request on the server event loop. * perf(parsers): parse identical GSAP source once Studio re-requests the parsed animations of unchanged source on every open, and several lint rules parse the same script in one run. A small memo keyed by the script text serves repeats; each caller gets its own copy. * perf(studio): build each preview document once per content version The preview route rebuilt the document for every browser without a cached copy. It now serves one build per ETag from memory, and the CLI keeps the last build on disk salted with its own file, so a restarted server serves the previous document without stale code. * perf(studio): build the opening film's preview while the browser starts The preview server built the document only when the player asked for it, after Studio had booted, so a first open waited about a second of idle server time. The CLI now requests the opening project's preview right after listening, and requests that arrive while a build runs share it instead of starting another. * perf(studio): request the preview document at page load Studio asked for the preview only once the player mounted, about 640 ms into a first open. It now requests it the moment the page loads, and again when the hash names another project, so the build or cache read overlaps Studio's boot. * feat(studio): show the cached frame 0 while the live preview boots Studio covers the live preview with frame 0 as the thumbnail route last rendered it, sized from the image so the stage does not shift, and removes it when the live frame is ready to show. The request is cache-only, so a miss never starts a render on the open path; the missing poster is rendered after the live frame shows, for the next open. * perf(cli): start Studio before the startup lint finishes hyperframes preview linted the whole project before it listened, 4 to 6 s on a large film, and did so in --json mode where the result is discarded. The lint now runs in the CLI worker while Studio starts and prints its summary when done; --json skips it. * perf(studio): render thumbnails from one loaded page, after the preview boots Every thumbnail opened a page and loaded the whole composition to shoot one frame, and the Compositions card asked for one while the live preview was still booting. The server now keeps one loaded page per preview document and seeks it per frame, reloading only when the project changes; card thumbnails wait for the project first ready preview. * perf(studio): parse GSAP animations after the preview boots Studio asked the server to parse the composition animations while the preview was opening; on a fresh server that cold parse ran on the event loop ahead of the preview build. The request now waits for the live preview to boot, or to fail loading, since only keyframes need it. * perf(cli): find orphaned browsers with one process-table read preview ran pgrep three times and then ps once per matching browser, all synchronously before listening: about half a second on a machine running other headless Chrome. One ps listing now finds every orphan in ~20 ms. * perf(studio): keep lint off the boot Opening a project started two lint processes while Studio booted: the terminal's startup lint and Studio's background lint, ~4 s of CPU next to the boot. Studio's background lint now waits until the live preview has booted, and lint processes run at the lowest CPU priority so they only take idle CPU. * perf(studio): open the editing session after the preview boots Opening a project read the composition source and parsed it for the editing session (linkedom plus hf-id assignment) on the main thread while the live preview was booting on the same thread: ~300 ms on a large film. The session now opens once the preview has booted; nothing the first frame shows needs it. * perf(studio): park the editor overlays until the preview boots The overlay frame loop polled preview geometry and off-canvas indicators from the first frame, forcing layout on a preview document that was still loading, on the thread its boot runs on: ~200 ms before the first frame on a large film. The loop now skips its subscribers until the preview has booted, then wakes. * perf(studio): treat the preview as booted once it can play Work deferred until the boot (editing session, lint, overlays, GSAP parse, card thumbnails) started at timeline-ready, which fires before the asset gate, so the editing-session parse ran inside the wait for play. The Player now latches the boot when the preview can show and play, or fails to load. * fix(studio): keep a left project's deferred work out of the one now open Deferred open-time work (source parse, background lint, editing session) waited on one global boot flag, so leaving a project before its preview booted let the next project's boot run the old project's work and write its results. The wait is now per project and resolves false once another project opens. A preview stuck without an error stops deferring that work after 5 s. * perf(cli): let the play page revalidate instead of refetching hyperframes play served the composition, its assets, the player and the runtime with no validator, so every reload refetched everything, the mp4 in full three times. They now carry an ETag and stay no-cache, so unchanged bytes cost a 304 and an edit is still seen. * fix(cli): serve an edited text asset even when its size and mtime match The play page tagged every asset by mtime and size, so a same-size rewrite within one filesystem tick kept the old tag and the browser kept stale bytes. Text assets now get a content-hash tag; media keeps the mtime and size tag Studio already uses. * fix(studio): close idle thumbnail pages so an idle Studio runs no composition Kept-loaded thumbnail pages stayed open for the life of the server, each running a live composition, so an idle Studio cost server CPU and memory that main does not. A page now closes after 10 s unused, and a page is closed only after the frame still running on it finishes. * fix(studio): stop a first open from reloading the preview once per scene file Studio stamped hf-ids into a composition the first time that composition was served, so opening a film of external scenes wrote to index.html and every scene file mid-session. Each write reached Studio as an outside edit and reloaded the preview, which failed for a large film. The server now stamps every composition once before it starts watching the project. * fix(studio): ignore play until the preview can play, so it is never undone Space (and any play() caller) could start playback before the preview frame finished loading. Its load step then paused and rewound to the restore point, so the play was silently lost. play() now waits for timelineReady, the same gate the Play button already uses. * fix(studio): hold play on a composition switch until the new preview loads Readiness was reset only when the project changed. Drilling into another composition kept the old ready flag while the new preview loaded, so Play or Space could start a play that its load step then undid. * refactor(studio): split background lint and thumbnail page reuse into single-purpose steps Keeps the boot wait in the background lint only, moves page lookup and idle close out of withPage, and drops a suppression the startup worker no longer needs. Behaviour unchanged. * chore(cli): declare the render-setup worker as a fallow entry It is spawned by path from cancellableProcess.ts, so it has no import-graph referrer; the entry list is where the repo records that, replacing the per-file ignore removed in the previous commit. * fix(cli): close a thumbnail page whose load fails The page never leaves the promise that loads it, so when the load throws (a WebGPU composition with no adapter, or the navigation timeout on a big film) nothing could close it. * fix(cli): match text assets by exact type and keep Range on them The pattern anchored only its first alternative, and text assets skipped buildRangeResponse, so a ranged request for a stylesheet or SVG got a full 200. * test: open the next project as Studio does, and read the stamped file once The absent-read guard test switched project without beginning its timeline session, so the per-project boot wait held the read. The stamp test checked then read the same file. * test(cli): rewrite the scene file without opening it first Opening the file by path counted as a check before the later read of the same path. * perf(cli): take a thumbnail's version from the cached project signature Each thumbnail walked the project and hashed every text file for its page version; the adapter already caches that signature and clears it from the watcher. An aborted request now also stops before its screenshot. * fix(studio): keep Play off until the preview's load step has run Play could enable before the preview document's load event, from the runtime's timeline message or from the blank page's load step settling on the preview's first message. The load step then paused and rewound the preview, so a Play pressed in between did nothing; a film with large images loads late enough for this to hit a cold open. Readiness now waits for the document's load step, whichever path asks for it. * refactor(studio): move the preview poster's state into usePreviewPoster NLEPreview.tsx passed the 600-line limit once main grew it; the poster's state and callbacks belong beside PreviewPoster. No behaviour change. * fix(cli): take an earlier thumbnail on a fresh page A composition need not draw the same frame when seeked back, so a kept page that had shown 20 s drew a different 10 s thumbnail than a fresh load. A kept page now only seeks forward, as a render does; an earlier time loads a fresh page. * perf(cli): close a kept thumbnail page a second after its last frame Held for 10 s, the page went on running its composition after the open's thumbnail burst, about 5 % of a core in the thumbnail browser while Studio sat idle. A burst's requests arrive back to back, so it still loads the page once. * fix(studio): count the preview as booted at its first frame The boot waited for every video and audio in the preview to finish buffering, which on a project with media took until the 5 s fallback. The thumbnails, editing session and lint that wait for the boot then started seconds late and kept an idle Studio busy. They now start once the first frame is up; media finishes buffering alongside. * perf(studio): render one thumbnail per composition for its card and clip A composition's sidebar card asked for its frame at 3.00 s and its timeline clip for the clip's midpoint, so opening a project rendered every scene twice. The clip now asks for the card's frame (the midpoint still, for compositions under 3 s), and the thumbnail cache serves both from one render: 20 scene renders drop to 12 on a 12-scene project. * chore: keep each changed file's comment share at or under main's The comment ratchet counts the comment lines this branch added. Each is cut, or kept as one line where it states an invariant (the GSAP parse cache returns copies; the boot starts waiting work at the first frame). The orphan-cleanup docblock is condensed and now names the process-table read it uses instead of the pgrep scan it replaced. No code line changed. * fix(studio): keep a 3 s clip off its end frame and give card and clip one encoded URL A composition exactly 3 s long asked for its thumbnail at 3.00 s, its end instant, which after a fade-out is an empty frame; compositions of 3 s or less now use their midpoint. The card built its thumbnail URL from the raw path while the clip encoded it, so names with & + [ or % asked for two different URLs, and a % in the name made the card's request fail on the server; both now encode. The readiness comment says the boot latches at the first frame, as it has since the boot moved. * fix(studio): encode the card's live preview URL too, and document the bundler's static guard The card's hover preview built its URL from the raw composition path, so a % in the file name made the preview route fail to decode it. The card now builds its composition URL in one place, encoded, for both its thumbnail and its hover preview. The static-guard bundle option keeps a one-line doc (its default and what it warns about); a neighbouring docblock is rewrapped to keep the comment share. * test(cli): mock studio-server once so the signature spy and the history hook both apply The main merge brought a second vi.mock of @hyperframes/studio-server into studioServer.test.ts; the later one replaced the first, so createProjectSignature was no longer a spy and the cached signature test read .mock of undefined. One mock now carries both overrides. |
||
|
|
7556b95ac6 |
feat(cli): warn when canvas content touches the frame edge (#4427)
* feat(cli): warn when canvas content touches the frame edge * test(cli): pin which frame edge the canvas warning reads * test(cli): type the canvas edge stub like the file's other getContext stubs |
||
|
|
7e8d2bd1f3 |
fix(cli): stop check --at false alarms and name a stale CLI version (#4417)
* fix(cli): stop check --at failing still frames as a frozen timeline * fix(cli): warn when the running CLI is older than the project's pin * fix(cli): keep judging spread samples when check --at is combined with other gates |
||
|
|
d27435c070 | fix(lint): report original source line and column locations (#4353) | ||
|
|
105984cac4 | feat(cli): add IBM Bob runtime support (#3930) | ||
|
|
e5689782c0 |
fix(cli): require non-empty feedback repro commands (#4263)
Co-authored-by: GPT-5.6 Sol <noreply@openai.com> Co-authored-by: miguel.sierra <miguel.sierra@heygen.com> Co-authored-by: heygengenesis[bot] <262951085+heygengenesis[bot]@users.noreply.github.com> |
||
|
|
45f92e5572 | fix(cli): a failed docker render names the container's last error (#4168) | ||
|
|
e8fdfd0d65 |
fix(cli): install onnxruntime-node and google-genai on first use (#4156)
* fix(cli): install onnxruntime-node and google-genai on first use Both carry deprecated transitive dependencies (boolean, node-domexception) that showed up as npm warnings on every npx hyperframes install. They move to devDependencies (types and the Intel-binding check still need them at build time; the bundler externalizes them) and load through a new on-demand loader that installs into the CLI's own cache dir the first time a feature needs them, with one stderr line, no prompt, and a manual-install command named in the error if the network is unavailable. hyperframes doctor lists each as installed (with version) or not-installed-yet. * fix(cli): sweep stale install staging and bound network retries * fix(cli): optional-package install only sweeps staging dirs whose process is dead Staging dirs now carry a per-call suffix after the pid, and the sweep skips any dir whose pid is alive, so concurrent first runs no longer delete each other's install. The install error suggests the network only for network failures, and the manifest path has one owner. install() takes an injectable npm runner so tests drive the real code. * fix(cli): optional-package sweep also clears old-format staging dirs and tests a live foreign dir The concurrent-callers test shared one pid, so it never exercised the sweep rule; a live foreign staging dir test now does. Old-format dirs of a dead pid are swept, and a failed cleanup no longer aborts a viable install. * fix(cli): optional-package sweep names a stale dir it could not remove A locked leftover is still skipped so the install proceeds, but the warning makes it visible. * docs(cli): shorten the install docblock to the comment limit Comment-only change. |
||
|
|
2db126d4c8 |
docs(skills): one owner doc for everyday edits and a hyperframes-studio skill (#4125)
* docs(skills): one owner doc for everyday edits and a hyperframes-studio skill Agents got contradictory advice on volume fades and were blocked from splits and copies by the Ask-agent guardrails. - creator-editing-recipes.md is the single owner: adds align-to-event, copy-a-group and swap-media recipes and the one volume rule (use the lane) - variables-and-media, data-attributes (skill and CLI copy), the gsap adapter and tracks-and-clips now defer to it - Ask-agent guardrails allow the neighbour edits a split, retime, copy or swap requires and point at the owner doc - new hyperframes-studio skill: sub-compositions for every scene, one caption track, one element kind per track, safe zones - drift test lints every html example in the owner doc with the real linter and fails on any doc teaching a volume tween * refactor(skills): drop unused tween allowlist from recipes drift test * docs(skills): name the track kind attribute in the studio skill * docs(skills): stop claiming lint rules that do not exist yet and fix track numbering Review fixes: - studio skill: drop the severity claim and the preview-overlay claim, group tracks by data-track-kind instead of numeric ranges that clashed with the recipes - recipes: audio data-start is root time only in the root composition; scene-local inside a sub-composition file - guardrails: timeline edits are exempt from the element-only and timing lines; the pointer names the skill reference, not a repo path - montage doc defers to the volume lane; skill count is 21 everywhere - drift test also scans the montage doc * fix(skills): the ducking generator emits a volume lane and captions defer to the studio safe zones - audio-duck.mjs prints a data-automation lane (clip-local times from the target data-start) instead of a timeline volume tween; --json adds the lane - operations.md shows the generated lane; the drift test scans it - embedded-captions safe-zone lines point at the studio skill, which owns the values * chore(skills): regenerate the skills manifest after rebase * docs(skills): remotion translation docs point volume ramps at the automation lane * docs(skills): studio safe zones use the action-safe 90 and title-safe 80 boxes * docs(skills): caption docs defer to the studio safe boxes * chore(skills): regenerate the skills manifest after rebase * fix(skills): studio skill names the safe margins; guardrail test matches the timeline-edit wording |
||
|
|
93921dccc8 | fix(cli): stop the workspace build check test from depending on wall-clock mtimes (#4098) | ||
|
|
2a9912ec99 |
fix(cli): stop the cancellable-process test reading a pid file mid-write (#4080)
Same defect shape as the earlier preflight cancel test fix: the wait condition only checked that the ready file existed, which goes true as soon as the write opens (and truncates) it, before its content lands. A read landing in that window parsed an empty string to 0. The wait condition now also validates the parsed value is a real positive pid before accepting it, retrying otherwise. Reproduced with a busy-wait harness that hit the empty-file race in roughly two of five attempts and confirmed clean after the fix. |
||
|
|
de9206e0f8 |
fix(cli): detect a missing or stale studio build before it crashes (#4076)
* fix(cli): name the missing or stale workspace package before preview crashes Running hyperframes preview from source with an unbuilt or stale package previously crashed deep inside that package's own import graph with a raw resolver error naming neither the package nor the fix. Add a preflight that runs before the preview command's own module import, so it also catches the crash that happens at CLI load time before the studio dev server ever spawns. * test(cli): cover the preview workspace-build gate wiring Adds a test that exercises the real CLI dispatch path (mocked module loaders, not source-text matching) to prove the preview command's build-check runs before its module loads and that a failure exits non-zero without double-printing. Also guards the exports-map reader against a malformed package.json instead of crashing raw. * fix(cli): correct why the audio-fx generated file is checked The comment claimed core/src/index.ts imports both generated files directly. Only runtime-inline.ts is; audio-fx-runtime-inline.ts is reachable solely via core's own audio-fx-runtime export subpath, which the engine package imports. The check itself was already correct to require both, only the stated reason was wrong. |
||
|
|
096459e76d |
fix(cli): collapse startup lint output to a one-line summary (#4052)
* fix(cli): collapse startup lint output to a one-line summary preview, publish, and render's non-abort lint check all dumped every finding to the terminal before their real result line, drowning it in noise unrelated to whether the project is in good shape. Full findings already surface in Studio's own Lint badge; all three now print a one-line summary by default, with --lint-verbose for the old full output. One formatter (formatLintStartupMessage), three call sites. * fix(cli): show lint findings in full on every abort path Any path that calls presentRenderLintAbort now prints full findings even without --lint-verbose, since the one-line collapse was hiding the reason a render stopped. * fix(cli): scope the lint summary pointer to what each command has open The one-line lint summary always pointed at "the Lint badge in Studio", even from render and publish where no Studio is running. It now points at --lint-verbose for those, and preview additionally names the hyperframes lint command since its own stdout is what an agent reads, not the badge. publish now forces full findings on its own entry-mismatch abort, the same fix render already shipped, so the reason for the abort isn't hidden behind a bare count. * docs(cli): trim a doc comment past the 4-line limit |
||
|
|
e4b1832fad | feat(cli): add hls output format (#4022) | ||
|
|
08d48ab753 |
fix(cli): guard process snapshot capture (#3974)
Co-authored-by: heygengenesis[bot] <262951085+heygengenesis[bot]@users.noreply.github.com> Co-authored-by: miguel.sierra <229591595+miguel-heygen@users.noreply.github.com> |