Commit Graph
100 Commits
Author SHA1 Message Date
vanceingalls 020c8986f4 fix(registry): force GSAP render at attach — seek(0) is a no-op on a fresh timeline 2026-07-23 21:01:24 +00:00
vanceingallsandClaude Sonnet 5 08620b75df feat(registry): make caption-pill-karaoke data-driven via caption-data runtime
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 20:46:26 +00:00
vanceingalls c67e9dc1f8 feat(registry): make caption-weight-shift data-driven via caption-data runtime 2026-07-23 20:18:39 +00:00
Vance Ingalls c39f3cf924 Merge pull request #2737 from heygen-com/07-22-fix_hooks_pre-commit_gate_denies_commit_instead_of_ending_the_turn
fix(hooks): pre-commit gate denies instead of ending the turn
2026-07-22 15:45:01 -07:00
Vance IngallsandClaude Opus 4.8 97ec7db5cc fix(hooks): pre-commit gate denies commit instead of ending the turn
The pre-commit build/lint/typecheck PreToolUse hook emitted {continue:false,
stopReason}, which halts the agent's turn — the dev had to type 'continue'
after every failed gate. Switch to a PreToolUse deny decision so the failing
commit is still blocked but the failure text is fed back to the agent as tool
feedback, letting it fix and retry without a manual nudge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 15:35:44 -07:00
Vance Ingalls 84e4eafacd Merge pull request #2723 from heygen-com/07-21-fix_engine_worker_autoscaler_memory_budget
fix(engine): realistic worker memory budget + sizing/feedback telemetry
2026-07-21 21:44:35 -07:00
Vance Ingalls c6462a0a22 fix(producer): emit heap advisory at orchestrator, lock message + telemetry props with tests 2026-07-21 20:39:06 -07:00
Vance Ingalls 12e599a6ba fix(engine): realistic worker memory budget + sizing/feedback telemetry 2026-07-21 14:31:14 -07:00
Vance Ingalls ed32898439 Merge pull request #2681 from heygen-com/07-21-feat_engine_warn_on_live_map_viewports_at_capture_init
feat(engine): warn when a live map viewport is detected at capture init
2026-07-21 14:07:43 -07:00
Vance Ingalls 89d4f63396 Merge pull request #2677 from heygen-com/via/anonymize-cli-feedback-repro
fix(skills): anonymize CLI feedback repro guidance
2026-07-21 13:58:29 -07:00
Vance Ingalls 8f171433f9 Merge pull request #2679 from heygen-com/07-21-fix_engine_carry_framestride_onto_workerresult_fixes_interleaved_worker_false-positive_
fix(engine): carry frameStride onto WorkerResult
2026-07-21 02:10:55 -07:00
Vance Ingalls 30ca51c615 feat(engine): warn when a live map viewport is detected at capture init 2026-07-21 01:08:02 -07:00
Vance Ingalls 4f53dd4f2c fix(engine): carry frameStride onto WorkerResult (fixes interleaved worker false-positive) 2026-07-21 00:26:24 -07:00
ViaandClaude Opus 4.7 78ab9bc889 fix(skills): anonymize CLI feedback repro guidance
The reproduction packet template in hyperframes-cli previously said
"Include the rerunnable command and working directory" and shipped a
`REPRO COMMAND: cd <project path> && ...` skeleton. Agents faithfully
followed both, so user home-directory prefixes (`/home/<user>/projects/...`,
`/Users/<user>/Documents/...`) have been landing verbatim in the public
CLI feedback channel — leaking user + machine identity that maintainers
don't need to reproduce a bug.

Fix, docs-only:

- SKILL.md: change "rerunnable command and working directory" to
  "rerunnable command (relative to the project directory)" plus an
  explicit note that feedback is public and absolute paths must not be
  pasted. Add a matching redaction rule for EXACT ERROR stack traces
  (keep basename+line, drop leading directory).
- references/preview-render.md: replace the `cd <project path>` skeleton
  with a bare `<HF_*/PRODUCER_* env> npx hyperframes <exact command>`
  template + inline comment reminding to run from the project directory
  without pasting absolute paths. Rewrite the "Preserve paths / redact
  secrets" line to lead with the anonymization rule and give concrete
  before/after examples (`./renders/out.mp4` vs
  `/Users/<user>/Documents/…/out.mp4`).

The COMPOSITION_STRUCTURE block was already privacy-preserving (counts
+ presence flags only, "no file paths, no src URLs, no user text") — no
change needed there.

skills-manifest.json regenerated for the hyperframes-cli hash.

Signed-off-by: Via <noreply@anthropic.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-21 05:04:18 +00:00
Vance Ingalls 32c41ea7dc Merge pull request #2661 from heygen-com/07-20-staticguard_compiler_recognition_5_facet
fix(lint): recognize compiler-derived data-end as legitimate
2026-07-20 13:23:15 -07:00
ViaandVia ac9f463108 fix(lint): recognize compiler-derived data-end as legitimate
`bundleToSingleHtml` compiles `data-duration` into `data-end` (in
`compileTimingAttrs`), then calls `validateHyperframeHtmlContract` against
the compiled HTML. The linter's `deprecated_data_end` rule fired on the
compiler's own consistent output — `<audio data-start="0" data-duration="18"
data-end="18">` — because `diagnoseDerivedEnd` unconditionally emitted a
`deprecated-end` diagnostic whenever both attributes were present, ignoring
whether the derived value matched.

Reporters routed this as "raw-source lint passes with 0 errors and 0
warnings, but `check --strict` still logs StaticGuard noise about
data-end without data-duration." Field cluster: cli-feedback crons 61-68,
n=25+ across darwin/arm64, darwin/x64, linux/x64, win32/x64, and versions
0.7.56 through 0.7.64. L3 reporter cite (ts=1784519869): "bundleToSingleHtml
compiles data-duration into data-end, then validates the compiled HTML and
reports its own generated data-end as deprecated."

Fix: `diagnoseDerivedEnd` now stays silent when the paired `data-end`
matches `data-start + data-duration` (within a 1ns epsilon to absorb
IEEE-754 residuals like `0.1 + 0.2 = 0.30000000000000004`). Truly-legacy
authoring shapes — `data-end` alone with no `data-duration`, or a
`data-end` that disagrees with `data-duration` — still fire
`deprecated_data_end`, with a refined message that names the drift on the
conflicting variant.

Facets covered: (a) validator treats compiler-derived `data-end` as legal
when paired with `data-duration`, and (e) recognizes the compile-time
rewrite site (`bundleToSingleHtml` → `compileHtml` → `compileTimingAttrs`).
Facets (b) stderr gating, (c) terminal JSON verdict, and (d) audio-not-
dropped are unblocked transitively — StaticGuard's `console.warn` is
already gated on `!isValid` and never drops audio; the check command
already emits JSON on every terminal path; so once the false-positive
diagnostic stops firing on compiler output, the noisy stderr line and the
misleading "check appears to fail" reporter framing both go away without
further wiring.

Co-Authored-By: Via <noreply@heygen.com>
2026-07-20 13:26:51 +00:00
Vance Ingalls c268f5ba85 chore: release v0.7.61 2026-07-17 01:07:09 -07:00
Vance Ingalls f084a7217d Merge pull request #2502 from heygen-com/task4-start-resolver-export
feat(core): export runtime start resolver with documentRef parameter
2026-07-17 00:47:42 -07:00
Vance Ingalls ac51891eb0 Merge pull request #2600 from heygen-com/fix/studio-slideshow-tab-gate
fix(studio): hide Slideshow tab for non-slideshow comps + default the flat inspector on
2026-07-17 00:46:50 -07:00
Vance Ingalls 1ddf9cc331 Merge pull request #2563 from heygen-com/via/thumbnail-id-escape
fix(studio,runtime): CSS.escape ids so digit-leading selectors don't crash
2026-07-17 00:43:31 -07:00
Vance Ingalls a7c0fa4fe5 Merge pull request #2564 from heygen-com/via/win-workdir-env
feat(cli): surface extract-cache dir in doctor + add --frames-cache-dir sugar
2026-07-17 00:43:06 -07:00
Vance Ingalls cf64f24e63 Merge pull request #2569 from heygen-com/via/macos12-dyld-crash-hint
fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on macOS <13 chrome-headless-shell dyld crash
2026-07-17 00:40:14 -07:00
Vance Ingalls 53b3621437 fix(studio): short-circuit slideshow-island detection with a substring check (review N2)
slideshowIslandRegex scanned the full file content on every editingFile
change even for the common non-slideshow case. Gate it behind a plain
substring check on SLIDESHOW_ISLAND_TYPE first — cheap, and avoids the
full-content RegExp pass for files that plainly have no island.

Added a test for the still-open behavior this preserves: a malformed
island (invalid JSON) still trips the substring check and the regex,
so the tab stays discoverable rather than silently disappearing.
2026-07-17 00:37:39 -07:00
Vance Ingalls a4167ede07 feat(studio): default the flat inspector on
Flip STUDIO_FLAT_INSPECTOR_ENABLED's default from false to true — the
bug-fix pass on the flat inspector is complete (right-aligned values,
Stroke width/style split, promote-badge overlap, Layout/Style section
gating for non-visual elements like audio). VITE_STUDIO_FLAT_INSPECTOR_ENABLED=false
still opts back into the legacy panel.

Updates the two tests that asserted the old false default: the flag's
own default test, and the "classic PropertyPanel input coverage" suite,
which relied on that default to reach the legacy panel and now mocks it
explicitly (mirroring the adjacent "flat" suite's existing pattern).
2026-07-17 00:37:39 -07:00
Vance Ingalls 67696cd8de fix(studio): hide the Slideshow tab and panel for non-slideshow compositions
The Slideshow tab rendered unconditionally, showing the branching editor
for any composition regardless of whether it was actually a slideshow —
a plain video comp offered a tab with nothing meaningful to edit.

Gate it on the composition carrying the slideshow JSON island
(<script type="application/hyperframes-slideshow+json">), the same
definitive marker the CLI's `present` command already requires (it
refuses to run without one). Presence-only, not full manifest
validation, so a malformed island still surfaces the tab rather than
disappearing entirely. Also bounce rightPanelTab off "slideshow" to
"renders" if the active composition stops being a slideshow while that
tab is open (e.g. switching files), since its button would otherwise
vanish with no way back to it.

Extracted the gating + scene-list derivation into useSlideshowTabState
to keep StudioRightPanel.tsx under the 600-LOC gate.
2026-07-17 00:37:39 -07:00
Vance Ingalls 8eccc6e9a3 Merge pull request #2599 from heygen-com/via/composition-structure-mandate
feat(skills): add COMPOSITION_STRUCTURE to feedback repro packet + soft-warn lint
2026-07-17 00:36:36 -07:00
Via 490642b78a fix(feedback-lint): tighten census value scoping and defect-keyword matching
Addresses C1-C7 from Rames's adversarial review + N2/N3 nits:

- C1 (positionFixed): inline probe now value-scopes to 'fixed' — previously
  fired on any position value (absolute, relative, sticky), producing a
  false-positive anatomy that would mislead maintainers pattern-matching
  the 'sub-comp + position:fixed capture' bug family.

- C2 (overflowHidden): symmetric fix — inline path now catches
  style='overflow: hidden' via the value-scoped probe, matching the
  <style>-tag branch. Also handles overflow-x/overflow-y variants.

- C3 (VISUAL_DEFECT_KEYWORDS): drop 'render' — CLI's primary command is
  'hyperframes render', so build/perf/hang reports were triggering an
  inappropriate COMPOSITION_STRUCTURE: nudge on the most common failure
  mode. Rely on the more specific tokens (black, blank, flicker, corrupt,
  wrong frame) to identify actual visual defects.

- C4 (mentionsVisualDefect): compile keywords into a word-bounded regex.
  'blackboard', 'blanket', 'visualize', 'corruptible' no longer false-
  positive. Accepted tradeoff: plural forms ('flickers') don't match.

- C5 (marker case-normalization): REPRO COMMAND: / COMPOSITION_STRUCTURE:
  checks now case-insensitive, matching mentionsVisualDefect's
  normalization. Reporters using 'Repro command:' or lowercase
  'composition_structure:' get credit for compliance.

- C6 (background/mask shorthand): inline branch previously required the
  longhand 'background-image:' / 'mask-image:' — style='background:
  url(bg.png)' silently returned false. Now checks both longhand AND
  shorthand-with-url() inline forms.

- C7 (usesGsap docstring): trim promise of data-gsap-* attribute scanning
  that detectGsap never implemented — attribute scan lives outside the
  <script>-only detection path.

- N2 (EMPTY_VALUES): include 'inherit', 'revert', 'revert-layer' — a
  style='position: inherit' is authored intent to defer, not authored
  intent to place.

- N3 (input size cap): early-exit to a zero census on HTML > 20 MB
  rather than feeding linkedom a hostile input. Not expected in normal
  usage; guard for future callers that might pass raw user uploads.

Extends the test locks: 6 new census tests (value-scoping, size cap) and
5 new lint tests (word-boundary rejections, render-noise rejections,
lowercase-marker acceptance). All existing tests unchanged in intent —
only the 'flickers' plural in one test updated to 'flicker' to reflect
the new word-boundary rule.

No behavior change to the wire path: lint is still soft-warn, census is
still never called from feedback.ts, no new dependencies.
2026-07-17 06:22:00 +00:00
Via 8f90fd9ec1 refactor(feedback): extract lint-warning loop to satisfy Fallow CRAP threshold
The added `for...of` loop over `lintFeedbackComment` warnings pushed the
`run` function's cyclomatic complexity from 4 to 5, landing the CRAP
score at exactly the 30.0 Fallow threshold. Extract the loop into
`printFeedbackLintWarnings` so `run` stays a flat driver — the helper
carries the incidental complexity.

No behavior change; all 29 unit tests + typecheck + oxlint + oxfmt +
local `fallow audit --base origin/main` pass clean.
2026-07-17 03:12:04 +00:00
Via 0aaac7aa30 feat(skills): add composition-structure block + soft-warn feedback lint
Extend the CLI feedback reproduction packet (#2498) with a fifth
mandated field, `COMPOSITION_STRUCTURE:`, and enforce presence of
`REPRO COMMAND:` / `COMPOSITION_STRUCTURE:` at feedback-submit time.

- Skill + reference now specify `COMPOSITION_STRUCTURE:` — a
  privacy-preserving structural anatomy (element census + attribute
  presence + timeline shape + delta + defect location) — required for
  any rating <=7 that describes a visual defect.
- `buildCompositionCensus()` + `renderCompositionCensusBlock()`
  auto-fill the block from composition HTML so agents don't ask the
  human user to hand-count `<video>` / `<img>` / sub-comp mounts.
  Counts + presence flags only — no file paths, no src URLs, no user
  text.
- `hyperframes feedback` soft-warns (never blocks) when a non-10
  `--comment` is missing `REPRO COMMAND:`, and when a rating-<=7
  visual-defect comment is missing `COMPOSITION_STRUCTURE:`. The
  warning points at the auto-census helper so agents remediate
  themselves.
- `coreSkillContent.test.ts` locks the new literal in both the skill
  and the reference file, following #2498's pattern.

Extends #2498. Follow-up: no change to `doctorSummary` generation, no
change to the feedback-submission API endpoint, no refactor of
#2498's doc-content Jest test.

Signed-off-by: Via
2026-07-17 02:58:11 +00:00
Via d05c899c88 fix(fallow): suppress line-shifted pre-existing findings + simplify CSS.escape polyfill
Fallow audit failed on the parent PR (#2563) with 8 findings, all of them
tracing back to line-shift fingerprint invalidation on pre-existing complexity/
duplication, plus one new-but-easily-simplified CRAP finding on the CSS.escape
polyfill in picker.test.ts.

Actions:

- picker.ts: 5 pre-existing inherited-complexity findings (isEffectivelyHidden,
  isPickableElement, buildElementLabel, getPickCandidatesFromPoint,
  pickManyAtPoint). All in the file at the parent SHA. The one-line
  buildElementSelector edit (+ 3-line comment) shifted every function below
  it, re-triggering the fingerprint. Added to health.ignore with rationale.

- screenshotClip.ts + vite.browser.ts: 19-line clip-computation clone that
  pre-dates this PR — the try/catch guard around querySelectorAll shifted
  screenshotClip.ts's clone-start line, re-flagging the inherited duplication.
  Added both files to duplicates.ignore with rationale (splitting the clone
  would require crossing puppeteer's page.evaluate serialization boundary).

- picker.test.ts CSS.escape polyfill: simplified from a 15-line char-by-char
  loop (CRAP 56.3, cyclo 14) to a compact regex + leading-digit special case
  (~4 cyclo). Still handles the digit-leading case this PR's regression test
  needs (`#0` -> `#\30 `); the round-trip through querySelector still asserts
  the element is picked back. All 16 picker tests + 3 screenshotClip tests
  still pass locally.

Change by Via
2026-07-17 02:53:07 +00:00
Via 9fc7390c2b fix(cli): import EXTRACT_CACHE_DIR_DISABLED_ALIASES in render.ts
Addresses Miga's SSOT review on #2564. The render command was
inlining the disable-alias list (["off","none","false","0"]) instead
of importing the exported constant, defeating the drift-safety the
constant exists to provide. Also switches the flag description string
to interpolate the alias set from the constant for consistency.

_— Via_
2026-07-17 02:52:04 +00:00
Via 0d16f19b07 fix(cli): surface HYPERFRAMES_BROWSER_PATH hint on macOS <13 chrome-headless-shell dyld crash
Field feedback (#hyperframes-cli-feedback ts=1784227832, darwin/x64,
macOS 12, HyperFrames CLI 0.7.60) hit
`dyld: Symbol not found: _kVTCompressionPropertyKey_ReferenceBufferCount`
from VideoToolbox when launching the pinned chrome-headless-shell
mac-152.0.7928.2. The symbol is macOS-13-only, so older hosts abort
the binary at dyld load before any browser process starts.

The reporter recovered by installing an older shell
(`@puppeteer/browsers install chrome-headless-shell@150`) and pointing
`PRODUCER_HEADLESS_SHELL_PATH` at it. Their check/snapshot commands
accepted that older cached shell (they do not force the pinned build),
but the render command requires v152 via `preferManagedChrome: true`
and could not fall back on its own. The generic "Try --docker" hint
didn't name any of the browser-path env vars.

Sibling failure mode to the download-time hint added in #2443 and the
closed-with-invite #2078 (SIGTRAP at launch on macOS arm64), and the
in-flight #2481 (Windows STATUS_STACK_BUFFER_OVERRUN); same
`HYPERFRAMES_BROWSER_PATH` remediation, different trigger + platform.

The match is gated on:
1. Puppeteer launch-failure wrapper text
2. dyld Symbol-not-found signal
3. a macOS-13-only symbol OR the VideoToolbox framework

so unrelated darwin launch failures do not mis-fire the hint. The
symbol name is macOS-version-specific by construction — if a user's
dyld cannot find `_kVTCompressionPropertyKey_ReferenceBufferCount`
their host is <13, no separate `os.release()` gate needed.

- Signed-off-by: Via -
2026-07-16 19:25:45 +00:00
ViaandVia ca35227506 feat(cli): surface extract-cache dir in doctor + add --frames-cache-dir sugar
Windows users with the OS temp dir on a small system drive have hit
C: exhaustion mid-render (Slack ts=1784219488 · CLI v0.7.58 · win32
15 GB / 8-core, ~5500 frames). The engine already honors
HYPERFRAMES_EXTRACT_CACHE_DIR for relocation, but the knob was
undocumented and invisible in diagnostics — the reporter had to piece
together a 4-flag compound workaround including EXTRACT_CACHE_DIR=off.

Changes:
- Extract the env-var resolver into a public engine API
  (resolveExtractCacheDir, defaultExtractCacheDir,
  EXTRACT_CACHE_DIR_DISABLED_ALIASES) with a typed resolution shape
  distinguishing "disabled by user" vs "default" vs "env override".
- Add a Frames-cache check to `hyperframes doctor` that reports the
  effective directory, its free space, source (env or default), and
  fails with a relocation hint when <2 GB free at that mount.
- Add `hyperframes render --frames-cache-dir <path>` as discoverable
  CLI sugar for the env var, including the opt-out aliases
  (off/none/false/0) and CWD-safe absolute-path resolution.
- Document the flag in docs/packages/cli.mdx with the field-signal
  citation, and add a render example row for the Windows workflow.
- Cover both surfaces with unit tests (6 doctor cases + 4 engine
  cases including all disabled-alias variants).

Refs Slack #hyperframes-cli-feedback ts=1784219488 (win32 v0.7.58).

Co-authored-by: Via <via-heygen[bot]@users.noreply.github.com>
2026-07-16 18:24:43 +00:00
Via 9bbdcc4ec9 fix(studio,runtime): CSS.escape ids so digit-leading selectors don't crash
The runtime picker built raw `#${id}` selectors while its sibling
attribute-selector branches (data-composition-id, data-composition-src,
data-track-index) already CSS.escape'd their values. When a user
composition has an element with a digit-leading id (e.g. `id="0"`),
the picker emits the selector `#0` which is invalid per the CSS spec —
downstream `document.querySelector` throws SyntaxError.

Same failure mode reached the Studio thumbnail: getElementScreenshotClip
called `document.querySelectorAll(selector)` unguarded, so an invalid
selector bubbling out of page.evaluate failed the whole thumbnail and
returned 500 to the browser (broken thumbnail image).

Fixes:
- packages/core/src/runtime/picker.ts — CSS.escape the id, matching the
  sibling branches on lines 100/102/104.
- packages/studio-server/src/helpers/screenshotClip.ts — catch
  SyntaxError from an invalid selector and return undefined so the
  caller falls back to a full-page screenshot, so the user still sees
  a thumbnail instead of a broken image.

Regression tests for both.

Reported via #hf-cli-feedback (Slack ts=1784218060, darwin/arm64,
CLI 0.7.60): "digit-leading worker IDs broke Studio thumbnail
querySelectorAll".

— Via
2026-07-16 18:07:58 +00:00
Vance IngallsandClaude Fable 5 21ab83320f chore: retrigger ci
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 11:01:58 -07:00
Vance IngallsandClaude Fable 5 94929815fa fix(core): re-export createRuntimeStartTimeResolver from index so dist emits it
src/runtime is excluded from core's tsconfig include set — runtime files
only reach dist when an included module imports them. Without a root
re-export, the ./runtime/start-resolver publishConfig entry pointed at
dist/runtime/startResolver.js which tsc never emitted, failing
verify:packed-manifests in the Build job. Same precedent as
parseStartExpression's index re-export.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 11:01:58 -07:00
Vance Ingalls fd19a1563d feat(core): export runtime start resolver with documentRef parameter 2026-07-16 11:01:27 -07:00
Vance Ingalls dbb684fa6c Merge pull request #2535 from heygen-com/fix/runtime-partial-timeline-pause-guard
fix(runtime): tolerate registry timelines without pause() in interactive transport
2026-07-16 03:03:22 -07:00
Vance Ingalls 74f52fcd96 Merge pull request #2534 from heygen-com/fix/panel-sections-audio-layout-style-gate
fix(studio): gate Layout and Style panel sections on the element having a rendered box
2026-07-16 02:43:57 -07:00
Vance IngallsandClaude Fable 5 7e0bd401c8 chore(core): regenerate position-edits-render artifact with current esbuild
The committed artifact was generated with an older esbuild than the
current lockfile resolves; CI's check:position-edits-render regen now
produces different (equivalent) minifier variable naming and fails the
diff gate. Regenerate to match — no source change to positionEdits.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 02:17:36 -07:00
Vance Ingalls eb967f1542 Merge pull request #2533 from heygen-com/fix/studio-promotable-badge-overlap
fix(studio): reserve a gutter for the promote-to-variable badge
2026-07-16 02:06:28 -07:00
Vance Ingalls 1a2e7524e2 Merge pull request #2532 from heygen-com/fix/studio-flat-stroke-select
fix(studio): make flat inspector's Stroke width free of style-name typing
2026-07-16 02:06:07 -07:00
Vance Ingalls f96b78165e Merge pull request #2528 from heygen-com/fix/studio-flat-row-values-right-aligned
fix(studio): right-align FlatRow's value input in the flat inspector
2026-07-16 02:05:48 -07:00
Vance IngallsandClaude Fable 5 8826d2d71d fix(runtime): tolerate registry timelines without pause() in interactive transport
A window.__timelines entry is authored content and may be a partial
RuntimeTimelineLike (duration/seek only, no pause). Timeline resolution
is deliberately permissive — duration-based — and such compositions
render fine, because the render path only seeks. But every interactive
transport path (play/pause/seek, bind, rebind-tick, boot) called
capturedTimeline.pause() unguarded, crashing studio playback with
'tl.pause is not a function' — the top recurring studio:unhandled_error
in telemetry across versions 0.6.121 through 0.7.59 (~150-175/day).

Guard all pause sites through one helper (typeof check + swallow, plus
a once-per-page timeline_missing_pause analytics event so composition
authors can find the partial timeline), matching the safeVoid pattern
player.ts already uses. In the rebind restore path, pause is guarded
separately so a missing pause() no longer aborts the seek/play restore
behind it in the same try/catch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 01:58:39 -07:00
Vance Ingalls ff26e5f2c8 Merge pull request #2529 from heygen-com/via/resolution-portrait-fix
fix(cli): accept portrait aspects for --resolution alias flag
2026-07-16 01:54:55 -07:00
Vance Ingalls 8a793ba2b2 fix(studio): gate Layout and Style panel sections on the element having a rendered box
Both panels showed Layout (X/Y/W/H/Rotation/Z-index) unconditionally —
no gate existed for it at all — and Style was gated only on
canEditStyles (a permission check), never on the element's tag. Neither
gate accounted for `<audio>`, which never paints a visual frame, so a
music track's inspector showed a full set of position/size/fill/shadow
controls with zero visual effect.

Add `layout`/`style` applicability to resolveEditingSections (core),
keyed on tag !== "audio", and gate both panels' Layout section and the
existing Style gate on it. Media/Motion/Grade/Text were already
correctly gated (verified via a research pass across both panels) and
are untouched.
2026-07-16 01:47:46 -07:00
Vance Ingalls 14dbfca5a7 fix(studio): give flat Text section rows more vertical breathing room
6px between rows left little clearance above a row's value for the
promote-to-variable badge (now positioned above the row). Widen the
row gap to 10px.
2026-07-16 01:17:04 -07:00
Vance Ingalls 5f045b6bdf fix(studio): move the promote-to-variable badge above the row instead of a padding gutter
Shrinking the wrapped control's width to make room for the badge (previous
commit) fixed the overlap but pushed the value left unnecessarily. Move the
badge to sit above the row instead, clearing the value without touching its
layout.
2026-07-16 01:11:30 -07:00
Vance Ingalls 9fa1b26570 fix(studio): reserve a gutter for the promote-to-variable badge
PromotableControl absolutely-positions its "◇ var" / "◆ {id}" badge over
the wrapped control without reserving any space, so on rows where the
value renders flush to the right edge (flat Font/Color rows) the badge
sat directly on top of the value text instead of beside it. Add a
right-padding gutter on the wrapper sized to each badge state, and cap
the bound chip to a fixed max-width so it always fits inside its gutter.
2026-07-16 01:06:11 -07:00
ViaandClaude 2d398ed274 fix(cli): wire aspect-agnostic resolution through cloudrun/lambda/batch + preflight recompute
Addresses R2 CHANGES_REQUESTED from Miga + Rames on PR #2529:

1. Sibling-surface gap (blocker): `hyperframes cloudrun render{,-batch}`,
   `hyperframes lambda render{,-batch}` all advertised the same tier-only
   aliases (`1080p` / `hd` / `4k` / `uhd`) but normalized them to `landscape`
   and never set `outputResolutionAspectAgnostic`. The distributed plumbing
   PR #2529 added received `undefined` from those callers, so portrait `1080p`
   still hit the original aspect-mismatch on Cloud Run / Lambda.

   Fix: introduce `resolveResolutionFlagPair` in `@hyperframes/parsers` (the
   single source of truth for the two-step normalize + aspect-agnostic
   detect) and route every distributed entrypoint through a shared
   `parseOutputResolutionFlag` CLI util so the alias signal now reaches
   `SerializableDistributedRenderConfig`. Studio Server keeps its
   canonical-only HTTP contract; that intent is now pinned in tests.

2. Preflight recompute (hardening): the earlier "downgrade aspect-mismatch"
   preflight cleared un-remapped mismatches, so IG 4:5 (non-preset aspect,
   no sibling) and portrait-4K comp + `--resolution 1080p` (remap +
   downsample) both slipped through to fail late in `resolveDeviceScaleFactor`.
   Now `checkRenderResolutionPreflight` computes the effective preset via
   `suggestMatchingPreset` (mirroring the compile stage's
   `adaptAspectAgnosticResolution`) and re-checks against that — only
   genuinely-fixable mismatches clear early. New tests pin both regressed
   input classes.

3. Docker forwarding boundary test (Miga's important #2): pinned
   `1080p` survives verbatim as `--resolution 1080p` in the Docker args
   so the in-container CLI can re-run `isAspectAgnosticResolutionAlias`.

4. Doc-nit (Miga): parsers/src/types.ts no longer references the
   nonexistent `resolveResolutionForComposition` — points at the actual
   remap helpers.

Fallow: cloudrun.ts / lambda.ts share 390 lines of pre-existing structural
symmetry (parallel AWS + GCP dispatchers), and lambda/render.ts +
render-batch.ts declare parallel RenderArgs interfaces. Both re-flagged
after threading the aspect-agnostic field through each surface; ignored
with justification in .fallowrc.jsonc. lambda.ts's `run` and
lambda/render.ts's `waitForCompletion` are pre-existing CRAP-score
hotspots untouched by this PR — added under health.ignore.

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

— Via
2026-07-16 08:01:56 +00:00
Vance Ingalls d5a259a3d2 fix(studio): make flat inspector's Stroke width free of style-name typing
Stroke width committed border-width and border-style together from one
free-text field, so setting a style meant typing an exact CSS keyword
(e.g. "dashed") with no indication of which ones were valid — the row
also duplicated the discoverable Stroke style select directly below it.
Stroke width now only commits border-width; style changes go exclusively
through the existing select.
2026-07-16 00:48:53 -07:00
Vance Ingalls b32f9a3e86 fix(studio): right-align FlatRow's value input in the flat inspector
FlatRow lays out label…gap…value across a `justify-between` row, but the
shared CommitField input it wraps had no text-align, so its text hugged
the LEFT edge of the value's own (often much wider) right-hand box —
looking left-aligned relative to the row, out of step with FlatSelectRow
and FlatSlider, which already right-align.

Added an optional `align` prop to CommitField (default "left", preserving
the legacy panel's MetricField/DetailField layouts where label-then-value
sits inline and left reads naturally) and pass `align="right"` from
FlatRow. Left the Motion Timing row's Start/End/Duration cells alone —
those stack label-above-value in a grid, a different pattern from the
inline label…value row this fix targets.

New tests: FlatRow's input has `text-right` (not `text-left`); the legacy
MetricField's input keeps `text-left` (not `text-right`), pinning
CommitField's default so the shared component doesn't drift for the
panel that didn't ask for this.

Full studio suite (2645 tests) green; typecheck/oxlint/oxfmt clean.
2026-07-16 00:48:53 -07:00
Vance Ingalls b0bb468877 Merge pull request #2519 from heygen-com/fix/studio-variable-promote-shared-session-sync
fix(studio): resync the shared SDK session after a Design-panel variable promote
2026-07-16 00:36:10 -07:00
Vance Ingalls d0d281de50 Merge pull request #2525 from heygen-com/via/audiopad-ffmpeg8-compat
fix(producer): audioPadTrim FFmpeg-8.x-compatible apad invocation
2026-07-16 00:35:53 -07:00
Vance Ingalls 3e4c761842 Merge pull request #2527 from heygen-com/via/artifact-validated-exit-gate
fix(cli): don't override exit code after artifact validated
2026-07-16 00:35:30 -07:00
Vance Ingalls 9d36bc9d95 Merge pull request #2526 from heygen-com/via/transcribe-timeout
feat(cli): configurable transcribe timeout with duration-scaled default
2026-07-16 00:35:08 -07:00
Vance Ingalls 578d6202b4 fix(studio): resync the shared SDK session after a Design-panel variable promote
Reported as "template variables are broken": binding an element's field to a
variable via the flat inspector's "◇ var" promote chip (or editing an
already-bound field's value) wrote the correct bytes to disk, but the
Variables tab kept showing the pre-edit value until the whole Studio page
was hard-reloaded.

Root cause: DesignPanelPromoteProvider deliberately opens its OWN SDK
session (`useSdkSession(projectId, selection.sourceFile ?? activeCompPath)`)
so that promoting inside a sub-composition binds the variable in the
sub-comp's own file, not the host's. For the common case — a top-level
element, same file as `activeCompPath` — this session is a SEPARATE
in-memory `Composition` instance from the shared one `VariablesPanel`
(Variables tab, Slideshow, etc.) reads. A persist through the promote
provider's session never fires the shared session's own "change" event.

Worse, the shared session's file-change listener runs
`isSelfWriteEcho(path, content)` to decide whether to reload — but
`sdkSelfWriteRegistry` is keyed by file path only, not by session instance
(its own doc comment assumes "the studio process has a single SDK session
lifecycle at a time"). It sees the promote provider's write registered
under the same path and concludes it's its own echo, permanently
suppressing the reload it actually needs.

Threaded `forceReloadSdkSession` (the same mechanism every other
server-side-write path in Studio already uses for exactly this "resync
after a write I didn't make myself" case) from App.tsx through
StudioRightPanel into DesignPanelPromoteProvider, and call it after every
successful promote/setDefault persist — unconditionally, not gated on the
promote target matching activeCompPath, since re-opening a file that
didn't change is a harmless no-op re-parse and a path-equality guard here
already produced one subtly wrong comparison (activeCompPath can be null
while the shared session still defaults to "index.html") before landing on
this simpler version. Verified live: editing a variable-bound field's
value now updates the Variables tab immediately, no reload required.

App.tsx crossed the 600-line file-size gate after threading the new prop;
extracted the tiny handleAddAssetAtPlayhead wrapper into its own
useAddAssetAtPlayhead hook (with a regression test) to bring it back under.

Full studio suite (2639 tests) green against a fresh main; typecheck/
oxlint/oxfmt clean.
2026-07-16 00:18:36 -07:00
ViaandClaude f3b941f1e3 fix(cli): reduce CRAP score by extracting exit-path helpers; bump contactSheet Windows timeout
Fallow flagged the `uncaughtException` arrow at CRAP 30.0 (right at
threshold). Extracting the three exit paths — emitCliErrorEvent,
reportPostRenderTerminationEvent, exitAfterPostRenderTermination,
exitAfterCliFailure — pulls the two `_flushSync?.()` optional-chain
branches out of the arrow body and drops cyclomatic to 3. Same shape
already used on compileStage in a sibling PR. Behavior preserved:
EPIPE → exit(0), renderSucceeded → report + flush + exit(0),
default → commandFailed + track + flush + exit(1) for uncaught;
renderSucceeded → report + return, default → commandFailed + track
for rejection.

Windows contactSheet flake: `createContactSheet > writes PNG output`
timed out at 20025ms (default ceiling 20000ms) on Windows. Test does
milliseconds of real work — the wall-clock overhead is sharp's native
binary fork under runner I/O jitter. PR #2492 already tried lightening
the test; the flake persists at the default ceiling. Bumping the
per-test timeout to 60s absorbs runner jitter without hiding real
slowdowns (a genuine sharp regression would blow through 60s just as
readily as 20s). Targeted per-test bump, not a global suite bump.

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

— Via
2026-07-16 07:08:45 +00:00
ViaandClaude 40f4cfe92f fix(producer): materialize audioPadTrim concat script to real file
The prior fix (dc410ca) dropped `pathToFileURL` from the pad-concat step
to make FFmpeg 8.x on Windows stop rejecting `file:///C:/…` URLs — but
kept feeding the concat script via `pipe:0` stdin. That combination
broke Linux CI: FFmpeg's concat demuxer resolves bare paths in the
script against the base URL of the script's own source, and when the
script is fed via `pipe:0` the base URL is `pipe:`. Absolute POSIX
paths (`/tmp/foo.aac`) then join to `pipe:/tmp/foo.aac`, which the
demuxer tries to open as a pipe and fails with:

    [concat @ 0x…] Impossible to open 'pipe:/tmp/…/audio.aac'
    pipe:0: End of file

Manually reproduced with `ffmpeg-static@7.0.2` on this repo's binary.

Fix: write the concat script to a real temp file (`<outputPath>.concat-
list.txt`) and pass `-i concatListPath` — matching the sibling concat
in `distributed/assemble.ts:180-186` exactly. A real file's directory
becomes the base URL, so absolute paths in the script resolve as-is on
both Linux and Windows. The `file://` scheme prefix stays out of the
script (Windows FFmpeg 8.x fix preserved) and no `pipe:` prefix gets
prepended (Linux regression fixed). Cleanup path list now covers both
the silence tail and the concat list script.

Also drops the now-unused `runFfmpegWithStdin` helper — no consumer
needs stdin plumbing anymore.

Regression pins in `audioPadTrim.test.ts`:
  - `does not emit file:// URLs …` — Windows arg-shape pin (unchanged
    intent, moved from `stdin` to `concatListContent` field).
  - `materializes the pad-concat script to a real file …` — new pin
    that asserts `-i` is not `pipe:0` and points at the concat list
    path, so the Linux failure mode can't regress.

CI failures fixed:
  - CI / Producer: integration tests (assemble.test.ts pad case)
  - regression / regression-shards shard-1 (style-3-prod field-signal
    end-to-end render exercising the assemble pad path)

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

— Via
2026-07-16 06:54:07 +00:00
ViaandClaude 7e58d050f8 fix(fallow): resolve audit findings for portrait --resolution PR
Three findings, all resolved:

- packages/producer/src/server.ts `render` (CRAP 31.6, cyclo 10 — minor):
  pre-existing complexity; the PR only threads
  `outputResolutionAspectAgnostic` through parseRenderOverrides /
  RenderInput and does not touch `render`. Line-shift fingerprint —
  exempted via health.ignore with justification comment.

- packages/producer/src/services/distributed/plan.ts `plan` (CRAP 36.7,
  cyclo 33 — major): pre-existing complexity; the PR only adds one
  optional field spread inside `plan` and does not add branches.
  Line-shift fingerprint — exempted via health.ignore with justification.

- packages/producer/src/services/render/stages/compileStage.ts
  `runCompileStage` (cyclo 23, cognitive 19 — minor): this one is a
  real complexity bump from the two-branch aspect-agnostic re-target
  block added in the fix. Extracted the block into a local helper
  `adaptAspectAgnosticResolution` so `runCompileStage` stays under both
  the cyclomatic (20) and cognitive (15) thresholds.

Verified locally with `fallow audit --base origin/main --fail-on-issues`
(exit 0, "No GitHub PR/MR findings") and `tsc --noEmit` on the producer
package.

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

— Via
2026-07-16 06:52:11 +00:00
ViaandClaude 46e9ecf3f2 fix(cli): accept portrait aspects for --resolution alias flag
The aspect-agnostic resolution aliases (`--resolution 1080p` / `hd` / `4k` / `uhd`) previously all normalized to a landscape preset, which rejected portrait 1080x1920 compositions with 'Output resolution incompatible'. Users had to specify the orientation-bearing alias (`1080p-portrait`) or render at native.

This threads two new fields (`outputResolutionAspectAgnostic` + `outputResolutionRaw`) through the render pipeline. At the CLI layer we detect whether the user's flag was an aspect-agnostic alias; at the compile stage we re-map the preset to the composition's orientation via the existing `suggestMatchingPreset` sibling-lookup (formerly private). Explicit orientation-bearing aliases and canonical presets stay strict.

Field signal: ts=1784176662 (darwin/arm64, CLI 0.7.59, `--resolution 1080p` on a 1080x1920 portrait comp).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

— Via
2026-07-16 06:23:14 +00:00
ViaandClaude 25a04d7fc8 fix(cli): don't override exit code after artifact validated
The render command's post-artifact-validated cleanup (telemetry flush,
feedback prompt, worker/browser teardown, stray promise rejections) can
throw AFTER the producer has committed a valid MP4 to disk. Field signal
ts=1784169760, ts=1784171150, ts=1784172467 (all win32/x64, CLI 0.7.58,
ffmpeg=no, 1080x1920): ffprobe + visual QA confirmed the outputs are
valid, but the CLI exited 1 after the terminal "artifact validated" log
with no final error message.

Introduce a `renderSucceeded` sentinel that flips after `executeRenderJob`
(or the Docker child render) resolves cleanly. From that point on:

  - Post-render steps in the render command (trackRenderMetrics,
    printRenderComplete, warnIfWebmAlphaDropped, maybePromptRenderFeedback)
    run through `runPostRenderStep`/`runPostRenderStepAsync` guards that
    swallow throws, log a compact warning to stderr, and sanitize a stray
    `process.exitCode` back to 0.

  - The CLI's top-level `uncaughtException` handler logs the throw for
    diagnosis but exits 0 instead of 1 when the render already succeeded.

  - The CLI's `unhandledRejection` handler stops flipping `commandFailed`
    (which drove the success:false telemetry field) when the render
    already succeeded.

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

— Via
2026-07-16 05:56:35 +00:00
ViaandClaude Opus 4.7 f8210d96da feat(cli): configurable transcribe timeout with duration-scaled default
Adds a `--timeout <ms>` CLI flag (and `HYPERFRAMES_TRANSCRIBE_TIMEOUT_MS`
env var) plus a model-slowdown factor in the auto-scaled default so
`hyperframes transcribe` doesn't hard-fail with `spawnSync ETIMEDOUT`
on slow CPUs running heavier whisper models.

Field-signal ts=1784165471 (win32/arm64 emulating x64 on Snapdragon,
CLI 0.7.59) reported the failure on a 63s wav with `-m medium` at ~13x
realtime — the historical 10x-realtime scale (PR #2463) gave 10.5 min
while the machine needed ~13.7 min. Splitting audio and merging offsets
was the manual workaround.

- Add `--timeout <ms>` and `HYPERFRAMES_TRANSCRIBE_TIMEOUT_MS` (min 5000).
  Explicit override bypasses auto-scaling; still capped at 12h.
- Add per-model slowdown factor (tiny 0.5, base 0.7, small 1, medium 2,
  large 4, large-v3-turbo 2). Multiplied into the 10s/audio-second
  baseline so medium/large get proportional headroom while `small.en`
  (the default) preserves the historical safety window.
- Wrap whisper's spawn error with a discoverability hint naming
  `--timeout`, the env var, and the effective timeout when the child
  was killed by SIGTERM/ETIMEDOUT (mirrors PR #2504 protocol-timeout).
- Docs: new `--timeout` row in `docs/packages/cli.mdx` Flags table.

Regression coverage in `packages/cli/src/whisper/transcribe.test.ts`
(56 tests) and `packages/cli/src/commands/transcribe.test.ts` (5 tests):
- Model factor per known name + case-insensitive + safe unknown fallback.
- 63s field-signal case on medium.en → 1_260_000ms (was 630_000ms).
- Explicit override honored below the auto floor + capped at 12h.
- Model factor ignored when overrideMs is set.
- SIGTERM/ETIMEDOUT detection + augmented message contract.
- CLI rejects below-minimum `--timeout` with error naming both the flag
  and the 5000ms floor.

— Via

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-16 05:49:46 +00:00
ViaandClaude dc410ca990 fix(producer): audioPadTrim FFmpeg-8.x-compatible apad invocation
The `audioPadTrim` module's pad-concat step generates a concat script
whose file directives use `file://` URLs (built via Node's
`pathToFileURL`). FFmpeg 8.x on Windows rejects these with
"Impossible to open file:///C:/…" — its `file:` protocol handler strips
the scheme leaving `///C:/…`, which Windows path parsing then rejects.

Field-signal (4 reports over ~24h, all win32/x64, CLI 0.7.59):
  - ts=1784169914 (Baoyu, 60s render, native audio assembly failed)
  - ts=1784177061 (andre 22cores, 345.87s composition, 9 WAV audio elements)
  - ts=1784177375 (KEY DIAGNOSTIC: 13 mono 44.1kHz mp3 tracks, ffmpeg
    8.1.1-full_build gyan.dev, "same project rendered fine in July with
    an older ffmpeg"; manual `ffmpeg -i track.mp3 -af apad=whole_dur=16
    -t 16 -c:a aac out.aac` works with the same binary, so the tool's
    audioPadTrim invocation is the incompatible part)
  - ts=1784177375 (duplicate reporter follow-up)

The concat approach itself is fine — the sibling concat scripts in
`assemble.ts` and `chunkEncoder.ts` pass raw paths (no `pathToFileURL`)
and work on Windows. `audioPadTrim.ts` was the outlier introduced in
PR #1615 (2026-06-20). Aligns with the codebase convention.

Regression pin: unit test asserts the pad-concat stdin never contains
the `file://` scheme, including for a Windows-shaped input path.

End-to-end verification requires a Windows + FFmpeg 8.x reviewer; the
unit test snapshots the arg shape.

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

— Via
2026-07-16 05:46:56 +00:00
Vance Ingalls a10e462d02 Merge pull request #2518 from heygen-com/fix/studio-flat-input-affordance
fix(studio): give flat-inspector value fields a resting-state input affordance
2026-07-15 21:24:13 -07:00
Vance Ingalls 399438b963 Merge pull request #2497 from heygen-com/fix/studio-flat-layers-panel-standalone
fix(studio): show Layers full-height in the flat inspector, not split with Design
2026-07-15 21:19:29 -07:00
Vance Ingalls 2b51c52634 fix(studio): give flat-inspector value fields a resting-state input affordance
Every editable value in the flat inspector (FlatRow's CommitField, the
Motion Timing row's Start/End/Duration cells, and every raw <select> —
Style/Text dropdowns, Grade's Custom LUT and Copy-grade-to scope) rendered
its underline/border only on hover (`border-transparent
group-hover:border-...` or no border at all). At rest a value looked like
plain static text, with nothing distinguishing it from a label — testers
reported not being able to tell which fields were editable.

Give each a dim-but-visible resting border (`border-panel-border-input/50`,
or `border-panel-accent/30` for the explicitCustom tier) that brightens on
hover/focus, instead of a fully transparent one. Purely visual — no
behavior change.

Full studio suite (2641 tests) green; typecheck/oxlint/oxfmt clean.
2026-07-15 20:54:07 -07:00
Vance Ingalls fb24ecac22 fix(studio): make setRightPanelTab itself flat-aware, not just the direct tab click
Review feedback on #2497 (Rames D Jusso) found a real gap: the exclusivity
this PR introduced only applied to the direct in-panel tab click, which
calls setExclusiveRightInspectorPane. Every OTHER caller that reaches
setRightPanelTab("design"|"layers") — element select (useDomSelection.ts),
closing block-params (App.tsx), the header Inspector button
(StudioHeader.tsx), and even this PR's own "!inspectorTabActive" entry
branch in handleInspectorPaneButtonClick — went through
trackedSetRightPanelTab's old unconditional additive
`{...panes, [tab]: true}`, reproducing the exact "both tabs highlight, only
one renders" bug this PR claims to fix. Confirmed via the reviewer's traced
repro: fresh boot, click Layers tab while no inspector tab is yet active →
rightInspectorPanes ends up {design:true, layers:true}.

Fixed at the reviewer's preferred choke point: trackedSetRightPanelTab
itself is now flat-aware, applying the same exclusive-radio update
setExclusiveRightInspectorPane does whenever STUDIO_FLAT_INSPECTOR_ENABLED
is on, falling back to the legacy additive update otherwise. This closes
the gap for every current and future caller of setRightPanelTab, not just
the one call site this PR touched.

New usePanelLayout.test.ts cases pin both directions: setRightPanelTab
stays additive under flat=off (legacy split-view behavior unchanged), and
enforces exclusivity under flat=on even when called directly (not through
the tab-click handler) — using the vi.doMock(manualEditingAvailability)
pattern already established in PropertyPanel.test.tsx for flag-dependent
module state.

Full studio suite (2643 tests) green; typecheck/oxlint/oxfmt clean.
2026-07-15 20:53:47 -07:00
Vance Ingalls 71d7be7921 Merge pull request #2501 from heygen-com/task3-position-edit-force-reset
feat(core): applyPositionEdits gains force option and undo reset path
2026-07-15 19:07:05 -07:00
Vance Ingalls 79b688f204 fix(studio): show Layers full-height in the flat inspector, not split with Design
The flat inspector split Layers and Design into a vertically-resizable
stacked pair whenever both panes were toggled on, mirroring the legacy
panel's layout. For the flat redesign this reads as two competing panels
crammed into one column; Layers should always render full-height by
itself there instead.

Gate the split-view branch behind !STUDIO_FLAT_INSPECTOR_ENABLED so it
still applies to the legacy panel, and fall through to Layers rendering
alone (the existing `layersPaneOpen` branch already does this — it just
never got reached previously because the split check ran first).

Also added setExclusiveRightInspectorPane (radio-style: selecting one pane
turns the other off) and use it for the Design/Layers tab clicks under the
flat flag, since leaving both panes independently toggleable would highlight
both tabs as "active" while only one actually renders.

New usePanelLayout.test.ts covers both the existing toggle behavior and the
new exclusive variant. Full studio suite (2634 tests) green; typecheck/
oxlint/oxfmt clean.
2026-07-15 19:05:38 -07:00
Vance IngallsandClaude Fable 5 4ac7b4fa82 fix(core): guard undefined NodeList index in applyPositionEdits loops
tsc (noUncheckedIndexedAccess) types marked[i]/orphaned[i] as
Element | undefined; vitest passed but bun run build failed. Narrow
before the isStylable predicate and regenerate the render-inline IIFE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 18:30:55 -07:00
Vance Ingalls c621fd0f29 Merge pull request #2515 from heygen-com/via/fastcapture-fallback-profiling
feat(engine): opt-in per-frame timing on fast-capture fallback path
2026-07-15 18:25:38 -07:00
Vance Ingalls a2dd40367f Merge pull request #2500 from heygen-com/task2-iframe-load-resync
fix(sdk): attachSync re-syncs the override snapshot on iframe load
2026-07-15 18:11:13 -07:00
Vance Ingalls bc9d6d64fa Merge pull request #2499 from heygen-com/task1-base-variable-defaults
feat(sdk): getVariableValue({ base: true }) reads pre-override declared defaults
2026-07-15 18:10:53 -07:00
Via 1c46eadceb fix(producer): propagate --exclude-tags to Dockerfile.test ENTRYPOINT
Follow-up on Miga's review of #2512. The regression fixture
`escape-hatch-fatal-fallback` is tagged `field-signal-reproducer` and
`known-broken` so it's skipped from the default sweep via
`--exclude-tags transparency,field-signal-reproducer` in the
`test:regression*` scripts in packages/producer/package.json. But
`Dockerfile.test`'s ENTRYPOINT invoked the harness directly (`bunx tsx
src/regression-harness.ts -- --sequential`), bypassing those scripts —
so `bun run docker:test*` and the aws-lambda smoke tests would still
try to run the known-broken fixture and fail. CI's own regression sweep
was insulated only because it hardcodes per-shard positional test names
that don't include this fixture, but that's incidental, not by design.

Bake the exclude-tags into the Dockerfile.test ENTRYPOINT itself so
every user of the image (local `docker:test*`, aws-lambda smoke, any
adopter running the reference image) picks up the same skip contract.
Docker CMD args appended after the entrypoint (e.g. matrix shard
positional test names in .github/workflows/regression.yml, or
`--mode=distributed-simulated`) still parse correctly — the harness
applies excludeTags after testNames-filtering (see discoverTestSuites
in regression-harness.ts).

Also exports `parseArgs()` from regression-harness.ts and adds
regression-harness-parse.test.ts to pin the `--exclude-tags` comma-parse
contract, so any future change to the parser or the values baked into
the Dockerfile / package.json will trip a red test rather than silently
diverging.

Verification A (harness comma-parses `--exclude-tags transparency,
field-signal-reproducer`) already worked pre-fix; the new test file
codifies it. Verification B (Docker ENTRYPOINT propagates the same
skip) is what this commit fixes.

Signed-off-by: Via
2026-07-16 00:33:33 +00:00
Via e6cdf4abb1 feat(engine): opt-in per-frame timing on fast-capture fallback path
Field-signal baseline: >=2 fallbacks/hr on darwin/arm64 from filter:blur
and filter:drop-shadow triggers. Fallback path perf is currently untimed,
so we can't know if the overhead is 10% or 10x. This PR adds opt-in
per-frame timing (HF_PROFILE_FALLBACK_CAPTURE=true) that emits p50/p95/p99
+ trigger reason via the observeRenderStage telemetry channel extended in
#2510. Diagnostic surface only -- no perf fix, no behavior change on
healthy paths.

Stack: PR #9 (final) of 9 (base via/escape-hatch-fallback-reproducer).

Signed-off-by: Via
2026-07-16 00:28:40 +00:00
Vance Ingalls 4740288050 Merge pull request #2512 from heygen-com/via/escape-hatch-fallback-reproducer
test(producer): reproducer fixture for both-escape-hatches-fail case
2026-07-15 17:23:22 -07:00
Vance Ingalls 55540faea7 Merge pull request #2511 from heygen-com/via/gpu-parity-gate
feat(engine): software-GPU parity diff helper for solid-black capture-shape bugs
2026-07-15 17:22:59 -07:00
Vance Ingalls 4e9047018d Merge pull request #2510 from heygen-com/via/parallel-capture-observability
feat(producer): calibration-aware heartbeat + worker-death terminal-error contract
2026-07-15 17:22:43 -07:00
Vance Ingalls b8bf4735dc Merge pull request #2508 from heygen-com/via/overlay-count-lint
feat(lint): flag high overlay-element counts (heavy-capture risk)
2026-07-15 17:22:27 -07:00
Vance Ingalls 0ab153e0c5 Merge pull request #2507 from heygen-com/via/parity-telemetry-gate
feat(producer): parity telemetry gate for per-clip frame-count invariant
2026-07-15 17:22:13 -07:00
Vance Ingalls 9db5f67972 Merge pull request #2506 from heygen-com/via/darwin-goto-nav-timeout-hint
feat(engine): surface escape hatches in page.goto Nav timeout errors
2026-07-15 17:22:01 -07:00
Vance Ingalls aac290d5cd Merge pull request #2505 from heygen-com/via/win32-streaming-encode-autodisable
feat(engine): auto-disable streaming-encode on Windows software-GPU compound
2026-07-15 17:21:45 -07:00
Vance Ingalls fda62cae08 Merge pull request #2504 from heygen-com/via/protocol-timeout-discoverability
feat(engine): surface protocolTimeout env + flag in Puppeteer timeout errors
2026-07-15 17:21:30 -07:00
Via e5c4e1970c test(producer): reproducer fixture for both-escape-hatches-fail case
Field signal ts=1784039841 (win32/x64, CLI 0.7.57): shifted-DOM-image-
layer bug at frame 120 reproduces with BOTH PRODUCER_FORCE_SCREENSHOT=true
AND HF_DE_PARALLEL_ROUTER=false set. First case where both known escape
hatches fail simultaneously. Standalone 1920x1080 GSAP paused timeline,
absolute PNG layers, 4 independent scenes.

Ships a skipped regression fixture to codify the shape. NOT a fix — no
root cause identified. The composition is preserved so a future
diagnostic pass has a real, checked-in repro and a proposed fix can be
validated against the same shape the field reported.

Skip mechanism (belt-and-suspenders):
  1. meta.json tagged `field-signal-reproducer` and `known-broken`;
     producer/package.json test:regression* scripts add the tag to
     --exclude-tags alongside the existing `transparency` skip.
  2. Not registered in any .github/workflows/regression.yml shard's
     args, so the CI regression sweep won't pick it up either.

Un-skip when a fix lands: drop the tags from meta.json AND add the
fixture id to a shard's args in the workflow. See src/README.md for
the field-signal envelope and diagnostic starting points.

Stack: PR #8 of 9 (base via/gpu-parity-gate).

Signed-off-by: Via
2026-07-15 23:35:58 +00:00
Via 97e094621f feat(engine): software-GPU parity diff helper for solid-black capture-shape bugs
Field signals ts=1784049136 (hardware-GPU intermittent black rectangles →
resolved with --no-browser-gpu --low-memory-mode --workers 1) and
ts=1784032286 (clip-path animated image → intermittent black rectangles →
resolved with deterministic precompose). Pattern: hardware-GPU writes
solid-black on some composition shapes; software-GPU / screenshot bypass
restores correctness. Raw per-pixel diff alone false-positives on every
compositor jitter frame; the diagnostic-grade signal is asymmetric
black-only-in-A pixels (solid-black where B has content).

Adds `packages/engine/src/utils/gpuParityDiff.ts`: pure helpers
(`diffGpuParityFrames`, `diffGpuParityPngs`, `verifyGpuParity`) that
compare two RGBA frames captured via different GPU paths, count per-pixel
diffs above a tolerance, and isolate black-only-in-A / black-only-in-B
pixel counts + bounding boxes. Symmetric black regions (real black content
present in both captures) are NOT flagged. PNG wrapper preserves the
underlying decode error as Error.cause on either side. All exposed via
`@hyperframes/engine`'s package index for downstream wiring.

19 unit tests cover identity, per-pixel tolerance, the field-bug shape,
the shared-black no-op case, bounding-box tightness across multiple
regions, the inverse pattern, dimension mismatch, data-length mismatch,
overlapping threshold rejection, custom tolerance, verdict output, PNG
end-to-end, and cause-preservation on both A and B decode failures.

Reduced-scope first pass. Wiring a `hyperframes verify-gpu-parity` CLI
command, dual-mode capture orchestration, and integration coverage against
a known-bad composition is intentionally deferred to a follow-up so the
diagnostic primitive can land and be exercised in isolation. The exported
surface is stable — a follow-up need only add the capture-and-diff driver.

Stack: PR #7 of 9 (base via/parallel-capture-observability).

Signed-off-by: Via
2026-07-15 23:26:50 +00:00
Via 971bcf39ae feat(producer): calibration-aware heartbeat + worker-death terminal-error contract
Field signals ts=1784019503 (heartbeat reports 0 frames during 64s
browser calibration — reads as broken but is healthy) and ts=1784042064
(1292s Windows render hard-exited during video frame extraction with
no final error string — silent worker crash).

Add calibrating/capturing state to heartbeat labels; surface synthetic
terminal error on unexpected worker exit when no explicit error was
emitted.

Stack: PR #6 of 9 (base via/overlay-count-lint).
Signed-off-by: Via <vance@heygen.com>
2026-07-15 23:14:49 +00:00
ViaandClaude Opus 4.7 75de927fb5 feat(lint): flag high overlay-element counts (heavy-capture risk)
Field signal ts=1784040753 (#hyperframes-cli-feedback): a composition
with ~40 heavy overlay DOM elements — `filter:blur`, oversized
`radial-gradient`, and `clip-path` animations — captures solid-black for
the first ~half of the render, recovering near the end. Reproduces
identically via drawElement AND forced --no-browser-gpu screenshot
capture AND `snapshot`, so the capture layer itself is the offender, not
encoder/mux. Independent of duration (padding the timeline grows the bad
zone proportionally, doesn't shift it). Presence alone matters — even
opacity:0 / visibility:hidden / unused overlays contribute. Reporter's
workaround was splitting into per-transition mini-compositions +
FFmpeg concat.

Add compositionCheck rule `composition_heavy_overlay_count_high`
(warning). Counts DOM elements that carry any of: inline
`style` filter:blur / clip-path (non-none) / radial-gradient, or a
class/id whose top-level CSS rule body sets one of those. `display:none`
elements are counted-out (removed from render tree); opacity:0 /
visibility:hidden overlays are counted-in per the field-signal repro
shape. Warns at 25 to give lead time before the observed 40-element bad
zone. Skips registry source and installed-block files, mirroring
`composition_file_too_large`. Includes a `ts=1784040753` reference in
fixHint so authors can trace the risk shape.

Stack: PR #5 of 9 (base via/parity-telemetry-gate).

Signed-off-by: Via

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 22:59:52 +00:00
Vance Ingalls c2764e0888 Merge pull request #2473 from heygen-com/07-15-fix_producer_extend_de_stall_watchdog_to_the_single-worker_streaming_path
fix(producer): extend DE stall watchdog to the single-worker streaming path
2026-07-15 15:55:52 -07:00
Via 69b393865a feat(producer): parity telemetry gate for per-clip frame-count invariant
Field signals ts=1784139267 (win32 15-injection blank later clips,
injection-count-scaled) + ts=1784144554 (darwin 147-authored-clip
visibility gap, authored-clip-count-scaled). check/snapshot passes,
final MP4 has blank/missing clips. Extends #2474's videoCount:0 probe
with per-clip captured-vs-expected frame ratio + threshold fail-loud
gate at render finalization.

Stack: PR #4 of 9 (base via/darwin-goto-nav-timeout-hint).
Signed-off-by: Via <via@heygen.com>
2026-07-15 22:46:45 +00:00
Via 58cff5f6d5 feat(engine): surface escape hatches in page.goto Nav timeout errors
Field signal ts=1784146416 (darwin/arm64, CLI 0.7.58, 7/10): host
page.goto hit Navigation timeout of 60000ms twice on a CSS 3D + audio
composition; Docker rendered the same composition successfully.
Puppeteer's stock "Navigation timeout of 60000 ms exceeded" text names
none of HyperFrames' existing escape hatches, so the reporter had no
signal that the failure had knobs.

Wraps main-render Puppeteer `page.goto` errors matching
/Navigation timeout|net::ERR_TIMED_OUT/i with an augmented message that
names:

- The effective timeout currently applied (`cfg.pageNavigationTimeout`).
- Raise-the-timeout: `PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS` env,
  `--browser-timeout` CLI flag (seconds).
- Browser-binary escape hatch: `HYPERFRAMES_BROWSER_PATH` env.
- Field-signal shape: darwin/arm64 + CSS 3D + audio compound Docker
  hint — gated on all three inputs being explicitly true; falls back
  to generic hints when any input is unknown.

Mirrors #2443's HYPERFRAMES_BROWSER_PATH surfacing pattern (which
covered download-time failures) at the runtime `page.goto` layer.
Non-matching errors flow through unchanged. Original error preserved
via `err.cause`.

Wired into `renderOrchestrator.executeRenderJob`'s top-level catch,
composed after `augmentProtocolTimeoutError` so the two augmenters
never both fire on the same error (mutually exclusive regexes).
Current wire-up passes no `hasCss3D` / `hasAudio` context — no
compile-time CSS-3D signal is threaded through the render pipeline,
and `hasAudio` is block-scoped inside the try. Per the helper's
fallback docs, unknown flags route to the generic env + browser-path
hints. A future compile-time CSS-3D scan can thread both flags to
enable the full compound Docker hint without touching this helper's
signature.

Stack: PR #3 of 9 (base via/win32-streaming-encode-autodisable).

Signed-off-by: Via <vance@heygen.com>
2026-07-15 22:33:37 +00:00
Via cbf2a2ec69 feat(engine): auto-disable streaming-encode on Windows software-GPU compound
Field signal ts=1784131903 (win32/x64, CLI 0.7.58, 156s UI-heavy):
stable ONLY with four flags together — --workers 1 --no-browser-gpu
--low-memory-mode + PRODUCER_ENABLE_STREAMING_ENCODE=false. Since
--no-browser-gpu and --low-memory-mode already imply screenshot
capture, three of the four flags are structurally coupled. Auto-detect
the compound at resolveConfig time and disable streaming-encode on
the caller's behalf; user explicit-set (PRODUCER_ENABLE_STREAMING_ENCODE
or overrides.enableStreamingEncode) always wins.

Composition duration is not known at the config layer, so the wire-up
passes compositionDurationSec:undefined and the helper reduces to the
three-condition compound (platform + softwareGpuForced + workers=1).
The 4-arg helper stays exported for downstream callers that DO know
duration (e.g., renderOrchestrator) and want the >120s guard.

Trade-off documented in code + PR body: false positives possible for
short (~<120s) Windows software-GPU single-worker renders. Mitigation
is the explicit opt-in escape hatch.

Emits a single [hyperframes] log line naming the trigger + how to opt
back in, so operators can tell an auto-disable apart from an explicit
opt-out. Adds streamingEncodeAutoDisabledOnWin32Compound internal
provenance for downstream telemetry.

Stack: PR #2 of 9 (base via/protocol-timeout-discoverability).

Signed-off-by: Via
2026-07-15 22:21:54 +00:00
Via 6944a1c2d0 feat(engine): surface protocolTimeout env + flag in Puppeteer timeout errors
Field signal ts=1784047847 (darwin/arm64, 8GB M1, 9 videos + 22 images):
reporter hit Runtime.callFunctionOn timeout and switched to FFmpeg
because the error didn't surface HyperFrames' existing knobs
(PRODUCER_PUPPETEER_PROTOCOL_TIMEOUT_MS env, --protocol-timeout CLI).

Wraps main-render Puppeteer errors matching /Runtime\.callFunctionOn
timed out|Target closed|protocolTimeout/i with an augmented message that
names the effective timeout, the env var, the CLI flag, and the
field-signal shape. Non-matching errors pass through unchanged
(returned as the same instance). Original error preserved via err.cause.

Also adds a dedicated --protocol-timeout row to the CLI docs Flags table
so PRODUCER_PUPPETEER_PROTOCOL_TIMEOUT_MS is discoverable via search.

Signed-off-by: Via <noreply@heygen.com>
2026-07-15 22:11:44 +00:00
Vance Ingalls 049f72d4d9 feat(core): applyPositionEdits gains force option and undo reset path 2026-07-15 14:50:46 -07:00
Vance Ingalls 4682da14f1 fix(sdk): attachSync re-syncs the override snapshot on iframe load 2026-07-15 14:50:41 -07:00
Vance Ingalls db5e062211 feat(sdk): getVariableValue({ base: true }) reads pre-override declared defaults 2026-07-15 14:50:33 -07:00
Vance Ingalls ff3b1541e5 chore: release v0.7.59 2026-07-15 11:57:49 -07:00
Vance Ingalls b70d849a47 fix(producer): bridge stall-timeout env var rename, disambiguate abort from stall on sequential path
Review feedback on #2473: HF_DE_PARALLEL_STALL_MS had no backwards-compat
shim after the rename to HF_DE_STALL_MS, and a parent abort during a wedged
sequential capture would surface as "stalled" instead of "aborted" in
downstream logs/telemetry (functionally harmless since isCancellation is
gated on abortSignal.aborted, not message text, but misleading to read).
2026-07-15 11:53:54 -07:00
Vance Ingalls 650977d1d8 fix(producer): extend DE stall watchdog to the single-worker streaming path 2026-07-15 11:53:54 -07:00
Vance Ingalls 10b3351974 Merge pull request #2471 from heygen-com/07-15-fix_cli_producer_headless_shell_path_alias
fix(cli): honor PRODUCER_HEADLESS_SHELL_PATH in findFromEnv
2026-07-15 00:59:03 -07:00