@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.
Files changed (66) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/app/root.tsx +59 -10
  3. package/corpus/templates/clips/changelog/2026-09-17-desktop-popover-closes-when-clicking-away.md +6 -0
  4. package/corpus/templates/clips/changelog/2026-09-17-self-hosted-extension-sign-in.md +6 -0
  5. package/corpus/templates/clips/chrome-extension/src/background.ts +45 -25
  6. package/corpus/templates/clips/chrome-extension/src/content-script.ts +43 -0
  7. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +55 -6
  8. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1 -0
  9. package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
  10. package/corpus/templates/design/actions/apply-visual-edit.ts +1 -0
  11. package/corpus/templates/design/actions/create-design-from-template.ts +50 -35
  12. package/corpus/templates/design/actions/create-design-version.ts +21 -0
  13. package/corpus/templates/design/actions/create-file.ts +35 -33
  14. package/corpus/templates/design/actions/delete-design.ts +29 -28
  15. package/corpus/templates/design/actions/delete-file.ts +154 -23
  16. package/corpus/templates/design/actions/generate-design.ts +28 -16
  17. package/corpus/templates/design/actions/get-component-details.ts +187 -33
  18. package/corpus/templates/design/actions/get-design-version.ts +9 -1
  19. package/corpus/templates/design/actions/go-to-main-component.ts +37 -35
  20. package/corpus/templates/design/actions/index-components.ts +241 -126
  21. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +12 -9
  22. package/corpus/templates/design/actions/present-design-variants.ts +68 -72
  23. package/corpus/templates/design/actions/rename-component.ts +294 -0
  24. package/corpus/templates/design/actions/rename-screen.ts +10 -10
  25. package/corpus/templates/design/actions/update-file.ts +327 -330
  26. package/corpus/templates/design/actions/update-screen-source.ts +2 -43
  27. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1 -0
  28. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +8 -1
  29. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +55 -33
  30. package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
  31. package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +4 -2
  32. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +5 -1
  33. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
  34. package/corpus/templates/design/app/components/design/types.ts +1 -0
  35. package/corpus/templates/design/app/hooks/use-design-versions.ts +3 -2
  36. package/corpus/templates/design/app/i18n-breakpoints.ts +0 -110
  37. package/corpus/templates/design/app/i18n-data.ts +0 -10
  38. package/corpus/templates/design/app/pages/DesignEditor.tsx +250 -107
  39. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +2 -0
  40. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +69 -47
  41. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +8 -1
  42. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +107 -2
  43. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
  44. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
  45. package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +74 -22
  46. package/corpus/templates/design/app/pages/design-editor/source-publication.ts +14 -2
  47. package/corpus/templates/design/changelog/2026-09-16-design-components-retain-linked-identities-when-edited-and-d.md +6 -0
  48. package/corpus/templates/design/changelog/2026-09-17-screen-deletion-is-immediate-and-undoable-with-history-that-.md +6 -0
  49. package/corpus/templates/design/e2e/fixtures/design-component-react/index.html +98 -0
  50. package/corpus/templates/design/e2e/fixtures/design-component-react/inline-component.html +65 -0
  51. package/corpus/templates/design/server/lib/design-data-mutation.ts +9 -5
  52. package/corpus/templates/design/server/lib/design-snapshot.ts +28 -16
  53. package/corpus/templates/design/server/lib/design-versions.ts +208 -130
  54. package/corpus/templates/design/server/lib/fusion-screens.ts +46 -20
  55. package/corpus/templates/design/server/lib/import-design-files.ts +5 -1
  56. package/corpus/templates/design/server/lib/localhost-connection.ts +54 -0
  57. package/corpus/templates/design/server/source-workspace.ts +412 -356
  58. package/corpus/templates/design/shared/component-library.ts +36 -6
  59. package/corpus/templates/design/shared/component-model.ts +8 -2
  60. package/dist/automation/index.d.ts +1 -1
  61. package/dist/client/chat/agentkit-protocol.js +148 -34
  62. package/dist/collab/struct-routes.d.ts +1 -1
  63. package/dist/progress/routes.d.ts +1 -1
  64. package/dist/server/core-routes-plugin.d.ts +1 -1
  65. package/package.json +4 -4
  66. 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
- type ComponentInstance,
47
+ instanceFromNode,
42
48
  } from "../shared/component-model.js";
43
49
  import { hasCapability } from "../shared/design-source-capabilities.js";
44
- import { designSourceTypeFromData } from "../shared/source-mode.js";
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
- // Use the durable SQL source for component prop reads. A connected editor
146
- // can briefly hold an older Yjs text snapshot while server-side prop writes
147
- // have already updated SQL; preferring collab here makes the inspector
148
- // rehydrate stale props even though the canvas renders the saved source.
149
- const html = file.content ?? "";
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 = componentNameFor(node);
341
+ const name = liveComponentNameFor(node, sourceType);
173
342
  if (!name) {
174
343
  throw new Error(
175
- `Node "${nodeId}" does not carry a data-agent-native-component attribute and is not a component root.`,
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 alpineData =
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
- // The bridge `resolveNodeToFile` op maps a node id to a source file + span.
216
- // For inline designs the capability is `available` but resolves to the
217
- // design file itself (no external source file). We surface whatever we
218
- // have from the index row.
219
- const sourceLocation =
220
- canResolveToFile && indexRow?.filePath
221
- ? {
222
- filePath: indexRow.filePath,
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: chatContext ? ("chat" as const) : ("legacy" as const),
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
- * DESIGN NOTE — mapping onto this codebase's data model: there is no
5
- * persisted "main component" concept anywhere here. A component is just a
6
- * name (`data-agent-native-component="Name"`) that happens to be stamped on
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). This codebase has no separate component-definition markup " +
70
- "— components are structurally duplicated HTML matched by name — so the " +
71
- "earliest instance found across the design's files stands in for the " +
72
- "main component. Returns isMain=true when the given instance already IS " +
73
- "that earliest one; otherwise navigates the editor to it (which may " +
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
- if (!componentName) {
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 data-agent-native-component attribute).`,
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 = entriesForComponent(entries, componentName);
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 = matches[0];
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
- ? `"${componentName}" has only one instance — this is it.`
222
- : `This is the earliest instance of "${componentName}" across the design.`,
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,