mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat(core): hf-id write-back to disk + serve-time surfacing (R7, Tasks 1-2) (#1289)
* 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> * test(core): previewAdapter contract failing tests (T10 spec for R7) * feat(core): hf-id write-back to disk + serve-time surfacing (R7, Task 1-2) * test(core): replace tautological stability tests with real disk tests for persistHfIdsIfNeeded Prior tests only exercised normalizeHfIds (pure function) and the existing pin guard in ensureHfIds — both pass on the parent commit without any Task 1 code. Replace with three tests that exercise the actual disk write-back: - writes data-hf-id to disk when source is untagged - does not rewrite disk when source is already tagged (idempotent) - returned id matches id written to disk (serve-time == persist-time invariant) These fail on the parent commit (persistHfIdsIfNeeded doesn't exist) and green after Task 1. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(core): route-level tests for data-hf-id surfacing and disk write-back (R7, Task 1-2) Two integration tests against the preview route (via Hono test harness): - served HTML carries data-hf-id on body elements (>= 2 matches for div+p) - disk file contains data-hf-id after first GET (write-back verified via readFileSync) These fail on the parent commit (no hfIdPersist wiring in preview.ts) and green after Task 1. Closes the verification gap flagged in review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
72e4f1a0f6
commit
740a83abb3
@@ -0,0 +1,69 @@
|
||||
import { describe, it, expect, afterEach } from "vitest";
|
||||
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { normalizeHfIds, persistHfIdsIfNeeded } from "./hfIdPersist.js";
|
||||
|
||||
describe("normalizeHfIds", () => {
|
||||
it("marks changed=true and adds data-hf-id to all body elements when untagged", () => {
|
||||
const raw = `<!doctype html><html><body><div><p>hello</p></div></body></html>`;
|
||||
const { html, changed } = normalizeHfIds(raw);
|
||||
expect(changed).toBe(true);
|
||||
expect(html).toContain('data-hf-id="hf-');
|
||||
const matches = html.match(/data-hf-id="hf-[a-z0-9]{4}"/g);
|
||||
expect(matches?.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("marks changed=false for already-normalized HTML (idempotent round-trip)", () => {
|
||||
const raw = `<!doctype html><html><body><div><p>hello</p></div></body></html>`;
|
||||
const first = normalizeHfIds(raw).html;
|
||||
const { html, changed } = normalizeHfIds(first);
|
||||
expect(changed).toBe(false);
|
||||
expect(html).toBe(first);
|
||||
});
|
||||
});
|
||||
|
||||
describe("persistHfIdsIfNeeded", () => {
|
||||
const tmpDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const d of tmpDirs) rmSync(d, { recursive: true, force: true });
|
||||
tmpDirs.length = 0;
|
||||
});
|
||||
|
||||
function tmpFile(content: string): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hfid-test-"));
|
||||
tmpDirs.push(dir);
|
||||
const file = join(dir, "index.html");
|
||||
writeFileSync(file, content, "utf-8");
|
||||
return file;
|
||||
}
|
||||
|
||||
it("writes data-hf-id to disk when source is untagged", () => {
|
||||
const raw = `<!doctype html><html><body><div>hello</div></body></html>`;
|
||||
const file = tmpFile(raw);
|
||||
const returned = persistHfIdsIfNeeded(file, raw);
|
||||
expect(returned).toContain('data-hf-id="hf-');
|
||||
const onDisk = readFileSync(file, "utf-8");
|
||||
expect(onDisk).toContain('data-hf-id="hf-');
|
||||
expect(onDisk).toBe(returned);
|
||||
});
|
||||
|
||||
it("does not rewrite disk when source is already tagged", () => {
|
||||
const raw = `<!doctype html><html><body><div>hello</div></body></html>`;
|
||||
const file = tmpFile(raw);
|
||||
const tagged = persistHfIdsIfNeeded(file, raw);
|
||||
const diskAfterFirst = readFileSync(file, "utf-8");
|
||||
const returned2 = persistHfIdsIfNeeded(file, tagged);
|
||||
expect(returned2).toBe(tagged);
|
||||
expect(readFileSync(file, "utf-8")).toBe(diskAfterFirst);
|
||||
});
|
||||
|
||||
it("returned id matches id written to disk (serve-time == persist-time invariant)", () => {
|
||||
const raw = `<!doctype html><html><body><span>text</span></body></html>`;
|
||||
const file = tmpFile(raw);
|
||||
const result = persistHfIdsIfNeeded(file, raw);
|
||||
const onDisk = readFileSync(file, "utf-8");
|
||||
expect(result).toBe(onDisk);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ensureHfIds } from "../../parsers/hfIds.js";
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
export { ensureHfIds };
|
||||
|
||||
export function normalizeHfIds(html: string): { html: string; changed: boolean } {
|
||||
const normalized = ensureHfIds(html);
|
||||
return { html: normalized, changed: normalized !== html };
|
||||
}
|
||||
|
||||
export function persistHfIdsIfNeeded(filePath: string, html: string): string {
|
||||
const { html: normalized, changed } = normalizeHfIds(html);
|
||||
if (changed) {
|
||||
try {
|
||||
writeFileSync(filePath, normalized, "utf-8");
|
||||
} catch {
|
||||
// non-fatal — serve with ids even if persist fails
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
Reference in New Issue
Block a user