Commit Graph
2865 Commits
Author SHA1 Message Date
Vance Ingalls 2983e74253 feat(studio): add flat trigger variants for ColorField, FontFamilyField, TextAreaField 2026-07-14 00:59:06 -07:00
Vance Ingalls 633171bb15 feat(studio): add flat footer (ask agent + record) for the inspector 2026-07-14 00:59:06 -07:00
Vance Ingalls 0152667526 feat(studio): add flat identity header for the inspector 2026-07-14 00:59:06 -07:00
Vance Ingalls b324a43ba0 feat(studio): add FlatGroup accordion and PinnedZoneDivider primitives 2026-07-14 00:59:06 -07:00
Vance Ingalls c1916f1c25 feat(studio): add FlatRow and FlatSegmentedRow flat-inspector primitives 2026-07-14 00:59:06 -07:00
Vance Ingalls 797df2a64a feat(studio): add 3-tier value/label color resolver for the flat inspector 2026-07-14 00:59:06 -07:00
Vance Ingalls 3dc11c7137 feat(studio): add flat-inspector tokens and STUDIO_FLAT_INSPECTOR_ENABLED flag 2026-07-14 00:59:06 -07:00
Miguel Ángel 6933e8acda fix(cli): consolidate snapshot and frame diagnostics (#2402)
* fix(snapshot): preserve exact requested times

* fix(cli): fail video snapshots without FFmpeg

* fix(cli): honor navigation timeout in diagnostics

* fix(cli): preserve snapshot alpha and create shot dirs
2026-07-14 01:46:44 -04:00
Miguel Ángel b98463ae3a fix(cli): consolidate layout and contrast audit correctness (#2401)
* fix(cli): respect transparent image pixels in occlusion audit

* test(cli): cover contained image letterboxing

* fix(cli): account for text strokes in contrast checks

* fix(cli): honor text overflow opt-outs

* fix(cli): skip contrast on transparent backdrops

* chore(skills): refresh generated manifest
2026-07-14 01:44:58 -04:00
Miguel Ángel 6fc92308d6 fix(engine): resolve root-absolute media from project (#2399) 2026-07-14 01:11:23 -04:00
Miguel Ángel 0dfc85b680 fix(engine): skip unnecessary dimension pad (#2398) 2026-07-14 00:42:16 -04:00
Miguel Ángel 90be05019b chore: release v0.7.57 (#2393) v0.7.57 2026-07-14 00:29:29 -04:00
Miguel Ángel 3f6ea0a1ab perf(producer): cache local font compression (#2397) 2026-07-14 00:26:37 -04:00
Miguel Ángel 3dcc6e6a80 fix(release): resolve packed-consumer transitive deps to local tarballs (#2396)
verifyPackedConsumer installs each package as a file: tarball, but each tarball pins its inter-@hyperframes deps to the exact release version. On a release bump that version is not on the npm registry yet, so bun resolves the transitive deps from the registry and fails. Add an overrides map forcing every @hyperframes/* to the sibling local tarball, making the check self-contained pre-publish. Exercised by the v0.7.57 release PR.
2026-07-13 23:51:17 -04:00
Vance Ingalls 58cfa0c655 Merge pull request #2359 from heygen-com/via/issue-3-software-gpu-screenshot
fix(engine): software-GPU browsers imply screenshot capture
2026-07-13 20:37:12 -07:00
Miguel Ángel 8bf7bc3af5 fix(cli): require explicit non-interactive init source (#2395) 2026-07-13 23:28:46 -04:00
Vance Ingalls f44bc3a525 fix(engine,producer): drive forceScreenshot from one authoritative local
Miguel R4 blocker on #2359: my R3 fix at renderOrchestrator only updated
the observability copy, leaving the authoritative captureForceScreenshot
local at compileResult.forceScreenshot (false for auto→software). The
frameCapture side clamped its own local and correctly routed screenshot,
but downstream orchestrator code overwrote observability back to
beginframe from the still-false local at two sites:

  - Parallel-stream label at renderOrchestrator.ts:2293 mis-labelled the
    stream as 'beginframe' when actual capture was 'screenshot'.
  - capture_strategy telemetry at renderOrchestrator.ts:2440-2450
    overwrote the earlier observability correction, so the final
    captureMode observation flipped back to 'beginframe' while the
    engine actually captured screenshot.

Fix: extract the clamp into a caller-facing helper
applyConcreteGpuScreenshotClamp(current, resolvedGpuMode, cfg) that
returns the (possibly-promoted) new boolean. Callers assign it back to
their authoritative local, so routing + telemetry + strategy code read
one value.

Changes:

  - packages/engine/src/config.ts: new exported
    applyConcreteGpuScreenshotClamp; delegates to
    shouldClampToScreenshotForConcreteGpu but computes the caller's
    final value, not just the clamp decision. Reads the programmatic
    opt-out from cfg.forceScreenshotExplicitlyOptedOut. Idempotent on
    already-true input.
  - packages/engine/src/index.ts: export the new helper.
  - packages/engine/src/services/frameCapture.ts: replace the inline
    OR expression with applyConcreteGpuScreenshotClamp.
  - packages/producer/src/services/renderOrchestrator.ts: assign result
    into the AUTHORITATIVE captureForceScreenshot local (was updating
    only observability). Downstream parallel-stream label at :2293 and
    capture_strategy telemetry at :2440-2450 now read the corrected
    value.

Tests: 6 new caller-level cases for applyConcreteGpuScreenshotClamp
covering the exact matrix Miguel called out:

  - resolved software + default false → promotes to true (screenshot)
  - resolved software + programmatic opt-out → stays false (BeginFrame)
  - resolved hardware + default false → stays false
  - resolved software + already-true → stays true (idempotent)
  - resolved software + env PRODUCER_FORCE_SCREENSHOT=false → stays false
  - resolved software + undefined cfg → promotes to true (frameCapture path)

Local: 67/67 engine config tests pass (was 61). oxfmt clean.
2026-07-14 03:17:29 +00:00
Vance Ingalls 72daac2a1d fix(engine): carry programmatic forceScreenshot opt-out to concrete-resolved site
Miguel R3 blocker on #2359: the runtime helper only checked the env opt-out
(PRODUCER_FORCE_SCREENSHOT=false), silently defeating the documented
programmatic escape hatch (overrides.forceScreenshot === false) on the
browserGpuMode:'auto' → software probe path. At the concrete-resolution
site the boolean forceScreenshot === false is ambiguous between default
and explicit opt-out — resolveConfig sees the provenance but the runtime
helper does not.

Fix: persist provenance on the resolved config.

- New INTERNAL EngineConfig field forceScreenshotExplicitlyOptedOut, set
  by resolveConfig when EITHER env or programmatic explicit-false is
  present. Purpose-documented in the type as 'not intended to be set by
  callers'.
- shouldClampToScreenshotForConcreteGpu gains an opts.programmaticOptOut
  parameter; returns false early when set. Env stays as the third arg
  (backward compatibility with existing tests).
- frameCapture.ts and renderOrchestrator.ts pass
  config.forceScreenshotExplicitlyOptedOut through at both call sites, so
  the auto→software probe path preserves the same escape hatches as
  literal browserGpuMode:'software'.

New tests: 5 additional cases across the helper (programmatic opt-out
alone; programmatic beats missing env) and resolveConfig provenance
(programmatic sets flag; env sets flag; neither leaves it undefined).
Local: 61/61 engine config tests pass (was 56).
2026-07-14 03:06:34 +00:00
Miguel Ángel 2a7a5e6236 feat(preview): keep review sessions alive (#2384)
* feat(preview): keep review sessions alive

* fix(preview): harden background lifecycle state

* refactor(preview): satisfy lifecycle quality gate
2026-07-13 22:55:19 -04:00
Miguel Ángel 4495cb7355 feat(cli): stable public URL for hyperframes publish (re-publish updates the same link) (#2363)
* feat(cli): stable-URL re-publish (send owned id, honest UX, --update, team id file)

Resolve a stable project id (committed team id > machine store > mint), send it
when authenticated so an owned re-publish updates the same URL in place, and
persist the server id+url. Report updated-vs-created honestly, add --update to
target a project explicitly, write a committable .hyperframes/project.json so a
team shares one link, and show the prior URL on re-publish.

* test(cli): env-gated E2E round-trip for stable-URL re-publish

Publish -> edit -> re-publish asserts one URL with updated content against a
live EF (HYPERFRAMES_E2E_API_URL + an authenticated runner); skipped otherwise.

* fix(cli): real team space, auth-gate --update/--space, safe team-file write

- --space + committed .hyperframes/project.json (projectId+spaceId) send X-Space-Id so a
  team converges on one link; personal space stays the default for solo users
- --update/--space error when unauthenticated, and warn loudly when a resolved-but-invalid
  token silently downgrades to a new anonymous URL (no more generic-tip-only)
- team-file write in its own try/catch so a read-only dir can't fake 'Publish failed'
- parseUpdateTarget handles scheme-less URLs + query/hash; X-Space-Id on metadata only (not S3 PUT)
- share readJsonRecord across the local + team descriptors

* test(cli): e2e team-space convergence + cross-space hijack guard

* test(cli): unit-test parseUpdateTarget url shapes (export for test)

* fix(cli): warn on committed-team miss too, not just --update
2026-07-13 22:47:28 -04:00
Miguel Ángel ca7c017e49 fix(media-use): tag avatar-video heygen recipes with X-HeyGen-Client-Source (#2391)
The agent-driven video recipes (heygen video create) went out untagged, so
media-use avatar / image-to-video usage landed as generic cli traffic and
wasn't attributable to media-use. Add --headers "X-HeyGen-Client-Source:
media-use" to the generating recipes (persistent flag, allowlisted by the
CLI) so those videos carry client_source in master_video_table meta and show
up in the API dashboards alongside the TTS path (#2365) and CLI (#2368).
2026-07-13 22:43:58 -04:00
Miguel Ángel 852d1891d3 fix(pr-to-video): enforce offline frame contract (#2383)
* fix(pr-to-video): enforce offline frame contract

* fix(pr-to-video): tighten frame attribute contract
2026-07-13 22:39:32 -04:00
Miguel Ángel fba5cb9c93 fix(pr-to-video): bound first-run workspace and context (#2382)
* fix(pr-to-video): bound first-run workspace and context

* fix(cli): expose validation gate in help

* fix(pr-to-video): harden workflow guardrails

* style(pr-to-video): apply repository formatting

* chore(skills): refresh pr-to-video manifest
2026-07-13 22:33:21 -04:00
Miguel Ángel 9ac4ab8dee test(engine): write HDR fixture color tags into the H.264 VUI (#2389)
The SDR-to-HDR extraction tests synthesize their HDR fixture with
-color_trc/-color_primaries flags and rely on the encoder propagating
them into the bitstream. The pinned Windows CI ffmpeg build drops the
transfer on that path, so after #2377 narrowed HDR detection to the
transfer function, the fixture probes as SDR on Windows and both tests
fail (hdrPreflightCount 0). Write the VUI directly with the
h264_metadata bitstream filter so the tag survives on every build.
2026-07-13 22:20:41 -04:00
Miguel Ángel 6f4c6308f0 fix(lint): isolate IIFE timing constants (#2378) 2026-07-13 20:53:24 -04:00
Miguel Ángel b231b6f963 fix(cli): surface keyframes from scripts and styles inside <template> (#2374)
Sub-compositions are required to wrap markup, script, and style in <template>, but template content is an inert DocumentFragment that document-level querySelectorAll does not traverse — so 'hyperframes keyframes' silently surfaced zero tweens and zero CSS keyframes for every spec-conformant sub-composition. Extract from the document and every template content fragment (nested templates included, walked iteratively). Documents the extraction ordering contract and the deliberately document-root-only sub-composition discovery scan; pins template, multi-template, nested-template, and mixed-script cases with tests.
2026-07-13 20:47:50 -04:00
Miguel Ángel 9639824f4e fix(render): require HDR transfer metadata (#2377) 2026-07-13 20:46:59 -04:00
Miguel Ángel 3a020bf543 fix(cli): reject missing init example values (#2376) 2026-07-13 20:42:11 -04:00
Ular Kimsanov 403ecb56d9 Merge pull request #2347 from heygen-com/fix/studio-restore-golden-behaviors
fix(studio): restore golden-branch timeline behaviors dropped by the stack rebuild
2026-07-13 17:13:37 -07:00
Miguel Ángel dd938c7a16 feat(media-use): tag HeyGen calls with X-HeyGen-Client-Source (#2365)
Send `X-HeyGen-Client-Source: media-use` on every media-use HeyGen API
request (both auth types, via heygenAuthHeaders + the heygenJSON transport),
so backend billing meta can isolate media-use consumption from other free
TTS and avatar-video usage. Unconditional of auth type — a paying user's
media-use call is still media-use — unlike the OAuth-only cli-source header
that gates the free allowance.
2026-07-13 20:03:31 -04:00
ukimsanov 512560b4c3 test(studio): delete-path duration rollback coverage, shared dismiss predicate on preview open 2026-07-13 16:48:52 -07:00
ukimsanov a33b3f35e1 fix(studio): address PR #2347 review findings (rounds 1-2)
Review 1 (restore commit):
- asset reveal now clears any open preview overlay (stuck-overlay repro:
  preview on A, click already-added B — A stayed open over the reveal)
- duration readout rolls back on failed persist: captureDurationRollback
  snapshots store + live root data-duration before the optimistic sync and
  restores both in every move/resize/delete/group catch (golden's
  previousDuration pattern)
- asset preview opened during running playback dismisses immediately (the
  RAF loop bypasses the store, so the subscription alone never fired)
- persistTimelineBatchEdit resolves the target (findTagByTarget) before
  treating identical output as a no-op — a mistargeted member now throws
  like the single-element path instead of being silently dropped
- a post-mutation history-fold failure no longer suppresses the preview
  sync: fold errors are surfaced separately and the rewritten script still
  syncs (previously the preview kept stale GSAP positions with no recovery)
- timelineRevealScroll guards degenerate viewports (windowSize <= 0)
- CodeQL: encodeURIComponent(projectId) at all timelineTimingSync fetches

Review 2 (single-source-of-truth pass):
- createTimelineElementFromManifestClip — the one manifest->element
  boundary — now carries authoredTrack and stackingContextId; expanded
  sub-comp children preserve both (authoredTrack in their OWN file's space)
- authoredTrackForLane scopes occupants to the dragged clip's sourceFile
  (a foreign file's authored values are a different coordinate space);
  nearest-same-file-lane offset fallback
- optimistic store updates mirror the persisted track into authoredTrack
  (and roll it back on failure), so consecutive drags before a reload
  resolve from fresh data
- spill sub-lanes: documented decision — dropping onto a spill lane is a
  legitimate same-track join (occupants share the authored track by
  construction); false 'never a lane-move target' docstring rewritten
- single-element fallback persists vertical-only moves (early return now
  requires neither start nor track changed; live DOM patch includes
  data-track-index)
- canonical contextKey helper for stacking-context normalization
- new pipeline test crosses the REAL factory boundary (sparse authored
  tracks -> factory -> expansion -> normalize -> drag commit -> persisted
  attribute), no injected fields
2026-07-13 16:48:52 -07:00
ukimsanov 760b88a6f3 fix(studio): flashless z-order commits and visible-overlap stepping
Two legibility fixes for the canvas z-order menu, from user feel-testing:

- z-only commits no longer remount the preview iframe. The commit hook
  already applies the inline z (+ injected position) to the live elements and
  updates the store synchronously; the post-commit reloadPreview() was a
  redundant full remount that read as a canvas 'blink' on every action.
  commitDomEditPatchBatches gains skipReload, engaged only when provably
  safe: every op is an inline-style patch AND the server reports every patch
  matched — anything else falls back to the reload so the preview reconverges
  with disk. The file-watcher's own reload stays suppressed by the existing
  domEditSaveTimestampRef window, so the skip is real.
- Bring Forward / Send Backward step over the next VISIBLY overlapping
  sibling. The nearest z-neighbor in a composition is often invisible at the
  current frame (runtime hides time-inactive clips with inline
  visibility/display; GSAP parks elements at opacity 0), so the step crossed
  something the user couldn't see — 'enabled but nothing happens'. The
  forward/backward set now filters on element-level computed visibility
  (display/visibility/opacity, injectable for tests); enable/disable shares
  the resolver so the menu is honest: actions disable when no visible
  neighbor exists. Front/back keep the full painting family.
- The neighbor that was stepped over gets a 600ms accent flash, drawn in the
  studio overlay layer (never in the iframe DOM), so the action shows its
  work.
2026-07-13 16:48:52 -07:00
ukimsanov 84963ea8ba fix(studio): persist canvas z-order actions correctly for static elements
An adversarial review of the canvas context-menu z-order pipeline (Bring to
Front / Forward / Backward / Send to Back) found the resolver math sound but
the glue between the menu and the commit hook broken:

- The menu optimistically wrote style.zIndex AND position: relative to the
  live elements BEFORE the commit hook ran. The hook decides whether to
  persist position by checking getComputedStyle(el).position === 'static' —
  always false after the pre-apply — so the position patch was never
  persisted on the menu path and the reorder silently reverted at the
  post-commit reload for any nested/static element (root clips survive only
  because the runtime forces position:absolute). The same pre-apply made the
  failure rollback capture the already-mutated values, restoring the broken
  state on persist errors. The menu no longer pre-applies; the hook owns the
  live writes (it already applied both synchronously) and now sees true
  priors. Siblings without a persistable identity still get their z applied
  live-only so a renumber stays visually coherent.
- The commit hook's entry.key store-sync plumbing had zero production
  callers; the store zIndex went stale until full reload. All three callers
  (canvas menu via PreviewOverlays, timeline lane z-sync, LayersPanel) now
  derive and pass the timeline store key (new deriveTimelineStoreKey helper).
- patchElementBatch discarded the server's per-patch matched[]; unresolvable
  siblings persisted partially and silently. Unmatched targets now warn and
  report save-failure telemetry (z-reorder-unmatched) without rolling back
  the matched subset.
- template/noscript elements counted as painting siblings, so renumber
  fallbacks wrote z-index/position into <template> tags in the source file.
  Excluded from the sibling family.
- The default undo coalesce key merged DISTINCT z actions within 300ms into
  one undo entry; the action kind is now part of the key (LayersPanel drags
  keep coalescing within a drag; explicit lane-move gesture keys untouched).
- rectsIntersect comment claimed touching rects intersect; the strict
  inequalities say otherwise — comment fixed.
2026-07-13 16:48:52 -07:00
ukimsanov 19139b91ed fix(studio): make vertical lane moves persist correctly and harden the z/lane pipeline
Vertical clip moves committed in the store but never survived: two persist
bugs plus a runtime renumber all fought the stable-track-lanes model.

- timelineMoveAdapter deliberately stripped the track from lane-reorder
  persists ('z-only reorder path' — the old z-driven lane model). Lane =
  authored data-track-index now: lane-reorder and track-insert both persist
  the track; plain timing moves omit it to stay SDK-fast-path eligible.
- Display lanes and file tracks are different coordinate spaces:
  normalizeToZones packs sparse authored tracks (1,2,... or gaps, or DOM-index
  fallbacks) onto contiguous display lanes, and lane edits persisted the LANE
  number — silently re-targeting the wrong row in any non-0-contiguous file.
  Elements now record their authoredTrack when remapped; a lane change
  persists the target lane's authored track (store stays in lane space).
- The runtime split same-track clips of different kinds (video vs caption
  div) onto separate renumbered tracks at discovery, so authored indices
  never round-tripped ('drop onto an existing track' bounced back). Removed:
  data-track-index is honored verbatim (render never reads it); kind-based
  row presentation belongs in the display layer if ever wanted.

Adversarial review fixes on the same pipeline:
- runtime: parseInt(attr) || fallback dropped authored track 0 for GSAP and
  overlay clips (parseAuthoredTrack helper honors 0)
- single-clip move fallback persisted only data-start — lane changes snapped
  back on reload (now passes the track to the patch builder)
- lane-change z-sync candidate ignored a multi-selection's time shift, so
  patches were computed against stale overlap sets
- track insert around a locked clip persisted a colliding renumber (the next
  normalize merged lanes); the insert is now refused with a warning
- computeStackingPatches compared leaf z across CSS stacking contexts, where
  ancestor z decides paint order; the sync now partitions by
  stackingContextId and never patches across contexts

Timeline geometry (user-reported):
- fit zoom leaves 20% trailing headroom (FIT_ZOOM_HEADROOM in
  timelineLayout.ts; single fit-pps source, so ruler/lanes/playhead/drag all
  inherit it)
- playhead line center now sits exactly on GUTTER + t*pps at every zoom
  (wrapper had shrink-wrapped to the 9px diamond, off-centering the line);
  ruler ticks center on their timestamp
- ruler: frame-mode steps snap to whole frames (no duplicate labels), hour
  steps added for far zoom-out, tick positions computed as exact multiples
  (no float drift)
2026-07-13 16:48:52 -07:00
ukimsanov 54f41b41b6 fix(studio): restore golden-branch timeline behaviors dropped by the stack rebuild
The Studio stack rebuild (#2291) landed the remaining NLE layers but dropped
or regressed several final-wave behaviors from the reviewed studio-dnd stack,
and never repaired the stale timelineZones.ts that #2279 introduced. Restores:

- TimelineRuler: sticky under vertical scroll, full-height gridlines removed
  (beat lines only), frame-number tick labels via a persisted timeDisplayMode
  store preference (PlayerControls toggle now store-backed)
- timelineZones: stable track lanes — lane = authored data-track-index
  ascending; z is paint order only (replaces the stale z-driven lane pack,
  which broke track insert-band commits that contractually depend on it)
- persistTimelineBatchEdit: a batch member whose patch is a no-op (attributes
  already at target values, e.g. in a track-insert renumber) is skipped
  instead of aborting and rolling back the whole batch — this alone made
  new-track creation (incl. the top insert band) fail silently
- useTimelineStackingSync: unresolvable clips read as NaN again so
  timelineStackingSync's Number.isFinite exclusion contract holds (z=0
  fabrications skewed stacking boundaries)
- timelineAssetDrop: drops land on the drop track (no overlap bump to
  max-track+1), data-hf-id stamped, audio gets data-volume
- timing edits: soft-reload the server's rewritten GSAP script instead of a
  full iframe remount (no all-clips flash on move/resize); full reload only
  when no scriptText or the soft path can't apply, and one full reload when a
  group edit touches non-active files (new hooks/timelineTimingSync.ts)
- duration: content-driven grow-AND-shrink on move/resize/delete, synced
  optimistically to the store and the live root data-duration at release
  (was a grow-only ratchet; shrink never updated the readout)

New UX: sidebar asset click opens a compact non-modal preview over the canvas
(dismiss on outside click, Escape, playback, or seek), and clicking an
already-added asset reveals its clip in the timeline (smooth minimal scroll
to its time and lane; vertical-only in fit zoom).

Verified by pointer-driving a real project: sticky ruler + gridline removal,
no iframe remount on move/resize (marker survives, GSAP tween positions
rewritten in place), duration readout 40->37->40 on shrink/stretch, and
top-insert-band track creation renumbering lanes correctly on disk.
2026-07-13 16:48:51 -07:00
Vance Ingalls d04bcbf7c4 Merge pull request #2360 from heygen-com/via/issue-1-webm-alpha-probe
fix(cli): detect WebM alpha lost when ALPHA_MODE tag is written without side data
2026-07-13 16:44:57 -07:00
Vance Ingalls c3b0b91feb chore(fallow): allow sampledAlphaIsFullyOpaque test-only export
Matches the isPathInside pattern in fileServer.ts. The helper is called from
webmAlphaCheck.ts's own webmAlphaAdvisory (same file), but fallow doesn't
count intra-file consumption or test-file imports. Suppress the false
positive rather than dropping the direct unit test — the 1/2/3-frame
byte-count gate + per-frame stride logic is load-bearing enough to warrant
independent tests, not just coverage through the outer advisory function.
2026-07-13 23:21:18 +00:00
Vance Ingalls c6da79cbdb Merge pull request #2358 from heygen-com/07-13-fix_core_cli_attribute_figma_rest_failures_to_the_endpoint_that_failed
fix(core,cli): attribute figma REST failures to the endpoint that failed
2026-07-13 16:16:24 -07:00
Vance Ingalls 2e44602f99 fix(engine,producer): apply software-GPU screenshot invariant at concrete-resolved point
Addresses Miguel's R1 blockers:

1. `browserGpuMode: "auto"` that runtime-probes to software slipped past the
   `resolveConfig` clamp — that clamp only sees the pre-resolve string. Add
   `shouldClampToScreenshotForConcreteGpu(resolvedGpuMode, currentForceScreenshot, env)`
   in `packages/engine/src/config.ts` and apply it at BOTH concrete-resolution
   sites:
   - `packages/engine/src/services/frameCapture.ts`: downgrades `preMode`
     from "beginframe" to "screenshot" when resolved GPU is software (respects
     `PRODUCER_FORCE_SCREENSHOT=false` env opt-out), fixing the routing.
   - `packages/producer/src/services/renderOrchestrator.ts`: updates
     `captureObservability.forceScreenshot` (and thus `captureMode`) at the
     same call site, fixing the observability truth on the auto → software
     case.

2. New unit tests in `config.test.ts`:
   - Documents the auto-branch gap (resolveConfig leaves auto as
     forceScreenshot=false — the runtime companion closes it).
   - 5 branch tests on `shouldClampToScreenshotForConcreteGpu` covering
     software / hardware / already-forced / env-opt-out / non-"false" env
     values.
   Full suite: 56/56 pass.

Scope narrowing on Blocker 2: the distributed rendering path at
`packages/producer/src/services/distributed/plan.ts:753-754` and
`renderChunk.ts:462-466` explicitly hardcodes `browserGpuMode:"software",
forceScreenshot:false` post-resolveConfig and stays outside this PR's
invariant boundary. `compileStage` may still flip it to true for alpha
formats, but generic MP4 distributed renders on SwiftShader hosts remain
BeginFrame. That's a separate architectural cleanup (needs its own
behavior-change trace); the PR body now scopes the invariant to the
in-process CLI/orchestrator path.
2026-07-13 23:13:30 +00:00
James Russo cb69d3fe00 fix(render): surface structured outcomes (#2153) 2026-07-13 19:07:39 -04:00
Miguel Ángel 6892b62662 docs(media-use): drop the HeyGen generative use-cases table (#2370)
Keep the image-to-video recipe; remove the broader capability table (photo
avatar, digital twin, cinematic, translate, lipsync, ai-clipping, voice). It
drifted toward the API-reference surface OP1 says media-use shouldn't carry, and
--request-schema already self-documents each command. Fix the now-dangling
'(below)' pointer in the image-to-video note.
2026-07-13 19:02:35 -04:00
Miguel Ángel 746b6d5b75 feat(cli): tag HeyGen API calls with X-HeyGen-Client-Source (#2368)
Send `X-HeyGen-Client-Source: hyperframes` from buildAuthHeaders on every
HeyGen API call (both OAuth and API-key), so backend billing meta can
isolate hyperframes CLI usage. The single buildAuthHeaders chokepoint covers
the core CLI + cloud client. Mirrors the media-use tagging; the OAuth-only
X-HeyGen-Source cli free-gate header is unchanged.
2026-07-13 19:02:22 -04:00
Miguel Ángel 78b9a814d5 docs(skills): add cloud render + variables to CLI skill, media-use generative use cases (#2356)
* docs(skills): add cloud render + variables to CLI skill, media-use generative use cases

The hyperframes-cli skill only documented self-managed AWS Lambda rendering; the
zero-infra HeyGen-hosted `cloud render` path (a real, shipped command with its
own docs page) was absent from every skill, so agents never surfaced it.

- hyperframes-cli: add `cloud` to the frontmatter verb list + entry point; new
  Cloud and Variables sections; new references/cloud.md distilled from
  docs/deploy/cloud.mdx; routing + workflow rows.
- media-use: add image-to-video recipe (heygen video create type:image) plus a
  table of other HeyGen generative use cases (photo avatar, digital twin, video
  translation, lipsync, voice design) in references/operations.md; surface them
  in the SKILL coverage/provider rows.
- Sync README + docs/guides/skills.mdx catalog entries to mention cloud render.

* docs(media-use): point HeyGen generative use cases at --request-schema

Verified against the installed heygen CLI (v0.3.0): no capability gap that would
need the raw API. Replace hardcoded body-field lists with a pointer to
`heygen video create --request-schema` (self-documenting, can't rot), correct
the image-to-video motion_prompt/expressiveness support, and add the
cinematic_avatar, ai-clipping, and photo-avatar creation paths.

* fix(skills): correct media-use manifest hash (clean-tree regen)

The prior regen was polluted by the gitignored skills/media-use/eval-report.html
(a suppressed mv error left it present), so the committed media-use hash didn't
match a clean checkout. Regenerate with no untracked artifacts present.
2026-07-13 18:53:19 -04:00
Xuanru Li 7f4eaeb568 feat(cli): coordinate-frame layout findings in check (#2354)
* feat(cli): coordinate-frame layout findings in check

Four production compositions shipped with 100-600px layout drift, each a
different coordinate-frame confusion the check graded info or missed
entirely: viewport pixels written as container left/top, gsap x/y
treated as absolute position, a -350px margin fighting flex centering,
and stage-relative path coords drawn into a nested SVG.

Three new layout findings close the class:
- positioned_out_of_parent: an absolute/fixed element rendering mostly
  outside its positioning ancestor (warning) — the parent needs no
  overflow clipping, which is what let container_overflow miss it.
- box_out_of_canvas: a painted panel breaching the canvas (warning) —
  text is canvas_overflow's, media is frame_out_of_frame's, painted
  boxes were nobody's.
- connector_detached: a connector path whose endpoints land far from
  every anchorable element (warning) — measured coordinates drawn into
  an SVG with a different origin.

canvas_overflow additionally promotes from info to warning when held
across samples AND the breach exceeds 5% of the canvas.

All three are persistence-tiered and respect data-layout-allow-overflow.
Verified against the four incident compositions: every one now surfaces
its drift as held warnings (previously: info or silence).

* fix(cli): harden coordinate-frame findings against review false positives

Reworks all three findings after two-lens review (adversarial FP hunt in
real Chrome + maintainer pass):

- escaped_container (was positioned_out_of_parent): uses offsetParent
  (transform-aware, skips fixed-as-canvas), exempts fully-detached
  callouts within an attachment allowance while still flagging
  touching-but-mostly-outside drift.
- panel_out_of_canvas (was box_out_of_canvas): paint alone qualifies
  (flat solid panels were a false negative), fully off-canvas rects are
  parked entrances and stay silent, pointer-events:none marks decorative
  layers, hero-sized breaches warn while small bleeds stay info.
- connector_detached: endpoints via getPointAtLength + getScreenCTM
  (viewBox, preserveAspectRatio, group transforms, every command type),
  defs/marker/clipPath subtrees skipped, word-boundary connector naming,
  containment tier limited to opaque non-ancestor targets (a text-bearing
  wrapper contains its own diagram's endpoints).
- canvas_overflow promotion requires partial visibility — a fully
  off-canvas rect is a parked entrance, not drift.

Verified: the four incident compositions still surface their drift as
held warnings; the review's false-positive repros (fixed HUD, callout,
parked entrance, corner bleed, marker arrowheads, g-transform and
viewBox-scaled connectors) are clean at warning level. Docs and the CLI
skill reference now describe the coordinate-frame findings.

* fix(cli): panel ownership is geometric — direct-text panels were a silent false negative

A painted panel whose direct text stays in-bounds while its box breaches
the canvas produced neither finding: canvas_overflow measures the text
range and panel_out_of_canvas skipped every own-text element. Skip the
panel finding only when the element's own text ALSO breaches (that
geometry belongs to canvas_overflow); pin the message/fixHint wording of
all three findings with positive assertions; document the SVG-internal
anchor blind spot.

* fix(cli): classify panel decoration by paint kind, not pointer-events

pointer-events:none exempted the framed-painting incident's gold frame
layers — hero content that happens to disable hit-testing. Decoration is
now gradient-only paint (spotlights, textures, vignettes); url() images,
solid fills and borders are content regardless of pointer-events.

* fix(cli): add fixHint to the test-local AuditIssue shape

* fix(cli): gradient stops decide content vs decoration; ownership matches canvas_overflow's tolerance

A gradient with any solid stop (alpha >= 0.6) is content — heroes and
cards painted with linear-gradient were invisible under the blanket
gradient exemption; all-translucent stops (spotlights, vignettes) stay
decoration. The text-ownership check now uses the audit tolerance that
canvas_overflow itself fires at, making the contract strict-mutex: any
text breach past that tolerance cedes the element, so a shallow 20px
text breach no longer double-reports.
2026-07-13 15:35:03 -07:00
Miguel Ángel 8e50e8477f fix(cli): skip contrast for intentionally covered text (#2366) 2026-07-13 18:22:30 -04:00
Vance Ingalls 56dcc4e47a fix(cli): accept 1–3-frame short WebMs + add direct pixel-probe tests
Address Miguel's R1 blocker + Rames/Miga's testability nit:

- The `-frames:v 3` decode samples AT MOST 3 frames; a legitimate 1- or
  2-frame WebM (256 or 512 bytes) was returned as `undefined` (probe
  failure), silently skipping the advisory even when every available
  pixel was opaque. Accept any positive whole-frame byte count ≤ 768
  (multiples of 256), distinguishing successful short-EOF from partial/
  malformed decode.
- Export `sampledAlphaIsFullyOpaque` and add 11 direct tests covering:
  3/2/1-frame opaque decodes → true; transparent pixel at pos 0 or
  final byte → false (guards the alpha-byte stride); non-frame-multiple
  / over-3-frame / zero byte counts → undefined; execFileSync throw →
  undefined; findFFmpeg missing → undefined; and one args-shape guard
  pinning the load-bearing `-c:v libvpx-vp9` before `-i` (without which
  the default decoder silently discards VP9 alpha and the whole check
  would false-positive on genuinely-transparent WebMs).
- Update advisory wording from "3 sampled decoded frames" to "every
  sampled decoded pixel" so the message is honest for short WebMs.

18/18 tests pass locally under `vitest run`.
2026-07-13 22:03:11 +00:00
Miguel Ángel 74fb4dbcc3 docs(cli): sync SRT upload docs from EF (#2364) 2026-07-13 17:51:25 -04:00
Vance Ingalls bd8ee05ae2 fix(cli): label the batch asset-import RENDER_FAILED with the images endpoint
Review feedback on #2358: the batch-miss RENDER_FAILED in
runAssetImportMany (asset.ts) throws the same typed error as
client.ts's single-node renderNode, but wasn't labeled — so
cli_error.endpoint would silently come back undefined for the
flow that most heavily exercises /v1/images.
2026-07-13 21:41:55 +00:00
Vance Ingalls 4ca1552e20 fix(core,cli): attribute figma REST failures to the endpoint that failed
cli_error had no way to tell which figma REST call (images, files_nodes,
variables_local, styles, ...) actually hit RATE_LIMITED/FORBIDDEN/etc, so
the dashboard could see failures spike but not which call caused them.

FigmaClientError now carries a low-cardinality endpoint label (never the
raw fileKey/nodeId), threaded through to cli_error's endpoint property.
2026-07-13 21:41:55 +00:00