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
* fix(producer): a media src that resolves to the page itself is no source
A src of '#', '#t=1', '?' or '?x=1' reads back from the browser as the page URL
plus a suffix. decodedUrlPath strips the suffix, so the probe opened index.html as
a media file and failed the render with 'media sources are text documents'.
Discovery now drops any src whose URL without query and hash equals the page URL,
which also covers the blank and whitespace forms from the earlier fix.
* fix(producer): name the page-itself check instead of commenting it
The beginFrame probe now tells a timeout from a real rejection.
A timeout relaunches Chrome and probes once more with a longer budget.
An unsupported answer still falls back at once.
The off-canvas indicators (always mounted) and the Layers panel rebuild their layer list every
100 ms while a scrub mutates the preview, and each rebuild read the computed style of every element
of every scene, on screen or not. The runtime already hides everything inside a clip its visibility
pass hid, so those subtrees hold no layer: the walk now skips a clip marked data-hf-clip-hidden
with an inline visibility of hidden. The marker constant moves to studio-preview-mark so the runtime
and Studio share one owner.
* fix(producer): a render whose capture stops advancing fails within minutes
* fix(producer): keep the bounded probe read the stall change dropped
* chore(producer): shorten a test comment
* fix(producer): every retry batch gets its own first-frame window; the stall test waits for the render to settle
A retry that runs as several batches opened one stall window for the whole attempt, so a later batch whose browsers take longer than the no-progress window to land a frame was killed where main would have finished. The window now opens per batch. The stall tests also waited a fixed number of turns for real file IO after the last frame, which failed about 3 runs in 10 on a loaded machine; they now wait for the render to settle.
* fix(producer): a test reads the stall error fields instead of suppressions; size budget follows
* test(producer): split the scripted capture attempt into small helpers
* test(producer): the stall test advances fake time only while the scripted capture waits
* fix(studio-server): a failed preview copy says why its output could not open
* fix(studio-server): a failed preview copy reports its causes and what its source is
* fix(studio-server): failure reports hide spaced paths and survive a vanished source
* refactor(studio-server): read a probe's result in its own function for the complexity gate
* fix(studio-server): close the source facts function the rebase left open
* fix(studio-server): a failure report reads the source size through one open descriptor
* fix(producer): chunk capture round-robins only for callers that bill wall time
* fix(engine): worker, extraction and encoder sizing read the CPU quota, not the host core count
* Revert "fix(engine): worker, extraction and encoder sizing read the CPU quota, not the host core count"
This reverts commit cdd4d858c8.
* chore(producer): drop the doc comments the comment ratchet rejects
* 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(studio-server): a copy being made says how far it has got, and can be retried
* feat(core): the preview reports its lighter copies' progress and retries failed ones
* feat(studio): the preview quality and its lighter copies' progress live in the player store
* feat(studio): every host forwards the preview quality, and the choice is kept per computer
* feat(studio): the preview quality knows if a person or the app chose it
* fix(core): the first report of the lighter copies is not held back by the throttle
* test(studio): the preview quality preference test writes under the real key
* fix(core): a quality change reports its copies at once, not after the throttle
* style: format the preview quality files
* feat(core): report how playback is going, and what the preview holds, to the host
* fix(studio): the preview quality levels come from a browser-safe core entry
* style: format the new entry's files
* style: drop two comments the file's ratchet and the citation check refuse
* feat(core): tell which lower levels would give a video a lighter copy
* fix(studio): drop the old level's playback health when the level changes
* feat(studio-server): preview facts carry the probed codec of every video
* refactor(studio): route the preview's reports through a table for the complexity gate
* fix(core): a copy's retry flag is sent on the first ask only, not on every poll
* fix(core): type the health report and restore the player hook's complexity directive
* style: cut four comments the comment ratchet refuses
* fix(studio): keep playerStore.ts within its 600-line cap
* fix(core): a scene mounting does not restart the playback health window
* fix(core): the playback health window restarts only when the display scale changes
* fix(core): a repeated display scale no longer restarts the playback health window
* fix(studio): route the preview's reports by an explicit list of message types
* 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
* test(studio): real-Chrome tests inline GSAP so their page waits on no request
* test(ci): smoke projects inline GSAP so required checks wait on no CDN
* 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(skills): a Remotion port carries Remotion's box-sizing rule so bordered boxes keep their size
* chore(skills): regenerate the skills manifest after the format pass
* fix(hooks): the skills manifest is hashed after the formatter has run
* test(studio): a drag is measured against the box the preview shows
* test(studio): another element is read bare until it is selected
* fix(core): preview audio fades play as ramps on the audio clock, so cuts no longer click
data-fade-in / data-fade-out were folded into one volume per 50 ms sync tick and applied as
a step (el.volume, or gain.value in the Web Audio transport), so a 20 ms fade never played
and each clip started and stopped at near full level: a click at every cut. The export
applies the fades (ffmpeg afade) and does not click.
The transport now books each clip's fades on a gain of its own when it schedules the
clip, for both decoded buffers and captured media elements: a linear ramp from where the
clip starts sounding up to its fade-in, and down to silence at its end. The sync tick hands
the transport the level without the fade (onElementVolume's new fifth argument, sent only
where a fade applies), so a booked fade is not applied twice. The plain el.volume fallback
keeps its stepped fade.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): one doc block on setElementVolume
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): keep the comment share where it was
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): booked fades hold through pre-roll, follow rate changes, skip unbounded ends
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: raise the producer unpacked budget to the measured size plus 1%
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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(studio-server): a preview copy is keyed by the file's content, so copied projects reuse it
* fix(studio-server): copies are keyed by content and made by one process at a time
* docs(studio-server): shorten the copy lock comment
* fix(studio-server): find a copy by place and time first, by whole-file content when that misses
* fix(studio-server): hash small sources in one read; wait longer in tests for the hash
* test(studio-server): cache folder listings ignore the copy index files
* style(studio-server): format the copy lookup files
* docs(studio-server): shorten the copy lookup comment
* fix(studio-server): hash a source through one open handle, not a check then a read
* fix(studio-server): hash through one descriptor, keeping the small-file path synchronous
* style(studio-server): format the cache key file
* 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
* feat(core): tell which video files deserve a lighter preview copy, and at which size
* feat(studio-server): serve lighter copies of heavy playable files, a keyframe every second
* feat(core): the preview plays lighter copies of heavy videos below full quality
* style: shorten two comments
* style: shorten a comment
* style: shorten a comment
* fix(core): format the preview quality files and split the copy sizing for the complexity gate
* style: format the studio-server preview files
* fix(core): type the copy job's level as a preview quality
* chore(producer): raise the package size budget for the preview runtime growth
* 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(core): pressing play puts a sped-up or looping video at the right moment in one seek
Pressing play, or seeking while playing, placed every video as if it played at 1x from its start and never looped. The media sync that runs right after knew better, saw a gap of seconds on a 2x or looping clip, and seeked again: two decoder resets per press. Both now read one function for where a media element belongs in its source at a given time, so the first placement is the only seek.
* fix(core): play keeps a video's own speed and skips seeking a video already in place
Setting the transport rate wrote that rate onto every media element, so a 1.15x clip played at 1x until the next media sync put it back; Studio sends a rate on every Play. The media sync already sets each element to its own rate times the transport rate, so the rate change now leaves elements alone and lets that sync apply it. Pressing Play also no longer seeks a video that is already at its source time, since every seek flushes the decoder.
* fix(core): pause and play leave media alone that another feature or the slideshow is driving
Play and pause placed every in-window video at the playhead, including a video the grading preview or a Studio scrub holds while paused, and slide media while the slideshow drives media itself. They now ask the media sync which clips are the runtime's to move.
* refactor(core): trim comments the comment-share check rejects
* 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
Frame extraction decoded every source in software, so a long 4K60 HEVC clip
spent much of its CPU on decoding. On macOS, extraction now asks ffmpeg for
VideoToolbox when this ffmpeg has it and the source cannot carry alpha; ffmpeg
falls back to software for a stream VideoToolbox cannot decode, and the frames
written are byte-identical.
* perf(producer): chunks capture frames round-robin so the encoder runs during capture
The overlapped encoder takes frames in order, but workers captured contiguous ranges, so it sat idle until worker 0 finished and then encoded the other workers' frames after capture. With the encoder on, workers now take frames round-robin and it keeps pace.
* chore(producer): drop a doc comment to keep the comment ratchet flat
* fix(producer): interleaved capture retries a dead worker as one stride task and keeps static-frame reuse
A worker that dies during round-robin capture leaves every Nth frame missing; the missing-frame retry turned each into its own one-frame batch. It now retries one stride task per dead worker. Screenshot-mode static reuse also required the previous frame to be exactly one back, which never holds at stride above 1; it now reuses when every frame in the gap is static, like the worker path.
* 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
* feat(studio): zoom the timeline to the selected clips
Toolbar action and Z fit the selected clips to the visible width, with the
range margin, and stay disabled until something is selected.
* fix(studio): gate zoom-to-selection on a resolvable clip
The scroll clamp already lives in the timeline zoom request. Drop the
unused copy, and disable the button and Z unless the selection matches
a timeline clip with duration. A collapsed timeline cancels
zoomTimelineToRange instead of laying out a percent the view cannot show.
* fix(studio): keep a zoom step when the timeline is collapsed
A collapsed timeline has no track for a range zoom, so zoom in and out
step the percent directly. Z is consumed only when that zoom actually
starts, and zoom-to-selection no longer builds the span three times.
* ci: retrigger checks after workflow approval
The approved run was cancelled during checkout, so the required checks
never executed.
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>
EditPopover positions itself with position: fixed and viewport
coordinates, but rendered inline inside the timeline's dockview panel,
whose transformed overlay becomes the containing block. The popover
landed relative to the panel and was clipped below the window. Portal it
to document.body like the other timeline menus.
Fixes#5376
* fix(studio-server): a refused preview frame says so at once instead of timing out
A frame the server refuses (project gone, bad variables) loaded a JSON body: no runtime and no error, so Studio waited three 15s budgets and reported 'waiting for runtime'. The refusal now reaches the frame as a page that reports it, like a failing preview script.
* chore(studio-server): remove redundant preview refusal comments