* fix(core): the playhead keeps moving at a cut while the incoming take has not started
* fix(core): shorten a comment in the clock
* fix(player): keep the WebKit stops script out of the type check
* fix(core): cut two comments from the clock
* fix(core): a still sub-composition's data-no-timeline on its template root skips the timeline wait
* chore(core): drop a comment the copy already says
* fix(cli): check flags caption words hidden behind a cut-out and ignores a mostly transparent cover
* fix(cli): a caption word's lifetime is shifted only by its composition hosts
* fix(cli): normalize-audio never cuts a clip that is quieter than the target
* fix(cli): carry the boost-hold rule in a name, not a comment
* fix(cli): normalize-audio no longer says a clip is matched when a limit holds it short of the target
* fix(cli): format normalize-audio, drop the extra comment, and say a held clip is held
* fix(cli): Apple Silicon Macs without ffmpeg get working arm64 ffprobe and ffmpeg
* fix(cli): drop the bundled ffmpeg installers, name the x86-only binary it skipped
* feat(cli): use dedicated HyperFrames OAuth clients
* docs(cli): shorten credential store contract comment
* fix(cli): respect Windows modes in OAuth media test
* fix(cli): keep media workflows on HyperFrames OAuth
* test(cli): scope billing status assertions to wallet rows
* test(cli): align telemetry fixtures and CI auth inputs
* test(cli): stalled-download tests give each stall time for its bytes to reach disk
* test(cli): a stalled download times out once its bytes are on disk, not on a clock
* feat(studio): a preview reload that gives up on the runtime says how far the page got
* refactor(studio): read the reload's page phase in one place, before the frame is let go
* test(studio): every reload page phase is covered, and the hook gains no branch for it
* refactor(studio): the reload page phase reads the window only once the frame has a document
* fix(cli): a project .env only sets the CLI's own settings
The .env in the working directory could set any environment variable for every
command except capture. It now sets only the keys the CLI reads from a project:
the HeyGen, ElevenLabs and Figma credentials and the vision keys, models, Vertex
settings and timeout. Other keys are ignored, and one stderr line names them.
* test(cli): the startup test and comment budget follow the single project .env key list
* fix(cli): a project .env can still turn telemetry off
* fix(engine): late-starting videos play to their last frame
A video whose first frame is after timestamp 0 could end early (frozen
tail) or late: ffprobe's stream duration does not run from the first
frame in every container (fragmented MP4, Matroska). Such a video's end
is now read from its last packet, once, where its facts are probed.
* test(engine): the WebM-with-audio case measures its first frame from the file
* test(engine): the first-frame probe ends its arguments with the input file
* fix(core): the preview's scene window settles when a scene can't unmount instead of hanging the page
* refactor(core): name the scene window's stuck step instead of commenting it
* fix(cli): allow-occlusion on the covering element silences text_occluded
* fix(cli): allow-occlusion also works on graded video and does not excuse a card's own text
* fix(engine): long films start capturing at once instead of after a full effect scan
* fix(engine): give the effect scan two minutes, under the app's stall limit
* fix(engine): show effect scan progress so a long scan reads as preparing, not stalled
* fix(core): telemetry redaction covers folders with spaces in any script
* style(core): shorten the redaction folder note
* fix(core): spaced folders without a root redact only before a file name
* style(core): drop the redaction note that described the leak as intended
* style(core): two fewer comment lines in the redactor
* fix(core): bound the spaced-folder match so long lines stay linear
* feat(cli): a feedback report names why the last check failed
* fix(cli): feedback names a check that threw or never finished, with when it ran
* fix(cli): feedback says how old the last check is
* fix(player): iPhone Safari keeps every take's sound after the first seconds of an edit
* fix(player): shorten the priming comment
* fix(player): prime iPhone takes only, and only unmuted ones
* fix(player): shorten a comment
* fix(player): skip only clips authored muted, and drop a doc comment
* ci: one regression shard writes the test image cache, so it stops evicting the rest
* test(ci): each image cache has one writer, shard 0 of a main push
* fix(cli): a snapshot still shows what playback shows, whatever other times are asked for
* test(cli): pin that still-capture pages ask for the skipped-frame replay
* perf(cli): give render the heap its auto workers need in small containers
Auto worker sizing caps capture workers at (V8 heap - 1024 MB) / 640 MB.
Node sizes its default heap from visible memory, so in a 10 GB container
the heap is 2,096 MB and every render captures with one worker, whatever
the core count.
When no --workers is given, no --docker, no heap was set by hand, and the
heap is the only bound holding workers back, render relaunches itself once
with --max-old-space-size set to what CPU and memory allow (capped at half
of memory). The engine exposes heapMbForAutoWorkers() for this.
* fix(cli): count a heap-relaunched render once, keep detached renders alive
The relaunched child skips the CLI's command telemetry, update and skills
notices, and app-history notice; the parent already records and prints
them. A detached render no longer gets SIGHUP forwarded, which its
default handler would treat as a kill. A heap set as --max_old_space_size
counts as set by hand.
* fix(engine): keep the desktop app's per-worker heap figure at 640 MB
Inside Electron the heap is a hard 4,192 MB cage, and that is where the
field OOM behind the old figure happened (#5149: 5 auto workers on a heavy
export). The new 320 MB figure is measured only in a system Node, so the
desktop app keeps 640 MB (4 workers there) until a heavy desktop export is
measured. Containers and plain Node keep 320.
* fix(engine): know the packaged desktop app by its client tag, which runs the CLI on plain Node
* fix(ci): skills sync steps past any taken ClawHub version, not one hard-coded skill
* fix(ci): skills sync steps past the five hidden ClawHub versions blocking it
* fix(cli): an unknown command like search says which command does that
* fix(cli): the synonym hint is checked before the spelling match, so list, find and browse reach it
* fix(core): a multi-worker or chunked render lands every frame where an in-order render does
* fix(core): replay only where tweens overlap, and from any off-grid scene time
* fix(core): a film with paused scenes replays only where its tweens overlap
* fix(cli): capture names the network's proxy when it refuses the connection to the site
* fix(cli): capture words a refused tunnel and an unreachable proxy apart, and echoes no raw address
* fix(cli): shorten the proxy failure comment
* fix(cli): check waits as long as the --timeout it was given before it counts as idle
* style(cli): shorten a comment line
* style(cli): drop the idleWindow doc comment the comment ratchet counts
* docs(telemetry): say signed-in usage is linked to your account, not anonymous
After hyperframes auth login the CLI sends a PostHog $identify with the
account email (username as fallback) and merges the install's earlier
anonymous usage; media-use does the same when it finds a signed-in
account. Several places still called all telemetry anonymous. Say it is
anonymous until you sign in, in the CLI reference (media-use, --skill,
feedback, events, telemetry), the hyperframes-cli skill references, and
the CLI help and notice strings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(telemetry): identify signed-in CLI users by their HyperFrames user id
The CLI and media-use identified a signed-in install by account email, so
CLI usage landed on a different PostHog person than the same account in
the HyperFrames web app, which keys on toUUID(username). Identify by that
same sha256-derived id instead, and $set email plus heygen_username on the
person. heygen_username is the cross-project join column for the Snowflake
event table (HeyGen's own project stores it as `name`). An account with no
username still falls back to the lowercased email.
Notices and docs now say usage is keyed on an id derived from the username
with email and username stored on it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(telemetry): trim comments to the package's comment share
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(telemetry): put heygen_username on every event while signed in
The Snowflake event sync may not carry PostHog person properties, so a
$set alone does not let an event row join across projects. The CLI now
adds the signed-in HeyGen username (raw, from ~/.heygen/credentials) to
every event and drops it once the credentials hold no user; media-use
does the same on each event it sends.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci: count auth/paths.ts as an edit-accuracy bench input
Telemetry now imports it to read the signed-in username.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(telemetry): stat and read the credentials through one file handle
CodeQL flagged the stat-then-read as a file system race.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(telemetry): key signed-in events on the account id; fix stale anonymity copy
Genesis review on #6703:
- telemetry --help listed 'Any personally identifiable information' as
never collected, and media-use's meta.md still said anonymous.
- Installs already identified by email under an older CLI never merge
into the new person, so send signed-in events with distinct_id = the
account id (back to the install id after logout), in the CLI and in
media-use, and stop claiming earlier usage always merges.
- A user block with no credential no longer counts as signed in.
- A UUID-shaped username passes through unhashed, as on the web.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): audio windows read beats off the whole clip; beats snap to the signal and mark silence
A film query that covers part of a clip measures beats, downbeats, bpm and sections on the
whole clip and cuts them to the window, as #6653 did for kind, so a window under 6 s still
has its beats.
One step after every provider ladder moves a provider's beats and downbeats onto the
signal's own beats within a quarter beat (the snapping api did alone) and marks each with
`silent`, true where the beat falls in silence. Beats are marked, never dropped; the JSON
change is the additive `silent` array.
— Jerrai
* fix(cli): beats with no pulse are unheard; a window with none answers no steady beat
Review fixes for #6692:
- The per-beat flag is now `unheard` (renamed from `silent`, not released): true in silence
or where no onset falls within 50 ms of the beat, so hiss, room tone or a held pad no
longer pass as beats.
- A window keeps the whole clip's beats only when at least two of its beats are heard;
otherwise it answers "no steady beat" as before, with no downbeats and no bpm.
- A window's bpm is the tempo of its heard beats, never the whole clip's.
- Comment lines trimmed so analyze.ts, run.ts and api.ts are at or under main's share.
— Jerrai
* fix(cli): a beat is unheard only where two beats in a row have no onset
Scored on the ground-truth set, flagging every onset-less beat also flagged heard beats the
onset detector misses: the drop downbeat after a snare roll, single beats under narration,
quiet beats late in a fade. A pulse gap now needs two onset-less beats in a row; hiss, room
tone and held pads span many beats and stay flagged, and silence is flagged as before.
— Jerrai
* fix(cli): a window with no pulse on the clip's grid reads its own beats, as before
Genesis found a window over a section far under the clip's loudest part answered "no steady
beat" where main, reading the window alone, found every beat: the clip's grid and onsets
skip a section that quiet (from about 40 dB under, for clicks). When a window keeps fewer than
two heard beats from the clip's grid, its beats, downbeats and bpm now come from the window
alone, as they did on main. Hiss still answers "no steady beat" on its own.
The hiss test's noise() repeats every 10,466 samples (0.22 s at 48 kHz), which the window's
own reading took for a pulse; that test now uses a xorshift generator.
— Jerrai
* fix(audio): unheard comes from level-free rhythm evidence, so quiet music stays heard
A beat was flagged by an absolute -50 dBFS silence and by onsets under 10% of the clip's
loudest hit, so music 30 dB or more under the loudest part read as unheard. Now:
- core localBeatEvidence reads the cached standardised onset curve over the 6 s around each
beat: the tracker's steady-beat confidence there (clarity at the local beat lag times the
beat's own lift) and the beat's lift over the span's mean.
- A beat is unheard in digital silence (-80 dBFS), or when its span keeps no steady beat
(confidence under 0.15) and its own onset does not stand out (lift under 1.5). Lift alone
keeps beats under narration heard, where syllables lower the span's clarity.
- A window keeps the clip's beats when at least two, and at least half, of them are heard.
— Jerrai
* fix(producer): a BeginFrame capture that draws no first frame in 20 s switches to screenshot
* test(cli): cover the Chrome launch failure messages a failed render prints