Commit Graph
1921 Commits
Author SHA1 Message Date
Miguel Angel Simon Sierra 39f33bd3a3 fix(studio): draw the crop UI in the element's rotated frame
Selecting a rotated (cropped) element appeared to straighten it: the crop
dim and dashed window were drawn on the axis-aligned bounding box, so the
bright window was a straight rectangle and the element's rotated corners
were masked to near-black — while the DOM transform was untouched.

clip-path applies in the element's LOCAL frame, before its transform, so
the crop visualization now renders inside a container rotated with the
element: readElementCropFrame decomposes the computed 2D matrix into
angle + per-axis scale (element scale finally factored into the px
mapping too) and 3D/unparseable transforms keep the axis-aligned
presentation. Pointer deltas rotate into the element frame before the
inset resolvers, so edge/pan drags track the rotated handles correctly.
2026-07-11 15:34:41 -04:00
Miguel Angel Simon Sierra 5d1cafff82 fix(studio): address review findings on graded-element editing
Review follow-ups (both reviewers, all findings):

- resize captures scope to the resize group: convert-to-keyframes
  resolvedFromValues and the whole-offset backfill pass the group filter,
  so an opacity-touching intro tween can't ride into a converted scale
  tween (the rotation fix's contract, now uniform across intercepts)
- commitStaticSet resolves every group's target set BEFORE committing and
  coalesces groups landing on the same legacy mixed set into one commit —
  the second commit can no longer chase a stale group-derived id
- installAuthoredOpacityCapture also stamps an element the moment it GAINS
  data-color-grading at runtime (attributeFilter), not just at insertion
- both writer twins now share the same emitted-set dedupe shape
- applySoftReload's positional tail becomes a SoftReloadOptions object
- readAllAnimatedProperties builds the group-filtered key set immutably
  instead of deleting from the set mid-iteration
- applyAuthoredInlineOpacity documents the priority-lossy round-trip
- the marquee hit-test reads activeCompositionPathRef like its neighbors

New tests: resize intercept (scale route + group filter + non-uniform
longhands), after-write-HTML / stamp / empty-stamp opacity restore, the
no-op-commit-with-missed-instant-patch soft-reload contract, and the
runtime-gained-grading stamp.
2026-07-11 15:18:49 -04:00
Miguel Angel Simon Sierra 67cfae2587 refactor: address review nits
- merge gsapResizeIntercept's duplicate module imports
- move the core-constant imports to the file headers (picker, domEditingDom)
- justify the cross-realm HTMLElement casts (iframe-realm nodes fail
  instanceof; access is duck-typed)
2026-07-11 14:58:21 -04:00
Miguel Angel Simon Sierra b7fddd548a fix(studio): crop restore value is owned by the lifted element's gesture
The deselect restore read a ref recomputed from RENDER state — on a direct
A→B selection switch, state re-syncs to B before A's effect cleanup runs,
so after a committed crop gesture A was restored with B's crop string (or
lost its crop when B had none). The committed value is now written at
gesture-commit time (tri-state: none committed / crop removal / the exact
committed string), so cleanup never touches render state. Adds component
tests for lift/restore ordering, including the direct A→B switch and the
uneditable-clip stand-down.
2026-07-11 14:58:17 -04:00
Miguel Angel Simon Sierra fe52966728 fix(lint): anchor the opacity-zero probe so 0.98 stops matching
gsap_from_opacity_noop matched 'opacity: 0' as a prefix — an authored
'opacity: 0.98' triggered the rule. One boundary-anchored predicate now
owns the exactly-zero test for both block and inline declarations,
including a block's last declaration without a trailing semicolon.

domEditingDom now imports the grading contract attribute from core
instead of re-declaring the literal.
2026-07-11 04:08:07 -04:00
Miguel Angel Simon Sierra 90d77d016f fix(parsers): never emit duplicate vars keys in either writer
A parsed timeline set carries immediateRender in extras; the recast
statement builder ALSO pushed the flag unconditionally, so every
split/re-add of a set wrote 'immediateRender: true, immediateRender:
true' into the file, doubling on each pass. Both writer twins (recast +
acorn) now emit each vars key exactly once, properties winning over
extras, and reconcileEditableProps skips newProps keys it already
preserved.
2026-07-11 04:08:04 -04:00
Miguel Angel Simon Sierra 066c3dae37 fix(studio): route panel property commits to a group-owning set
commitStaticSet merged every property into the FIRST set found for the
selector: a panel W edit on an element whose only set was positional
produced tl.set("#el",{x,y,width}) — a mixed-group set the split
machinery exists to prevent — labeled "Set 3D transform" in undo.

Commits now batch per property group into a set that owns that group
(exact group match, then a mixed set already carrying the group, then a
fresh off-timeline gsap.set), with undo labels derived from the group
(Move layer / Resize layer / Rotate layer / Set 3D transform).
2026-07-11 04:08:00 -04:00
Miguel Angel Simon Sierra cbfb6ba943 fix(studio): crop tool stands down for clips it cannot edit
The always-on crop lifted EVERY selected element's clip-path and restored
only what it could parse as a px inset — selecting an element with a
circle/polygon/percentage clip visually un-clipped it, and deselecting
deleted the authored clip outright.

readElementCropInsets is now tri-state (zeros = no clip, null = a clip
the tool can't represent): uneditable clips get no lift and no handles,
and the lift restores the pre-lift inline value verbatim unless a crop
gesture actually committed.
2026-07-11 04:07:57 -04:00
Miguel Angel Simon Sierra 3525c7ff52 fix(studio): correct gesture commits for scaled and graded elements
- resize on a scale-driven element commits per-axis scale (scaleX/scaleY
  for non-uniform drags) with keyframe normalization to the longhands, and
  clears the width/height draft so size can't double-apply; the intercept
  moves to gsapResizeIntercept.ts
- the drop frame applies the corrected position synchronously in the same
  microtask chain as the soft reload (no network-window jump), and the
  draft pins the anchor through accumulated moves on scaled elements
- gesture size/position math divides by the element's own content scale
- convert-to-keyframes resolves current values through the property-group
  filter for ALL capture passes (opacity/rotationX from unrelated tweens
  no longer leak into a rotation commit), and a grading-hidden source's
  opacity is read from its canvas, not the inline hide
- canvas pointer-down confirms the hover target with a synchronous
  hit-test before starting a marquee (stale-hover race lost selections)
2026-07-11 04:07:06 -04:00
Miguel Angel Simon Sierra 5cc14c2221 fix(studio): stop tween re-inits from baking runtime opacity transients
Editing commits made elements vanish or dim permanently: invalidating the
whole timeline (or re-running the composition script on soft reload) made
GSAP re-capture tween bounds while runtime transients were live — the
grading hide's opacity 0, or a mid-flight tween value — so from()/to()
bounds got poisoned and the element rendered invisible from then on.

- patch only the edited tween in place, never timeline.invalidate()
- soft reload restores every animated element's authored inline opacity
  (after-write HTML first, parse-time stamp as fallback) before the script
  re-runs and re-captures
- a paired x/y commit whose second half is a no-op (changed=false) still
  applies its instant patch, so panel edits reflect without deselecting
2026-07-11 03:34:21 -04:00
Miguel Angel Simon Sierra 3147c8e063 fix(core): capture authored inline opacity at parse time and follow source geometry
The color-grading engine hides its source element with inline
'opacity: 0 !important', so any code that later reads or re-captures the
element's opacity sees the hide instead of the authored value. Stamp the
authored inline opacity on every [data-color-grading] element at document
parse time (MutationObserver installed at runtime-bundle eval, before any
composition script runs) and prefer the stamp when hiding/restoring.

Also re-sync the grading canvas when the source's inline geometry mutates
(rAF-throttled style observer): a studio drag moves the source via its
transform, which fires no media event, so the visible canvas froze in
place until the next seek.
2026-07-11 03:33:32 -04:00
Miguel Angel Simon Sierra 1d97ddaf8c chore: release v0.7.50 2026-07-10 18:48:40 -04:00
Miguel Ángel 00d059b39f Merge pull request #2138 from heygen-com/feat/check-command
feat(cli): hyperframes check — the single-session verification gate
2026-07-10 18:47:23 -04:00
Miguel Angel Simon Sierra 659cb6a236 feat(cli): --frame-check accepts a severity/seek/tol spec
The pipeline already carried FrameCheckOptions; only the flag was
boolean, which meant a pipeline caller tuning severity or seek points
would have them silently dropped — the two sides only agreed because
today's caller happens to match the defaults. Bare --frame-check keeps
the defaults; the value form mirrors --caption-zone's grammar, freezing
the contract before a release pins it.
2026-07-10 18:04:09 -04:00
Miguel Angel Simon Sierra 67cdf0fdb7 fix(cli): address review — clip-duration audit in check, failure classing, crop observability
Port validate's per-media-element clip audit into check's session
(clip_media_fit findings): an intrinsic duration meaningfully shorter
than the data-duration slot silently shortens the slot at render time,
and neither lint nor the runtime listeners can see it. A linter crash
now reports as check_lint_failure instead of masquerading as a runtime
failure. Finding-crop capture failures stay non-gating but emit a
stderr note and a telemetry error event so rollouts can measure the
second-session failure rate.
2026-07-10 17:53:04 -04:00
Miguel Ángel 16ab8b2935 fix(core): preserve playhead during volume probing (#2143) 2026-07-10 17:18:16 -04:00
Miguel Ángel 718c67b387 fix: escape NUL bytes in HFMASK regex (Bun blank renders) + Windows junction for studio preview links (#2140)
* fix(core): escape NUL delimiters in HFMASK mask token and restore regex

Raw 0x00 bytes in the maskInertRegions token and restore regex made
timingCompiler.ts binary to git and shipped raw NULs into dist/cli.js.
Bun's transpiler (<= 1.3.11) corrupts raw NULs in regex literals into
literal backslash-uFFFD text, so restore never matched: every masked
<style>/<script> region was dropped, the player never initialized, and
bunx renders produced blank white frames showing HFMASK tokens.

Use \u0000 escapes instead, which survive any transpile layer, and add
a byte-level regression test (behavior is identical under Node, so only
a byte check catches this).

Fixes the first half of #2139.

* fix(cli): use NTFS junctions for studio project links on Windows

linkProjectIntoStudioData called symlinkSync(dir, path, "dir"), which
needs Developer Mode or elevation on Windows, so preview and dev in
local-studio mode died with EPERM for default-configured users.
Junctions need no privilege, work for directories, and keep the live
write-back the studio depends on (a copy fallback would decouple the
studio from the real project). Covers both preview and dev, which share
the helper.

Fixes the second half of #2139.
2026-07-10 16:22:50 -04:00
Miguel Angel Simon Sierra 96cb5e39dd test(cli): type the guard test's mock so the full typecheck passes 2026-07-10 15:06:20 -04:00
Miguel Angel Simon Sierra c169dbaa54 test(cli): import the page-function guard in its new unit test 2026-07-10 14:36:01 -04:00
Miguel Angel Simon Sierra 2ceb0683a8 fix(cli): page closures survive keepNames transpilation; format motion-blur
Running the CLI from source (tsx dev script, as CI's smoke job does)
transpiles with keepNames, which rewrites named inner functions in
serialized page closures into __name(...) calls — a helper that exists
in the Node bundle but not in the browser realm. The unified seek
closure was the first validate-path page function with named inner
functions, so 'hyperframes validate' threw '__name is not defined' in
CI while the dist build worked. Every session opener now installs a
no-op __name shim via evaluateOnNewDocument before any page script
runs, immunizing all serialized closures regardless of build mode.
2026-07-10 14:30:54 -04:00
Miguel Angel Simon Sierra 4b03866c02 fix(cli): contrast gate judges only readable content; examples pass check
Three filters keep the escalated contrast gate honest, each surfaced by
running check across the registry examples:
- data-layout-ignore (the layout audit's existing decorative opt-out)
  now also excludes set-dressing text from the contrast audit — one
  vocabulary for 'not copy a viewer must read'.
- Text that has (nearly) left the canvas is skipped: sampling a clamped
  off-canvas box reads border pixels and produced the classic false
  white-on-white (a cursor exiting the frame).
- Contrast failures follow the same persistence rule as layout findings:
  observed at a single sample of a multi-sample sweep demotes to
  warning; held failures gate.

Example fixes the sweep exposed: motion-blur's 21 deliberately-dim rail
labels are marked decorative (its vivid labels pass on their own);
nyt-graph's subtitle and source-note adopt the gate's suggested
compliant gray; decision-tree's declared duration drops 15s to 10s —
its content ends at ~9.5s and every render shipped a blank white tail.
2026-07-10 14:08:31 -04:00
Miguel Angel Simon Sierra cea3458016 refactor(cli): single-source-of-truth pass over the check branch
Every duplicated decision gets one owner: rectToBbox lives in checkTypes
(was verbatim in pipeline and browser layers); the audit seek tuning is
one exported AUDIT_SEEK_OPTIONS consumed by check and the deprecated
inspect path; zoom padding/scale defaults export from the capture module
instead of re-literalized in three files; the optional run_id property
is built by one helper across all three telemetry events; check's
--max-transition-samples parsing reuses its own positiveInteger helper;
validate drops a leftover re-export and redundant explicit-default args
go away.

Tests: the contrast candidate round-trip gains a real integration
anchor (the actual browser script eval'd in-page, a wrapper asserting
finish receives the page-script bbox shape) replacing regex-over-source
as the primary guard; the redundant geometry source-golden and a
duplicated deprecation-envelope assertion are dropped.
2026-07-10 13:52:31 -04:00
Miguel Angel Simon Sierra cf7c1d7609 docs(cli,skills): teach check as the canonical verification gate
Scaffolded projects' npm run check now invokes the single check command
instead of chaining lint, validate, and inspect (three Chrome boots
become one). The CLI skill, its correctness reference, the entry skill's
capability map, README/docs catalog rows, the Mintlify CLI page (new
check section, deprecation banner on inspect), template CLAUDE/AGENTS
(byte-identical), root CLAUDE/AGENTS, and every creation-workflow skill
that taught the old sequence all point at check. snapshot keeps its
standalone sections; validate/inspect stay documented as deprecated
aliases with their check equivalents.
2026-07-10 13:30:09 -04:00
Miguel Angel Simon Sierra 94f6de8b8d feat(cli): persistence-tiered findings, frozen-sweep guard, occlusion coverage
Layout findings now distinguish held defects from entrance/exit
transients: a dynamic issue seen at a single grid sample demotes to
info, while content_overlap held across two-plus samples (or 500ms+)
promotes to error, resolving the long-standing re-promotion TODO. Static
compositions keep their severity. check gains a sweep_static error when
a 3s+ composition shows zero geometry change across every sample (a
frozen timeline makes every green verdict unreliable); skipped when the
motion sidecar already reported motion_frozen. text_occluded findings
carry a coveredFraction; atomic labels (short, no whitespace) flag on
any cover while prose needs 15%, since partial cover changes what a
short label reads as.

Deprecation-test scaffolding consolidates into deprecationTestHarness;
tier logic and logger tests restructured under the complexity gate
without suppression markers.

Detection mechanics adapted from Adam Rosler's open-sourced
visual-linter design (github.com/Adam-Rosler/hyperframes-visual-linter-design);
the elementFromPoint paint model, opt-out attributes, and single-audit
architecture are unchanged.
2026-07-10 13:27:52 -04:00
Miguel Angel Simon Sierra f4cef54b8b feat(cli): snapshot --zoom and per-finding crops on check --snapshots
snapshot --zoom <selector|x,y,w,h> + --zoom-scale (default 3) crops via
Puppeteer clip at raised deviceScaleFactor — density changes, layout
never does. Selector resolves per frame with 24px padding; no match is
a loud error, and a frame whose clamped region is a sliver (element
collapsed or animated off-canvas) is skipped with a stderr note rather
than written as a useless few-pixel image.

check --snapshots additionally writes finding-NN-<code>.png crops for
error findings with bboxes (cap 12, deterministic re-seek in a second
session) and draws labeled annotation boxes on overview frames via a
transient overlay injected only after audits complete. Skill reference
gains the zoom workflow: check reports a finding, zoom into it, fix,
re-check.
2026-07-10 13:27:52 -04:00
Miguel Angel Simon Sierra 58f45ef758 feat(cli): deprecate validate, inspect, layout in favor of check
One stderr notice per invocation and _meta.deprecated: true in JSON mode
(shared helper next to withMeta; layout owns both inspect and layout via
createInspectCommand). Help descriptions gain the pointer. No behavior
change; removal ships separately once migration telemetry says usage
has decayed.

fix(producer): route info/debug logs to stderr — the compiler's
'Localized remote media' line was landing on stdout ahead of validate's
--json payload, breaking every piped consumer. Diagnostics now share
stderr with warn/error; render progress uses its own channel.
2026-07-10 13:27:52 -04:00
Miguel Angel Simon Sierra 3a02942a03 feat(cli): run-ID telemetry correlation and check breakdown event
HYPERFRAMES_RUN_ID (trimmed, 128-char cap) attaches as run_id to the
generic cli_command / cli_command_result events, absent when unset, so
an orchestrator setting it per design element can group a verify loop's
invocations in analytics. check additionally emits one check_report
event per invocation (including lint-short-circuited and failing runs):
gate booleans, per-class error/warning counts, launch/seek/contrast
phase timings, sample counts, ok and exit code. Timings stay internal;
no command output changes.
2026-07-10 13:27:52 -04:00
Miguel Angel Simon Sierra 7ab6c2b7a2 feat(cli): caption-zone and frame-check gates on check
Ports the EF bridge's captionZone and frameCheck semantics as opt-in
flags so the bespoke bridge can be retired: --caption-zone takes
fractional band geometry (x0;y0;x1;y1) with optional severity routing
and seek points, defaults matching the bridge (caption seek [1], frame
seek [0.5], 2px tolerance, 0.05 opacity floor, 4px minimum size, 0.95
full-frame exclusion, center-in-band comparison, tag|text dedup).
--frame-check adds media bounds detection (img/svg/video/canvas) the
always-on text canvas_overflow never covered, reusing overflowFor.
Breach floor: max(120px, 6% of min canvas dimension). Band math derives
from the composition's own canvas, portrait included. Both gates off by
default; plain check output unchanged.
2026-07-10 13:27:52 -04:00
Miguel Angel Simon Sierra 7d6d41361b feat(cli): add check — single-session verification gate
One command, one Chrome boot: in-process lint gate (browser skipped on
lint errors), passive runtime capture wired before navigation, layout +
motion + contrast audits over one seek grid, optional --snapshots
persisting the contrast-pass screenshots. Aggregated --json envelope
{ok, lint, runtime, layout, motion, contrast, snapshots}; findings carry
selector/data-*/source-file/bbox/time anchors, contrast findings include
fg/bg, measured vs required ratio, and a compliant color suggestion.
Contrast AA failures gate the exit code (they were warning-only in
validate); --strict gates warnings.

Contrast candidates round-trip verbatim between __contrastAuditPrepare
and __contrastAuditFinish: the page script owns their shape (bbox w/h),
and normalizing them Node-side made every sample rect NaN — the audit
reported zero checked elements as green. Regression-pinned in
check.test.ts; E2E on a low-contrast fixture now exits 1 with 8 findings.

Measured on kinetic-type: check 5.6s vs 23.0s for sequential
validate + inspect + snapshot.
2026-07-10 13:27:52 -04:00
Miguel Angel Simon Sierra feb256df8a refactor(cli): unify seek/settle and Chrome launch across browser commands
seekCompositionTimeline becomes the single seek implementation with
per-caller settle options (rAF mode, font wait, settle sleep), replacing
the divergent local seekTo copies in validate and layout. All three
launch paths now build args via the engine's buildChromeArgs; screenshot
paths keep the engine's software-GPU default for deterministic output.

inspect gains one transient content_overlap warning on product-promo
(t=12.22s): the gsap.ticker.tick flush samples timeline state the old
layout seek missed.
2026-07-10 13:27:52 -04:00
Vance Ingalls 6152437d2a chore: release v0.7.49 2026-07-10 09:57:48 -07:00
Vance Ingalls a8f242e615 Merge pull request #2112 from heygen-com/vi/figma-scopes-retry
fix(figma): auth/retry/batch hardening, mapper fidelity, skill routing, setup docs
2026-07-09 19:56:52 -07:00
Vance IngallsandClaude Opus c8eff1a4ba fix(core,cli): figma IMAGE fills dropped, rasterize double-paint, tokens false-success
nodeToHtml routed rasterize eligibility off node.type alone, so a
RECTANGLE/FRAME with an IMAGE fill fell through to the generic <div>
path — fillCss() has no IMAGE case, so it rendered an empty box.
IMAGE-filled nodes now route to rasterize like vectors, regardless of
node.type.

Rasterized nodes (vectors, now image fills too) were also getting
their own fill/corner-radius CSS applied on top of the already-
rendered <img> — a flat color block behind/around the real art,
flattening non-rectangular shapes into rounded rects. decorationCss
now skips background and corner-radius/clip for rasterized nodes;
opacity and effects still apply since those aren't baked into the
export.

tokens.ts's styles-fallback path hardcoded entries: [] regardless of
how many published styles were actually found, so the CLI printed
"recorded published style metadata instead" even when styles()
returned zero results. Added styleCount to the result so the message
reflects what happened, and points at the MCP get_variable_defs
fallback when there's nothing to fall back to.

Co-Authored-By: Claude Opus <noreply@anthropic.com>
2026-07-09 19:23:19 -07:00
Vance Ingalls 8c996100cf Merge pull request #2114 from heygen-com/feat/position-edits-render-reapply
fix(core,producer): render SDK position edits in producer pipeline
2026-07-09 19:22:55 -07:00
Vance Ingalls 70ac80b08b fix: clear fallow and windows artifact checks 2026-07-09 18:55:11 -07:00
Miguel Ángel 3fd340f6ba chore: release v0.7.48 (#2118) 2026-07-09 21:49:58 -04:00
Miguel Ángel ef38321d5d fix(cli): restore tts --text-file compatibility (#2117) 2026-07-09 21:45:35 -04:00
Vance Ingalls 960c31f668 fix(core): make generated artifact formatting deterministic 2026-07-09 18:36:22 -07:00
Miguel Ángel 7bd8c0942e fix(cli): keep slow browser installs from losing their lock [P0] (#2116)
* fix(cli): keep slow browser installs from losing their lock

* refactor(cli): simplify browser install lock policy

* fix(cli): keep browser lock heartbeat failures non-fatal
2026-07-09 21:30:30 -04:00
Vance Ingalls 0971f3b2a7 chore(core): refresh position edits render artifact 2026-07-09 18:21:32 -07:00
Vance Ingalls d2f32c831c fix(core): narrow wrapped seek function type 2026-07-09 18:09:45 -07:00
Vance IngallsandClaude Fable 5 67a4fb6b70 fix(core): use execFileSync for generated-file format step
CodeQL: shell command built from environment values — the oxfmt
invocation interpolated a filesystem-derived absolute path into a shell
string. execFileSync with array args avoids the shell entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 17:52:29 -07:00
Vance Ingalls 68ae4e5160 fix(core,producer): harden render reapply wiring 2026-07-09 17:38:31 -07:00
Vance Ingalls 4e3d639e1a fix(core): preserve position edit fold detection during seeks 2026-07-09 17:38:31 -07:00
Vance Ingalls 85bab88afb fix(core,producer): render SDK position edits in producer pipeline 2026-07-09 17:38:31 -07:00
Miguel Angel Simon Sierra 3aa1cf0d83 chore: release v0.7.47 2026-07-09 20:34:59 -04:00
Vance IngallsandClaude Fable 5 43af582534 docs(figma): sync guide scopes + error table; gate batch line, regen index in finally
Addresses @miguel-heygen's review on #2112:
- BLOCKER: docs/guides/figma.mdx now matches the shipped code/skill —
  adds the Library content: Read-only scope row (+ corrects the
  'falls back, expected' line that was false without it), and the
  troubleshooting table now says bad PATs surface as 403 Invalid token
  (not 401), names the scope in FORBIDDEN, and documents RATE_LIMITED retry.
- nit: the batch summary line no longer claims '1 figma request' when every
  node was a cache hit — says 'all reused from cache — no figma request'.
- nit: index.md regen moved to a finally, so a mid-batch RENDER_FAILED
  leaves index.md consistent with the nodes that did freeze.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 16:17:39 -07:00
Vance IngallsandClaude Fable 5 87e2a70f9a fix(core,cli): address PR review — consistent 403 error shape, cap Retry-After, URL-safe ref split
Rames's inline findings on #2112:
- forbiddenError now RETURNS in every branch (BAD_TOKEN no longer throws
  inside) so the caller's single throw covers all cases — no mixed
  throw/return contract for a future wrapping caller.
- retryAfterMs capped at 60s: a spec-legal Retry-After: 3600 no longer
  silently blocks the CLI for an hour before RATE_LIMITED.
- asset ref gathering extracted to gatherAssetRefs() and made URL-safe:
  bare fileKey:nodeId tokens comma-split, but a figma URL with commas in
  its query (multi-select node-id=1:2,3:4) is kept whole.
- Documented in SKILL that 429 retry lives in the shared request path, so
  EVERY read endpoint retries (not just asset) — blast-radius note the
  reviewer asked for. variables intentionally still retries: its fallback
  is REQUIRES_ENTERPRISE-only, and a 429 there is transient, not a gate.

Tests: retry-cap (3600→60000), non-styles endpoint retry, gatherAssetRefs
URL-vs-bare split. client 24, cli asset 11.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 15:31:11 -07:00
Miguel ÁngelandClaude Opus 4.8 3b93f516b4 feat(media-use): use CLI free HeyGen usage (#2027)
* feat(media-use): use CLI free HeyGen usage

* fix(media-use): address #2027 R1 nits — gate cli-source header to OAuth, export origin constant

- X-HeyGen-Source is now sent only on OAuth (Bearer) requests, not API-key ones —
  the backend ignores it for API-key traffic (normal billing), so it was dead
  metadata there. buildAuthHeaders + heygenAuthHeaders + tests updated.
- Export HEYGEN_CLI_ORIGIN_HEADER ("X-HeyGen-Client-Origin") for future cli:<origin>
  consumers.
- Document the deliberate paid/X4 confirm-before-call decision on heygen.tts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv

* refactor(cli): drop unused origin-header export, dedup auth-client tests

Fallow flagged 5 findings on this PR:
- major: HEYGEN_CLI_ORIGIN_HEADER was exported but never emitted or
  imported — speculative dead code ("future consumers"). Remove it; a
  real consumer can add the constant when one exists.
- 4x minor duplication in client.test.ts: fold the repeated
  `.rejects.toSatisfy(auth-code)` assertion into expectAuthCode(), and the
  repeated try/catch scrubbed-message assertion into expectRejectionMessage().

No behavior change; auth/client tests still 17/17.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 18:28:26 -04:00
Vance IngallsandClaude Fable 5 1d18b30625 test(cli): add renderNodes to tokens.test figma client mock
CI typecheck caught the tokens.test mock missing the new renderNodes
member on FigmaClient (asset/component mocks were updated, this one was
missed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 15:22:56 -07:00