Skip to content

Commit af64d2f

Browse files
committed
feat(studio): carry hfId on TimelineElement, wire through buildPatchTarget (R7, T5b)
1 parent d31b153 commit af64d2f

4 files changed

Lines changed: 78 additions & 2 deletions

File tree

packages/studio/src/hooks/useTimelineEditing.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,22 @@ interface UseTimelineEditingOptions {
4545

4646
// ── Helpers ──
4747

48-
function buildPatchTarget(element: { domId?: string; selector?: string; selectorIndex?: number }) {
48+
function buildPatchTarget(element: {
49+
domId?: string;
50+
hfId?: string;
51+
selector?: string;
52+
selectorIndex?: number;
53+
}) {
4954
if (element.domId) {
50-
return { id: element.domId, selector: element.selector, selectorIndex: element.selectorIndex };
55+
return {
56+
id: element.domId,
57+
hfId: element.hfId,
58+
selector: element.selector,
59+
selectorIndex: element.selectorIndex,
60+
};
61+
}
62+
if (element.hfId) {
63+
return { hfId: element.hfId, selector: element.selector, selectorIndex: element.selectorIndex };
5164
}
5265
if (element.selector) {
5366
return { selector: element.selector, selectorIndex: element.selectorIndex };
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// @vitest-environment jsdom
2+
import { describe, expect, it } from "vitest";
3+
import { parseTimelineFromDOM, createImplicitTimelineLayersFromDOM } from "./timelineDOM";
4+
5+
function makeDoc(html: string): Document {
6+
const d = document.implementation.createHTMLDocument();
7+
d.body.innerHTML = html;
8+
return d;
9+
}
10+
11+
describe("parseTimelineFromDOM — hfId from data-hf-id", () => {
12+
it("harvests hfId from a data-start element that has data-hf-id", () => {
13+
const doc = makeDoc(`
14+
<div data-composition-id="root">
15+
<div id="hero" class="clip" data-start="0" data-duration="5" data-hf-id="hf-abc123"></div>
16+
</div>
17+
`);
18+
19+
const elements = parseTimelineFromDOM(doc, 10);
20+
const hero = elements.find((el) => el.domId === "hero");
21+
22+
expect(hero).toBeDefined();
23+
expect(hero?.hfId).toBe("hf-abc123");
24+
});
25+
26+
it("leaves hfId undefined when element has no data-hf-id", () => {
27+
const doc = makeDoc(`
28+
<div data-composition-id="root">
29+
<div id="plain" class="clip" data-start="0" data-duration="5"></div>
30+
</div>
31+
`);
32+
33+
const elements = parseTimelineFromDOM(doc, 10);
34+
const plain = elements.find((el) => el.domId === "plain");
35+
36+
expect(plain).toBeDefined();
37+
expect(plain?.hfId).toBeUndefined();
38+
});
39+
});
40+
41+
describe("createImplicitTimelineLayersFromDOM — hfId from data-hf-id", () => {
42+
it("harvests hfId from an implicit layer child that has data-hf-id", () => {
43+
const doc = makeDoc(`
44+
<div data-composition-id="root">
45+
<div id="layer" class="clip" data-hf-id="hf-xyz789"></div>
46+
</div>
47+
`);
48+
49+
const layers = createImplicitTimelineLayersFromDOM(doc, 10);
50+
const layer = layers.find((el) => el.domId === "layer");
51+
52+
expect(layer).toBeDefined();
53+
expect(layer?.hfId).toBe("hf-xyz789");
54+
});
55+
});

packages/studio/src/player/lib/timelineDOM.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ export function createTimelineElementFromManifestClip(params: {
7272
let selectorIndex: number | undefined;
7373
let sourceFile: string | undefined;
7474

75+
let hfId: string | undefined;
7576
if (hostEl) {
7677
domId = hostEl.id || undefined;
78+
hfId = hostEl.getAttribute("data-hf-id") ?? undefined;
7779
selector = getTimelineElementSelector(hostEl);
7880
selectorIndex =
7981
doc && selector ? getTimelineElementSelectorIndex(doc, hostEl, selector) : undefined;
@@ -98,6 +100,7 @@ export function createTimelineElementFromManifestClip(params: {
98100
duration: clip.duration,
99101
track: clip.track,
100102
domId,
103+
hfId,
101104
selector,
102105
selectorIndex,
103106
sourceFile,
@@ -127,6 +130,7 @@ export function createTimelineElementFromManifestClip(params: {
127130
}
128131
if (hostEl) {
129132
entry.domId = hostEl.id || undefined;
133+
entry.hfId = hostEl.getAttribute("data-hf-id") ?? undefined;
130134
entry.selector = getTimelineElementSelector(hostEl);
131135
entry.selectorIndex =
132136
doc && entry.selector
@@ -187,6 +191,7 @@ export function createImplicitTimelineLayersFromDOM(
187191

188192
layers.push({
189193
domId: child.id || undefined,
194+
hfId: child.getAttribute("data-hf-id") ?? undefined,
190195
duration: rootDuration,
191196
id: identity.id,
192197
key: identity.key,
@@ -262,6 +267,7 @@ export function parseTimelineFromDOM(doc: Document, rootDuration: number): Timel
262267
duration: dur,
263268
track: isNaN(track) ? 0 : track,
264269
domId: el.id || undefined,
270+
hfId: el.getAttribute("data-hf-id") ?? undefined,
265271
selector,
266272
selectorIndex,
267273
sourceFile,

packages/studio/src/player/store/playerStore.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export interface TimelineElement {
2222
duration: number;
2323
track: number;
2424
domId?: string;
25+
/** Stable `data-hf-id` attribute value — used as primary patch target when present */
26+
hfId?: string;
2527
/** Best-effort selector used when patching source HTML back from timeline edits */
2628
selector?: string;
2729
/** Zero-based occurrence index for non-unique selectors */

0 commit comments

Comments
 (0)