@agent-native/core 0.182.1 → 0.182.2-nightly-20260917210345
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/clips/app/root.tsx +59 -10
- package/corpus/templates/clips/changelog/2026-09-17-desktop-popover-closes-when-clicking-away.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-17-self-hosted-extension-sign-in.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +45 -25
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +43 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +55 -6
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
- package/corpus/templates/design/actions/apply-visual-edit.ts +1 -0
- package/corpus/templates/design/actions/create-design-from-template.ts +50 -35
- package/corpus/templates/design/actions/create-design-version.ts +21 -0
- package/corpus/templates/design/actions/create-file.ts +35 -33
- package/corpus/templates/design/actions/delete-design.ts +29 -28
- package/corpus/templates/design/actions/delete-file.ts +154 -23
- package/corpus/templates/design/actions/generate-design.ts +28 -16
- package/corpus/templates/design/actions/get-component-details.ts +187 -33
- package/corpus/templates/design/actions/get-design-version.ts +9 -1
- package/corpus/templates/design/actions/go-to-main-component.ts +37 -35
- package/corpus/templates/design/actions/index-components.ts +241 -126
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +12 -9
- package/corpus/templates/design/actions/present-design-variants.ts +68 -72
- package/corpus/templates/design/actions/rename-component.ts +294 -0
- package/corpus/templates/design/actions/rename-screen.ts +10 -10
- package/corpus/templates/design/actions/update-file.ts +327 -330
- package/corpus/templates/design/actions/update-screen-source.ts +2 -43
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +8 -1
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +55 -33
- package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +4 -2
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +5 -1
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
- package/corpus/templates/design/app/components/design/types.ts +1 -0
- package/corpus/templates/design/app/hooks/use-design-versions.ts +3 -2
- package/corpus/templates/design/app/i18n-breakpoints.ts +0 -110
- package/corpus/templates/design/app/i18n-data.ts +0 -10
- package/corpus/templates/design/app/pages/DesignEditor.tsx +250 -107
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +2 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +69 -47
- package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +8 -1
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +107 -2
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
- package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +74 -22
- package/corpus/templates/design/app/pages/design-editor/source-publication.ts +14 -2
- package/corpus/templates/design/changelog/2026-09-16-design-components-retain-linked-identities-when-edited-and-d.md +6 -0
- package/corpus/templates/design/changelog/2026-09-17-screen-deletion-is-immediate-and-undoable-with-history-that-.md +6 -0
- package/corpus/templates/design/e2e/fixtures/design-component-react/index.html +98 -0
- package/corpus/templates/design/e2e/fixtures/design-component-react/inline-component.html +65 -0
- package/corpus/templates/design/server/lib/design-data-mutation.ts +9 -5
- package/corpus/templates/design/server/lib/design-snapshot.ts +28 -16
- package/corpus/templates/design/server/lib/design-versions.ts +208 -130
- package/corpus/templates/design/server/lib/fusion-screens.ts +46 -20
- package/corpus/templates/design/server/lib/import-design-files.ts +5 -1
- package/corpus/templates/design/server/lib/localhost-connection.ts +54 -0
- package/corpus/templates/design/server/source-workspace.ts +412 -356
- package/corpus/templates/design/shared/component-library.ts +36 -6
- package/corpus/templates/design/shared/component-model.ts +8 -2
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/chat/agentkit-protocol.js +148 -34
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/package.json +4 -4
- package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +0 -66
|
@@ -25,7 +25,13 @@ import { z } from "zod";
|
|
|
25
25
|
|
|
26
26
|
import { getDb, schema } from "../server/db/index.js";
|
|
27
27
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
28
|
+
import {
|
|
29
|
+
fetchLocalhostSnapshot,
|
|
30
|
+
resolveLocalhostBridgeConnection,
|
|
31
|
+
resolveLocalhostConnectionScope,
|
|
32
|
+
} from "../server/lib/localhost-connection.js";
|
|
28
33
|
import { resolveSourceCapabilities } from "../shared/capability-resolver.js";
|
|
34
|
+
import { sanitizeMarkup } from "../shared/capture-sanitize.js";
|
|
29
35
|
import { buildCodeLayerProjection } from "../shared/code-layer.js";
|
|
30
36
|
import type { CodeLayerNode, CodeLayerSource } from "../shared/code-layer.js";
|
|
31
37
|
import {
|
|
@@ -38,10 +44,14 @@ import {
|
|
|
38
44
|
componentNameFor,
|
|
39
45
|
componentNodeIdMatches,
|
|
40
46
|
extractProps,
|
|
41
|
-
|
|
47
|
+
instanceFromNode,
|
|
42
48
|
} from "../shared/component-model.js";
|
|
43
49
|
import { hasCapability } from "../shared/design-source-capabilities.js";
|
|
44
|
-
import {
|
|
50
|
+
import { isStandaloneHttpUrl } from "../shared/html-content.js";
|
|
51
|
+
import {
|
|
52
|
+
designConnectionIdFromData,
|
|
53
|
+
designSourceTypeFromData,
|
|
54
|
+
} from "../shared/source-mode.js";
|
|
45
55
|
|
|
46
56
|
export function canRestoreComponentMain(
|
|
47
57
|
node: Pick<CodeLayerNode, "dataAttributes">,
|
|
@@ -65,6 +75,124 @@ function parseJson<T>(raw: string | null | undefined, fallback: T): T {
|
|
|
65
75
|
}
|
|
66
76
|
}
|
|
67
77
|
|
|
78
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
79
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function metadataForFile(
|
|
83
|
+
data: Record<string, unknown>,
|
|
84
|
+
fileId: string,
|
|
85
|
+
): Record<string, unknown> {
|
|
86
|
+
for (const key of ["screenMetadata", "localhostScreens"]) {
|
|
87
|
+
const metadata = data[key];
|
|
88
|
+
const entry = isRecord(metadata) ? metadata[fileId] : undefined;
|
|
89
|
+
if (isRecord(entry)) return entry;
|
|
90
|
+
}
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function stringValue(
|
|
95
|
+
attributes: Record<string, string>,
|
|
96
|
+
names: readonly string[],
|
|
97
|
+
): string | undefined {
|
|
98
|
+
for (const name of names) {
|
|
99
|
+
const value = attributes[name]?.trim();
|
|
100
|
+
if (value) return value;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function positiveIntegerValue(
|
|
106
|
+
attributes: Record<string, string>,
|
|
107
|
+
names: readonly string[],
|
|
108
|
+
): number | undefined {
|
|
109
|
+
const raw = stringValue(attributes, names);
|
|
110
|
+
if (!raw || !/^\d+$/.test(raw)) return undefined;
|
|
111
|
+
const value = Number(raw);
|
|
112
|
+
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function liveComponentNameFor(
|
|
116
|
+
node: CodeLayerNode,
|
|
117
|
+
sourceType: ReturnType<typeof designSourceTypeFromData>,
|
|
118
|
+
): string | null {
|
|
119
|
+
return (
|
|
120
|
+
componentNameFor(node) ??
|
|
121
|
+
(sourceType !== "inline"
|
|
122
|
+
? (stringValue(node.dataAttributes, ["data-component-name"]) ?? null)
|
|
123
|
+
: null)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function liveInstanceForNode(
|
|
128
|
+
node: CodeLayerNode,
|
|
129
|
+
name: string,
|
|
130
|
+
): NonNullable<ReturnType<typeof instanceFromNode>> {
|
|
131
|
+
const existing = instanceFromNode(node);
|
|
132
|
+
if (existing) return existing;
|
|
133
|
+
const stableNodeId =
|
|
134
|
+
stringValue(node.dataAttributes, ["data-agent-native-node-id"]) ?? node.id;
|
|
135
|
+
return {
|
|
136
|
+
instanceId: stableNodeId,
|
|
137
|
+
name,
|
|
138
|
+
props: extractProps(node),
|
|
139
|
+
selector: node.selector,
|
|
140
|
+
nodeId: stableNodeId,
|
|
141
|
+
componentId:
|
|
142
|
+
stringValue(node.dataAttributes, [COMPONENT_ID_ATTR]) ?? undefined,
|
|
143
|
+
componentRef:
|
|
144
|
+
stringValue(node.dataAttributes, [COMPONENT_REF_ATTR]) ?? undefined,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function sourceLocationForNode(
|
|
149
|
+
node: CodeLayerNode,
|
|
150
|
+
indexRow:
|
|
151
|
+
| {
|
|
152
|
+
filePath?: string | null;
|
|
153
|
+
exportName?: string | null;
|
|
154
|
+
}
|
|
155
|
+
| undefined,
|
|
156
|
+
canResolveToFile: boolean,
|
|
157
|
+
componentName: string,
|
|
158
|
+
) {
|
|
159
|
+
if (!canResolveToFile) return undefined;
|
|
160
|
+
const filePath =
|
|
161
|
+
stringValue(node.dataAttributes, [
|
|
162
|
+
"data-source-file",
|
|
163
|
+
"data-agent-native-source-file",
|
|
164
|
+
"data-agent-native-source-path",
|
|
165
|
+
]) ?? indexRow?.filePath;
|
|
166
|
+
if (!filePath) return undefined;
|
|
167
|
+
const line = positiveIntegerValue(node.dataAttributes, [
|
|
168
|
+
"data-source-line",
|
|
169
|
+
"data-agent-native-source-line",
|
|
170
|
+
]);
|
|
171
|
+
const column = positiveIntegerValue(node.dataAttributes, [
|
|
172
|
+
"data-source-column",
|
|
173
|
+
"data-agent-native-source-column",
|
|
174
|
+
]);
|
|
175
|
+
return {
|
|
176
|
+
filePath,
|
|
177
|
+
exportName: indexRow?.exportName ?? undefined,
|
|
178
|
+
...(line ? { line } : {}),
|
|
179
|
+
...(column ? { column } : {}),
|
|
180
|
+
componentName:
|
|
181
|
+
stringValue(node.dataAttributes, ["data-component-name"]) ??
|
|
182
|
+
componentName,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export class ComponentDetailsLiveProjectionError extends Error {
|
|
187
|
+
readonly statusCode = 424;
|
|
188
|
+
readonly code = "LIVE_PROJECTION_UNAVAILABLE";
|
|
189
|
+
|
|
190
|
+
constructor(message: string) {
|
|
191
|
+
super(message);
|
|
192
|
+
this.name = "ComponentDetailsLiveProjectionError";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
68
196
|
// ─── Action ───────────────────────────────────────────────────────────────────
|
|
69
197
|
|
|
70
198
|
export default defineAction({
|
|
@@ -131,6 +259,7 @@ export default defineAction({
|
|
|
131
259
|
designId: schema.designFiles.designId,
|
|
132
260
|
filename: schema.designFiles.filename,
|
|
133
261
|
content: schema.designFiles.content,
|
|
262
|
+
data: schema.designs.data,
|
|
134
263
|
})
|
|
135
264
|
.from(schema.designFiles)
|
|
136
265
|
.innerJoin(
|
|
@@ -142,11 +271,51 @@ export default defineAction({
|
|
|
142
271
|
|
|
143
272
|
if (!file) throw new Error("Design HTML file not found.");
|
|
144
273
|
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
274
|
+
// Inline screens use durable SQL. URL-backed screens must be projected from
|
|
275
|
+
// the live bridge because the stored value is only a route URL.
|
|
276
|
+
let html = file.content ?? "";
|
|
277
|
+
if (isStandaloneHttpUrl(html)) {
|
|
278
|
+
if (sourceType !== "localhost") {
|
|
279
|
+
throw new ComponentDetailsLiveProjectionError(
|
|
280
|
+
`Component details for URL-backed screen "${file.filename}" require a localhost bridge snapshot; source type "${sourceType}" has no live component mapping.`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const designData = parseJson<Record<string, unknown>>(file.data, {});
|
|
284
|
+
const metadata = metadataForFile(designData, file.id);
|
|
285
|
+
const connectionId =
|
|
286
|
+
(typeof metadata.connectionId === "string" &&
|
|
287
|
+
metadata.connectionId.trim()) ||
|
|
288
|
+
designConnectionIdFromData(designData);
|
|
289
|
+
if (!connectionId) {
|
|
290
|
+
throw new ComponentDetailsLiveProjectionError(
|
|
291
|
+
`URL-backed screen "${file.filename}" has no localhost connection metadata. Reopen visual edit or reconnect the localhost app before requesting component details.`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
const { ownerEmail, orgId } = await resolveLocalhostConnectionScope({
|
|
295
|
+
designId,
|
|
296
|
+
});
|
|
297
|
+
const connection = await resolveLocalhostBridgeConnection({
|
|
298
|
+
connectionId,
|
|
299
|
+
ownerEmail,
|
|
300
|
+
orgId,
|
|
301
|
+
});
|
|
302
|
+
const previewToken =
|
|
303
|
+
typeof metadata.previewToken === "string"
|
|
304
|
+
? metadata.previewToken
|
|
305
|
+
: null;
|
|
306
|
+
html = sanitizeMarkup(
|
|
307
|
+
await fetchLocalhostSnapshot({
|
|
308
|
+
bridgeUrl: connection.bridgeUrl,
|
|
309
|
+
previewToken,
|
|
310
|
+
url: html,
|
|
311
|
+
}),
|
|
312
|
+
).trim();
|
|
313
|
+
if (!html) {
|
|
314
|
+
throw new ComponentDetailsLiveProjectionError(
|
|
315
|
+
`The localhost bridge returned an empty snapshot for "${file.filename}". Reload the live frame and retry.`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
150
319
|
|
|
151
320
|
// ── Projection lookup ────────────────────────────────────────────────────
|
|
152
321
|
const codeLayerSource: CodeLayerSource = {
|
|
@@ -169,28 +338,16 @@ export default defineAction({
|
|
|
169
338
|
);
|
|
170
339
|
}
|
|
171
340
|
|
|
172
|
-
const name =
|
|
341
|
+
const name = liveComponentNameFor(node, sourceType);
|
|
173
342
|
if (!name) {
|
|
174
343
|
throw new Error(
|
|
175
|
-
`Node "${nodeId}" does not carry
|
|
344
|
+
`Node "${nodeId}" does not carry component provenance and is not a component root.`,
|
|
176
345
|
);
|
|
177
346
|
}
|
|
178
347
|
|
|
179
348
|
// ── Simple props from attributes ─────────────────────────────────────────
|
|
180
349
|
const observedProps = extractProps(node);
|
|
181
|
-
const
|
|
182
|
-
typeof node.attributes["x-data"] === "string"
|
|
183
|
-
? node.attributes["x-data"]
|
|
184
|
-
: undefined;
|
|
185
|
-
|
|
186
|
-
const instance: ComponentInstance = {
|
|
187
|
-
instanceId: node.id,
|
|
188
|
-
name,
|
|
189
|
-
props: observedProps,
|
|
190
|
-
alpineData,
|
|
191
|
-
selector: node.selector,
|
|
192
|
-
nodeId,
|
|
193
|
-
};
|
|
350
|
+
const instance = liveInstanceForNode(node, name);
|
|
194
351
|
|
|
195
352
|
// ── Lookup persisted component_index row ─────────────────────────────────
|
|
196
353
|
const [indexRow] = await db
|
|
@@ -212,17 +369,14 @@ export default defineAction({
|
|
|
212
369
|
const persistedStories = parseJson<unknown[]>(indexRow?.stories, []);
|
|
213
370
|
|
|
214
371
|
// ── Source location (real-app only) ──────────────────────────────────────
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
canResolveToFile
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
exportName: indexRow.exportName ?? undefined,
|
|
224
|
-
}
|
|
225
|
-
: undefined;
|
|
372
|
+
// Live bridge provenance is preferred; indexed metadata remains the
|
|
373
|
+
// fallback when the bridge did not stamp a source span.
|
|
374
|
+
const sourceLocation = sourceLocationForNode(
|
|
375
|
+
node,
|
|
376
|
+
indexRow,
|
|
377
|
+
canResolveToFile,
|
|
378
|
+
name,
|
|
379
|
+
);
|
|
226
380
|
|
|
227
381
|
return {
|
|
228
382
|
designId,
|
|
@@ -28,6 +28,9 @@ function parseChatContext(raw: string | null): DesignVersionChatContext | null {
|
|
|
28
28
|
context[key] = candidate;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
if ((value as { surface?: unknown }).surface === "editor") {
|
|
32
|
+
context.surface = "editor";
|
|
33
|
+
}
|
|
31
34
|
return Object.keys(context).length > 0 ? context : null;
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -77,7 +80,12 @@ export default defineAction({
|
|
|
77
80
|
designId,
|
|
78
81
|
label: version.label,
|
|
79
82
|
createdAt: version.createdAt,
|
|
80
|
-
source:
|
|
83
|
+
source:
|
|
84
|
+
chatContext?.surface === "editor"
|
|
85
|
+
? ("editor" as const)
|
|
86
|
+
: chatContext
|
|
87
|
+
? ("chat" as const)
|
|
88
|
+
: ("legacy" as const),
|
|
81
89
|
fileCount: version.fileCount ?? snapshot.files.length,
|
|
82
90
|
chatContext,
|
|
83
91
|
designTitle: snapshot.designTitle ?? null,
|
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* go-to-main-component — Figma's "Go to main component".
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* more than one element; every instance is an independently-duplicated copy
|
|
8
|
-
* of HTML (see `shared/component-model.ts`, `component_index` schema — it
|
|
9
|
-
* stores props/variants/runtime-selectors metadata, never a canonical
|
|
10
|
-
* definition). A true "main component" would require a data-model addition
|
|
11
|
-
* (e.g. an `isMain`/definition pointer on `component_index`) — out of scope
|
|
12
|
-
* here; flagged for a follow-up if promoting a specific instance as
|
|
13
|
-
* authoritative becomes a real need.
|
|
14
|
-
*
|
|
15
|
-
* Closest tractable equivalent: treat the EARLIEST instance of the same
|
|
16
|
-
* component name — scanning every design file in `createdAt` order, document
|
|
17
|
-
* order within each file — as the "main" analogue, mirroring the common case
|
|
18
|
-
* where a component's original occurrence predates its copies. If the given
|
|
19
|
-
* instance already IS that earliest one, this returns `isMain: true` instead
|
|
20
|
-
* of navigating (nothing to jump to). Otherwise it writes a `navigate`
|
|
21
|
-
* app-state command (same mechanism as `navigate` / `open-component-source`)
|
|
22
|
-
* so the editor selects the target instance, including switching design
|
|
23
|
-
* files when the main instance lives on a different screen.
|
|
4
|
+
* A canonical linked component has a persisted opaque id on its main root and
|
|
5
|
+
* references. Resolve that id first. Legacy name-only annotations still use
|
|
6
|
+
* the earliest same-name instance as their compatibility fallback.
|
|
24
7
|
*
|
|
25
8
|
* Navigation-only + inline/Alpine designs only (real-app sources return a
|
|
26
9
|
* CTA). Because this writes the transient `navigate` application-state
|
|
@@ -43,6 +26,8 @@ import {
|
|
|
43
26
|
scanComponentLibrary,
|
|
44
27
|
} from "../shared/component-library.js";
|
|
45
28
|
import {
|
|
29
|
+
COMPONENT_ID_ATTR,
|
|
30
|
+
COMPONENT_REF_ATTR,
|
|
46
31
|
componentNameFor,
|
|
47
32
|
componentNodeIdMatches,
|
|
48
33
|
} from "../shared/component-model.js";
|
|
@@ -66,12 +51,11 @@ async function liveContent(
|
|
|
66
51
|
export default defineAction({
|
|
67
52
|
description:
|
|
68
53
|
"Resolve the 'main' instance of a component (Figma's Go to main " +
|
|
69
|
-
"component).
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"switch design files).",
|
|
54
|
+
"component). Canonical linked components resolve by their persisted " +
|
|
55
|
+
"component id; legacy name-only annotations fall back to the earliest " +
|
|
56
|
+
"same-name instance across the design's files. Returns isMain=true when " +
|
|
57
|
+
"the selected instance is already the main; otherwise navigates the " +
|
|
58
|
+
"editor to it.",
|
|
75
59
|
schema: z.object({
|
|
76
60
|
designId: z.string().describe("Design project ID"),
|
|
77
61
|
nodeId: z
|
|
@@ -154,9 +138,12 @@ export default defineAction({
|
|
|
154
138
|
}
|
|
155
139
|
|
|
156
140
|
const componentName = componentNameFor(node);
|
|
157
|
-
|
|
141
|
+
const componentId =
|
|
142
|
+
node.dataAttributes[COMPONENT_ID_ATTR]?.trim() ||
|
|
143
|
+
node.dataAttributes[COMPONENT_REF_ATTR]?.trim();
|
|
144
|
+
if (!componentName && !componentId) {
|
|
158
145
|
throw new Error(
|
|
159
|
-
`Node "${nodeId}" is not a component root (no
|
|
146
|
+
`Node "${nodeId}" is not a component root (no component name or linked identity).`,
|
|
160
147
|
);
|
|
161
148
|
}
|
|
162
149
|
|
|
@@ -190,16 +177,31 @@ export default defineAction({
|
|
|
190
177
|
);
|
|
191
178
|
|
|
192
179
|
const entries = scanComponentLibrary(filesForScan);
|
|
193
|
-
const matches =
|
|
180
|
+
const matches = componentId
|
|
181
|
+
? entries.filter(
|
|
182
|
+
(entry) =>
|
|
183
|
+
entry.componentId === componentId ||
|
|
184
|
+
entry.componentRef === componentId,
|
|
185
|
+
)
|
|
186
|
+
: entriesForComponent(entries, componentName!);
|
|
194
187
|
|
|
195
188
|
if (matches.length === 0) {
|
|
196
189
|
// Shouldn't happen (the current node itself matches), but guard anyway.
|
|
197
190
|
throw new Error(
|
|
198
|
-
`No instances of component "${componentName}" found across the design's files.`,
|
|
191
|
+
`No instances of component "${componentName ?? componentId}" found across the design's files.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const resolvedComponentName = componentName ?? matches[0]?.name;
|
|
195
|
+
if (!resolvedComponentName) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Component identity "${componentId}" has no named canonical root.`,
|
|
199
198
|
);
|
|
200
199
|
}
|
|
201
200
|
|
|
202
|
-
const main =
|
|
201
|
+
const main = componentId
|
|
202
|
+
? (matches.find((entry) => entry.componentId === componentId) ??
|
|
203
|
+
matches[0])
|
|
204
|
+
: matches[0];
|
|
203
205
|
// Compare against the caller's own stable `nodeId` param (a durable
|
|
204
206
|
// data-agent-native-node-id), not `node.id` (an ephemeral id scoped to
|
|
205
207
|
// this projection call) — `scanComponentLibrary` resolves the same
|
|
@@ -210,7 +212,7 @@ export default defineAction({
|
|
|
210
212
|
return {
|
|
211
213
|
designId,
|
|
212
214
|
nodeId,
|
|
213
|
-
componentName,
|
|
215
|
+
componentName: resolvedComponentName,
|
|
214
216
|
sourceType,
|
|
215
217
|
ctaRequired: false,
|
|
216
218
|
isMain: true,
|
|
@@ -218,8 +220,8 @@ export default defineAction({
|
|
|
218
220
|
navigated: false,
|
|
219
221
|
note:
|
|
220
222
|
matches.length === 1
|
|
221
|
-
? `"${
|
|
222
|
-
: `This is the earliest instance of "${
|
|
223
|
+
? `"${resolvedComponentName}" has only one instance — this is it.`
|
|
224
|
+
: `This is the earliest instance of "${resolvedComponentName}" across the design.`,
|
|
223
225
|
};
|
|
224
226
|
}
|
|
225
227
|
|
|
@@ -237,7 +239,7 @@ export default defineAction({
|
|
|
237
239
|
return {
|
|
238
240
|
designId,
|
|
239
241
|
nodeId,
|
|
240
|
-
componentName,
|
|
242
|
+
componentName: resolvedComponentName,
|
|
241
243
|
sourceType,
|
|
242
244
|
ctaRequired: false,
|
|
243
245
|
isMain: false,
|