feat(core): clip-model hf- ids minted at parse, emitted as data-hf-id (R1)#1270
Conversation
miguel-heygen
left a comment
There was a problem hiding this comment.
Minimal and correct wire-up. The three-level fallback data-hf-id → el.id → counter is the right priority order, and emitting data-hf-id="${element.id}" from generateElementHtml is the correct write-back strategy that pins ids across round-trips.
One concern:
P2 — linkedom in the browser bundle:
hfIds.ts imports from linkedom, which htmlParser.ts now depends on. If htmlParser.ts runs in browser context (it uses DOMParser), linkedom gets bundled into the browser payload (~500KB minified). Is this module tree-shaken or only used in the Node.js/worker path? If it's in the browser bundle, it's a non-trivial addition for a parsing helper. Worth confirming the bundle impact is acceptable before this merges, or considering whether ensureHfIds can be called upstream (in the server-side render pipeline) instead.
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Tiny wiring PR — 12/8 across 3 files. generateElementHtml now emits data-hf-id alongside id, and parseHtml calls ensureHfIds then reads data-hf-id || el.id || \element-${++idCounter}`for the clip id. Test updates flip hardcoded id literals to/^hf-[a-z0-9]{4}$/` regex matches.
Concern
[Legacy clip-model id-roundtrip produces non-conformant data-hf-id values] When a clip-model with id="my-title" (user-set, no data-hf-id) goes through parse → emit:
parseHtml: readsdata-hf-id || el.id→ falls through toel.id = "my-title". Soelement.id = "my-title".generateElementHtml: emitsid="my-title"ANDdata-hf-id="my-title". Both share the same value.- Re-parse:
ensureHfIdsseesdata-hf-id="my-title", pins it.parseHtmlreads "my-title" again. Stable.
The targeting code in #1271/#1272 doesn't care about the value pattern — it uses [data-hf-id="..."] exact-match — so functionally this works. But the spec test it("[spec] generated hf- ids match /^hf-[a-z0-9]{4}$/") only fires for elements WITHOUT a pre-existing id. So a clip-model authored before R1 lands carries non-conformant data-hf-id values forever (or until manually re-minted).
Two options worth considering:
- Force-mint a hf- prefix in
generateElementHtmlwhenelement.iddoesn't match/^hf-[a-z0-9]{4}$/— the element.id stays whatever it was, butdata-hf-idgets a freshly-minted hf-prefixed value. This converges all clip-models to hf-spec on next save. - Document the migration as intentional — call out in the PR body / a code comment that legacy clip-models will carry their user-set ids in
data-hf-iduntil the round-3 write-back lands. Then it's not a surprise.
If the latter is the plan (which is consistent with "not wired in yet"), worth a one-liner so future archaeologists don't think it's a bug.
Nits
[generateElementHtml change is one line — should the constant be derived?] \data-hf-id="${element.id}"`duplicates theidvalue verbatim. If element.id ever drifts away from the hf-id contract (e.g. a future emitter path sets element.id to something legacy-shaped), the data-hf-id will silently follow. A`data-hf-id="${getStableHfId(element)}"`indirection — wheregetStableHfId` validates or re-mints — would isolate the contract.
Not blocking; the current shape is fine for R1.
[Tests flipped from id-literal to regex-match are appropriately strict] toMatch(/^hf-[a-z0-9]{4}$/) matches the spec exactly. The one find-by-type change (result.elements.find((e) => e.type === "video")) instead of find-by-id is a clean adaptation — keeps the test's intent (verify the video clip is parsed) without coupling to the now-dynamic id.
What I didn't verify
- That
generateElementHtmlis the only emission path for clip HTML. The briefing called out "DOM render, JSON snapshot, debug dump, any test fixtures" as paths that might also serialize. A quick grep fordata-start=ordata-end=in other generators would close that, but the diff scope here only touches one emitter — if there's another path, that's a follow-up not a blocker on this PR.
Verdict
Clean wiring. Stacked on top of #1269 correctly. Main thing worth deciding is whether the legacy-id roundtrip (non-conformant data-hf-id values) is the intended migration path or whether generateElementHtml should force-mint a hf- prefix. Both are defensible — pick whichever you prefer; if it's the former, one-line comment closes the loop. Leaving as a comment.
— Review by Rames D Jusso
…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>
|
Addressed review (86562c2): documented the legacy-id round-trip — a pre-R1 |
86562c2 to
beb8b9f
Compare
beb8b9f to
fde1c2d
Compare
miguel-heygen
left a comment
There was a problem hiding this comment.
Building on Rames's review.
Rames flagged two things I want to sharpen:
Escalating: legacy id roundtrip is documented now, but the R7 write-back path must not re-mint over a pre-existing non-conformant data-hf-id. The comment in htmlParser.ts (L192-200) correctly explains that a clip with id="my-title" will get data-hf-id="my-title" and "will be re-minted once R7 write-back persists freshly-minted ids." But persistHfIdsIfNeeded in #1289 calls ensureHfIds, and ensureHfIds (per hfIds.ts:51) skips elements that already have data-hf-id set — so the legacy data-hf-id="my-title" will NOT be re-minted. The comment's promised migration will never fire. Either update the comment to say "legacy values persist forever unless the user hand-edits the source" or update persistHfIdsIfNeeded to force-mint hf-prefixed ids for non-conformant values. The current behavior is safe (exact-match targeting still works on "my-title"), but the comment is misleading.
Confirming: linkedom bundle concern. Rames flagged that hfIds.ts imports linkedom which now flows into htmlParser.ts. Worth a bundle analyzer check — this matters for any consumer that ships htmlParser into a browser context. If it's workers-only, fine. If it's browser-main, linkedom at ~500KB needs to be justified or replaced with a document.createTreeWalker path in-browser.
New: generateElementHtml emits data-hf-id="${element.id}" unconditionally, but element.id may already be an hf- id (round-trip) or a legacy id. For the round-trip case this is correct (same id pinned). For a freshly-created element from the Studio, element.id is whatever the Studio assigned — typically a counter like element-12. That gets emitted as data-hf-id="element-12", which then gets pinned by ensureHfIds and survives forever. The hf- prefix is never applied in this case — the Studio path never goes through parseHtml's ensureHfIds call before emitting. This is a second case where the expected mint never fires.
Overall the wiring is correct and the T2 tests turning green confirms the primary contract. But the migration story for non-hf-prefixed ids needs to be either fixed or explicitly documented as "not part of R1."
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Round 2 (delta since f391075 — one new commit fde1c2d docs(core): document legacy-id round-trip in clip-model readback (R1 review)).
Vance picked Option 2 from my round-1 ask — comment-as-documentation rather than force-mint. The added comment in htmlParser.ts:196-202 is the right shape: explicitly calls out the legacy round-trip path (id="my-title" → data-hf-id="my-title"), spells out why it's safe (targeting uses exact-match, not shape-match), and pins when migration happens (R7 write-back). Future archaeologists won't read this as a bug.
My round-1 nit about getStableHfId(element) indirection in generateElementHtml isn't addressed — that was a stylistic call from me and your current shape is fine for R1. Leaving it.
Clean execution; leaving as a comment.
— Review by Rames D Jusso
miguel-heygen
left a comment
There was a problem hiding this comment.
31d9074 fixes my migration comment concern — the comment now correctly says legacy values are NOT re-minted automatically. ✅
The Studio-created element path (emitting data-hf-id="${element.id}" where element.id may be element-12) is still in the diff but is a separate issue from the comment; behavior-identical to before. If that's out of scope for R1, fine — just confirming it's a known gap, not a surprise.
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Round 3 (delta since fde1c2d — one new commit 31d9074e updating the legacy-id comment).
Pulled the current file at 31d9074e head. The comment at htmlParser.ts:195-203 now reads:
"...This is safe indefinitely: targeting uses exact
[data-hf-id="…"]match (it does not require the hf- prefix). ensureHfIds skips elements that already carry data-hf-id, so legacy values are NOT re-minted automatically — they persist until the user re-saves the composition through Studio. Not a bug."
That's Option (a) landed clean. The corrected comment now accurately describes the actual behavior across the R1/R7 boundary — ensureHfIds skip-pinned semantics are stated explicitly, the "re-mints only at R7 write-back" claim is removed, and the user-action-required reset path ("user re-saves through Studio") is named. This is what I should have caught in my round-2 ack — the comment is now what I'd want to see.
Owning my round-2 miss: I acked the prior comment without verifying its R7-behavior claim against ensureHfIds's skip-pinned condition, despite having reviewed that exact behavior in my own #1289 round-1. Saved the lesson to memory (feedback_verify_cross_pr_claims_against_other_pr_code.md) — applies to any future review that ack's a cross-PR claim. Won't repeat.
Ready from where I sit; leaving as a comment.
— Review by Rames D Jusso
jrusso1020
left a comment
There was a problem hiding this comment.
Approved on behalf of James.
…1269) ## What Adds `ensureHfIds(html: string): string` in `packages/core/src/parsers/hfIds.ts`. Single DOM pass (via linkedom) that mints a `data-hf-id` attribute on every eligible element before the caller sees the markup. **Id derivation:** FNV-1a 32-bit hash of `tagName | sorted-attrs(\x00/\x01 separated) | ownText`, last 4 chars of base-36, `hf-` prefix. Collision resolution appends a sibling counter and re-hashes. Preserves existing ids (elements with `data-hf-id` already set are skipped). Excludes non-visual tags: `script`, `style`, `template`, `meta`, `link`, `noscript`, `base`. **Fragment handling:** detects bare HTML fragments (no `<!doctype` / `<html`) and wraps in a full document shell before parsing, then returns `body.innerHTML` — matching the pattern used by `parseSourceDocument` in `sourceMutation`. ## Why Counter-based ids (`element-0`, `element-1`, …) are positional. Inserting a new layer at position 0 shifts every id below it. The R1 milestone requires content-based, stable ids so that targeting operations (split, patch, probe) stay valid across re-parses and element insertions. T2 spec (`stableIds.test.ts`) defines the contract: same content → same id, adding a sibling doesn't change other ids, format matches `/^hf-[a-z0-9]{4}$/`. ## How - `toHfId(hash)` — `slice(-4)` of `hash.toString(36)` for better distribution across the suffix space - `data-hf-id` is excluded from the hash input (prevents circular dependency on the attribute being set) - Already-assigned ids tracked in a `Set`; duplicates get a counter suffix before re-hashing - Fragment detection: `/<!doctype|<html[\s>]/i.test(html)` — if bare, wrap→parse→`body.innerHTML` ## Test plan - [x] T2 spec: `packages/core/src/parsers/stableIds.test.ts` — 7 tests pass (3 were `.fails` stubs targeting R1; 4 were pre-existing baselines that must not regress) - [x] No changes to existing htmlParser tests needed at this layer (wiring is PR #1270)
…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>
4476246 to
c12854b
Compare
31d9074 to
abad19f
Compare
The base branch was changed.
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>
abad19f to
9ddd145
Compare
| const withIds = ensureHfIds(html); | ||
| const parser = new DOMParser(); | ||
| const doc = parser.parseFromString(html, "text/html"); | ||
| const doc = parser.parseFromString(withIds, "text/html"); |
Pre-R1 tests expected clip ids to reflect legacy `id=` attributes.
After R1, ensureHfIds runs first and mints data-hf-id — so clip.id
reflects the minted hf- value unless the element already has data-hf-id.
Fix: add explicit data-hf-id to test HTML elements where tests assert
specific id values. Update no-id test to expect hf- format (/^hf-[a-z0-9]{4}$/)
instead of the pre-R1 generated-id fallback (/^element-\d+$/).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* 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> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* 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>
…1286) * 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) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s 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>

What
Wires
ensureHfIds(PR #1269) intoparseHtmlso every parsed composition gets stabledata-hf-idattributes before the element model is built.Clip model id chain:
data-hf-idis now the primary identity; existing HTMLidattrs and the counter fallback are preserved for backwards compatibility.Bridge for legacy callers:
updateElementInHtmlandremoveElementFromHtmlinhtmlParser.tsnow try[data-hf-id="${id}"]whengetElementByIdfails, so string-based callers that pass ahf-xxxxvalue continue to work without knowing about the new field.Why
Without this PR,
ensureHfIdsis a no-op — the helper exists but nothing calls it. This is the commit that makes T2 tests turn green and makes everyparseHtmlcall produce stablehf-ids. Depends on PR #1269.How
htmlParser.tsbaseline snapshots updated to reflect thehf-id format (see168c0452c).Test plan
.failstests now pass — elements gethf-prefixed ids, format matches/^hf-[a-z0-9]{4}$/, adding a sibling doesn't change existing idshtmlParser.test.tsbaselines updated and passingstableIds.test.tsbaselines (round-trip, uniqueness, existing-id preservation) still green