mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): single-flight reorder source read, tag loose-match sourceHfIdCount, brace style
This commit is contained in:
@@ -294,7 +294,12 @@ export function useDomEditSession({
|
||||
// the SDK resolves each reordered element (the reorderElements op's targets).
|
||||
onReorderShadow: sdkSession
|
||||
? (targets: string[]) => {
|
||||
const reorderSrc = activeCompPath ? () => readProjectFile(activeCompPath) : undefined;
|
||||
// Single-flight: every target in one reorder batch shares the same file, so
|
||||
// memoize the read instead of firing one fetch per unresolved target.
|
||||
let reorderSrcPromise: Promise<string> | undefined;
|
||||
const reorderSrc = activeCompPath
|
||||
? () => (reorderSrcPromise ??= readProjectFile(activeCompPath))
|
||||
: undefined;
|
||||
for (const target of targets)
|
||||
void recordResolverParity(sdkSession, target, "reorderElements", reorderSrc);
|
||||
}
|
||||
|
||||
@@ -221,12 +221,13 @@ export function sdkGsapTweenPersist(
|
||||
"addGsapTween",
|
||||
gsapSrc ? () => gsapSrc(targetPath) : undefined,
|
||||
);
|
||||
} else
|
||||
} else {
|
||||
recordAnimationResolverParity(
|
||||
sdkSession,
|
||||
op.animationId,
|
||||
op.kind === "set" ? "setGsapTween" : "removeGsapTween",
|
||||
);
|
||||
}
|
||||
// Leading dark-launch gate so flag-off does no SDK touch (getElement) at all —
|
||||
// matches the other three chokepoints' discipline.
|
||||
if (!STUDIO_SDK_CUTOVER_ENABLED) return Promise.resolve(false);
|
||||
|
||||
@@ -273,6 +273,22 @@ describe("C. Resolver-parity detection", () => {
|
||||
expect(lastShadow()?.sourceHfIdCount).toBe(2);
|
||||
});
|
||||
|
||||
it("C8 sourceLooseMatchOnly: hfId matches source only as plain text, not a data-hf-id attribute", async () => {
|
||||
mockFlags.STUDIO_SDK_RESOLVER_SHADOW_ENABLED = true;
|
||||
const session = { getElement: () => null, getElements: () => [] } as unknown as Composition;
|
||||
// "hf-widget" appears only inside a class name, never as data-hf-id="hf-widget".
|
||||
const source = `<div class="hf-widget-container">no attribute match here</div>`;
|
||||
runResolverShadow(
|
||||
session,
|
||||
"hf-widget",
|
||||
[{ type: "inline-style", property: "color", value: "red" }],
|
||||
source,
|
||||
);
|
||||
const ev = lastShadow();
|
||||
expect(ev?.sourceHfIdCount).toBe(0);
|
||||
expect(ev?.sourceLooseMatchOnly).toBe(true);
|
||||
});
|
||||
|
||||
it("C10: unmappable op type produces no mismatch (excluded, not flagged)", async () => {
|
||||
const session = await openComposition(BASE_HTML);
|
||||
// "unknown-op" is not in MAPPED_OP_TYPES, so it must be silently excluded.
|
||||
@@ -423,6 +439,19 @@ describe("F. recordResolverParity", () => {
|
||||
expect(ev?.mismatchCount).toBe(1);
|
||||
expect(ev?.sourceHfIdCount).toBeUndefined();
|
||||
});
|
||||
|
||||
it("tags sourceLooseMatchOnly when hfId matches source only as plain text, not a data-hf-id attribute", async () => {
|
||||
mockFlags.STUDIO_SDK_RESOLVER_SHADOW_ENABLED = true;
|
||||
const session = await openComposition(BASE_HTML);
|
||||
// "hf-widget" appears only inside a class name, never as data-hf-id="hf-widget".
|
||||
await recordResolverParity(session, "hf-widget", "setTiming", () =>
|
||||
Promise.resolve('<div class="hf-widget-container"></div>'),
|
||||
);
|
||||
const ev = lastShadow();
|
||||
expect(ev?.mismatchCount).toBe(1);
|
||||
expect(ev?.sourceHfIdCount).toBe(0);
|
||||
expect(ev?.sourceLooseMatchOnly).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── G. recordAnimationResolverParity (GSAP animationId ops) ──────────────────
|
||||
|
||||
@@ -272,6 +272,10 @@ export function runResolverShadow(
|
||||
// every style/text/attr edit (the editor's chattiest path) at default-ON.
|
||||
if (mismatches.length === 0) return;
|
||||
const isElementNotFound = mismatches.some((m) => m.kind === "element_not_found");
|
||||
const strictCount =
|
||||
isElementNotFound && sourceContent !== undefined
|
||||
? countHfIdInSource(sourceContent, hfId)
|
||||
: undefined;
|
||||
trackStudioEvent("sdk_resolver_shadow", {
|
||||
hfId,
|
||||
// sessionElementCount > 0 + element_not_found = runtime-only element;
|
||||
@@ -282,13 +286,11 @@ export function runResolverShadow(
|
||||
// instance; =1 = single static node the SDK parse dropped (foreign-content
|
||||
// exclusion / sub-comp inlining gap); =0 = the runtime-node filter above
|
||||
// uses a loose substring match (biased toward keeping signal) while this
|
||||
// count uses a strict attribute match — an emitted event with count 0
|
||||
// means hfId appeared in source as plain text (e.g. a class name, comment,
|
||||
// or script string) but never as a data-hf-id attribute. Treat 0 as "not
|
||||
// a genuine attribute occurrence," not as a contradiction.
|
||||
...(isElementNotFound && sourceContent !== undefined
|
||||
? { sourceHfIdCount: countHfIdInSource(sourceContent, hfId) }
|
||||
: {}),
|
||||
// count uses a strict attribute match — see sourceLooseMatchOnly below.
|
||||
...(strictCount !== undefined ? { sourceHfIdCount: strictCount } : {}),
|
||||
// Loose suppression check matched (kept this event) but the strict
|
||||
// attribute count came back 0 — see the sourceHfIdCount comment above.
|
||||
...(strictCount === 0 ? { sourceLooseMatchOnly: true } : {}),
|
||||
mismatchCount: mismatches.length,
|
||||
mismatches: JSON.stringify(redactMismatches(mismatches)),
|
||||
});
|
||||
@@ -334,6 +336,7 @@ export async function recordResolverParity(
|
||||
}
|
||||
// Runtime-generated node the static parse can't model — suppress (mirrors the dom-edit path).
|
||||
if (source !== undefined && !source.includes(hfId)) return;
|
||||
const strictCount = source !== undefined ? countHfIdInSource(source, hfId) : undefined;
|
||||
trackStudioEvent("sdk_resolver_shadow", {
|
||||
hfId,
|
||||
opLabel,
|
||||
@@ -342,7 +345,13 @@ export async function recordResolverParity(
|
||||
// on an emitted (non-suppressed) event — the suppression check above is a
|
||||
// loose substring match (biased toward keeping signal); see the longer
|
||||
// comment on this field in runResolverShadow for the full explanation.
|
||||
...(source !== undefined ? { sourceHfIdCount: countHfIdInSource(source, hfId) } : {}),
|
||||
...(strictCount !== undefined ? { sourceHfIdCount: strictCount } : {}),
|
||||
// Loose suppression check matched (kept this event) but the strict
|
||||
// attribute count came back 0 — hfId appeared as plain text (class name,
|
||||
// comment, script string) but never as a data-hf-id="..." attribute.
|
||||
// Lets telemetry consumers filter this cohort without parsing the
|
||||
// sourceHfIdCount comment above.
|
||||
...(strictCount === 0 ? { sourceLooseMatchOnly: true } : {}),
|
||||
mismatchCount: 1,
|
||||
mismatches: JSON.stringify([
|
||||
{ kind: "element_not_found", hfId } satisfies SdkResolverMismatch,
|
||||
|
||||
Reference in New Issue
Block a user