@hyperframes/studio 0.7.23 → 0.7.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-DPO5Gw_v.js → index-CGW4LOlP.js} +1 -1
- package/dist/assets/{index-B8XMgbWF.js → index-CnZuUYe6.js} +37 -37
- package/dist/assets/{index-BRZTrsSs.js → index-DsVS-Zmd.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/hooks/useDomEditSession.ts +6 -1
- package/src/utils/sdkCutover.ts +2 -1
- package/src/utils/sdkResolverShadow.test.ts +29 -0
- package/src/utils/sdkResolverShadow.ts +17 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.24",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"gsap": "^3.13.0",
|
|
47
47
|
"marked": "^14.1.4",
|
|
48
48
|
"mediabunny": "^1.45.3",
|
|
49
|
-
"@hyperframes/core": "0.7.
|
|
50
|
-
"@hyperframes/
|
|
51
|
-
"@hyperframes/
|
|
52
|
-
"@hyperframes/
|
|
53
|
-
"@hyperframes/
|
|
49
|
+
"@hyperframes/core": "0.7.24",
|
|
50
|
+
"@hyperframes/sdk": "0.7.24",
|
|
51
|
+
"@hyperframes/parsers": "0.7.24",
|
|
52
|
+
"@hyperframes/player": "0.7.24",
|
|
53
|
+
"@hyperframes/studio-server": "0.7.24"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "19",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"vite": "^6.4.2",
|
|
66
66
|
"vitest": "^3.2.4",
|
|
67
67
|
"zustand": "^5.0.0",
|
|
68
|
-
"@hyperframes/producer": "0.7.
|
|
68
|
+
"@hyperframes/producer": "0.7.24"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"react": "19",
|
|
@@ -294,7 +294,12 @@ export function useDomEditSession({
|
|
|
294
294
|
// the SDK resolves each reordered element (the reorderElements op's targets).
|
|
295
295
|
onReorderShadow: sdkSession
|
|
296
296
|
? (targets: string[]) => {
|
|
297
|
-
|
|
297
|
+
// Single-flight: every target in one reorder batch shares the same file, so
|
|
298
|
+
// memoize the read instead of firing one fetch per unresolved target.
|
|
299
|
+
let reorderSrcPromise: Promise<string> | undefined;
|
|
300
|
+
const reorderSrc = activeCompPath
|
|
301
|
+
? () => (reorderSrcPromise ??= readProjectFile(activeCompPath))
|
|
302
|
+
: undefined;
|
|
298
303
|
for (const target of targets)
|
|
299
304
|
void recordResolverParity(sdkSession, target, "reorderElements", reorderSrc);
|
|
300
305
|
}
|
package/src/utils/sdkCutover.ts
CHANGED
|
@@ -221,12 +221,13 @@ export function sdkGsapTweenPersist(
|
|
|
221
221
|
"addGsapTween",
|
|
222
222
|
gsapSrc ? () => gsapSrc(targetPath) : undefined,
|
|
223
223
|
);
|
|
224
|
-
} else
|
|
224
|
+
} else {
|
|
225
225
|
recordAnimationResolverParity(
|
|
226
226
|
sdkSession,
|
|
227
227
|
op.animationId,
|
|
228
228
|
op.kind === "set" ? "setGsapTween" : "removeGsapTween",
|
|
229
229
|
);
|
|
230
|
+
}
|
|
230
231
|
// Leading dark-launch gate so flag-off does no SDK touch (getElement) at all —
|
|
231
232
|
// matches the other three chokepoints' discipline.
|
|
232
233
|
if (!STUDIO_SDK_CUTOVER_ENABLED) return Promise.resolve(false);
|
|
@@ -273,6 +273,22 @@ describe("C. Resolver-parity detection", () => {
|
|
|
273
273
|
expect(lastShadow()?.sourceHfIdCount).toBe(2);
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
+
it("C8 sourceLooseMatchOnly: hfId matches source only as plain text, not a data-hf-id attribute", async () => {
|
|
277
|
+
mockFlags.STUDIO_SDK_RESOLVER_SHADOW_ENABLED = true;
|
|
278
|
+
const session = { getElement: () => null, getElements: () => [] } as unknown as Composition;
|
|
279
|
+
// "hf-widget" appears only inside a class name, never as data-hf-id="hf-widget".
|
|
280
|
+
const source = `<div class="hf-widget-container">no attribute match here</div>`;
|
|
281
|
+
runResolverShadow(
|
|
282
|
+
session,
|
|
283
|
+
"hf-widget",
|
|
284
|
+
[{ type: "inline-style", property: "color", value: "red" }],
|
|
285
|
+
source,
|
|
286
|
+
);
|
|
287
|
+
const ev = lastShadow();
|
|
288
|
+
expect(ev?.sourceHfIdCount).toBe(0);
|
|
289
|
+
expect(ev?.sourceLooseMatchOnly).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
276
292
|
it("C10: unmappable op type produces no mismatch (excluded, not flagged)", async () => {
|
|
277
293
|
const session = await openComposition(BASE_HTML);
|
|
278
294
|
// "unknown-op" is not in MAPPED_OP_TYPES, so it must be silently excluded.
|
|
@@ -423,6 +439,19 @@ describe("F. recordResolverParity", () => {
|
|
|
423
439
|
expect(ev?.mismatchCount).toBe(1);
|
|
424
440
|
expect(ev?.sourceHfIdCount).toBeUndefined();
|
|
425
441
|
});
|
|
442
|
+
|
|
443
|
+
it("tags sourceLooseMatchOnly when hfId matches source only as plain text, not a data-hf-id attribute", async () => {
|
|
444
|
+
mockFlags.STUDIO_SDK_RESOLVER_SHADOW_ENABLED = true;
|
|
445
|
+
const session = await openComposition(BASE_HTML);
|
|
446
|
+
// "hf-widget" appears only inside a class name, never as data-hf-id="hf-widget".
|
|
447
|
+
await recordResolverParity(session, "hf-widget", "setTiming", () =>
|
|
448
|
+
Promise.resolve('<div class="hf-widget-container"></div>'),
|
|
449
|
+
);
|
|
450
|
+
const ev = lastShadow();
|
|
451
|
+
expect(ev?.mismatchCount).toBe(1);
|
|
452
|
+
expect(ev?.sourceHfIdCount).toBe(0);
|
|
453
|
+
expect(ev?.sourceLooseMatchOnly).toBe(true);
|
|
454
|
+
});
|
|
426
455
|
});
|
|
427
456
|
|
|
428
457
|
// ─── G. recordAnimationResolverParity (GSAP animationId ops) ──────────────────
|
|
@@ -272,6 +272,10 @@ export function runResolverShadow(
|
|
|
272
272
|
// every style/text/attr edit (the editor's chattiest path) at default-ON.
|
|
273
273
|
if (mismatches.length === 0) return;
|
|
274
274
|
const isElementNotFound = mismatches.some((m) => m.kind === "element_not_found");
|
|
275
|
+
const strictCount =
|
|
276
|
+
isElementNotFound && sourceContent !== undefined
|
|
277
|
+
? countHfIdInSource(sourceContent, hfId)
|
|
278
|
+
: undefined;
|
|
275
279
|
trackStudioEvent("sdk_resolver_shadow", {
|
|
276
280
|
hfId,
|
|
277
281
|
// sessionElementCount > 0 + element_not_found = runtime-only element;
|
|
@@ -282,13 +286,11 @@ export function runResolverShadow(
|
|
|
282
286
|
// instance; =1 = single static node the SDK parse dropped (foreign-content
|
|
283
287
|
// exclusion / sub-comp inlining gap); =0 = the runtime-node filter above
|
|
284
288
|
// uses a loose substring match (biased toward keeping signal) while this
|
|
285
|
-
// count uses a strict attribute match —
|
|
286
|
-
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
...(
|
|
290
|
-
? { sourceHfIdCount: countHfIdInSource(sourceContent, hfId) }
|
|
291
|
-
: {}),
|
|
289
|
+
// count uses a strict attribute match — see sourceLooseMatchOnly below.
|
|
290
|
+
...(strictCount !== undefined ? { sourceHfIdCount: strictCount } : {}),
|
|
291
|
+
// Loose suppression check matched (kept this event) but the strict
|
|
292
|
+
// attribute count came back 0 — see the sourceHfIdCount comment above.
|
|
293
|
+
...(strictCount === 0 ? { sourceLooseMatchOnly: true } : {}),
|
|
292
294
|
mismatchCount: mismatches.length,
|
|
293
295
|
mismatches: JSON.stringify(redactMismatches(mismatches)),
|
|
294
296
|
});
|
|
@@ -334,6 +336,7 @@ export async function recordResolverParity(
|
|
|
334
336
|
}
|
|
335
337
|
// Runtime-generated node the static parse can't model — suppress (mirrors the dom-edit path).
|
|
336
338
|
if (source !== undefined && !source.includes(hfId)) return;
|
|
339
|
+
const strictCount = source !== undefined ? countHfIdInSource(source, hfId) : undefined;
|
|
337
340
|
trackStudioEvent("sdk_resolver_shadow", {
|
|
338
341
|
hfId,
|
|
339
342
|
opLabel,
|
|
@@ -342,7 +345,13 @@ export async function recordResolverParity(
|
|
|
342
345
|
// on an emitted (non-suppressed) event — the suppression check above is a
|
|
343
346
|
// loose substring match (biased toward keeping signal); see the longer
|
|
344
347
|
// comment on this field in runResolverShadow for the full explanation.
|
|
345
|
-
...(
|
|
348
|
+
...(strictCount !== undefined ? { sourceHfIdCount: strictCount } : {}),
|
|
349
|
+
// Loose suppression check matched (kept this event) but the strict
|
|
350
|
+
// attribute count came back 0 — hfId appeared as plain text (class name,
|
|
351
|
+
// comment, script string) but never as a data-hf-id="..." attribute.
|
|
352
|
+
// Lets telemetry consumers filter this cohort without parsing the
|
|
353
|
+
// sourceHfIdCount comment above.
|
|
354
|
+
...(strictCount === 0 ? { sourceLooseMatchOnly: true } : {}),
|
|
346
355
|
mismatchCount: 1,
|
|
347
356
|
mismatches: JSON.stringify([
|
|
348
357
|
{ kind: "element_not_found", hfId } satisfies SdkResolverMismatch,
|