@agent-native/core 0.182.1 → 0.182.2-nightly-20260917225811
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 +107 -19
- package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +1 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +174 -14
- package/corpus/templates/design/actions/apply-visual-edit.ts +3 -1
- package/corpus/templates/design/actions/create-component.ts +173 -12
- 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 +272 -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/EditPanel.tsx +17 -1
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +228 -24
- package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +17 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +151 -32
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +4 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +1 -1
- 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/component-section.tsx +273 -60
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +9 -2
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +13 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +14 -0
- package/corpus/templates/design/app/components/design/types.ts +9 -1
- 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 +669 -161
- package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +20 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +2 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +21 -3
- 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/create-component.ts +44 -3
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +69 -47
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +157 -52
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +76 -1
- package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +152 -44
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +131 -2
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +5 -1
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
- package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +58 -8
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
- package/corpus/templates/design/app/pages/design-editor/history.ts +3 -0
- 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-16-fix-design-nested-layer-dragging-and-screen-duplication-pari.md +6 -0
- package/corpus/templates/design/changelog/2026-09-17-design-drag-and-drop-keeps-duplicates-cross-screen-moves-and.md +6 -0
- package/corpus/templates/design/changelog/2026-09-17-design-preserves-reusable-component-identity-and-props-acros.md +6 -0
- package/corpus/templates/design/changelog/2026-09-17-open-vector-paths-no-longer-show-fills-and-interact-dimensio.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/e2e/helpers.ts +6 -1
- 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 +12 -2
- package/corpus/templates/design/shared/local-jsx-visual-edit.ts +231 -5
- package/corpus/templates/design/shared/source-mode.ts +22 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/AssistantChat.d.ts +4 -1
- package/dist/client/AssistantChat.js +26 -3
- package/dist/client/chat/agentkit-protocol.js +148 -34
- package/dist/client/chat/message-components.js +7 -5
- package/dist/client/chat/tool-call-display.js +4 -3
- package/dist/client/conversation/AgentConversation.js +4 -2
- package/dist/client/tool-display.d.ts +9 -0
- package/dist/client/tool-display.js +15 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +2 -0
- package/dist/localization/core-messages/de-DE.js +2 -0
- package/dist/localization/core-messages/en-US.d.ts +2 -0
- package/dist/localization/core-messages/en-US.js +2 -0
- package/dist/localization/core-messages/es-ES.js +2 -0
- package/dist/localization/core-messages/fr-FR.js +2 -0
- package/dist/localization/core-messages/hi-IN.js +2 -0
- package/dist/localization/core-messages/ja-JP.js +2 -0
- package/dist/localization/core-messages/ko-KR.js +2 -0
- package/dist/localization/core-messages/pt-BR.js +2 -0
- package/dist/localization/core-messages/zh-CN.js +2 -0
- package/dist/localization/core-messages/zh-TW.js +2 -0
- package/dist/localization/core-messages.d.ts +2 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/package.json +3 -3
- 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 {
|
|
@@ -39,9 +45,14 @@ import {
|
|
|
39
45
|
componentNodeIdMatches,
|
|
40
46
|
extractProps,
|
|
41
47
|
type ComponentInstance,
|
|
48
|
+
instanceFromNode,
|
|
42
49
|
} from "../shared/component-model.js";
|
|
43
50
|
import { hasCapability } from "../shared/design-source-capabilities.js";
|
|
44
|
-
import {
|
|
51
|
+
import { isStandaloneHttpUrl } from "../shared/html-content.js";
|
|
52
|
+
import {
|
|
53
|
+
designConnectionIdFromData,
|
|
54
|
+
designSourceTypeFromData,
|
|
55
|
+
} from "../shared/source-mode.js";
|
|
45
56
|
|
|
46
57
|
export function canRestoreComponentMain(
|
|
47
58
|
node: Pick<CodeLayerNode, "dataAttributes">,
|
|
@@ -65,6 +76,126 @@ function parseJson<T>(raw: string | null | undefined, fallback: T): T {
|
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
|
|
79
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
80
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function metadataForFile(
|
|
84
|
+
data: Record<string, unknown>,
|
|
85
|
+
fileId: string,
|
|
86
|
+
): Record<string, unknown> {
|
|
87
|
+
for (const key of ["screenMetadata", "localhostScreens"]) {
|
|
88
|
+
const metadata = data[key];
|
|
89
|
+
const entry = isRecord(metadata) ? metadata[fileId] : undefined;
|
|
90
|
+
if (isRecord(entry)) return entry;
|
|
91
|
+
}
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function stringValue(
|
|
96
|
+
attributes: Record<string, string>,
|
|
97
|
+
names: readonly string[],
|
|
98
|
+
): string | undefined {
|
|
99
|
+
for (const name of names) {
|
|
100
|
+
const value = attributes[name]?.trim();
|
|
101
|
+
if (value) return value;
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function positiveIntegerValue(
|
|
107
|
+
attributes: Record<string, string>,
|
|
108
|
+
names: readonly string[],
|
|
109
|
+
): number | undefined {
|
|
110
|
+
const raw = stringValue(attributes, names);
|
|
111
|
+
if (!raw || !/^\d+$/.test(raw)) return undefined;
|
|
112
|
+
const value = Number(raw);
|
|
113
|
+
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function liveComponentNameFor(
|
|
117
|
+
node: CodeLayerNode,
|
|
118
|
+
sourceType: ReturnType<typeof designSourceTypeFromData>,
|
|
119
|
+
): string | null {
|
|
120
|
+
return (
|
|
121
|
+
componentNameFor(node) ??
|
|
122
|
+
(sourceType !== "inline"
|
|
123
|
+
? (stringValue(node.dataAttributes, ["data-component-name"]) ?? null)
|
|
124
|
+
: null)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function liveInstanceForNode(
|
|
129
|
+
node: CodeLayerNode,
|
|
130
|
+
name: string,
|
|
131
|
+
): NonNullable<ReturnType<typeof instanceFromNode>> {
|
|
132
|
+
const existing = instanceFromNode(node);
|
|
133
|
+
if (existing) return existing;
|
|
134
|
+
const stableNodeId =
|
|
135
|
+
stringValue(node.dataAttributes, ["data-agent-native-node-id"]) ?? node.id;
|
|
136
|
+
const alpineDataRaw = node.attributes["x-data"];
|
|
137
|
+
return {
|
|
138
|
+
instanceId: stableNodeId,
|
|
139
|
+
name,
|
|
140
|
+
props: extractProps(node),
|
|
141
|
+
alpineData: typeof alpineDataRaw === "string" ? alpineDataRaw : undefined,
|
|
142
|
+
selector: node.selector,
|
|
143
|
+
nodeId: stableNodeId,
|
|
144
|
+
componentId:
|
|
145
|
+
stringValue(node.dataAttributes, [COMPONENT_ID_ATTR]) ?? undefined,
|
|
146
|
+
componentRef:
|
|
147
|
+
stringValue(node.dataAttributes, [COMPONENT_REF_ATTR]) ?? undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function sourceLocationForNode(
|
|
152
|
+
node: CodeLayerNode,
|
|
153
|
+
indexRow:
|
|
154
|
+
| {
|
|
155
|
+
filePath?: string | null;
|
|
156
|
+
exportName?: string | null;
|
|
157
|
+
}
|
|
158
|
+
| undefined,
|
|
159
|
+
canResolveToFile: boolean,
|
|
160
|
+
componentName: string,
|
|
161
|
+
) {
|
|
162
|
+
if (!canResolveToFile) return undefined;
|
|
163
|
+
const filePath =
|
|
164
|
+
stringValue(node.dataAttributes, [
|
|
165
|
+
"data-source-file",
|
|
166
|
+
"data-agent-native-source-file",
|
|
167
|
+
"data-agent-native-source-path",
|
|
168
|
+
]) ?? indexRow?.filePath;
|
|
169
|
+
if (!filePath) return undefined;
|
|
170
|
+
const line = positiveIntegerValue(node.dataAttributes, [
|
|
171
|
+
"data-source-line",
|
|
172
|
+
"data-agent-native-source-line",
|
|
173
|
+
]);
|
|
174
|
+
const column = positiveIntegerValue(node.dataAttributes, [
|
|
175
|
+
"data-source-column",
|
|
176
|
+
"data-agent-native-source-column",
|
|
177
|
+
]);
|
|
178
|
+
return {
|
|
179
|
+
filePath,
|
|
180
|
+
exportName: indexRow?.exportName ?? undefined,
|
|
181
|
+
...(line ? { line } : {}),
|
|
182
|
+
...(column ? { column } : {}),
|
|
183
|
+
componentName:
|
|
184
|
+
stringValue(node.dataAttributes, ["data-component-name"]) ??
|
|
185
|
+
componentName,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export class ComponentDetailsLiveProjectionError extends Error {
|
|
190
|
+
readonly statusCode = 424;
|
|
191
|
+
readonly code = "LIVE_PROJECTION_UNAVAILABLE";
|
|
192
|
+
|
|
193
|
+
constructor(message: string) {
|
|
194
|
+
super(message);
|
|
195
|
+
this.name = "ComponentDetailsLiveProjectionError";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
68
199
|
// ─── Action ───────────────────────────────────────────────────────────────────
|
|
69
200
|
|
|
70
201
|
export default defineAction({
|
|
@@ -86,10 +217,36 @@ export default defineAction({
|
|
|
86
217
|
.string()
|
|
87
218
|
.optional()
|
|
88
219
|
.describe("Design file id. Defaults to index.html."),
|
|
220
|
+
runtime: z
|
|
221
|
+
.object({
|
|
222
|
+
name: z.string().min(1),
|
|
223
|
+
nodeId: z.string().min(1),
|
|
224
|
+
selector: z.string().min(1),
|
|
225
|
+
props: z.array(
|
|
226
|
+
z.object({ name: z.string().min(1), value: z.string() }),
|
|
227
|
+
),
|
|
228
|
+
literalProps: z
|
|
229
|
+
.array(z.object({ name: z.string().min(1), value: z.string() }))
|
|
230
|
+
.optional(),
|
|
231
|
+
alpineData: z.string().nullable().optional(),
|
|
232
|
+
componentId: z.string().optional(),
|
|
233
|
+
componentRef: z.string().optional(),
|
|
234
|
+
isMain: z.boolean().optional(),
|
|
235
|
+
sourceLocation: z
|
|
236
|
+
.object({
|
|
237
|
+
filePath: z.string().min(1),
|
|
238
|
+
exportName: z.string().optional(),
|
|
239
|
+
})
|
|
240
|
+
.optional(),
|
|
241
|
+
})
|
|
242
|
+
.optional()
|
|
243
|
+
.describe(
|
|
244
|
+
"Runtime component metadata from a URL-backed preview. This keeps the inspector on the live DOM projection when the SQL file stores only a route URL.",
|
|
245
|
+
),
|
|
89
246
|
}),
|
|
90
247
|
readOnly: true,
|
|
91
248
|
http: { method: "GET" },
|
|
92
|
-
run: async ({ designId, nodeId, fileId }) => {
|
|
249
|
+
run: async ({ designId, nodeId, fileId, runtime }) => {
|
|
93
250
|
// ── Access check ────────────────────────────────────────────────────────
|
|
94
251
|
const access = await resolveAccess("design", designId);
|
|
95
252
|
if (!access) throw new Error("Design not found");
|
|
@@ -114,6 +271,62 @@ export default defineAction({
|
|
|
114
271
|
? "Prop write-back requires the bridge applyEdit capability. Preview controls remain available until source write hardening is enabled."
|
|
115
272
|
: undefined;
|
|
116
273
|
|
|
274
|
+
// URL-backed files store the route URL in SQL rather than the rendered
|
|
275
|
+
// DOM. Use the accepted runtime projection for the inspector instead of
|
|
276
|
+
// pretending that URL is an HTML source document.
|
|
277
|
+
if (runtime) {
|
|
278
|
+
const [indexRow] = await db
|
|
279
|
+
.select()
|
|
280
|
+
.from(schema.componentIndex)
|
|
281
|
+
.where(
|
|
282
|
+
and(
|
|
283
|
+
eq(schema.componentIndex.designId, designId),
|
|
284
|
+
eq(schema.componentIndex.name, runtime.name),
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
.limit(1);
|
|
288
|
+
const persistedProps = parseJson<unknown[]>(indexRow?.props, []);
|
|
289
|
+
const persistedVariants = parseJson<Record<string, string[]>>(
|
|
290
|
+
indexRow?.variants,
|
|
291
|
+
{},
|
|
292
|
+
);
|
|
293
|
+
const persistedStories = parseJson<unknown[]>(indexRow?.stories, []);
|
|
294
|
+
const instance: ComponentInstance = {
|
|
295
|
+
instanceId: runtime.nodeId,
|
|
296
|
+
name: runtime.name,
|
|
297
|
+
props: runtime.props,
|
|
298
|
+
alpineData: runtime.alpineData ?? undefined,
|
|
299
|
+
selector: runtime.selector,
|
|
300
|
+
nodeId: runtime.nodeId,
|
|
301
|
+
componentId: runtime.componentId,
|
|
302
|
+
componentRef: runtime.componentRef,
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
designId,
|
|
306
|
+
nodeId,
|
|
307
|
+
sourceType,
|
|
308
|
+
instance,
|
|
309
|
+
name: runtime.name,
|
|
310
|
+
isMain:
|
|
311
|
+
runtime.isMain ??
|
|
312
|
+
Boolean(runtime.componentId && !runtime.componentRef),
|
|
313
|
+
canRestore: false,
|
|
314
|
+
observedProps: runtime.props,
|
|
315
|
+
literalProps: runtime.literalProps,
|
|
316
|
+
persistedProps,
|
|
317
|
+
persistedVariants,
|
|
318
|
+
persistedStories,
|
|
319
|
+
sourceLocation: runtime.sourceLocation,
|
|
320
|
+
capabilities: {
|
|
321
|
+
canResolveToFile,
|
|
322
|
+
hasFullIndex,
|
|
323
|
+
canEditProps,
|
|
324
|
+
ctaRequired,
|
|
325
|
+
ctaMessage,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
117
330
|
// ── Fetch design file ────────────────────────────────────────────────────
|
|
118
331
|
const conditions = [
|
|
119
332
|
accessFilter(schema.designs, schema.designShares, undefined, "viewer", {
|
|
@@ -131,6 +344,7 @@ export default defineAction({
|
|
|
131
344
|
designId: schema.designFiles.designId,
|
|
132
345
|
filename: schema.designFiles.filename,
|
|
133
346
|
content: schema.designFiles.content,
|
|
347
|
+
data: schema.designs.data,
|
|
134
348
|
})
|
|
135
349
|
.from(schema.designFiles)
|
|
136
350
|
.innerJoin(
|
|
@@ -142,11 +356,51 @@ export default defineAction({
|
|
|
142
356
|
|
|
143
357
|
if (!file) throw new Error("Design HTML file not found.");
|
|
144
358
|
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
359
|
+
// Inline screens use durable SQL. URL-backed screens must be projected from
|
|
360
|
+
// the live bridge because the stored value is only a route URL.
|
|
361
|
+
let html = file.content ?? "";
|
|
362
|
+
if (isStandaloneHttpUrl(html)) {
|
|
363
|
+
if (sourceType !== "localhost") {
|
|
364
|
+
throw new ComponentDetailsLiveProjectionError(
|
|
365
|
+
`Component details for URL-backed screen "${file.filename}" require a localhost bridge snapshot; source type "${sourceType}" has no live component mapping.`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
const designData = parseJson<Record<string, unknown>>(file.data, {});
|
|
369
|
+
const metadata = metadataForFile(designData, file.id);
|
|
370
|
+
const connectionId =
|
|
371
|
+
(typeof metadata.connectionId === "string" &&
|
|
372
|
+
metadata.connectionId.trim()) ||
|
|
373
|
+
designConnectionIdFromData(designData);
|
|
374
|
+
if (!connectionId) {
|
|
375
|
+
throw new ComponentDetailsLiveProjectionError(
|
|
376
|
+
`URL-backed screen "${file.filename}" has no localhost connection metadata. Reopen visual edit or reconnect the localhost app before requesting component details.`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
const { ownerEmail, orgId } = await resolveLocalhostConnectionScope({
|
|
380
|
+
designId,
|
|
381
|
+
});
|
|
382
|
+
const connection = await resolveLocalhostBridgeConnection({
|
|
383
|
+
connectionId,
|
|
384
|
+
ownerEmail,
|
|
385
|
+
orgId,
|
|
386
|
+
});
|
|
387
|
+
const previewToken =
|
|
388
|
+
typeof metadata.previewToken === "string"
|
|
389
|
+
? metadata.previewToken
|
|
390
|
+
: null;
|
|
391
|
+
html = sanitizeMarkup(
|
|
392
|
+
await fetchLocalhostSnapshot({
|
|
393
|
+
bridgeUrl: connection.bridgeUrl,
|
|
394
|
+
previewToken,
|
|
395
|
+
url: html,
|
|
396
|
+
}),
|
|
397
|
+
).trim();
|
|
398
|
+
if (!html) {
|
|
399
|
+
throw new ComponentDetailsLiveProjectionError(
|
|
400
|
+
`The localhost bridge returned an empty snapshot for "${file.filename}". Reload the live frame and retry.`,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
150
404
|
|
|
151
405
|
// ── Projection lookup ────────────────────────────────────────────────────
|
|
152
406
|
const codeLayerSource: CodeLayerSource = {
|
|
@@ -169,28 +423,16 @@ export default defineAction({
|
|
|
169
423
|
);
|
|
170
424
|
}
|
|
171
425
|
|
|
172
|
-
const name =
|
|
426
|
+
const name = liveComponentNameFor(node, sourceType);
|
|
173
427
|
if (!name) {
|
|
174
428
|
throw new Error(
|
|
175
|
-
`Node "${nodeId}" does not carry
|
|
429
|
+
`Node "${nodeId}" does not carry component provenance and is not a component root.`,
|
|
176
430
|
);
|
|
177
431
|
}
|
|
178
432
|
|
|
179
433
|
// ── Simple props from attributes ─────────────────────────────────────────
|
|
180
434
|
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
|
-
};
|
|
435
|
+
const instance = liveInstanceForNode(node, name);
|
|
194
436
|
|
|
195
437
|
// ── Lookup persisted component_index row ─────────────────────────────────
|
|
196
438
|
const [indexRow] = await db
|
|
@@ -212,17 +454,14 @@ export default defineAction({
|
|
|
212
454
|
const persistedStories = parseJson<unknown[]>(indexRow?.stories, []);
|
|
213
455
|
|
|
214
456
|
// ── Source location (real-app only) ──────────────────────────────────────
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
canResolveToFile
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
exportName: indexRow.exportName ?? undefined,
|
|
224
|
-
}
|
|
225
|
-
: undefined;
|
|
457
|
+
// Live bridge provenance is preferred; indexed metadata remains the
|
|
458
|
+
// fallback when the bridge did not stamp a source span.
|
|
459
|
+
const sourceLocation = sourceLocationForNode(
|
|
460
|
+
node,
|
|
461
|
+
indexRow,
|
|
462
|
+
canResolveToFile,
|
|
463
|
+
name,
|
|
464
|
+
);
|
|
226
465
|
|
|
227
466
|
return {
|
|
228
467
|
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,
|