mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 10:06:21 +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
@@ -368,13 +368,90 @@ describe("probeElementInSource", () => {
|
||||
// Covers the same surface as T3 (Studio sourcePatcher) — Core sourceMutation supports
|
||||
// all patch types (inline-style, attribute, text-content) via patchElementInHtml.
|
||||
describe("T7 — data-hf-id targeting (spec for R1)", () => {
|
||||
it.todo("updates inline style by data-hf-id when no HTML id attribute is present");
|
||||
it("updates inline style by data-hf-id when no HTML id attribute is present", () => {
|
||||
const source = `<h1 data-hf-id="hf-x7k2" style="color: red">Hello</h1>`;
|
||||
const { html, matched } = patchElementInHtml(source, { hfId: "hf-x7k2" }, [
|
||||
{ type: "inline-style", property: "color", value: "blue" },
|
||||
]);
|
||||
expect(matched).toBe(true);
|
||||
expect(html).toMatch(/color:\s*blue/);
|
||||
expect(html).toContain('data-hf-id="hf-x7k2"');
|
||||
});
|
||||
|
||||
it.todo("updates text content by data-hf-id");
|
||||
it("updates text content by data-hf-id", () => {
|
||||
const source = `<p data-hf-id="hf-a1b2">Old text</p>`;
|
||||
const { html, matched } = patchElementInHtml(source, { hfId: "hf-a1b2" }, [
|
||||
{ type: "text-content", property: "", value: "New text" },
|
||||
]);
|
||||
expect(matched).toBe(true);
|
||||
expect(html).toContain("New text");
|
||||
});
|
||||
|
||||
it.todo("updates attribute by data-hf-id");
|
||||
it("updates attribute by data-hf-id", () => {
|
||||
const source = `<div data-hf-id="hf-c3d4" data-start="0"></div>`;
|
||||
const { html, matched } = patchElementInHtml(source, { hfId: "hf-c3d4" }, [
|
||||
{ type: "attribute", property: "start", value: "2.5" },
|
||||
]);
|
||||
expect(matched).toBe(true);
|
||||
expect(html).toContain('data-start="2.5"');
|
||||
});
|
||||
|
||||
it.todo("data-hf-id attribute survives the patch (can be targeted again)");
|
||||
it("data-hf-id attribute survives the patch (can be targeted again)", () => {
|
||||
const source = `<h1 data-hf-id="hf-x7k2" style="color: red">Hello</h1>`;
|
||||
const { html } = patchElementInHtml(source, { hfId: "hf-x7k2" }, [
|
||||
{ type: "inline-style", property: "color", value: "blue" },
|
||||
]);
|
||||
expect(html).toContain('data-hf-id="hf-x7k2"');
|
||||
});
|
||||
|
||||
it.todo("hfId lookup falls through to selector when hfId is not found in the document");
|
||||
it("hfId lookup falls through to selector when hfId is not found in the document", () => {
|
||||
const source = `<h1 class="headline" style="color: red">Hello</h1>`;
|
||||
const { html, matched } = patchElementInHtml(
|
||||
source,
|
||||
{ hfId: "hf-missing", selector: ".headline" },
|
||||
[{ type: "inline-style", property: "color", value: "blue" }],
|
||||
);
|
||||
expect(matched).toBe(true);
|
||||
expect(html).toMatch(/color:\s*blue/);
|
||||
});
|
||||
|
||||
it("does not break out of the selector on a crafted hfId (CSS injection guard)", () => {
|
||||
// A value with a quote/bracket must be escaped, not injected — it should
|
||||
// simply match nothing and leave the source untouched, never throw.
|
||||
const source = `<h1 class="safe">A</h1><h1 class="victim">B</h1>`;
|
||||
const evil = `x"] , [class="victim`;
|
||||
const run = () =>
|
||||
patchElementInHtml(source, { hfId: evil }, [
|
||||
{ type: "text-content", property: "textContent", value: "HACKED" },
|
||||
]);
|
||||
expect(run).not.toThrow();
|
||||
const { html, matched } = run();
|
||||
expect(matched).toBe(false);
|
||||
expect(html).toBe(source);
|
||||
expect(html).not.toContain("HACKED");
|
||||
});
|
||||
|
||||
// The Studio edit path targets by id/selector (it never sends hfId). Once a
|
||||
// persisted data-hf-id exists in source, those edits must NOT strip it — else
|
||||
// the stable handle is destroyed by the next edit. This is the preservation
|
||||
// guarantee the write-back design depends on.
|
||||
it("preserves an existing data-hf-id when the element is patched by id", () => {
|
||||
const source = `<h1 id="hero" data-hf-id="hf-x7k2" style="color: red">Hello</h1>`;
|
||||
const { html, matched } = patchElementInHtml(source, { id: "hero" }, [
|
||||
{ type: "inline-style", property: "color", value: "blue" },
|
||||
]);
|
||||
expect(matched).toBe(true);
|
||||
expect(html).toMatch(/color:\s*blue/);
|
||||
expect(html).toContain('data-hf-id="hf-x7k2"');
|
||||
});
|
||||
|
||||
it("preserves an existing data-hf-id when the element is patched by selector", () => {
|
||||
const source = `<p class="body" data-hf-id="hf-a1b2">Old</p>`;
|
||||
const { html, matched } = patchElementInHtml(source, { selector: ".body" }, [
|
||||
{ type: "text-content", property: "textContent", value: "New" },
|
||||
]);
|
||||
expect(matched).toBe(true);
|
||||
expect(html).toContain("New");
|
||||
expect(html).toContain('data-hf-id="hf-a1b2"');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user