Commit Graph
249 Commits
Author SHA1 Message Date
WaterrrForeverandClaude Opus 5.5 688229314f feat(skills)!: publish seven knowledge skills aligned with Studio and retire the workflow skills (#5651)
* refactor(skills): fold six domain skills into hyperframes and media-use guides

hyperframes-core, -animation, -keyframes, -creative and -studio move to
skills/hyperframes/{core,animation,keyframes,creative,studio}/, and
hyperframes-audio moves to skills/media-use/mix/. Each moved SKILL.md becomes
a frontmatter-free GUIDE.md so installers that scan recursively for SKILL.md
no longer see them as skills; the published list goes from 21 to 15.

Relative links, code paths and skill-name mentions are rewritten to the new
locations, the catalog surfaces, marketplace core-skills list, fallback core
set, module-copy targets and skills-manifest.json follow, and
`hyperframes skills update` now removes the retired skills (RETIRED_SKILLS)
when they are still installed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): slim the core entries and give media-use a HeyGen and a free path

The four entries every run opens are rewritten to what an agent cannot infer:
hyperframes 21.8 KB to 7.7 KB, hyperframes-cli 24.8 KB to 6.9 KB,
hyperframes-registry 9.9 KB to 4.3 KB, media-use 9.7 KB to 4.9 KB. Detail moved
whole into references (routing close calls and creator edits, CLI pin upgrades,
catalog search tiers, CLI conventions and feedback reports).

- hyperframes: state table, routing, the render contract, a terminal work loop
  (check, snapshot, preview before render), the desktop-app hand-off, and a
  table of the folded guides.
- media-use: one table of HeyGen versus free local tools per need; HeyGen
  sign-in, credits and the CLI's video-agent, avatar, clone, lipsync, translate
  and clipping commands; every free path works with no account.
- scripts/check-core-skills.mjs holds entries and descriptions to budget and
  checks every taught `npx hyperframes` command and path; runs in lint.
- Core entries are exempt from the formatter: its table padding cost ~1.5 KB
  per entry on every run.
- Content tests follow the text to its new home.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): plugin runs skip skills update; media-use explains HeyGen sign-in and TTS setup

The smoke eval showed a presenter request never reaching media-use and the
free voiceover failing on a missing kokoro-onnx; plugin runs also ran the
skills update the plugin rules forbid.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(skills): drop a committed Python bytecode file and ignore bytecode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): align the entry with the Studio skills and keep the workflows' measured facts

The entry drops routing and the intent interview and carries the contract, the
editable-project rules and the work loop. Studio's format, runtimes, motion,
look and captions references come over for terminal use, frame presets move to
assets/, media-use gains Studio's mixing and local-command references, and
film.md and overlays.md keep what the workflow skills measured.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills)!: retire the workflow skills and folded guides; publish seven knowledge skills

The skill pack now publishes exactly seven skills - hyperframes,
hyperframes-cli, hyperframes-registry, media-use, figma, slideshow and
remotion-to-hyperframes - carrying knowledge, not process. The eight
workflow skills, the folded domain guides and the routing/interview
references are deleted; their measured facts already live in the
hyperframes references (format, runtimes, motion, look, captions, film,
overlays) and media-use's mixing reference.

- bake-basemap.mjs and locate.mjs move to skills/hyperframes/scripts/
  (bake-basemap gains --help); extract-audio-data.py moves to
  media-use/audio/scripts/ for the caption motion reference.
- skills update: the eight workflows join RETIRED_SKILLS so existing
  installs prune them (by lock attribution only, since their names are
  generic); naming a retired skill reports where its knowledge went and
  succeeds instead of failing as unknown.
- Capture prompts point at the hyperframes skill's references/film.md;
  the live-map warning names the new bake-basemap path.
- Content tests that pinned knowledge are retargeted to the kept
  references; tests of deleted workflows, scripts and process go.
- Catalog surfaces, templates and docs describe the seven skills.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): resolve the local.md captions link and allow capture's output files in citations

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): regenerate the skills manifest from tracked files only

The committed media-use hash included an ignored __pycache__ bytecode file present
in the local checkout, so CI's clean checkout hashed differently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(docs): restore generated catalog pages and docs navigation to main

Catalog PRs commit item sources only; CI regenerates the pages from the edited
registry sources. docs/docs.json is catalog navigation, so its Workflows group
rename is left for a non-catalog PR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* build(cli): raise the CLI size budget for the skill references it bundles

The CLI bundles skills/hyperframes, which now carries the folded domain guides and
frame presets (155 KB to 1.08 MB in git). Budgets are the CI-measured size plus 5%:
packed 7526634, unpacked 22593213.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: keep repo-sweep guards meaningful after the workflow skills left

The last shell ffprobe caller and most root index.html examples lived in retired
skills. The shell detector is now pinned on a literal command line, and the
structure-rule sweep needs more than 5 roots (11 remain) rather than 30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): slim figma, slideshow and remotion to the Studio shape; restore the storyboard board

slideshow 33.5 KB to 4.9, figma 18.2 to 6.2, remotion-to-hyperframes 11.0 to
4.9, each keeping the facts only it holds (the figma motion helpers, verify gate,
storyboard decoding and usage beacons; present and hand-off; the Remotion lint,
SSIM check and API map). The board page format from the storyboard recipe comes
back as hyperframes/references/storyboard.md: the desktop app's Storyboard tab
reads boards/storyboard.html by its frame, title and time classes. The entry and
the init template now say to snapshot the moments an edit changed before
replying, which the terminal eval found skipped in 22 of 38 runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): manifest without local bytecode; Studio's edit prompt names kept files

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): render when the person asks for the file or for no stops, not only after approval

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): keep the voiceover carve in media-use; presets stage their own fonts

Review follow-ups on #5651:
- carve.mjs and its tests move to skills/media-use/audio/scripts; the audio
  docs, mixing.md and two Studio comments point there.
- code-editorial's FRAME.md says to copy its bundled WOFF2s and add the
  @font-face blocks, instead of promising the retired build-frame.mjs.
- media-use states the host rule before the HeyGen sign-in steps.
- storyboard-format marks transition_in as a note the agent builds itself.
- Two stale citations (creator-editing-recipes.md, sub-compositions.md).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(skills): regenerate the manifest without local bytecode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): the CLI render step follows step 4's rule, not approval alone

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): run the core-skills check from lint again after the main merge

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): films that tell a story start from three storylines; maps use real imagery

Opus 5.5 arm D read motion.md and applied it, but its launch film was a
feature list (pain, three feature cards, proof, logo): film.md left story
to the model. And the map case never opened film.md, so it drew Natural
Earth outlines instead of baking satellite tiles.

- references/story.md: the generic half of Studio's moment-film story and
  craft (one thing, three storylines with world and device, the hook,
  beats, worlds, copy, brand, tells of a generated film). The person picks
  a storyline, or the agent takes its recommendation when asked for the
  file or for no stops.
- The entry's work loop names the storyline step for films over ~15 s; the
  references table routes stories to story.md and maps to film.md.
- film.md points at story.md and gives maps and real places a section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): story.md follows the entry: no voiceover unless asked

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): bake-basemap starts from a globe and stops waiting out its idle timeout

The Opus 5.5 map run spent 13 of its 38 minutes on parallel bakes that
stalled and 15 more recording 300 frames, after rewriting the script because
it could only start on a flat map.

- PROJECTION=globe with a seeded starfield and atmosphere, and FROM/PAN to
  turn the globe to CENTER before the dive.
- Each frame polls for loaded tiles instead of waiting for an "idle" event
  that had often already fired: 30 frames went from 198-219 s to 10-13 s
  locally with byte-identical frames.
- Progress prints seconds a frame and minutes left; film.md shows the globe
  call and says to run one bake at a time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): render in the foreground; wait on a detached render's log, never pgrep -f

Both Opus 5.5 explainer runs started a detached render, then waited with
`until ! pgrep -f "hyperframes render"`, which matches its own loop: each
render took 88-117 s and the wait sat for the tool's full 10 minutes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): route each kind of film; storylines carry look and sound; add review and sound

The entry routes by request (launch, explainer, music, data, vertical, 3D,
character) to a self-contained file under references/types/, and a film
over ~15 s now gets music and sounds on its visible actions unless the
person asked for silence. Each storyline names its look and its sound, so
picking one picks both; STORYBOARD.md frames carry `reads` and `sound`.

review.md is the whole-film pass: the check warnings, the review sheets,
seven questions to look for, P0/P1/P2 notes and REVIEW.md. motion.md gains
eases by role, six title entrances, a reading-time rule and nine measured
transitions; look.md a design plan before the first scene, with presets
kept for decks and named picks. story.md adds the second default that
generated design falls into, with what to do instead.

The blank template and format.md no longer teach the fade-and-slide-up
entrance on a dark canvas.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(cli): check warns on unread lines, dead stretches, small type, empty frame 0 and silence

A coding agent cannot watch its film, so check now measures what a viewer
notices over time. A new `craft` section holds warnings only (exit 0
unless --strict), sampled on the dense 8 fps grid the overlap pass
already seeks, skipped for --range checks:

- line_unread: a reading-size line (>= 30 px tall at 1080) on screen
  under 0.4 s for 1-2 words, else under 3/4 of 0.5 s + 0.3 s a word;
  the film's last line needs 2 s. Short lines are re-measured between
  samples to ~0.01 s before judging. Captions-track and
  data-layout-ignore text are exempt.
- dead_stretch: more than 3 s where the keepsMoving liveness signature
  does not change, outside the final 3 s and any data-hold element;
  quiet when sweep_static fires or a whole-composition keepsMoving
  assertion owns the limit.
- text_below_floor: text under 2.6% of the short side (28 px on 1080)
  held 0.5 s, one finding per file listing every element.
- first_frame_empty: frame 0 paints only the background.
- film_silent: over 15 s with no unmuted audio or audible video.

Each message names the fix and the skill reference to read. keepsMoving
now also leaves the film's last 3 s (a still end card) out of its
static runs, so the two agree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(cli): snapshot --review captures a beat sheet, seam strips and a phone sheet

snapshot --review picks the frames that show a film over time instead of
evenly spaced ones, in the same single browser session:

- review-beats.jpg: one settled frame per scene at its start + 0.3 s,
  plus one per timed data-beat="name" element, labelled time and id.
  Scenes are the composition's top-level timed clips and sub-composition
  hosts, read like `timeline` reads rows (captions and audio excluded)
  and timed by the runtime's start resolver over the live page.
- review-seam-NN.jpg: frames at -0.2, -0.1, 0, +0.1, +0.2 s around
  every cut (each scene start, and any scene end no scene starts at).
- review-phone.jpg: the beat frames at 360 px wide.

--composition scopes the review to one sub-composition; with fewer than
two scenes the beats spread evenly and a note says so. snapshot also
gains --json: one envelope with every frame's path and time and, under
--review, the scenes, beats and seams behind the sheets; progress moves
to stderr.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): craft warnings stay out of --strict; a hold gets a real next action, not a caret

Craft findings (unread lines, dead stretches, small type, an empty first
frame, silence) are judgment calls a film may break on purpose, so
--strict keeps gating defects only. The check reference no longer
suggests a blinking caret to keep a still frame moving.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): review spreads beats when every scene starts together; map credits skip the type floor

A map film whose basemap and overlay both start at 0 s got one beat
frame; the even spread now keys on distinct scene starts. film.md tells
agents to mark the imagery credit data-layout-ignore and back it, since
the new type floor flagged it on the first real run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): review frames each scene at its middle too, not only 0.3 s in

On the first real explainer, 0.3 s into a scene showed a bare colour
field before its title entered. Each scene longer than about a second
and a half now adds a frame at its middle, so the sheet shows what the
scene builds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): a film is built from a numeric SPEC.md, with defaults banned by name and a look test

Studied in full: flxrnc's Motion Lab (vague vs directed prompt), 0xMovez's
course, EianLu's riso skill, agentchengfeng's style samples and the two
Opus 5.5 guides. Their common point: adjectives are where the default look
lives, so the agent now writes SPEC.md before any scene (GOAL, OUTPUT,
TIME, LOOK, MOTION, SHOTS, BANNED, QA), with a number for every adjective.

spec.md bans the defaults by name, unless the brand or reference uses
them (a dark void with small things floating, glow and particles, the
cream-serif-mono-label look, fade-and-slide-up, corner brackets), and
grows that list after the first render. Each storyline gets a 3 s test of
its hero shot before the pick; a look is written as the rules of one
medium. Composition numbers: the hero fills 40% of the frame, one hero
move per shot, the shot's information in within 2.5 s then a held poster.

motion.md adds springs by damping ratio, shorter exits, overlap, stagger
and the beat grid; review.md reviews as a harsh director and grows BANNED;
vertical.md takes the riso skill's measured safe band; explainer.md the
shapes of information.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): story before look; every object reads as itself; posters only for info cards

The first spec-driven C02 swapped the dark default for a flat Swiss
poster, and the story went with it: two black bars nobody read as mirrors,
slogans instead of cause and effect, a storyline picked for its picture.

The spec now serves the story: a storyline is picked for the story a
stranger would follow, the look test asks first whether a stranger
understands what they see, and SPEC.md gains an OBJECTS line giving each
thing the material and details that make it read without a label. The
2.5 s held-poster rule is limited to information cards; in a story film
every shot causes the next. Explainers start from a person or situation
the viewer knows. Review names every object as a stranger would and
treats one that needs explaining as a P0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): storylines are three films in words, each with a device and a form

The three storylines are pitched as text and the person picks from them;
nothing is built for a storyline before the pick, so the 3 s hero-shot
look test is gone. Ideas pair a device with a form, five or more first,
keeping the three that differ most, and the one stop states the rest in
an assumption line that names every paid item beside its free option.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci: list main's new embedded-captions hf-cli test among the retired workflow files

The merge with main removed it with the rest of the retired skill, and the
deletion guard rejects any removal it has not been told about.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(core): keep the audio preset comment within core's size budget

The comment's new citation to media-use's mixing reference made the published
package 14 bytes larger than its budget, which sits at main's exact size.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): craft keeps its own 8 fps grid; comments back within the checks

Review on #5651 found that the merge moved craft onto the overlap pass's
times, which main caps at 180, so a brief line in a long film fell between
samples and line_unread missed it. Craft now builds its own grid at 8 fps up to
600 samples, as before the merge, and a 60 s film with a line shown for 0.2 s
pins it.

The Comments check passes again: REVIEW.md and STORYLINES.md join the files a
skill writes into a project, the comment lines this branch added come back
under each file's share at the merge-base, and the audio preset comment drops
a word the check reads as history.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 19:19:31 +00:00
Miguel Ángel 25126bca6c feat(cloud): 4K renders work for transparent MOV and WebM (#6045)
* feat(cloud): 4K renders work for transparent MOV and WebM

* test(producer): the 4K alpha chunk probe ends its options before the input
2026-10-21 18:29:15 +00:00
Miguel Ángel b306438e8b feat(cli): cloud render uploads heavy media as HeyGen assets (#5931)
* feat(cli): cloud render uploads heavy media as HeyGen assets

Media of 5 MB or more leaves the project zip: it uploads through the
direct-upload flow as a HeyGen asset and the zip's HTML and CSS point at
the asset URL. Uploads are cached by content hash and re-checked before
reuse. Media the asset API refuses or caps is re-encoded at visually
lossless quality first. The zip must end under 200 MB or the command
stops and names the largest files left.

* feat(cli): cloud render offloads GIFs, still images and script-referenced media

Animated GIFs in the entry composition go through the renderer's own GIF
preparation and upload as the resulting video. Still AVIF, GIF, BMP and
TIFF images convert to PNG. Plain string literals in scripts and JSON that
name offloaded media point at the asset URL. ffprobe calls end their
options with "--", and two comment lines are trimmed for the ratchet.

* feat(cli): cloud render uploads only the parts of long media a composition plays

A video or audio file that has to be re-encoded is cut to the union of the
windows its clips play, with half a second of margin on the source's frame
grid, and the staged composition's media starts shift to match. Clips in
sub-compositions, looping clips, speed ramps and files a script names keep
the full file.

* fix(cli): cloud render no longer decodes media files as text when rewriting references

* fix(cli): cloud render keeps media chosen at render time and encodes the staged bytes

Only media a reference points at is offloaded, so a file picked through
--variables or built by a script stays in the zip. Variable-bound clips and
clips with a media fragment keep their full length. Entry GIFs resolve the way
the renderer rebases a nested entry and are prepared only from 5 MB, and every
encode reads the staged bytes its cache key hashes. Query strings and media
fragments survive the rewrite, a null cache file reads as empty, and the docs
say asset URLs are public and unsigned.

* fix(cli): cloud render comments cite only what this repo holds

* fix(cli): cloud render handles media over 2 GiB and explains a too-large upload

Media of 5 MB or more stays on disk until it is used: it is hashed and uploaded
as a stream, and a file edited after staging stops the run instead of caching
the wrong upload. Files left in the zip are sized before any is read, so a
4 GB file reports "too large" instead of crashing.

The asset API rejects an over-limit upload as invalid_parameter with "File too
large"; that now gets the dry-run hint instead of the --help one.

* fix(cli): cloud render checks and reads each staged file through one handle

Staging, the on-disk staged read and the re-encoded output read each opened the
path twice: once to stat, once to read. A file swapped or edited between the two
could be read without the size or change check seeing it. Each now opens once,
checks the open file's stat before and after the read, and reads from it.

* test(cli): a heavy file a script builds from split strings stays in the cloud render zip

* fix(cli): cloud render keeps media scripts may reach and never trims clips scripts retime

- An offloaded file keeps its zip copy when script code holds both its stem and
  extension, so a path joined at runtime still loads.
- Entry GIFs are prepared from a private copy checked against the staged file,
  so the GIF cache never stores a later edit.
- No cut when any script can write a clip's timing attributes, dataset,
  currentTime, playbackRate, src or loop.
2026-10-21 14:34:00 +00:00
Miguel Ángel f0ef4cb9d4 fix(producer): disk-space error suggests --workers 1 only when one worker would stream (#5934)
* fix(producer): disk-space error suggests --workers 1 only when one worker would stream

* test(producer): streaming-stage suite mocks the new streaming-gate export

captureStage now asks explainStreamingEncodeGate for the disk-space remedy;
the bun module mock of renderOrchestrator in captureStreamingStage.test.ts
lacked it, so the suite failed at import.
2026-10-20 09:12:50 +00:00
Miguel Ángel 5583ae4293 perf(cli): commands run without esbuild, puppeteer-core or sharp until they need them (#5930)
* perf(cli): commands run without esbuild, puppeteer-core or sharp until they need them

A host that bundles the CLI can now leave these packages out: each command
loads them only where it uses them, and sharp installs once on first use when
it is missing. The build check fails on any static import of them again.

* fix(cli): use a host's sharp from NODE_PATH and let hosts turn first-use installs off

* fix(cli): capture fails when sharp is missing, and one install serves every caller

A missing sharp now stops the asset download up front instead of counting every
image as unavailable. Concurrent first uses share one install, a failed install
is not retried in the same run, and preview thumbnails report why. Hosts can turn
off installs for a list of packages, e.g. HYPERFRAMES_NO_OPTIONAL_INSTALL=sharp.

* fix(cli): snapshot --describe still works without sharp, resizing only when it is installed

* style(cli): format the sharp loader and its tests

* build(cli): sharp is an optional dependency, so a host can ship the CLI without it

* build(studio-server): keep sharp out of the bundle now that it is optional
2026-10-20 09:12:47 +00:00
Miguel Ángel 992df8b203 perf(cloud): move project bytes once through shared content storage (#5772)
* perf(producer): distributed chunks download only the videos they show

A project video under compiled/ that chunks extract their own frames from
now goes only to the chunks that show it, like video-sources/ files. A
chunk skips the readiness wait for a deferred video it does not show, so
it no longer logs media_load_failed for a file it was never given.

* fix(producer): cloud site uploads pack the files their id hashes, symlinks included

* perf(aws-lambda): chunks read only the byte ranges of a video they need

A chunk that extracts its own frames from a deferred project video now
reads it from S3 by byte range through a short-lived pre-signed URL,
instead of downloading and hashing the whole object first. S3 attests the
object's stored SHA-256 before the URL is signed; every other artifact is
still downloaded and verified as before.

* perf(aws-lambda): upload project files once to a shared content-addressed area

* chore(producer): keep planV2 comment share at the base level

* fix(engine): verify the TLS certificate of https video inputs and never surface their signed query

* fix(aws-lambda): renders re-upload a site's expired files instead of failing

* perf(aws-lambda): the plan step probes project videos in place instead of downloading them

* fix(engine): https video reads reconnect and time out, and redaction covers wrapped causes

Signed-URL reads now pass -rw_timeout, -reconnect and -reconnect_delay_max (both Lambda builds accept them) and ffmpeg also -reconnect_on_network_error, so a transient S3 error or a stalled read fails fast or recovers instead of waiting out the process timeout. Query redaction now descends into error.cause.

* fix(aws-lambda): share entry bundles and ratchet package budget

* fix(aws-lambda): site ids uploaded before files.json still render

A site id from an earlier sites create has only sites/<id>/project.tar.gz.
renderToLambda now falls back to that tarball, which the plan step still
reads, and when neither key exists it fails before starting with both keys
named and the fix (run sites create again).

* fix(aws-lambda): renders fail before upload on a stack too old to read sites

A stack deployed before files.json sites untars the site file in its plan
step and fails after four retries. renderToLambda now reads the state
machine definition first and stops with STACK_TOO_OLD and the redeploy
command when the plan task does not list SITE_MANIFEST_INVALID, which new
SAM and CDK stacks now treat as terminal. The README's upgrade section
drops the planProtocol v1 fallback, which hits the same failure.

* docs(aws-lambda): document shared content storage and the upgrade path

The deploy guide still described the tarball HeadObject short-circuit. It
now covers the cas/sha256/ store and its 30-day lifecycle rule, the
STACK_TOO_OLD, SITE_FILES_MISSING and SITE_NOT_FOUND errors, that old site
ids keep working, and that every project uploads once more after the
upgrade. A test keeps a troubleshooting entry for each pre-submit error.

* fix(producer): chunks log each video they read by URL, without its query

A chunk reading a video straight from the bucket left no trace in its log.
It now logs one line per remote source with the path and the URL minus its
query string, so signatures never reach the log.

* fix(engine): ffmpeg errors never keep part of a signed URL's query

A process's stderr tail was trimmed before its URLs were redacted, so a
cut inside a signed URL left a query fragment the URL pattern no longer
matched, and the fragment reached the error. The stderr tail is now
redacted before every trim, and the redactor also strips a signing
parameter cut off from its URL.

* fix(aws-lambda): comments name the site manifest instead of a file path

* fix(engine): stderr redaction survives chunk splits, and every ffmpeg spawn uses it

The stderr tail was redacted on each chunk and then cut, so once a cut
severed a long URL's scheme, the rest of its signature in the next chunk
matched neither pattern. The tail is now kept raw with 2x slack and
redacted only when the text is produced, then cut to size. The unused
producer audioExtractor (its own spawn, unredacted stderr) is deleted and
the parity harness's PCM decode goes through ManagedChildProcess; a test
fails on any ffmpeg/ffprobe spawn in engine, producer or aws-lambda that
does not.

* chore(producer): record the audioExtractor deletion as intended

* fix(engine): drop a signature fragment the stderr tail cut from its URL

The raw tail is kept at twice the cap, but that cut can itself land inside a
signature, and when redaction shrinks the rest below the cap the fragment
survived. After a cut, the severed token is dropped before redaction.

* refactor(engine): name the severed-token drop instead of commenting it

* test(engine): a cut-off URL leaves nothing behind; aws-lambda size budget to measured

* fix(producer): chunk URL-read tests use main's frame list; producer size budget to measured

* chore(producer): size budget at the measured size, no headroom

* chore(producer): size budget back to measured x1.05, the check's own rule
2026-10-19 19:23:51 +00:00
Miguel Ángel 9ad12da18a fix(cli): non-English transcription reaches whisper, and whisper failures say why (#5892)
* fix(cli): non-English transcription reaches whisper, and whisper failures say why

transcribe --language <non-English> runs an installed whisper (Parakeet picks its own language); whisper models are pinned by size and sha256 and re-downloaded when damaged; a whisper-cli failure names its build, model file and last stderr lines without paths; the unused --dtw flag is dropped.

* docs(cli): --engine and --language say a non-English language runs an installed whisper

Also strips paths that follow a colon or a bracket from whisper-cli's error tail.
2026-10-19 16:40:29 +00:00
Miguel Ángel d2b8ea7644 fix(cli): timeline delete --start/--end removes only that range (#5867)
* fix(cli): timeline delete --start/--end removes only that range

timeline delete accepted --start and --end but never read them, so a range
delete removed the whole clip. It now cuts out only that range, the same way
split, split, delete would, and keeps the parts before and after.

Every edit verb shared one flag set, so a verb silently ignored flags it does
not use. Each verb now declares only the options it reads, from one table that
also checks apply-plan keys, so an unused flag or key is refused before
anything is written.

* refactor(cli): name the per-verb option table instead of commenting it
2026-10-19 01:23:00 +00:00
Miguel Ángel 2a14cbc924 perf(aws-lambda): size distributed renders from account concurrency limits (#5813)
* perf(aws-lambda): run chunks through a Distributed Map to lift the 40-chunk cap

The inline Map runs at most 40 chunk renders at once. Both state machine
definitions (CDK and SAM) now use a Distributed Map with STANDARD child
executions, capped at the function's reserved concurrency so chunks past it
queue instead of being throttled. Progress reads each chunk's child
execution history so frames, cost and chunk errors still show up.

* fix(aws-lambda): retry Lambda throttles for minutes and stop on oversized chunks

A throttled task used to burn its four catch-all retries in about 30 s.
It now gets its own jittered backoff of several minutes, because another
render on the same function can still hold the reserved slots. Plan
treats CHUNK_EXCEEDS_WORKER_TIMEOUT as terminal in both definitions.

* chore(aws-lambda): trim stack comments to keep the comment share flat

* fix(aws-lambda): keep render --wait working at hundreds of chunks

Progress polls read the chunk Map Run's counts (one DescribeMapRun) instead of
every child execution's history, take exact chunk frames and cost from the Map
output once it exits, and read failed children only when the counts show a
failure. The SDK client retries throttled calls up to 8 times with backoff.

* feat(aws-lambda): size each render's concurrency to the account instead of a fixed 8

* fix(lambda): keep the concurrency SDK external in published modules

* fix(cli): keep an existing stack's reserved concurrency on redeploy

Redeploying without --concurrency sent ReservedConcurrency=-1, which
removed the cap of 8 that earlier CLI versions reserved by default and
stopped recording it in the state file. The override is now left out
when the flag is omitted, so SAM keeps the stack's current value (a new
stack still gets the template's unreserved default). The state file
records the deployed value, and deploy prints the kept reservation.

* fix(aws-lambda): keep progress working for callers without DescribeMapRun

Callers whose IAM policy came from an earlier `lambda policies user`
lack states:DescribeMapRun, so reading a running Distributed Map threw
AccessDeniedException out of `lambda progress` and `render --wait`
while the render itself went on to succeed. getRenderProgress now
catches only that error around the map-run read, returns the parent
snapshot (status, plan and assemble progress) and adds a warning to
re-apply the policy, which both commands print. Other errors still
throw.

* fix(cli): let the documented deploy policy drop a reserved concurrency

When a redeploy removes a function's reserved concurrency, CloudFormation
calls lambda:DeleteFunctionConcurrency. The policy printed by
`lambda policies role|user` granted only PutFunctionConcurrency, so the
update failed and rolled back. Grant it, and pin it with
states:DescribeMapRun in the required-actions test.

* fix(aws-lambda): read smoke chunk hashes from the Distributed Map output

With chunks in a Distributed Map, their task results live in child
executions, so the smoke gate's chunk-hash check found 0 chunks in the
parent history and failed every v1/v2 comparison. Read Chunks[] from the
RenderChunks / RenderChunksV2 MapStateExited output instead, as
getRenderProgress does. The stack this script deploys only has the
Distributed Map, so the inline-Map path is gone.

* fix(producer): cap a pinned chunk size at the worker concurrency, not 16

With --chunk-size or --target-chunk-frames and no --max-parallel-chunks,
the plan still capped the render at 16 chunks however many workers the
account allowed, so --chunk-size 30 on a 60 s render gave 16 chunks of
113 frames. The cap is now one wave across the worker concurrency, as
the --max-parallel-chunks docs say; 16 remains when the concurrency is
unknown.

* feat(cli): remove a stack's reserved concurrency with --concurrency=none

Since an omitted --concurrency now keeps the stack's reservation, there
was no CLI way back to the unreserved default. --concurrency=none sends
ReservedConcurrency=-1, the state file records the stack as unreserved
and deploy says the reservation was removed. Positive N and an omitted
flag behave as before; any other value is rejected instead of being
silently ignored.

* chore(aws-lambda): raise the package size budget for account-sized concurrency

Measured with pnpm@10.17.1 pack after rebasing on main with the S3 assembly change: 67,763 packed / 244,685 unpacked; the budget is the check's own 5% headroom over that.
2026-10-19 00:57:30 +00:00
Miguel Ángel 5658f70065 fix(core): color grading applies to canvas compositions (#5860)
* fix(core): color grading applies to canvas compositions

data-color-grading on a <canvas> was ignored by the runtime and refused by
lint, the Studio panel and the CLI writer. One source-tag list in the grading
contract now feeds all of them. A canvas is sized from its drawing buffer and
re-uploaded on every seek and transport tick.

* fix(core): keep the comment share of grading files at the base
2026-10-19 00:38:53 +00:00
Miguel Ángel cddb34feac perf(cli): cloud render downloads the finished video over parallel connections (#5864)
* perf(cli): cloud render checks render status every 2 seconds instead of 10

A finished render was noticed up to 10 s late. Polling every 2 s cuts that
wait to at most 2 s and stays well under the status endpoint's rate limit.
`--poll-interval` still overrides it.

* perf(cli): cloud render downloads the finished video over parallel connections

The first request asks for the first 8 MB. When the server answers 206 with
the full size, the rest of the file is fetched in up to 7 more ranged
requests at once and each part is written at its offset into the staged
file. A server that ignores Range (200), a small file, or an empty file
is still fetched in one request. A failed part cancels the others and the
existing output is kept, as before.
2026-10-19 00:12:57 +00:00
Miguel Ángel 26d7dbca36 docs(cli): say capture needs direct internet and ignores HTTPS_PROXY (#5752) 2026-10-18 22:15:27 +00:00
Miguel Ángel 2b9009ab52 fix(studio): keep a preview reload waiting while a slow project boots (#5823)
* fix(studio): keep a preview reload waiting while a slow project boots

Studio gives a reload up to three 15 s budgets, but the player failed it at
8 s and Studio's own 5 s safety net stopped listening for the runtime. A host
can now own the load deadline (no-load-timeout); script and runtime errors
still fail at once.

* fix(player): send the 8 s load deadline through the probe's one error path
2026-10-18 05:46:53 +00:00
Miguel Ángel c69fd2c6ec perf(producer): size distributed chunks to the worker limit and concurrency (#5765)
* perf(producer): size distributed chunks to the worker limit and concurrency

* chore(cli): drop a comment the field name already says
2026-10-17 08:45:47 +00:00
Robin EbersandCursor cc9e1bb33a feat(producer): distributed renders accept 25 fps (#5423)
Lambda and Cloud Run renders refused 25 fps although it is an integer
rate like 24, 30 and 60, and PAL footage is 25 fps. The distributed fps
set is now {24, 25, 30, 60} in the producer's config validation, the
plan v2 manifest, the render request mapping, the Lambda and Cloud Run
event types, the CLI's lambda and cloudrun commands and the regression
harnesses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 22:00:32 -07:00
kevin 6dba67a0c3 fix(cli): honour HYPERFRAMES_SKIP_SKILLS in skills update and bare skills (#5743)
skills update (bare or named) and bare skills now exit 0 with a one-line
notice instead of installing or pruning when HYPERFRAMES_SKIP_SKILLS=1, so a
pinned skills install survives the router's trigger-time update. Closes #5695.
2026-10-17 02:34:40 +00:00
Miguel Ángel 8be76fc86c fix(cli): pin website capture connections to public peers (#5668)
* fix(cli): pin website capture connections to public peers

* style(cli): keep capture policy explanations within comment limits

* test(cli): separate case-sensitive dotenv assertions

* fix(cli): honor capture deadlines and bootstrap isolation

* chore: trim redundant capture dotenv comments

* fix(capture): join signal cleanup and guard leading options

* docs(capture): trim redundant cleanup comment
2026-10-16 02:37:14 +00:00
WaterrrForeverandClaude Opus 5.5 cea8aa8a7d feat(cli): judge text behind a transparent video by its decoded alpha in check (#5657)
* feat(cli): judge text behind a transparent video by its decoded alpha in check

The layout audit treated every <video> as opaque over its whole box, so a
caption between footage and a remove-background cutout always failed
text_occluded and needed data-layout-allow-occlusion, which also hid a word
the subject's head fully covered.

check now draws the video's current (seeked) frame into one reused,
downscaled scratch canvas, once per sample and only for a video a text probe
lands under. A fully opaque frame keeps today's behaviour. A frame with
transparency is judged per rendered word against its alpha: partial cover is
an info finding carrying the most covered word's share, and a word 80% hidden
or two neighbouring words each half hidden is an error. A frame that cannot
be read falls back to opaque with a video_alpha_unread info note.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(cli): serve the video-alpha fixtures from memory, not request paths

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): check finds words under narrow, mirrored or backed cutouts

Review follow-ups on #5657:
- A matte the probe walk passes over, even at clear-alpha points, now triggers
  the per-word check, so a feature between two probe columns (an arm, a mic
  stand) still fails the word it hides.
- Mirrored videos and images map viewport points through the flip; a rotated,
  skewed or 3D-transformed video falls back to opaque with video_alpha_unread.
- A video with an opaque CSS background keeps its whole box occluding.
- cli.mdx lists every unread cause and why a fully transparent frame is one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 18:53:16 +00:00
Xuanru Li 34776f3def fix(cli): analyze finalized audio assets after direct upload (#5647) 2026-10-15 09:23:10 +00:00
Xuanru Li 124492e427 feat(cli): the api provider speaks the HeyGen audio analysis contract (#5641)
* feat(cli): the api provider speaks the HeyGen audio analysis contract

The api rung now sends its key as x-api-key, a new Idempotency-Key on each
request, and 16 kHz mono 16-bit WAV: the channels are averaged and resampled
with a windowed sinc whose cutoff sits under 8 kHz, so nothing folds back in.
It reads the answer from a { "data": ... } envelope, or bare. Times stay in
seconds, and the answered times still snap onto beats measured on the signal
as decoded. The response type gains time_resolution_seconds.

* fix(cli): reject redirects from audio analysis requests

* fix(cli): preserve audio when stereo downmix cancels
2026-10-15 06:54:29 +00:00
Xuanru Li 1544b519a9 feat(cli): beats tracks the music with the TypeScript beat tracker, without Chrome (#5634)
* feat(cli): audio answers beats, downbeats, bpm, sections and describe through a provider ladder

Beats and downbeats try api, apple-mu, beat-this, then signal; sections try api, apple-mu,
then signal; describe tries api, then apple-sound. bpm is read off whichever provider placed
the beats. Each provider says whether it can run here and why not, and the ladder warns
when it passes one over.

The signal rung is a TypeScript tracker: log-band spectral flux, autocorrelation tempo with
a log-normal prior, Ellis dynamic-programming beats with a windowed local tempo, a low-band
bar phase, and Foote novelty sections snapped to beats. It reports no steady beat when the
onsets do not repeat.

The api rung posts the audio to HYPERFRAMES_AUDIO_API_URL with HYPERFRAMES_AUDIO_API_KEY and
moves each answered time onto the nearest signal beat within a quarter beat, reporting what
moved and what did not. beat-this runs Beat This! from a configured Python and local weights
and never downloads. apple-sound runs macOS's sound classifier through a Swift helper.
apple-mu reports that Music Understanding needs macOS 27.

words are no longer accepted: they come from hyperframes transcribe.

* docs(cli): audio's beats, downbeats, bpm, sections, describe and their provider ladder

* feat(cli): beats tracks the music with the TypeScript beat tracker, without Chrome

hyperframes beats and Studio's beat detection now share analyzeMusicFromSignal: band energies,
then the spectral-flux tracker in audioRhythm.ts, then each beat's loudness for its strength.
The CLI decodes with the same ffmpeg path as hyperframes audio, so beats no longer launches
headless Chrome. Studio still decodes in the browser and passes every channel.

The old analyzer averaged bpm-detective's tempo with an energy-peak tempo it had only compared
after folding both into one octave, and phased a rigid grid from noisy peaks. On 22 real tracks
with exact script grids it found the tempo 18% of the time with beat F 0.437; this finds it
91% of the time with F 0.887, and refuses the other two rather than guess.

Below a tracker confidence of 0.2 no beats are reported, so ambient or rubato music gets no
invented grid and the CLI still writes nothing. bpm-detective, the energy-peak grid, the
headless analyzer and its prebuilt bundle are gone.

* fix(core): tempo reads 96 BPM off beats on 10 ms frames, not 95.2

The tracker places beats on 10 ms frames, so a 625 ms beat lands 620 or 630 ms apart and the
median interval reported 95.2 or 96.8 BPM. Tempo now averages the intervals within 10% of the
median: as robust to a skipped or doubled beat, without the frame-sized step. Both
hyperframes audio and hyperframes beats read it.

* fix(cli): remove redundant audio context comment

* fix(core): remove redundant tempo interval comment

* fix(ci): record beat analyzer removals and browser evidence

* fix(core): keep flat audio novelty in one section

* chore: keep PR capture evidence out of the source tree

* fix(cli): honor BPM provider pins before analysis
2026-10-15 06:23:03 +00:00
Xuanru Li 8e340d220e feat(cli): audio answers beats, downbeats, bpm, sections and describe through a provider ladder (#5632)
* feat(cli): audio answers beats, downbeats, bpm, sections and describe through a provider ladder

Beats and downbeats try api, apple-mu, beat-this, then signal; sections try api, apple-mu,
then signal; describe tries api, then apple-sound. bpm is read off whichever provider placed
the beats. Each provider says whether it can run here and why not, and the ladder warns
when it passes one over.

The signal rung is a TypeScript tracker: log-band spectral flux, autocorrelation tempo with
a log-normal prior, Ellis dynamic-programming beats with a windowed local tempo, a low-band
bar phase, and Foote novelty sections snapped to beats. It reports no steady beat when the
onsets do not repeat.

The api rung posts the audio to HYPERFRAMES_AUDIO_API_URL with HYPERFRAMES_AUDIO_API_KEY and
moves each answered time onto the nearest signal beat within a quarter beat, reporting what
moved and what did not. beat-this runs Beat This! from a configured Python and local weights
and never downloads. apple-sound runs macOS's sound classifier through a Swift helper.
apple-mu reports that Music Understanding needs macOS 27.

words are no longer accepted: they come from hyperframes transcribe.

* docs(cli): audio's beats, downbeats, bpm, sections, describe and their provider ladder

* fix(cli): remove redundant audio context comment

* fix(core): keep flat audio novelty in one section

* fix(cli): honor BPM provider pins before analysis
2026-10-15 05:16:55 +00:00
Xuanru Li bb49858b8a feat(cli): hyperframes audio measures where sounds land, in film time (#5602)
* feat(cli): hyperframes audio measures where sounds land, in film time

Adds @hyperframes/core/audio-analysis: an energy envelope, spectral-flux
and energy onsets, the moments a sound lands (onset, steepest rise, peak,
tail end), BS.1770 loudness with true peak, silence spans and a first
guess at sfx/voice/music, all pure functions of PCM. Each capability runs
through a provider ladder so model or service providers can join later.
The radix-2 FFT moves out of audioCarve so both share it.

`hyperframes audio` decodes with FFmpeg and analyzes files, or the film's
own audio and video clips by element, second, range or boundary, reading
only the part of each file that plays. Timeline rows now carry
`mediaStart`, moved by an in-point host's cut, so file time maps to film
time.

* feat(cli): audio placements carry the element's own data-start, local to its composition

* feat(cli): audio placements carry the element's own data-start, local to its composition

* feat(cli): audio places a file with --place-at, drops --boundary

--start becomes --place-at and --media-start becomes --skip-file-seconds,
which only goes with --place-at, refuses to skip the whole file, and warns
when the skipped part held the file's loudest moment. --boundary is gone:
--at and --range cover the sounds a cut touches.

* fix(cli): pass a sound file to ffprobe as a file, on every platform

ffprobe treated a name starting with a dash as an option because the argument
list never ended the flags. The path check compared a Windows drive path to a
POSIX string, and a new timeline comment raised that file's comment share.

* fix(cli): hear a reversed stereo channel, and warn when its peak was skipped

Averaging the channels cancelled a reversed one, so a real hit was reported
as silence. The skip warning also compared against the decode margin, so a
peak just before the kept window produced no warning.
2026-10-15 02:32:10 +00:00
Miguel Ángel c0636d017f feat(cli): snapshot --keep redoes one still and leaves the others (#5605)
* feat(cli): snapshot --keep redoes one board still and leaves the others

* fix(skills): the board recipe waits for a released snapshot --keep

* fix(cli): snapshot --keep refuses two times with one name and replaces linked stills
2026-10-14 19:25:31 +00:00
Miguel Ángel d65eefad5e feat(cli): init --keep-original copies a clip as is, without transcoding (#5612)
* feat(cli): init --keep-original copies a clip as is, without transcoding

`hyperframes init --keep-original` copies the --video file under its own
name and wires it into the project the way a web-safe clip is wired today:
no transcode and no prompt, interactive or not. Default behaviour is unchanged.

Theora leaves init's web-safe codecs and joins the preview proxy's
browser-hostile codecs, so init converts it and the preview proxy serves an
H.264 copy instead of a blank picture.

* fix(studio-server): keep the codec table's comment share at its base level
2026-10-14 18:19:17 +00:00
Xuanru Li 23d1fe5b42 feat(cli): save check, snapshot and render artifacts per revision (#5522)
* feat(cli): save check, snapshot and render artifacts per revision

* fix(cli): keep clean --snapshots inside the project

A symlinked .hyperframes or artifacts folder was followed, so the command
deleted snapshot and check files outside the project.

* test(cli): keep check, snapshot, and render successful when recording fails

A held history or a corrupt output registry warns and leaves the command result alone.

* fix(cli): drop snapshot records when a replaced run is not recorded

A reused --output rewrites the files even when history is busy.
Those records are removed so the old revision does not keep the new pixels.
The preview checkpoint stops waiting after five seconds.
A bad PNG header no longer fails the snapshot.

* fix(studio-server): keep the video-state comment share from rising

The forget helper's doc comment pushed the file above its base share.

* fix(cli): post the preview checkpoint and keep cleanup inside artifacts

A preview checkpoint with no body was sent as GET, so Studio never ran it.
Cleanup now requires the real path to stay under the artifacts folder.
A failed follow-up drop does not warn twice.
The studio-server unpacked budget covers the record-dropping helper.

* docs(cli): shorten the snapshot skill notes

The command examples already show range and composition.
The prose only keeps the output path and the flags that are easy to misuse.

* chore: leave package headroom for artifact cleanup

* test(cli): compare artifact paths portably

* style(cli): format portable artifact assertion
2026-10-13 06:20:31 +00:00
Xuanru Li 7c73b3caee feat(cli): guard history turns with --exclusive, --who and --if-open (#5518)
* feat(cli): guard history turns with --exclusive, --who and --if-open

* fix(cli): make exclusive history turns atomic

Re-read the open turn only after acquiring history ownership so
concurrent exclusive begins cannot both claim the same project.
Cover the race with a deterministic two-process regression test.

* fix(cli): refuse a second exclusive turn while a preview is open

The preview's begin window now rejects another agent's live turn, and an
idle turn no longer blocks exclusive begin. The two-process test waits on
pids collected from claim drafts instead of seeing both files at once.
2026-10-13 04:22:41 +00:00
Xuanru Li 36526bf1a6 feat(cli): scope check to a time range or a set of components (#5516)
* feat(cli): scope check to a time range or a set of components

* fix(cli): keep ranged check gates truthful

Refuse ranged caption and frame gates when none of their seek times
falls inside the requested range. Make ranged motion reporting,
overlap timestamps, filtering tests, and scope documentation match.
2026-10-13 03:13:26 +00:00
Xuanru Li 1cd086f7e3 feat(cli): snapshot a time range or one composition (#5519) 2026-10-13 02:12:54 +00:00
c3816a121b docs(lint): add a reference page for every lint finding code (#5493)
* docs(lint): add a reference page for every lint finding code

docs/reference/lint-rules.mdx lists every code emitted by the linter
(132: the 121 rule codes in packages/lint/src/rules/*.ts plus the 11
project-level probes that run as part of a project lint) with its
severity, what it catches, and how to fix it, grouped by the rule file
the code comes from. Conditional severities (Studio-host structure
checks, GSAP seek-order hazards) say when they escalate.

Link it from docs/packages/lint.mdx and add it to the Composition
reference group in docs.json.

Issue: #5385

* docs(lint): document the color grading codes and correct four entries

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: James <james.russo@heygen.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 00:17:35 +01:00
Meghna VasudevaandClaude Opus 5.5 2973b3c8e4 docs(cli): document open, catch-up, timeline and history (#5442)
Add the four commands that `hyperframes --help` lists but the CLI reference
page left out, with their options taken from each command's args and
examples checked against a sample project.

Closes #5386

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:57:32 +00:00
Miguel Ángel 44e5a47e09 fix(cli): media-treatment applies one treatment to every matched image or video (#5443)
* fix(cli): media-treatment applies one treatment to every matched image or video

* fix(cli): media-treatment reports its action from every target and documents the per-element merge

* fix(cli): media-treatment patches each match by its stable id so one write cannot redirect another

* fix(cli): media-treatment sets every match in one parse instead of re-finding elements

* fix(cli): media-treatment refuses media that is not an HTML element, as before

* chore(skills): refresh the media-use manifest hash for the treatment reference edit
2026-10-11 03:45:37 +00:00
Miguel Ángel b20f3f7ad9 fix(cli): check warns instead of failing on a still title card (#5369)
* fix(cli): check passes a still title card whose timeline follows the seek

* fix(cli): check warns on a still frame and fails only when an animation is stuck

* fix(cli): count a seek clock once per sample and point the error at the registered timeline

* fix(cli): judge a finished animation by its own completion state, not its end time

* test(cli): pin a Web Animation paused at its end as finished

* fix(cli): judge a reversed animation's end in its own direction, and read plain timelines

* test(cli): pin a plain timeline held at its end as finished

* test(cli): keep an unfinished plain timeline beside the finished one

* fix(cli): skip a timeline whose time cannot be read instead of calling it stuck

* fix(cli): skip an animation whose time is not a plain number, such as a scroll-driven one

* fix(cli): a partial timeline with plain values no longer stops the browser checks
2026-10-10 00:38:09 +00:00
Miguel Ángel 908f4395b4 feat(cli): let transcribe --to write srt or vtt straight from a media file (#5215)
* feat(cli): let transcribe --to write srt or vtt straight from a media file

* fix(cli): fail a missing transcribe --output folder before decoding

* fix(cli): reject a transcribe --output that names a folder

* test(cli): check CJK caption grouping on the parsed cues

* fix(cli): keep transcribe --output from overwriting the media or its transcript

- Refuse an --output that resolves to the input file or the transcript.json the run writes,
  before the engine starts.
- When the caption file cannot be written after transcribing, say the transcript was saved
  and name the caption path and reason, instead of reporting a failed transcription.
- Docs: a one-run export groups cues for languages without spaces; exporting the saved
  transcript later may split them differently.

* fix(cli): refuse caption paths that alias the media or transcript

- The overwrite check compares files, not path text: an existing target is matched by device
  and inode, a new one by its real folder and its name ignoring letter case. Links, folder
  aliases such as /var and /private/var, and other letter case are all refused.
- The check also covers the default caption path, so exporting transcript.srt to srt no
  longer rewrites its own input.
- A trailing -o with no path fails before transcribing.
- One constant names transcript.json for the engines, the import and the check.

* fix(cli): refuse caption links to the transcript before it exists

- A caption path that is a link is followed to where the write would land, so a link to the
  transcript.json the run is about to create is refused before transcribing.
- A test pins the folder letter-case check on a case-insensitive disk, and says which branch ran.

* fix(cli): check the caption file against the transcript right before writing it

- Right before the caption write, by when the transcript exists, a destination that is the
  same file as the input or transcript.json is refused. The OS resolves every link, relative
  link and alias, so the hand-written link following is gone.
- The run keeps the transcript and says the caption file could not be written and why.
  An export whose caption write fails now says so too, instead of crashing.
- The check before transcribing stays as an early refusal for the common cases.

* fix(cli): compare caption paths by name when the disk reports no inode

- Some network shares report inode 0 for every file; such a pair is compared by folder and
  name instead of being called the same file. The check before transcribing and the one at
  write time now share one comparison.
- The link-through-a-file tests accept ENOENT as well as ENOTDIR, the code Windows reports.

* fix(cli): resolve both caption paths fully when the disk reports inode 0

- When either file reports inode 0 and both exist, compare their fully resolved real paths,
  so a link to the media or the transcript is still refused. The folder and name compare
  stays for a path that does not exist yet.
- Tests zero the inode on every path, on the caption path only, or on the protected file only.

* test(cli): say plainly why the caption-write test accepts two error codes
2026-10-09 22:54:36 +00:00
Miguel Ángel 0576b24c75 feat(cli): models list shows which speech models transcribe can use (#5323)
* feat(cli): models list shows which speech models transcribe can use

* refactor(cli): models list and transcribe share one Parakeet runner check

* fix(cli): models list skips whisper links transcribe would download again

* style(cli): keep models list comments under the package share
2026-10-09 07:21:54 +00:00
Miguel Ángel c7d41dee5a fix(cli): enforce capture screenshot limits and report interruptions (#5171)
* fix(cli): enforce capture screenshot limits and report interruptions

* refactor(cli): split screenshot step out of capture animation phase

Keeps the screenshot limit outside the mirrored core-extraction argument
list and moves scroll screenshots into their own step, so both stay under
the audit's duplication and complexity gates with no behaviour change.
2026-10-08 15:41:50 +00:00
Miguel Ángel 8dae5ef89b fix(core): make grading writes previewable and honor computed properties (#5157)
* fix(core): write only authored hue curves, so a single hue curve passes lint

* fix(cli): apply over a grading saved with empty hue curves instead of refusing it

* fix(core): read grading properties from computed style, and stop advising an inline start

* fix(cli): preserve validation during legacy hue repair

* fix(cli): preview grading writes without apply
2026-10-08 15:21:47 +00:00
Miguel Ángel cf1b04bb71 feat(cli): accept host-managed HeyGen access tokens (#5177)
* fix(auth): accept host-managed HeyGen access tokens

* fix(auth): share environment header validation

* docs(auth): condense credential resolver guidance

* fix(auth): report the account a browser login just signed in

Identify the account from the tokens the login issued, not a fresh credential
lookup that an environment token or API key outranks.

* docs(auth): list the host access token in the CLI credential order

* fix(cli): name a rejected host access token on publish

Publish told a host-token user to log in again, which cannot help while the token outranks the login.

* docs(auth): show the media credential order main resolves

* fix(cli): warn at logout that a host access token still signs commands

One map of environment credential variables, owned by the resolver, now feeds the logout warning and both publish messages.

* docs(auth): scope the media credential order to the audio scripts

* refactor(cli): read every env credential name from the resolver's map
2026-10-08 10:33:57 +00:00
Miguel Ángel 80c2547eac feat(cli): send missing features and host-app friction as ratingless feedback (#5140)
* docs(skills): ask agents inside a host app to report its friction as feedback

* feat(cli): send missing features and host-app friction as ratingless feedback

* fix(cli): keep the rating error, refuse blank reports and document ratingless feedback

* docs(skills): leave names and figures out of missing-feature reports

* docs(skills): keep anything private out of missing-feature reports
2026-10-07 06:40:37 +00:00
Miguel Ángel 2d98d5c15f fix(check): stop false sweep_static and text-overlap alarms on compositions that are fine (#5147)
* fix(check): see color, filter and media time under seek, and name data-no-timeline for stills

* fix(check): warn instead of fail when only audio advances, and judge text overlap on glyphs

* fix(check): judge glyph overlap by the glyph box, and see capitalized words and one-side borders

* fix(check): capitalize past leading punctuation, test outline color, trim a comment

* fix(check): capitalize words the way Chromium does, so digit-led words stay lowercase
2026-10-07 05:38:45 +00:00
Miguel Ángel 34552a21d2 fix(cli): let parakeet-mlx detect the language and name the Parakeet install under auto (#5124)
* fix(cli): let parakeet-mlx detect the language and name the Parakeet install under auto

* docs(cli): say what --language does under each engine

* docs(cli): whisper transcribes as the --language, it does not filter

* chore(cli): drop a comment the install-hint condition already says
2026-10-06 19:04:28 +00:00
Miguel Ángel 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
2026-10-04 05:07:49 +00:00
Miguel Ángel 7dea1e581f fix(studio): playhead keyframe edits keep their ease, undo names its edit and never waits on videos (#4889)
* fix(studio): an edit on a GSAP-animated layer writes its value at the playhead into the file's tween

* fix(studio): undo pressed while an edit saves undoes that edit, not one started after the key

* fix(studio): an edit stays saved when the history reply after it cannot be read

* feat(studio): export ColorField and GradientField; a session style commit takes a map

* refactor(studio): name the map form instead of commenting it

* feat(studio): a host can move layers by exact film px, one undo per call

handleDomGroupMoveBy(moves) on useDomEditSession and DomEditValue moves each
selection by its delta in composition px, for Desktop's align and distribute.

It reuses the group drag's own path: a drag member per element (press-time
route, measured offset map), the delta carried to screen px through the
preview frames, then handleGsapAwareGroupPathOffsetCommit, so one shared
coalesce key gives one undo entry and GSAP members take the GSAP write.
A member that can't move refuses the call before anything is written; the
refusal rejects with its reason and shows no Studio toast (the group commit
takes refusalToast: false; telemetry stays). Zero deltas are skipped.

Tests: a pre-offset element and one under a half-scale parent move by exactly
{10,0} and {0,-5} under one group key; a half-scale parent with a 30 deg
rotated ancestor lands within the 0.001 px a translate is written with; a
GSAP member goes through the GSAP write; three refusal kinds write nothing,
leave the boxes where they were, show no toast and leave no gesture mark,
while a canvas group drag still toasts; the session hands the call to the
group commit with refusalToast: false. Each mutation (no frame mapping, no
zero skip, no restore, no teardown, no capability check, toast on refusal)
fails at least one of them.

* fix(studio): an outside change during a drag waits for the drop to show

While a gesture mark is live in the preview, refreshPlayer takes the shadow load instead of
swapping scenes in place. A swap replaced the node under the pointer mid-drag; the shadow load
is then held by the existing gesture hold and, if the drop saved, reloaded fresh by the stale
rule, so this adds no second "released" signal.

Test: an outside change during a live drag on a preview that can swap scenes leaves the dragged
node in place, shows nothing under the pointer, and after the drop loads fresh with the dropped
position. It fails on the parent without the condition ("swapped under the pointer").

Cost case, measured in a real headless preview on the edit-accuracy bench's per-frame teleport
check. Every GSAP instant patch was forced to miss and fall back to reloadPreview during its own
commit; move and rotate of a held element (gsap.set and tl.set holds), px and xPercent
placement, r0 and r30, root and nested, at 100%:
- The gesture mark was live when the fallback asked for the reload, but had always ended by the
  time refreshPlayer ran (refreshKey is consumed in an effect after the commit settles). So the
  new condition never fired there: root previews offer no scene swap and load shadow on both
  builds, and nested previews swap on both builds.
- Worst post-release frame offset, 32 cases on this build and 32 on the parent: 0.00 px over
  76-106 frames each (release to settle). No frame was off by more than 0.5 px.
- Sensitivity: a 3 px nudge injected 50 ms after the fallback reads back as 3.00 px.

* fix(studio): an edit no longer reopens the live-updates stream, and thumbnails ask one at a time

* fix(studio): an undo no longer waits behind paused preview videos for a connection

* feat(studio): a host whose API needs same-origin cookies can have Studio send them

* docs(studio-server): a host on same-origin cookies sets the Studio credentials opt-out

* test(studio): an undo is not held up by the preview's paused videos

* fix(studio): a resize inside one gesture writes a mixed tween once, in place, never beside it

* chore(studio): keep the carried fixes inside the comment ratchet; telemetry posts through studioApiFetch

* test(studio): a drag on a mixed tween is one write under the gesture key

* fix(studio): freeze-frame, loudness and peak-map requests go through studioApiFetch too

* fix(studio): an undo with no project open reloads in full instead of skipping nested files

* refactor(studio): drop the soft reload's reparse seeding, the held-element sweep covers it

* test(studio): keep the undo-restore browser test off the network and inside its timeout

* test(studio): nested composition reads expect the cookie-free request studioApiFetch sends

* fix(studio): an edit at the playhead rewrites its tween in place, so later tweens keep their times

* fix(studio): a rotate on a tween that turns another axis holds its angle and never turns early

* fix(studio): plan every group member before the first write so one refusal moves none

A group drag or nudge preflighted each member against the move refusal rule only. The value-at-playhead planner ran later, in the commit loop, after earlier members were already flushed to disk, so a tween with extras, eased keyframes or an unknown ease left the group half moved.

The group preflight now runs the commit's own route and keyframe plan for every member with its real offset, so any refusal refuses the whole group with nothing written and one toast attributed to the refusing member. The plan is not reused in the commit: earlier members' writes change the file under it, so each member re-plans against the fresh tween, from the same planValueEdit.

* chore(studio): the fetch lint rule also catches window.fetch, globalThis.fetch and self.fetch

* docs(studio-server): set the credentials flag before Studio loads; a mounting host injects it

* test(studio): the undo socket test needs six pinned videos under Vite and checks each undo restores

* fix(studio): Studio leaves cookies out of its requests only when served from loopback

* fix(studio): undo takes back the first edit, and the first save never waits on media

* fix(studio): undo of a move whose save failed stays undone and steps back no older edit

* fix(studio): a style set by its JavaScript name saves under its CSS name

* test(studio): the undo socket test holds round 0 to a full restore too

* fix(studio): the host revert keeps returning what shows the edit again

* fix(studio): an entrance rewritten at the playhead still shows its start state before it starts

* fix(studio): undo takes back the edit pending at the key even after a later edit claims

* chore(studio): lift the late-write quarantine now that undo never waits on videos; format

* test(studio): pin an entrance's start render on the written tween, not a happy-dom GSAP run

* revert(studio): leave entrances as on main, immediateRender does not render a keyframed start

This reverts commit ee9d130d3c and its written-vars test.

* revert(studio): undo goes back to the newest claim after the key, as on main

This reverts commit 5b9009a823.

* revert(studio): first-undo history opening moves to the next PR

This reverts commit 4e1e75d7f4.

* revert(studio): the undo socket test exempts round 0 again until first-undo lands

This reverts commit 1f8f97047c.

* test(studio): bank the edit accuracy cases this PR makes pass

* refactor(studio): share one project file fetch between the plain and optional reads

* test(studio): share the WebMCP host shim between the browser gates

* test(studio-server): share one stub adapter across the route tests

* refactor(studio): type the preview slot props from the NLE context

* test(studio): share the setup the new editing tests copied between files

* refactor(studio): split playhead tween picking and undo path merging into small tested helpers

* test(studio): pin when a resize at the playhead also writes the anchor move

* test(parsers): pin rewriting a tween in place as keyframes

* chore(studio): cut comments the new helper names already say

* fix(studio): composition thumbnails keep two fetches in flight, as the document lifetime fix expects

* fix(studio): a playhead edit parses a tween not yet played and matches only its own tween

* fix(parsers): a new position argument goes after the last argument, so trailing commas stay valid

* fix(studio): a second undo pressed during one save steps back to the edit before it

* test(studio): pin the slot an aborted thumbnail frees and the shared tween refusal

* refactor(studio): split the unplayed-tween parse into small helpers and name the claim release

* fix(studio): a playhead edit keeps the tween's default ease, delay timing and loop settings

* fix(studio): a group move refuses layers animated in two files and plans at the real drop

* style(studio): format two test files

* refactor(studio): check a group's files inside the preflight helper

* refactor(studio): move the group preflight checks out of the editing hook, under 600 lines

* fix(studio): read an unreached tween without redrawing other layers, and compare write files

* test(studio): use the shared selection helper in main's keyframe-at-time telemetry test

* chore(studio): fix a comment that read as a repo path and cut one added comment

* fix(studio): read a later tween's start as playback does, and count every keyframe add

* fix(studio): two undo presses during one save step back two edits, not one twice

* fix(studio): an undo that succeeds by any route retires the claim on the edit it undid

* fix(studio): a claim is never handed back to an edit another undo has already undone

* fix(studio): an undo picks its claimed edit at the press and skips it once undone

* fix(studio): a press refuses, as main did, once its edit is undone and a newer edit exists

* fix(studio): an unplayed keyframe tween clears every keyframe's channels before it is read

* fix(studio): reading an unplayed tween puts back every transform value its clear removed

* fix(studio): reading an unplayed tween restores each layer's whole style and GSAP state

* fix(studio): reading an unplayed tween skips plain-object targets and restores attributes

* fix(studio): shared tween-target filter uses GSAP's own styled-element test

* fix(studio): match a parsed tween on any target, and check a later keyframe channel's start

* docs(studio): say the unplayed-tween read restores styled elements only

* test(studio): a drag on a fromTo start shows the dropped x through the runtime seek
2026-10-03 13:10:54 -07:00
maopishouzzzandMiguel Angel Simon Sierra f2eb6d9cc1 fix(lint): flag seek-unsafe CSS transitions (#3797)
* fix(lint): flag seek-unsafe CSS transitions

* fix(lint): allow zero-duration transition resets in css_transition_used

The registry's reduced-motion and render-lock resets use transition-duration: 0s and
transition-delay: 0s, which never run on the browser clock. Also drops the one real
seek-unsafe transition the rule finds in the product-promo example.

* refactor(lint): split the zero-time check out of isSeekUnsafeTransition

* fix(lint): report seek-unsafe CSS transitions as a warning

---------

Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com>
2026-10-02 07:47:13 -07:00
miga-heygenandMiguel Angel 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>
2026-10-02 07:32:48 -07:00
Miguel Ángelandmiga-heygen 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>
2026-10-02 06:25:18 -07:00
37f30b1d06 feat: keep a video's sound on the clip by default (1/8) (#4813)
* feat(cli): one owner for init media placeholders, audio stays on the video

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cli): init keeps a video's sound on the clip; silent files scaffold muted

Supersedes the patchVideoSrc changes in #3977 and #3942 and carries their tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: miga-heygen <miga-heygen@users.noreply.github.com>
Co-Authored-By: kapelame <kapelame@users.noreply.github.com>

* feat(registry): example templates keep the a-roll's sound on the video

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(lint): video audio rules recommend keeping sound on the video

Update video_missing_muted and video_audio_double_source message and
fixHint text to recommend the new default: keep audio on the video with
data-has-audio="true" (not muted). Tests verify the new wording is in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(parsers): a video is either audible (data-has-audio) or muted, never both

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(studio): follow the compiler's audio rule in the audio-track toggle

Confirm before background removal drops a video's sound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): hyperframes-core teaches sound on the video by default

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): workflows and scaffold guidance keep a video's sound on the clip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: footage with sound keeps it on the video clip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): init from-file a-roll is timed, muted strip is attribute-exact

The from-file a-roll video gets data-start="0" so a scaffold lints clean, and
the orphaned audio-only slot comment is dropped. setVideoAudio now walks the
tag attribute by attribute, so a class value containing the word muted
survives. The ffprobe-missing warning says the video is assumed to have sound
and how to switch to muted, and DEFAULT_META notes the failure surfaces at
render until lint support lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(studio): keep has-audio in step with the muted toggle, test cutout confirm

Muting a video now clears has-audio and unmuting sets it to true, in both the
Design and flat media panels, matching the Has audio track toggle. Audio
elements are unchanged. Adds coverage for both panels plus the cutout
window.confirm guard: declined leaves src alone, accepted swaps src and mutes,
a silent video never prompts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(parsers): updateElementInHtml hasAudio only applies to videos, true or false

An audio element or an undefined hasAudio no longer picks up a muted attribute;
only an explicit false on a video writes muted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: video_missing_muted rule text, silent-footage notes, consistent audible examples

lint.mdx and cli.mdx describe the current video_missing_muted rule instead of
the retired unmuted-video wording. The editing recipes say how to adapt for
silent footage and require the outgoing clip's sound to end at a J-cut. The
Remotion media example shows the audible mapping consistently, data-attributes
states that has-audio is required unless muted, and the hairline frame note no
longer claims the video is muted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): encode init media filenames and probe --audio duration

Function replacers stop dollar-sign pattern expansion in filenames; src is percent-encoded.
--audio now passes the probed track length as the composition duration in both init paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(lint): audio-state-aware media rules and time-aware double-source check

video_missing_muted honors any explicit data-has-audio.
video_audio_double_source only considers audible videos and only fires
when the same-src clips overlap in time.
duplicate_audio_track now covers audible videos.
audio_file_without_element no longer claims silence when an audible video
carries the sound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): audio-stream check for remotion ports, followable J-cut recipe

Remotion Video maps to data-has-audio only when the file has an audio stream.
The J/L-cut recipe now shows both shots with muted pictures and separate audio.
embedded-captions removes the audio.mp3 scratch file it extracts.
skills-manifest.json regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(lint): trim comments to satisfy the comment ratchet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(studio): atomic muted edits, cutout keeps sound on a linked audio

Muted and Has audio track commit one batched write (one persist, one
undo entry) and remove data-has-audio instead of writing an empty
marker. Unmuting a video probes its src: audible -> true, silent ->
false, probe failed -> absent. Remove background on an audible video
inserts a linked sibling <audio> with the original sound in the same
write, replacing the drop-the-sound confirm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(producer): skip hidden videos when auto-adding video audio

A video hidden in Studio is dropped by the mixer, so its src never reached
existingAudioSrcs and the render re-added it at full volume. The video parser
now records the shared hidden predicate and the auto-detect step skips hidden
videos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(studio): keep useDomEditSession under the 600-line cap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: miga-heygen <miga-heygen@users.noreply.github.com>
Co-authored-by: kapelame <kapelame@users.noreply.github.com>
2026-10-01 23:56:36 +00:00
Miguel Ángel 663b02297d feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk (#4701)
* feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk

`hyperframes clean` lists leftover render work dirs, debug renders, the history of projects
that no longer exist, idle video proxies and extracted video frames, with sizes, and removes
them. `--dry-run` removes nothing, `--json` is for agents, `--snapshots` also removes
snapshot folders (otherwise listed as reclaimable). It never touches outputs, sources or
anything a running render or preview still owns.

* docs(cli): say that clean does not search a project's subfolders

* fix(cli): clean test compares folder names on Windows and the skills manifest is regenerated

* fix(studio-server): the proxy cache sweep removes only files the transcoder named

It removed any idle .mp4 or .webm in .transcode-cache, so a user file placed there, or reached through
a symlinked cache folder, could go. Proxies are always <sha256>.<ext> and their temp files
.tmp-<uuid>-<proxy name>; nothing else is swept.

* style(producer): keep a renderDirOwner doc comment within 120 columns
2026-09-29 00:43:42 +00:00
Miguel Ángel 840f657028 feat(player): play and loop a range of a film with range-start and range-end (#4688)
* feat(player): play and loop a range of the film with range-start and range-end

The runtime takes a set-play-range control and stops on the range end frame itself.
The player wraps to range-start with loop, fires ended without it, and reports a
range cut to the film with rangeclamped. Video and __timelines players check it per tick.

* fix(player): hold a range on its last frame and let the runtime own range jumps

A runtime with the play-range capability jumps to range-start itself when a new range leaves
the playhead outside, so a stale state no longer ends the film; older runtimes keep the
player fallback, guarded until a state lands inside. Every range stop shows the last frame
before range-end; a paused playhead re-parks only when it falls outside the new range.

* fix(player): stop a range a check early and enforce its end on a video in a background tab

* test(player): share the range video setup

* fix(player): a slow frame no longer cuts a range short; a video without a range is unchanged

* fix(player): a range still stops early where video time moves once per frame

* refactor(player): cut the range look-ahead comment to one line

* fix(player): a per-frame video time rounded down still stops a range early

* test(player): pin no rounding tolerance at a film's own end

* test(studio): expect the play-range capability on control messages
2026-09-28 23:38:32 +00:00
Miguel Ángel 326bd0c9a1 feat(player): play video files with type="video/..." (#4673)
* feat(player): play video files with type="video/..."

A src with a video type now plays in a <video playsinline> behind the same
API and events as a composition: play/pause/seek, currentTime, duration,
ready with the video's size, timeupdate, ended, loop, durationchange and
resize. A load failure fires error at once with the MediaError code, and a
blocked play fires playbackerror and leaves the player paused.

The video is driven through the direct-timeline adapter and clock, and every
src load goes through one _loadSrc that picks the video or the iframe.

* fix(player): keep video playback steady across option changes, pauses and errors

* fix(player): keep video playback in step with the video after seek, pause and srcdoc

* fix(player): limit video-mode changes to video players and pause on srcdoc switches

* refactor(player): cut the video-source header comment to two lines
2026-09-28 18:04:13 +00:00