mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
feat(core): sourceMutation data-hf-id targeting (R1, T7) (#1272)
* feat(core): clip-model hf- ids minted at parse, emitted as data-hf-id (R1) * docs(core): document legacy-id round-trip in clip-model readback (R1 review) Addresses Rames' review on #1270: clarifies that a pre-R1 clip authored with id="my-title" round-trips as data-hf-id="my-title" (non-hf-shaped but stable, exact-match) by design — targeting uses exact [data-hf-id="…"] match and does not require the hf- shape; legacy values re-mint only at the R7 write-back. Not a bug. Comment-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(core): fix misleading legacy-id migration comment in htmlParser.ts The original comment said legacy data-hf-id values "are re-minted only once the R7 write-back persists freshly-minted ids to source" — which is incorrect. ensureHfIds skips elements that already carry data-hf-id, so legacy values (e.g. data-hf-id="my-title") persist indefinitely and are NOT automatically re-minted. Exact-match targeting still works correctly. Update comment to reflect actual behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(studio): sourcePatcher data-hf-id targeting (R1, T3) * fix(studio): warn on duplicate match in execDataAttrPattern (R1, T3 review) Addresses Rames' review on #1271: execDataAttrPattern returned the first regex match without checking for a second. A duplicate id/data-hf-id in source (id drift) would silently patch one element and leave the other stale. Now warns when more than one element matches. By the mint contract it should never fire. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(studio): pin hfId-is-authoritative-over-selector contract (R1, T3 review) Adds test: "hfId match is authoritative — selector is not used as a narrowing filter". When hfId matches element A and selector points at element B, findTagByTarget returns A without consulting selector as a narrowing filter. Pins the intended behaviour so a future refactor cannot silently start narrowing by selector. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(core): sourceMutation data-hf-id targeting (R1, T7) * test(core): update htmlParser baselines for R1 hf- id format Elements now get data-hf-id minted by ensureHfIds; parser reads data-hf-id as model id, so HTML id attrs are no longer the model id. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(core): data-hf-id survives id/selector patch (R1, T7) Locks the preservation guarantee the write-back design depends on: a Studio edit targeting by id or selector (it never sends hfId) must not strip an existing data-hf-id, or the stable handle is destroyed by the next edit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core): escape hfId in selector + warn on duplicate match (R1, T7 review) Addresses review on #1272 (Miguel P3 + Rames): findTargetElement interpolated target.hfId raw into a [data-hf-id="..."] selector. Escape it (CSS attr-value injection guard) and warn when a hfId matches more than one element instead of silently patching an arbitrary one. Adds an injection-guard test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
42c247d28f
commit
1cd4ad6ab8
@@ -26,13 +26,13 @@ describe("parseHtml", () => {
|
||||
const result = parseHtml(html);
|
||||
|
||||
expect(result.elements).toHaveLength(2);
|
||||
expect(result.elements[0].id).toBe("text1");
|
||||
expect(result.elements[0].id).toMatch(/^hf-[a-z0-9]{4}$/);
|
||||
expect(result.elements[0].startTime).toBe(0);
|
||||
expect(result.elements[0].duration).toBe(5);
|
||||
expect(result.elements[0].name).toBe("Title");
|
||||
expect(result.elements[0].type).toBe("text");
|
||||
|
||||
expect(result.elements[1].id).toBe("text2");
|
||||
expect(result.elements[1].id).toMatch(/^hf-[a-z0-9]{4}$/);
|
||||
expect(result.elements[1].startTime).toBe(2);
|
||||
expect(result.elements[1].duration).toBe(5);
|
||||
});
|
||||
@@ -53,7 +53,7 @@ describe("parseHtml", () => {
|
||||
|
||||
expect(result.elements).toHaveLength(1);
|
||||
expect(result.elements[0].type).toBe("composition");
|
||||
expect(result.elements[0].id).toBe("comp1");
|
||||
expect(result.elements[0].id).toMatch(/^hf-[a-z0-9]{4}$/);
|
||||
if (result.elements[0].type === "composition") {
|
||||
expect(result.elements[0].compositionId).toBe("abc123");
|
||||
expect(result.elements[0].src).toBe("/compositions/abc123");
|
||||
@@ -76,20 +76,20 @@ describe("parseHtml", () => {
|
||||
|
||||
expect(result.elements).toHaveLength(3);
|
||||
|
||||
const video = result.elements.find((e) => e.id === "vid1");
|
||||
const video = result.elements.find((e) => e.type === "video");
|
||||
expect(video).toBeDefined();
|
||||
expect(video?.type).toBe("video");
|
||||
expect(video?.id).toMatch(/^hf-[a-z0-9]{4}$/);
|
||||
if (video?.type === "video") {
|
||||
expect(video.src).toBe("video.mp4");
|
||||
}
|
||||
|
||||
const audio = result.elements.find((e) => e.id === "aud1");
|
||||
const audio = result.elements.find((e) => e.type === "audio");
|
||||
expect(audio).toBeDefined();
|
||||
expect(audio?.type).toBe("audio");
|
||||
expect(audio?.id).toMatch(/^hf-[a-z0-9]{4}$/);
|
||||
|
||||
const img = result.elements.find((e) => e.id === "img1");
|
||||
const img = result.elements.find((e) => e.type === "image");
|
||||
expect(img).toBeDefined();
|
||||
expect(img?.type).toBe("image");
|
||||
expect(img?.id).toMatch(/^hf-[a-z0-9]{4}$/);
|
||||
});
|
||||
|
||||
it("handles missing attributes gracefully", () => {
|
||||
@@ -398,9 +398,12 @@ describe("parseHtml", () => {
|
||||
`;
|
||||
const result = parseHtml(html);
|
||||
|
||||
expect(result.keyframes["text1"]).toBeDefined();
|
||||
expect(result.keyframes["text1"]).toHaveLength(2);
|
||||
expect(result.keyframes["text1"][0].id).toBe("kf1");
|
||||
const elId = result.elements[0]?.id ?? "";
|
||||
expect(elId).toMatch(/^hf-[a-z0-9]{4}$/);
|
||||
const kfs = result.keyframes[elId];
|
||||
expect(kfs).toBeDefined();
|
||||
expect(kfs).toHaveLength(2);
|
||||
expect(kfs[0].id).toBe("kf1");
|
||||
});
|
||||
|
||||
it("parses stage zoom keyframes", () => {
|
||||
|
||||
Reference in New Issue
Block a user