Skip to content

Commit 7eedbff

Browse files
committed
Improve Figma Studio handoff diagnostics
1 parent d16f7f0 commit 7eedbff

3 files changed

Lines changed: 170 additions & 35 deletions

File tree

plugins/figma-to-wordpress-studio/src/payload.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { WebsiteArtifact } from "./index";
2-
import type { GeneratedArtifact, NormalizedSelection } from "./types";
2+
import type { GeneratedArtifact, NormalizedSceneNode, NormalizedSelection } from "./types";
33

44
export type WebsiteArtifactFileRole = "html" | "css" | "js" | "asset" | "metadata";
55

@@ -19,6 +19,17 @@ export interface WebsiteArtifactBundle {
1919
import_source: "figma-to-wordpress-studio";
2020
}
2121

22+
export interface FigmaSourceDebugSummary {
23+
scope: NormalizedSelection["selectionIntent"]["scope"];
24+
pageId: string;
25+
selectedNodeCount: number;
26+
nodeCount: number;
27+
assetCount: number;
28+
diagnosticCount: number;
29+
warningCount: number;
30+
errorCount: number;
31+
}
32+
2233
export interface FigmaSourcePayload {
2334
schema: "wordpress-studio/figma-source/v1";
2435
source: {
@@ -43,14 +54,20 @@ export interface FigmaSourcePayload {
4354
importAssets: true;
4455
};
4556
};
46-
debug?: {
57+
debug: {
58+
handoffId?: string;
59+
summary: FigmaSourceDebugSummary;
4760
generatedArtifact?: GeneratedArtifact["studioImportPayload"];
4861
diagnostics: GeneratedArtifact["diagnostics"];
4962
metadata?: GeneratedArtifact["metadata"];
5063
};
5164
}
5265

53-
export function toFigmaSourcePayload(selection: NormalizedSelection, artifact?: GeneratedArtifact | null): FigmaSourcePayload {
66+
export function toFigmaSourcePayload(
67+
selection: NormalizedSelection,
68+
artifact?: GeneratedArtifact | null,
69+
handoffId?: string,
70+
): FigmaSourcePayload {
5471
return {
5572
schema: "wordpress-studio/figma-source/v1",
5673
source: {
@@ -75,11 +92,31 @@ export function toFigmaSourcePayload(selection: NormalizedSelection, artifact?:
7592
importAssets: true,
7693
},
7794
},
78-
debug: artifact ? {
79-
generatedArtifact: artifact.studioImportPayload,
80-
diagnostics: artifact.diagnostics,
81-
metadata: artifact.metadata,
82-
} : undefined,
95+
debug: {
96+
handoffId,
97+
summary: sourceDebugSummary(selection, artifact),
98+
generatedArtifact: artifact?.studioImportPayload,
99+
diagnostics: artifact?.diagnostics || [],
100+
metadata: artifact?.metadata,
101+
},
102+
};
103+
}
104+
105+
export function sourceDebugSummary(
106+
selection: NormalizedSelection,
107+
artifact?: GeneratedArtifact | null,
108+
): FigmaSourceDebugSummary {
109+
const selectedRootNodes = selection.selectedNodes.length ? selection.selectedNodes : [selection.currentPage];
110+
111+
return {
112+
scope: selection.selectionIntent.scope,
113+
pageId: selection.selectionIntent.pageId,
114+
selectedNodeCount: selection.selectionIntent.selectedNodeIds.length,
115+
nodeCount: selectedRootNodes.reduce((count, node) => count + countSceneNodes(node), 0),
116+
assetCount: selection.assets.length,
117+
diagnosticCount: artifact?.diagnostics.length || 0,
118+
warningCount: artifact?.diagnostics.filter((diagnostic) => diagnostic.level === "warning").length || 0,
119+
errorCount: artifact?.diagnostics.filter((diagnostic) => diagnostic.level === "error").length || 0,
83120
};
84121
}
85122

@@ -93,6 +130,10 @@ export function toWebsiteArtifactBundle(artifact: WebsiteArtifact, _selection: N
93130
};
94131
}
95132

133+
function countSceneNodes(node: NormalizedSceneNode): number {
134+
return 1 + (node.children || []).reduce((count, child) => count + countSceneNodes(child), 0);
135+
}
136+
96137
function toWebsiteArtifactBundleFile(filePath: string, content: string): WebsiteArtifactBundleFile {
97138
const file: WebsiteArtifactBundleFile = {
98139
path: filePath.startsWith("website/") ? filePath : `website/${filePath}`,

plugins/figma-to-wordpress-studio/src/ui.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ const refreshButton = document.querySelector<HTMLButtonElement>("#refresh");
99
const studioButton = document.querySelector<HTMLButtonElement>("#studio");
1010
const studioHandoffEndpoint = "http://127.0.0.1:48732/figma-to-wordpress/import";
1111

12+
type StudioHandoffResponse = {
13+
success?: boolean;
14+
error?: string;
15+
requestId?: string;
16+
siteName?: string;
17+
siteUrl?: string;
18+
importSummary?: unknown;
19+
};
20+
1221
function log(message: string, details?: unknown) {
1322
if (typeof details === "undefined") {
1423
console.info(`[Figma to WordPress Studio] ${message}`);
@@ -49,13 +58,16 @@ async function openInStudio() {
4958
return;
5059
}
5160

61+
const handoffId = `figma-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
62+
const sourcePayload = toFigmaSourcePayload(currentSelection, currentArtifact, handoffId);
63+
const summary = sourcePayload.debug.summary;
64+
5265
if (studioButton) {
5366
studioButton.disabled = true;
5467
}
55-
setStatus("Sending this Figma file to WordPress Studio...");
68+
setStatus(`Sending ${summary.nodeCount} nodes to WordPress Studio...`);
5669

5770
try {
58-
const sourcePayload = toFigmaSourcePayload(currentSelection, currentArtifact);
5971
const response = await fetch(studioHandoffEndpoint, {
6072
method: "POST",
6173
headers: {
@@ -66,24 +78,35 @@ async function openInStudio() {
6678
siteName: currentSelection.name,
6779
}),
6880
});
69-
const data = await response.json().catch(() => null) as { success?: boolean; error?: string } | null;
81+
const data = await response.json().catch(() => null) as StudioHandoffResponse | null;
7082

7183
if (!response.ok || !data?.success) {
72-
throw new Error(data?.error || `Studio handoff failed with HTTP ${response.status}.`);
84+
const requestRef = data?.requestId ? ` Ref ${data.requestId}.` : "";
85+
throw new Error(`${data?.error || `Studio handoff failed with HTTP ${response.status}.`}${requestRef}`);
7386
}
7487

75-
setStatus("Studio created the WordPress site and is opening it in your browser.");
88+
setStatus(`Studio accepted ${data.siteName || currentSelection.name}${data.siteUrl ? ` at ${data.siteUrl}` : ""}.`);
7689
sendToPlugin({ type: "notify", message: "Sent to WordPress Studio." });
7790
log("Studio import handoff accepted.", {
7891
endpoint: studioHandoffEndpoint,
92+
handoffId,
93+
requestId: data.requestId,
94+
siteName: data.siteName,
95+
siteUrl: data.siteUrl,
96+
importSummary: data.importSummary,
7997
schema: sourcePayload.schema,
8098
selectionScope: sourcePayload.intent.scope,
8199
pageId: sourcePayload.intent.pageId,
82100
selectedNodes: sourcePayload.intent.selectedNodeIds.length,
101+
sourceSummary: summary,
83102
debugArtifact: artifactBundleSummary(currentArtifact),
84103
});
85104
} catch (error) {
86-
console.error("[Figma to WordPress Studio] Studio handoff failed.", error);
105+
console.error("[Figma to WordPress Studio] Studio handoff failed.", {
106+
handoffId,
107+
sourceSummary: summary,
108+
error,
109+
});
87110
setStatus(error instanceof Error ? error.message : "Could not reach WordPress Studio.");
88111
} finally {
89112
updateActions();

plugins/figma-to-wordpress-studio/tests/generate-artifact.test.mjs

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -156,56 +156,127 @@ test("generates a Studio CLI import payload for the Figma handoff", async () =>
156156
assert.ok(artifact.studioImportPayload.files.some((file) => file.path === "website/index.html"));
157157
});
158158

159-
test("generates a Studio Figma source payload for the primary handoff", async () => {
159+
test("generates a source-first Figma handoff payload with debug summary", async () => {
160160
const { toFigmaSourcePayload } = await loadPayloadModule();
161161
const selection = {
162162
id: "root",
163163
name: "Studio Figma Source Demo",
164164
type: "DOCUMENT",
165165
exportedAt: "2026-01-01T00:00:00.000Z",
166-
root: { id: "doc", name: "Document", type: "DOCUMENT", visible: true },
166+
root: {
167+
id: "doc",
168+
name: "Document",
169+
type: "DOCUMENT",
170+
visible: true,
171+
children: [
172+
{
173+
id: "page-1",
174+
name: "Landing",
175+
type: "PAGE",
176+
visible: true,
177+
children: [
178+
{
179+
id: "frame-1",
180+
name: "Hero",
181+
type: "FRAME",
182+
visible: true,
183+
children: [
184+
{
185+
id: "headline",
186+
name: "Headline",
187+
type: "TEXT",
188+
visible: true,
189+
characters: "Import with Studio",
190+
},
191+
],
192+
},
193+
],
194+
},
195+
],
196+
},
167197
source: {
168198
provider: "figma",
169199
plugin: "figma-to-wordpress-studio",
170200
fileKey: "abc123",
171201
fileName: "Studio Figma Source Demo",
172202
editorType: "figma",
173-
currentPage: { id: "page", name: "Landing" },
203+
currentPage: { id: "page-1", name: "Landing" },
174204
},
175205
selectionIntent: {
176206
scope: "selected-nodes",
177-
pageId: "page",
207+
pageId: "page-1",
178208
pageName: "Landing",
179-
selectedNodeIds: ["frame"],
180-
rootNodeIds: ["frame"],
209+
selectedNodeIds: ["frame-1"],
210+
rootNodeIds: ["frame-1"],
181211
},
182212
currentPage: {
183-
id: "page",
213+
id: "page-1",
184214
name: "Landing",
185215
type: "PAGE",
186216
visible: true,
187-
children: [{ id: "frame", name: "Hero", type: "FRAME", visible: true }],
217+
children: [
218+
{
219+
id: "frame-1",
220+
name: "Hero",
221+
type: "FRAME",
222+
visible: true,
223+
children: [
224+
{
225+
id: "headline",
226+
name: "Headline",
227+
type: "TEXT",
228+
visible: true,
229+
characters: "Import with Studio",
230+
},
231+
],
232+
},
233+
],
188234
},
189-
selectedNodes: [{ id: "frame", name: "Hero", type: "FRAME", visible: true }],
190-
assets: [],
235+
selectedNodes: [
236+
{
237+
id: "frame-1",
238+
name: "Hero",
239+
type: "FRAME",
240+
visible: true,
241+
children: [
242+
{
243+
id: "headline",
244+
name: "Headline",
245+
type: "TEXT",
246+
visible: true,
247+
characters: "Import with Studio",
248+
},
249+
],
250+
},
251+
],
252+
assets: [{ id: "asset-1", name: "Hero.png", format: "PNG", dataUrl: "data:image/png;base64,YQ==" }],
191253
};
192-
const sourcePayload = toFigmaSourcePayload(selection, {
254+
const artifact = {
193255
title: selection.name,
194256
html: "",
195257
css: "",
196258
studioImportPayload: { schema: "blocks-engine/php-transformer/site-artifact/v1" },
197259
files: {},
198-
diagnostics: [],
199-
});
260+
diagnostics: [{ level: "warning", nodeId: "frame-1", nodeName: "Hero", message: "Review layout" }],
261+
};
262+
const source = toFigmaSourcePayload(selection, artifact, "handoff-123");
200263

201-
assert.equal(sourcePayload.schema, "wordpress-studio/figma-source/v1");
202-
assert.equal(sourcePayload.source.metadata.fileKey, "abc123");
203-
assert.equal(sourcePayload.intent.scope, "selected-nodes");
204-
assert.equal(sourcePayload.scenegraph.currentPage.id, "page");
205-
assert.equal(sourcePayload.scenegraph.selectedNodes[0].id, "frame");
206-
assert.equal(sourcePayload.transform.route, "static-site-importer/figma");
207-
assert.equal(sourcePayload.transform.options.preserveSourceScenegraph, true);
208-
assert.equal(sourcePayload.debug.generatedArtifact.schema, "blocks-engine/php-transformer/site-artifact/v1");
264+
assert.equal(source.schema, "wordpress-studio/figma-source/v1");
265+
assert.equal(source.source.metadata.fileKey, "abc123");
266+
assert.equal(source.intent.scope, "selected-nodes");
267+
assert.deepEqual(source.intent.selectedNodeIds, ["frame-1"]);
268+
assert.equal(source.scenegraph.currentPage.id, "page-1");
269+
assert.equal(source.scenegraph.selectedNodes[0].id, "frame-1");
270+
assert.equal(source.transform.route, "static-site-importer/figma");
271+
assert.equal(source.transform.options.preserveSourceScenegraph, true);
272+
assert.equal(source.debug.handoffId, "handoff-123");
273+
assert.equal(source.debug.generatedArtifact.schema, "blocks-engine/php-transformer/site-artifact/v1");
274+
assert.equal(source.debug.summary.scope, "selected-nodes");
275+
assert.equal(source.debug.summary.selectedNodeCount, 1);
276+
assert.equal(source.debug.summary.nodeCount, 2);
277+
assert.equal(source.debug.summary.assetCount, 1);
278+
assert.equal(source.debug.summary.diagnosticCount, artifact.diagnostics.length);
279+
assert.equal(source.debug.summary.warningCount, 1);
209280
});
210281

211282
test("reports missing image sources with node identity", async () => {

0 commit comments

Comments
 (0)