* fix(producer): convert HEIC images to JPEG at compile time
Tiled HEIC photos probe as several video streams, so the media-type
preflight rejected them as image:video, and Chrome cannot decode HEIC.
Sniff the ftyp brands of every <img>, join and rotate the tile grid with
ffmpeg, verify the declared size, and swap in a cached JPEG.
* fix(producer): shorten the HEIC prep comments
* fix(producer): name host tool failures and keep a failed HEIC preview image as authored
Also: HEIC tests run in the integration lane; the primary grid is picked by default disposition.
* chore(producer): raise the package size budget for the HEIC prep
* feat(cli): check catches audio the render would lose or play as silence
check gains an Audio section, read from the media list the render mixes:
- error: a clip's source has no audio the render can read (its mix fails,
the film ships without sound)
- warning: a clip starts past the end of its file, or the part it plays
is silent
- info: the film has no sound at all
The bundle and media list are now built once and shared with the HDR
inspection; the audio pass runs while the browser pass does.
Fixes#2775
* fix(cli): check reports an unreadable audio source the way render handles it
A source with no audio track is refused by render's own media-type rule, now exported from the producer and called by check; a source that does not decode as audio fails the mix and stops the render.
* perf(cli): check samples a long clip at a few spots before reading all of it for silence
Sound at any spot ends the decode; a clip is only called silent after its whole played part is read.
* chore(cli): drop a comment the shared finding helper's name already says
* feat(cli): hyperframes <dir> opens the project in the app
* refactor(cli): one command-word lookup and one rewritten command line for the folder shortcut
* refactor(cli): shorten the folder shortcut comments to fit the comment ratchet
* fix(media-use): serialize concurrent preference records
* chore: prepare conflict resolution against current main
* fix(media-use): serialize concurrent preference records
* docs(media-use): clarify project preference file guidance
* chore: prepare conflict resolution against current main
* fix(media-use): refresh preference records against main
* chore: prepare conflict resolution against current main
* chore: merge main and refresh preferences skill manifest
* chore: prepare conflict resolution against current main
* chore: merge main and refresh preferences skill manifest
* chore: prepare conflict resolution against current main
* chore: merge main and refresh preferences skill manifest
* fix(cli): preserve UTF-8 across subprocess output chunks
* chore: prepare conflict resolution against current main
* fix(cli): retain UTF-8 output handling with current main
* chore: prepare conflict resolution against current main
* fix(cli): retain UTF-8 decoding with process teardown updates
* chore: prepare conflict resolution against current main
* fix(cli): preserve decoded subprocess progress on current main
* docs(cli): scope Unicode output guarantees to cancellable workers
* docs(cli): name commands with Unicode-safe diagnostics
The examples topic listed title-card and video-edit, which do not exist,
and none of the eight examples in registry/examples. List blank plus the
registry examples with their registry-item descriptions, and add a test
that fails when the topic and registry/examples disagree.
Fixes#5393
* feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review
The --skip-contrast-in-motion flag and HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION
env give way to a check-review capability in HF_TOOL_CONTEXT, so only a harness
turns it on. The skill text and manifest go back to main.
* feat(cli): check-review findingFrames saves one plain frame per finding time
With HF_TOOL_CONTEXT check-review.findingFrames, every error/warning finding in
layout or contrast with a real bbox gets frameTime (the middle sample of its
longest held run, by the heldMs gap rule) and frame (the absolute path of an
unannotated full frame shared by every finding at that time). One seek and
screenshot per distinct time, capped at 60 times with errors first, into the
run's snapshot folder; not listed in snapshots, history or events. Without the
capability, output is unchanged.
* docs(cli): finding-frame comments name the check-review capability, not a flag
* fix(cli): validate finding frame times and preserve cancellation
* fix(cli): count finding frame progress toward the check deadline
* feat(cli): check-review inspect attaches the inspector's report to each framed finding
With findingFrames on, the finding-frames page is served with the
inspector's GSAP hooks. After each frame is captured, every finding framed
at that time is inspected by its data-hf-id, else its selector, and the
inspector's own report is attached as the finding's inspect field. A failed
inspection becomes that finding's { error } and never fails the check.
Without the capability nothing changes.
Drops the unused @types/mime-types devDependency that the dependency audit
flags once packages/cli/package.json is touched.
* fix(cli): stop finding inspections after cancellation
* fix(cli): reject ambiguous finding inspection targets
* fix(ci): build inspector before Windows CLI smoke
* fix(cli): parse viewport HTML with explicit server parser
* feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review
The --skip-contrast-in-motion flag and HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION
env give way to a check-review capability in HF_TOOL_CONTEXT, so only a harness
turns it on. The skill text and manifest go back to main.
* feat(cli): check-review findingFrames saves one plain frame per finding time
With HF_TOOL_CONTEXT check-review.findingFrames, every error/warning finding in
layout or contrast with a real bbox gets frameTime (the middle sample of its
longest held run, by the heldMs gap rule) and frame (the absolute path of an
unannotated full frame shared by every finding at that time). One seek and
screenshot per distinct time, capped at 60 times with errors first, into the
run's snapshot folder; not listed in snapshots, history or events. Without the
capability, output is unchanged.
* docs(cli): finding-frame comments name the check-review capability, not a flag
* fix(cli): validate finding frame times and preserve cancellation
* fix(cli): count finding frame progress toward the check deadline
* feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review
The --skip-contrast-in-motion flag and HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION
env give way to a check-review capability in HF_TOOL_CONTEXT, so only a harness
turns it on. The skill text and manifest go back to main.
* fix(cli): satisfy comment ratchet for check capability
* feat(cli): opt-in check flag that leaves out contrast measured mid-animation
`hyperframes check --skip-contrast-in-motion`, or
HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION=1, leaves out a contrast failure
measured while a GSAP tween on the text or an ancestor is running, or while
the text's cumulative opacity is below 1. Repeating tweens and tweens of 4 s
or longer (camera drifts) do not count as motion. Text no GSAP tween targets
is reported as before. With neither set, check is unchanged.
The motion state is read at the same contrast sample, with no extra seek or
screenshot.
* fix(cli): read contrast motion on current GSAP clocks
* fix(cli): keep infinite timeline motion ambient
* perf(cli): lambda deploy warms the video function so the first render does not wait for its image
A freshly deployed container-image function loads its image on first use, which added about 12 s to the first render's plan on some deploys. Deploy now invokes the function's warm action once after the stack is up (default on, --no-warm skips it). A failed warm-up prints a warning and the deploy still succeeds.
* chore(cli): move the deploy warm-up to its own file, regenerate the skills manifest
Regenerates skills-manifest.json for the lambda reference change, keeps runDeploy under the complexity gate by pulling the warm step into a helper, and leaves sam.ts untouched.
* fix(cli): the deploy warm-up asks the aws CLI for JSON output whatever the profile sets
A profile configured for text or yaml output made the reply unparseable, which printed a spurious warm-up warning.
* refactor(cli): split the reserved-concurrency notice and warm-up gate out of runDeploy
* fix(cli): preserve quoted multiline .env values
applyDotEnv parsed .env content line by line: a quoted value's closing
quote was only ever looked for on the same physical line, so a value
like HYPERFRAMES_VERTEX_SERVICE_ACCOUNT='{...}' spread across several
lines got truncated to the leftover text on its opening line, and the
continuation lines were silently dropped (no "=" to parse as entries).
Join physical lines into logical ones before parsing: when a line
opens a quote that isn't closed on that same line, keep appending
following lines until one contains the closing quote character. This
matches how Node's util.parseEnv treats quoted multiline values.
Fixes#6075
* fix(cli): do not let a commented or unclosed quote swallow later .env lines
logicalLines looked for an open quote on every physical line before
entryOf had a chance to classify it as blank or a comment, so a
commented-out example like `# KEY="sk-old` (an unclosed quote inside
a comment) opened a quote that never closed, joining every line below
it into one value and dropping the real keys that followed.
A genuinely stray unclosed quote with no later closing line had the
same failure mode: the join ran to EOF and swallowed the rest of the
file instead of leaving the malformed line on its own, so a later
file like A='x / B=2 lost B entirely.
Skip blank/comment lines before checking for an open quote (matching
entryOf's own skip), and if the closing-quote search reaches EOF
without finding one, fall back to the opening line on its own and
resume parsing from the next line. Also strip a trailing \r per line
so a CRLF multiline value doesn't carry it into the joined string.
* fix(cli): trim comments on dotEnv.ts to stay within the comment-ratchet budget
Dropped the comments on openQuoteOf and closingLineIndex (their names
already say what they do) and cut the logicalLines comment down to
two lines, bringing the file back to 6/93 comment lines against the
base's 4/57.
A dry run hashed every referenced media file in full to look for an earlier upload, strictly one at a time, and printed nothing under --json until the end. It now hashes only files the local upload cache could hold (a file whose size matches no cached asset, or any file when nothing was uploaded, cannot hit), and --json writes one progress line per file to stderr.
* fix(cli): media-treatment and normalize-audio find clips inside mounted scenes
* fix(cli): report the scene file with forward slashes and drop added comments
* fix(cli): normalize-audio reads a scene's audio relative to the scene file
* fix(cli): stop shipping an env var name that endpoint scanners flag
Cisco Secure Endpoint ships an IoC signature that matches the literal
name of the openclaw state-dir env var. Any file containing it is
quarantined, which deletes a CLI dist chunk (the CLI then fails on its
second run) and the Studio app.asar.
Key the openclaw agent-runtime rule on OPENCLAW_CONFIG_PATH only. openclaw
sets both variables on the subprocesses it spawns, so detection is
unchanged. Drop the state-dir name from the comments and from the test,
which now covers detection through the config-path variable.
Fixes#5245
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): keep the openclaw comment under the comment-share ratchet
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): drop the external-repo citation from the openclaw comment
The comment-citations check resolves file:line references against this
repo, and that path lives in the openclaw repo.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
snapshot uploaded every frame to Gemini whenever GEMINI_API_KEY or
GOOGLE_API_KEY was set, even with no flag. Frames now stay local unless
--describe is passed. A bare --describe uses the default prompt (it used
to send an empty one), --describe "question" asks a custom one, and
--describe false still works.
Fixes#6138
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* perf(studio): music beat analysis no longer freezes the page or runs during playback
* fix(core): media-treatment --analyze finishes on long HDR clips
Grade analysis decoded the clip from the start and kept every frame until the
fps filter had emitted five samples. Past 50 s the filter rate floors at 0.1
fps, so it always decoded the first 40 s at full rate and resolution: a
3-minute 4K60 10-bit HLG clip timed out at 15 s, and every long clip was
graded from its first 40 s only.
Each sample now seeks to the middle of one fifth of the clip and decodes one
frame. The timeout applies per sample and its error names the sample time and
HYPERFRAMES_ANALYZE_TIMEOUT_MS.
* refactor(core): name the keyframe seek instead of commenting it
* fix(core): count one frame per grade sample on ffmpeg 8
WORD_RUNS split at apostrophes, so "can't" counted as two words and a line with contractions got a longer hold floor than its real word count.
Refs #6338
On Windows 11 with Smart App Control on, Code Integrity refuses sharp's
unsigned libvips DLL. With `--against`, the per-frame render | reference
pair sheet called sharp unguarded, so a blocked sharp failed the whole
snapshot with sharp's multi-line error dump, after the first frame.
Guard the pair sheet: a sharp load failure prints one warning, later
frames skip their pair sheet without retrying, and every frame and
reference frame is still written. Any other pair-sheet error still fails
the snapshot. captureSnapshots reports the skip, so the summary lists
only ref-*.png when no pair sheets were made.
describeSharpLoadFailure turns sharp's load error into one line and names
Windows App Control for Win32 error 4551 ("An Application Control policy
has blocked this file"). The contact-sheet warning keeps warning on every
failure and uses it to shorten sharp's message, and --describe no longer
tells the user to install a sharp that is installed but blocked.
Refs #4845
Claude-Session: https://claude.ai/code/session_01JSZqP4FbpqviXu9v2LjBwE
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The skills check resolves main's HEAD with `git ls-remote`. A private or
missing repo answers with an auth challenge, and GIT_TERMINAL_PROMPT=0 only
covers git's own terminal prompt: credential helpers and askpass still run,
and Git Credential Manager (the Git for Windows default helper) opens a
sign-in window that blocks the check until it is closed.
Run ls-remote with credential helpers and askpass disabled
(`-c credential.helper=`, GIT_ASKPASS="", GCM_INTERACTIVE=never). Also skip
git for a slug that does not look like `owner/repo`; such a slug gets a
plain 403 rather than an auth challenge, so this check is defensive. The
repos read here are public, so no credentials are lost.
Fixes#4702
Claude-Session: https://claude.ai/code/session_01JSZqP4FbpqviXu9v2LjBwE
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(engine): ffmpeg steps and downloads stop on no progress, not while still advancing
* test(cli): an ffmpeg step that stops advancing reports inactivity, not a deadline
* perf(engine): fast capture stays on for frames a shadow, blur or clip effect does not touch
A single drop-shadow, blur, backdrop-filter or blend element, or one clip-path or mask
tween, sent the whole render to screenshot capture, 2-3x slower than drawElement. The two
init gates now name the frames the effect can show on. Only those frames capture by
screenshot, in a second browser opened on the first of them; every other frame keeps
drawElement.
* fix(engine): scan effects in a throwaway session; hand risk frames over only where captured
The effect scan seeked the render's own page to the end, leaving its timeline state ahead of
the render. It now runs in a throwaway session with the render's own seek. A hidden effect
element with a visible descendant counts as shown. Per-frame capture is opt-in per caller
(deFrameFallback), so disk, segmented, chunk and parallel-worker sessions keep the
whole-render fallback, and a route that cannot hand frames over drops drawElement up front.
* fix(engine): a timeline callback keeps the whole-render fallback; trim comment share
A tl.call or onComplete can add an effect's class to any element mid-render, so the elements
found at the start are not the only ones it can reach. Such a timeline is unbounded and keeps
the whole-render screenshot fallback.
* style(engine): shorten the live-frame scan comment
* perf(engine): after a timeline callback, not before, every frame may show the effect
A callback can add an effect's class to any element, so only the frames from the first
callback on lose their element bounds. Frames before it keep the scanned bounds, so a late
callback no longer sends the whole render to screenshot capture.
* perf(engine): open the second screenshot browser only when memory is available for it
Per-frame capture needs a second browser (479 MB measured at 1080p). When the render would
leave less than 1.5 GB available after it, the render keeps the whole-render screenshot
fallback instead. getAvailableMemoryMb moves from the CLI to the engine's system-memory
module, which already owns the host's memory readings; os.freemem() reads 367 MB on this
49 GB Mac, so the guard uses free, inactive, speculative and purgeable pages.
* fix(engine): budget the whole 1 GB per-frame capture adds, not just the second browser
The render's process tree goes from 1002 to 2001 MB at 1080p: the screenshot browser (479 MB)
plus a drawElement browser that is itself about 500 MB larger than a screenshot one. The guard
budgeted 500 MB. It now budgets the 1 GB measured, scaled by output pixels.
* test(cli): check the browser that ran the hanging script, and that every browser it started is gone
* test(cli): type the launch recorder's pid file and drop the added comment
* fix(cli): a failed frame check no longer switches parallel capture off for good
The per-install breaker tripped on the first self-verify fallback and nothing ever cleared it. Now a second fallback within 20 router renders turns the router off, a new CLI version or 20 clean renders turn it back on, and the stored record keeps the rejected frame (reason, frame, dB, threshold, version, time).
* docs(cli): shorten three comments to the line limits
* refactor(cli): keep the breaker record parsing small and the comments within the ratchet
* fix(cli): a long video copy reports progress, resumes in 15 s pieces and stops only when stalled
* perf(core): a video copy's size box follows the stage within a fifth, not a half
* docs(cli): shorten two comments to the line limit
* fix(cli): the copy heartbeat drops a finished copy; size budgets follow the stall and progress code
* chore: size budgets rise by this change's measured bytes
* fix(cli): snapshot waits for a slow scene to finish building instead of saving a stale frame
* fix(cli): drop the added comments, the names say it
* test(cli): give the contact-sheet engine mock the render-ready timeout snapshot reads