feat(studio): wire hfId through DOM-edit patch targets, activate hfId lookup path (R7, T5a) (#1297)

This commit is contained in:
Vance Ingalls
2026-06-09 14:19:46 -07:00
committed by GitHub
parent 2071c85c9a
commit 82c754f27a
4 changed files with 45 additions and 17 deletions
@@ -26,6 +26,7 @@ export {
// Layers, text fields, capabilities, selection, patch ops
export {
buildDefaultDomEditTextField,
buildDomEditPatchTarget,
buildDomEditStylePatchOperation,
buildDomEditTextPatchOperation,
collectDomEditLayerItems,
@@ -1,9 +1,32 @@
// @vitest-environment jsdom
import { describe, expect, it } from "vitest";
import { resolveDomEditSelection } from "./domEditingLayers";
import { resolveDomEditSelection, buildDomEditPatchTarget } from "./domEditingLayers";
const opts = { activeCompositionPath: "index.html", isMasterView: true, skipSourceProbe: true };
describe("buildDomEditPatchTarget", () => {
it("includes hfId when selection has hfId", () => {
const target = buildDomEditPatchTarget({
id: undefined,
hfId: "hf-abc",
selector: ".foo",
selectorIndex: 0,
});
expect(target.hfId).toBe("hf-abc");
});
it("includes id and selector when hfId absent", () => {
const target = buildDomEditPatchTarget({
id: "hero",
hfId: undefined,
selector: "#hero",
selectorIndex: undefined,
});
expect(target.id).toBe("hero");
expect(target.hfId).toBeUndefined();
});
});
describe("resolveDomEditSelection — hfId from data-hf-id", () => {
it("populates hfId from the element data-hf-id attribute", async () => {
const el = document.createElement("div");
@@ -555,3 +555,14 @@ export function isTextEditableSelection(selection: DomEditSelection): boolean {
}
// buildElementAgentPrompt is in domEditingAgentPrompt.ts
export function buildDomEditPatchTarget(
selection: Pick<DomEditSelection, "id" | "hfId" | "selector" | "selectorIndex">,
): { id?: string | null; hfId?: string; selector?: string; selectorIndex?: number } {
return {
id: selection.id,
hfId: selection.hfId,
selector: selection.selector,
selectorIndex: selection.selectorIndex,
};
}
+9 -16
View File
@@ -5,7 +5,11 @@ import type { PatchOperation } from "../utils/sourcePatcher";
import { trackStudioEvent } from "../utils/studioTelemetry";
import { saveProjectFilesWithHistory } from "../utils/studioFileHistory";
import { primaryFontFamilyValue } from "../utils/studioFontHelpers";
import { getDomEditTargetKey, type DomEditSelection } from "../components/editor/domEditing";
import {
buildDomEditPatchTarget,
getDomEditTargetKey,
type DomEditSelection,
} from "../components/editor/domEditing";
import {
applyStudioPathOffset,
applyStudioBoxSize,
@@ -182,11 +186,7 @@ export function useDomEditCommits({
if (options?.shouldSave && !options.shouldSave()) return;
const patchTarget: { id?: string | null; selector?: string; selectorIndex?: number } = {
id: selection.id,
selector: selection.selector,
selectorIndex: selection.selectorIndex,
};
const patchTarget = buildDomEditPatchTarget(selection);
// Mark the save timestamp before the file write so the SSE file-change
// handler suppresses the reload even if the event arrives before the
@@ -471,16 +471,8 @@ export function useDomEditCommits({
if (typeof originalContent !== "string")
throw new Error(`Missing file contents for ${targetPath}`);
const patchTarget: { id?: string; selector?: string; selectorIndex?: number } = selection.id
? {
id: selection.id,
selector: selection.selector,
selectorIndex: selection.selectorIndex,
}
: selection.selector
? { selector: selection.selector, selectorIndex: selection.selectorIndex }
: ({} as never);
if (!patchTarget.id && !patchTarget.selector) {
const patchTarget = buildDomEditPatchTarget(selection);
if (!patchTarget.id && !patchTarget.selector && !patchTarget.hfId) {
throw new Error("Selected element has no patchable target");
}
@@ -561,6 +553,7 @@ export function useDomEditCommits({
{
element: entry.element,
id: entry.id ?? null,
hfId: entry.element.getAttribute("data-hf-id") ?? undefined,
selector: entry.selector,
selectorIndex: entry.selectorIndex,
sourceFile: entry.sourceFile,