feat(core): implement createPreviewAdapter (R7, Task 3) (#1291)

* 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>

* feat(core): implement createPreviewAdapter — greens 20 T10 tests (R7, Task 3)

elementAtPoint: resolvePoint callback → walk ancestors for data-hf-id,
skip data-hf-root without data-hf-id (stage root), skip opacity-0 elements.

applyDraft: find element by hfId, record originalTranslate, set
--hf-studio-offset-x/y (move) or --hf-studio-width/height (resize),
mark data-hf-studio-manual-edit-gesture.

revertDraft: remove draft CSS props, clear gesture marker, restore
originalTranslate if one was recorded.

commitPreview: extract patch (move→moveElement, resize→resize with w/h
renamed to width/height), clear gesture marker, return patch or null.

getElementTimings: scan [data-hf-id] elements, parse data-start/data-end
as floats, return map with undefined fields for absent attributes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(core): remove explicit data-hf-id from htmlParser tests so ensureHfIds mints hf- ids

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:
Vance Ingalls
2026-06-09 00:39:53 -07:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent 740a83abb3
commit 1a23938bef
2 changed files with 108 additions and 16 deletions
+7 -7
View File
@@ -17,8 +17,8 @@ describe("parseHtml", () => {
<html>
<body>
<div id="stage">
<div id="text1" data-hf-id="text1" data-start="0" data-end="5" data-name="Title"><div>Hello World</div></div>
<div id="text2" data-hf-id="text2" data-start="2" data-end="7" data-name="Subtitle"><div>Sub</div></div>
<div id="text1" data-start="0" data-end="5" data-name="Title"><div>Hello World</div></div>
<div id="text2" data-start="2" data-end="7" data-name="Subtitle"><div>Sub</div></div>
</div>
</body>
</html>
@@ -42,7 +42,7 @@ describe("parseHtml", () => {
<html>
<body>
<div id="stage">
<div id="comp1" data-hf-id="comp1" data-start="0" data-end="10" data-type="composition" data-composition-id="abc123">
<div id="comp1" data-start="0" data-end="10" data-type="composition" data-composition-id="abc123">
<iframe src="/compositions/abc123"></iframe>
</div>
</div>
@@ -65,9 +65,9 @@ describe("parseHtml", () => {
<html>
<body>
<div id="stage">
<video id="vid1" data-hf-id="vid1" data-start="0" data-end="10" src="video.mp4" data-name="My Video"></video>
<audio id="aud1" data-hf-id="aud1" data-start="0" data-end="5" src="music.mp3" data-name="Music"></audio>
<img id="img1" data-hf-id="img1" data-start="2" data-end="8" src="photo.jpg" data-name="Photo" />
<video id="vid1" data-start="0" data-end="10" src="video.mp4" data-name="My Video"></video>
<audio id="aud1" data-start="0" data-end="5" src="music.mp3" data-name="Music"></audio>
<img id="img1" data-start="2" data-end="8" src="photo.jpg" data-name="Photo" />
</div>
</body>
</html>
@@ -391,7 +391,7 @@ describe("parseHtml", () => {
<html>
<body>
<div id="stage">
<div id="text1" data-hf-id="text1" data-start="0" data-end="5" data-keyframes='${keyframes}'><div>Hello</div></div>
<div id="text1" data-start="0" data-end="5" data-keyframes='${keyframes}'><div>Hello</div></div>
</div>
</body>
</html>