mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 10:14:30 +00:00
1cf601202d6c7bd032cf56adef2a9b7d357fb3ed
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3a717fa719 |
fix(parsers,sdk,studio-server,studio): unify hf-id space across preview, disk, and SDK session (#1981)
* fix(parsers,sdk,studio-server,studio): unify hf-id space across preview, disk, and SDK session Root-causes the setTiming element_not_found resolver-shadow divergence class: timeline edits carry hf-ids read from the live preview DOM, but the preview minted ids AFTER rewriting attributes (and never persisted them for sub-comps), while the SDK session mints from the raw file — content-keyed minting then yields different ids for the same element. Template-based comps were worse: the SDK excluded the whole <template> subtree, so the session had zero elements and every edit diverged. - parsers: ensureHfIds now descends into <template> subtrees (linkedom's querySelectorAll does not), minting and pinning inner ids - sdk: buildRoots/buildElement treat <template> as a transparent container, and resolution (resolveScoped, animation-id map) searches template subtrees via querySelectorAllDeep — template comps now model, resolve, and edit - studio-server: the sub-comp preview route persists hf-ids to the raw file BEFORE the rewrite pipeline (mirrors the main route), pinning one id space across served DOM, disk, and SDK session - studio: resolver-shadow skips structurally-empty sessions (no event, no attempt) and tags fail-open emissions with sourceReadFailed so read errors are distinguishable from unwired readers in telemetry Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(parsers,sdk,studio-server,studio): scope template descent, guard persist route Addresses the 10 verified findings from the PR #1981 review: - Restrict template transparency to COMPOSITION templates (<template data-composition-id>) everywhere — ensureHfIds, SDK buildChildren, querySelectorAllDeep. A plain <template> (runtime clone-source) keeps its old fully-excluded behavior: stamping its interior would duplicate one persisted id across every runtime clone, and modeling it would show phantom timeline clips. - Guard the sub-comp persist: only .html files (the wildcard route can serve any project path — stamping an SVG corrupted it on disk), try/catch the read (file-removed race becomes 404, not 500), salt the etag (v2) so pre-fix cached clients don't 304 past the id pin, and thread the stamped content into buildSubCompositionHtml so served ids match the mint even when the disk write is skipped. - Rewrite querySelectorAllDeep as a document-order DOM walk — appending template matches after top-level matches made duplicate-id tiebreaks disagree with the preview's unwrapped DOM (wrong-element edits). - Recurse sourceMutation.querySelectorAllWithTemplates so server-side ops resolve ids at any template depth, matching SDK resolution. - Replace the empty-session silent skip with ONE tagged session_empty event per session — silence would blind the tripwire to exactly the modeling-gap class that exposed the template bug. Attempts stay uncounted (an unmodelable comp can't cut over). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(studio-server): close TOCTOU in sub-comp hf-id persist (CodeQL js/file-system-race) Replace the route-level stat/read/persist sequence with stampFileHfIds: validation (fstat), read, mint, and write-back all go through ONE open file descriptor (O_NOFOLLOW where supported), so the path cannot be swapped between validation and write. Falls back to read-only stamping when the file isn't writable — content-keyed minting means the SDK derives the same ids from the same bytes even without the disk write. Addresses miguel-heygen's blocking review on PR #1981. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(studio-server): linear-time template-attr match (CodeQL js/polynomial-redos) promoteTemplateCompositionId's single-pattern regex backtracked polynomially on crafted input. Two-step match: grab each <template> open tag linearly, then find data-composition-id within that short tag text. Same semantics (first template carrying the attr wins). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c5d725abd0 |
fix(sdk,studio): resolve animation ids from parsed script, not just DOM-matched elements (#1957)
recordAnimationResolverParity reported a false animation_not_found divergence for any tween whose selector doesn't currently CSS-match a live DOM element, because it only checked el.animationIds (DOM-gated). The real server-side op it shadows resolves purely from the parsed script. Adds Composition.getAllAnimationIds() as a DOM-independent id set and checks it too, matching the server's actual resolution behavior. |
||
|
|
fe821ff8bc |
feat(studio): add resolver-shadow attempt counter for soak-gate denominator (#1926)
* feat(studio): add resolver-shadow attempt counter for soak-gate denominator Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(studio): harden attempt-counter exception safety and tab-hide flush ordering PR review feedback (4 reviewers): recordAttempt() sat outside the try/catch in all three emit functions, so a throw inside it (e.g. setInterval/ addEventListener failing in a non-standard environment) would break the "never throws" contract. Also, the new visibilitychange listener races studioTelemetry.ts's own tab-hide handler — whichever fires first can beacon the queue before or after this module's rollup lands in it, silently dropping the attempt count for short sessions closed before the 5-minute timer fires. Fixes: move recordAttempt() inside each function's try block; export flushViaBeacon() from studioTelemetry.ts and call it explicitly after queuing the rollup, so delivery no longer depends on listener registration order; capture the visibilitychange handler by reference so __resetAttemptSchedulingForTests() actually removes it instead of leaking a duplicate on re-arm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
69b927a300 | fix(studio): single-flight reorder source read, tag loose-match sourceHfIdCount, brace style | ||
|
|
c215a20c8e |
fix(studio): extend resolver-shadow runtime-node filter to timing/GSAP/reorder chokepoints (#1839)
* fix(studio): source-filter recordResolverParity (async + sourceHfIdCount) * refactor(studio): extract cutover-eligibility checks to unblock filesize cap * fix(studio): pass on-disk source to recordResolverParity at timing/gsap/delete chokepoints * fix(studio): void the reorderElements recordResolverParity call (now async) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(studio): resolve max-review findings (telemetry race, await, reorder filter, count invariant) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a4eacaec37 |
fix(studio): filter runtime-generated nodes from resolver-shadow telemetry (#1795)
The sdk_resolver_shadow tripwire flagged element_not_found for nodes a composition <script> creates at runtime (caption word/group spans, etc.). These have no static data-hf-id, so the SDK session (a static parse) cannot model them by design; the divergence is noise, not a resolver bug. - Runtime-node filter: suppress element_not_found when the resolved hf-id is absent from the on-disk source. An id PRESENT in source but missing from the session stays flagged (the genuine v0.6.110-class resolver divergence). - Add sessionElementCount to all element_not_found / animation_not_found emits (0 = empty/broken session, >0 = element-specific). - Add sourceHfIdCount to emitted element_not_found: =1 = static node the parse dropped (foreign-content exclusion / sub-comp gap), >1 = duplicate-id resolver ambiguity. Scoped to the DOM-edit path. Telemetry-only; no disk writes, no edit change. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5fb5153e4e |
fix(studio): shadow resolves bare leaf via dispatch path, not getElement (#1552)
The resolver-parity shadow tripwire decided element_not_found via Composition.getElement, which is canonical-only for a bare id by design (removeElement/getElement must agree on the same instance — the session.subcomp "ambiguous bare id" suite). But the cutover persist path dispatches the studio's bare data-hf-id and resolves it via resolveScoped, which locates the leaf anywhere in the document (canonical preferred, else first match). So getElement under-resolved a bare leaf living inside an inlined sub-composition (scopedId "host/leaf"), and the shadow emitted a false element_not_found the real dispatch path never hits — ~445 such events in PostHog, all one user editing an inlined yt-lower-third. Add resolveSnapshot in the shadow mirroring resolveScoped, used at all three element_not_found sites. getElement is unchanged (its contract is correct). Regression tests cover the inlined-sub-comp leaf case. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
933b88ec33 |
feat(studio): resolver-parity shadow tripwire (decoupled telemetry) (#1547)
* feat(studio): resolver-parity shadow tripwire (decoupled telemetry) New sdkResolverShadow.ts module: checks whether the SDK session resolves the same element id the server path would address, then verifies value parity after in-memory dispatch. Emits sdk_resolver_shadow telemetry on divergence. Decoupled from STUDIO_SDK_CUTOVER_ENABLED via its own flag STUDIO_SDK_RESOLVER_SHADOW_ENABLED (default false). Headline signal: element_not_found — the resolver divergence class that caused the v0.6.110 regression. Writer-parity suite (#1533) cannot see this class; this tripwire exists specifically to catch it. All 12 acceptance-test-plan items pass (A1-A3, B4-B6, C7-C10, D11, E12). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(studio): default STUDIO_SDK_RESOLVER_SHADOW_ENABLED to true Tripwire should run out of the box — operators opt out, not in. * fix(studio): resolver shadow must not mutate the live session (restore via inverse patches) The shadow runs on the SAME sdkSession the cutover path uses, one line before sdkCutoverPersist. sdkResolverShadowCheck dispatched the edit into that session to read values back but never undid it — so with the shadow enabled the edit was pre-applied, and sdkCutoverPersist then saw before === after and silently fell back to the server path. Enabling the tripwire disabled cutover. Fix: capture the inverse patches of the shadow dispatch (session.on("patch")) and applyPatches them to restore the session before returning, on every path (success, dispatch_error, element_not_found after dispatch). The session ends the check exactly as it started; cutover's before/after diff is unaffected. Tests: B5 now asserts the live session is restored (color back to original, not left on the shadow value) and B5b proves a cutover-style before/dispatch/after diff still fires after a preceding shadow run. The earlier B5 used two separate sessions and so never exercised the shared-session path the bug lived in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): extend resolver shadow to timing/delete/gsap-add chokepoints The shadow only ran on the DOM-edit path (inline-style/text/attribute via onTrySdkPersist) — blind to the rest of the cutover surface, which is where the resolver bugs that motivated it actually live (v0.6.110 was a GSAP property op; CF2 #15/#16 were timing-resolver bugs). On-for-everyone telemetry that only sees style/text/attr edits misses the riskiest paths. Adds a read-only element-resolution tripwire (recordResolverParity) — emits the headline `element_not_found` signal when the SDK can't resolve a target the server path is addressing, with NO dispatch/mutation. Wired before the cutover gate (decoupled) in the element-targeted chokepoints: sdkTimingPersist, sdkDeletePersist, and sdkGsapTweenPersist's add op. To avoid a circular import (sdkResolverShadow imported patchOpsToSdkEditOps from sdkCutover; sdkCutover now imports recordResolverParity from sdkResolverShadow), patchOpsToSdkEditOps moves to a neutral sdkOpMapping.ts that both import from. animationId-resolving GSAP ops (set/remove tween, keyframe ops, deleteAllForSelector) resolve an animation, not an element, so element-resolution parity doesn't apply — left as a follow-up (separate animation-resolution signal). Tests: recordResolverParity emit-on-divergence / parity-no-op / flag-off-no-op / read-only (no mutation). Full studio suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): resolver shadow covers animationId GSAP ops (animation_not_found) Extends the tripwire to the GSAP-edit surface that resolves an animationId rather than an element: setGsapTween/removeGsapTween, addGsapKeyframe, removeGsapKeyframe, removeGsapProperty, removeAllKeyframes, convertToKeyframes. Adds recordAnimationResolverParity — read-only, emits the new `animation_not_found` kind when the SDK can't resolve the animationId the server GSAP path is addressing. The SDK's resolvable animation ids are the located ids attached to elements (buildAnimationIdMap), so a target absent from every element's animationIds is a resolver divergence. No dispatch, no mutation. Wired centrally in dispatchGsapOpAndPersist via an optional resolverTarget arg (runs before its cutover gate); sdkGsapTweenPersist records inline before its own leading gate (set/remove → animation parity, add → element parity). deleteAllForSelector resolves by selector, not an id — left out. Tests: animation_not_found on unresolved id / parity no-op on a real located id / flag-off no-op. Full studio suite green; no circular dep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): resolver shadow review fixes — divergence-only emit + restore in finally Addresses PR #1547 review (Miga, Rames): - #1 (medium): runResolverShadow emitted `sdk_resolver_shadow` on every edit, including parity (mismatchCount 0) — a PostHog event per style/text/attr edit at default-ON. Now emits only on divergence, matching recordResolverParity / recordAnimationResolverParity. Parity is silent across all three paths. - restore() moved into a `finally` in sdkResolverShadowCheck: if checkOpValue throws between dispatch and restore, the patch listener no longer leaks and the shared session is always undone (the cutover-coupling failure mode this module guards against). dispatch errors still return dispatch_error. - Comment on why batch is compatible with per-op inverse capture (a future SDK refactor that coalesces batch must keep emitting inverse patches). Tests: A2 now forces a divergence to emit; A2b pins parity-is-silent; A4 covers null/undefined hfId no-op. 26 shadow tests, full studio suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |