@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
@@ -0,0 +1,294 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import {
3
+ agentEnterDocument,
4
+ agentLeaveDocument,
5
+ } from "@agent-native/core/collab";
6
+ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
7
+ import { assertAccess, resolveAccess } from "@agent-native/core/sharing";
8
+ import { and, eq } from "drizzle-orm";
9
+ import { z } from "zod";
10
+
11
+ import { getDb, schema } from "../server/db/index.js";
12
+ import { snapshotDesignBeforeAgentEdit } from "../server/lib/design-versions.js";
13
+ import {
14
+ lockDesignSourceMutation,
15
+ readLiveSourceFile,
16
+ resolveSourceWorkspace,
17
+ writeInlineSourceFilesBatch,
18
+ } from "../server/source-workspace.js";
19
+ import {
20
+ buildCodeLayerProjection,
21
+ patchCodeLayerNodeAttributes,
22
+ } from "../shared/code-layer.js";
23
+ import {
24
+ COMPONENT_ID_ATTR,
25
+ COMPONENT_NAME_ATTR,
26
+ COMPONENT_REF_ATTR,
27
+ componentIndexId,
28
+ componentNameFor,
29
+ } from "../shared/component-model.js";
30
+ import { designSourceTypeFromData } from "../shared/source-mode.js";
31
+
32
+ export class ComponentRenameAmbiguousError extends Error {
33
+ readonly statusCode = 409;
34
+
35
+ constructor(
36
+ message = "The component is a legacy same-name-only component and cannot be renamed safely.",
37
+ ) {
38
+ super(message);
39
+ this.name = "ComponentRenameAmbiguousError";
40
+ }
41
+ }
42
+
43
+ export class ComponentRenameConflictError extends Error {
44
+ readonly statusCode = 409;
45
+
46
+ constructor(message = "A component with this name already exists.") {
47
+ super(message);
48
+ this.name = "ComponentRenameConflictError";
49
+ }
50
+ }
51
+
52
+ export function renameLinkedComponentHtml(
53
+ html: string,
54
+ componentId: string,
55
+ newName: string,
56
+ ): { content: string; changed: boolean } {
57
+ const projection = buildCodeLayerProjection(html);
58
+ const updates = projection.nodes
59
+ .filter(
60
+ (node) =>
61
+ node.dataAttributes[COMPONENT_ID_ATTR]?.trim() === componentId ||
62
+ node.dataAttributes[COMPONENT_REF_ATTR]?.trim() === componentId,
63
+ )
64
+ .map((node) => ({
65
+ node,
66
+ attributes: { [COMPONENT_NAME_ATTR]: newName },
67
+ }));
68
+ const content =
69
+ updates.length > 0
70
+ ? (patchCodeLayerNodeAttributes(html, updates) ?? html)
71
+ : html;
72
+ return { content, changed: content !== html };
73
+ }
74
+
75
+ export default defineAction({
76
+ description:
77
+ "Rename a canonical linked Design component across every inline HTML file.",
78
+ schema: z.object({
79
+ designId: z.string().trim().min(1),
80
+ componentId: z
81
+ .string()
82
+ .trim()
83
+ .min(1)
84
+ .describe("Persisted canonical component id"),
85
+ newName: z.string().trim().min(1).max(255),
86
+ }),
87
+ run: async ({ designId, componentId, newName }, context) => {
88
+ const access = await resolveAccess("design", designId);
89
+ if (!access) throw new Error("Design not found");
90
+ if (
91
+ designSourceTypeFromData((access.resource as { data?: unknown }).data) !==
92
+ "inline"
93
+ ) {
94
+ return { designId, componentId, renamed: false, ctaRequired: true };
95
+ }
96
+ await assertAccess("design", designId, "editor");
97
+ const workspace = await resolveSourceWorkspace(designId, {
98
+ includeContent: true,
99
+ includeBoard: true,
100
+ });
101
+ const htmlFiles = workspace.files.filter(
102
+ (file) => file.fileType === "html",
103
+ );
104
+ const liveFiles = await Promise.all(
105
+ htmlFiles.map(async (file) => ({
106
+ file,
107
+ live: await readLiveSourceFile(file),
108
+ })),
109
+ );
110
+ const projectedNodes = liveFiles.flatMap(
111
+ ({ live }) => buildCodeLayerProjection(live.content).nodes,
112
+ );
113
+ const canonical = projectedNodes.find(
114
+ (node) => node.dataAttributes[COMPONENT_ID_ATTR]?.trim() === componentId,
115
+ );
116
+ const oldName = canonical ? componentNameFor(canonical) : null;
117
+ if (!oldName) {
118
+ throw new ComponentRenameAmbiguousError();
119
+ }
120
+ if (newName === oldName) {
121
+ return { designId, componentId, renamed: false };
122
+ }
123
+ const oldIndexId = componentIndexId(designId, oldName);
124
+ const newIndexId = componentIndexId(designId, newName);
125
+ if (newIndexId !== oldIndexId) {
126
+ const [existing] = await getDb()
127
+ .select({ id: schema.componentIndex.id })
128
+ .from(schema.componentIndex)
129
+ .where(
130
+ and(
131
+ eq(schema.componentIndex.id, newIndexId),
132
+ eq(schema.componentIndex.designId, designId),
133
+ ),
134
+ )
135
+ .limit(1);
136
+ if (existing) throw new ComponentRenameConflictError();
137
+ }
138
+ const conflictingMarkup = projectedNodes.some((node) => {
139
+ const name = componentNameFor(node);
140
+ if (!name || componentIndexId(designId, name) !== newIndexId) {
141
+ return false;
142
+ }
143
+ return (
144
+ node.dataAttributes[COMPONENT_ID_ATTR]?.trim() !== componentId &&
145
+ node.dataAttributes[COMPONENT_REF_ATTR]?.trim() !== componentId
146
+ );
147
+ });
148
+ if (conflictingMarkup) throw new ComponentRenameConflictError();
149
+
150
+ const linkedNodes = projectedNodes.filter(
151
+ (node) =>
152
+ node.dataAttributes[COMPONENT_ID_ATTR]?.trim() === componentId ||
153
+ node.dataAttributes[COMPONENT_REF_ATTR]?.trim() === componentId,
154
+ );
155
+ const componentSelectors = Array.from(
156
+ new Set(
157
+ linkedNodes.map(
158
+ (node) =>
159
+ `[data-agent-native-node-id="${node.dataAttributes["data-agent-native-node-id"] ?? node.id}"]`,
160
+ ),
161
+ ),
162
+ );
163
+ const legacySelectors = Array.from(
164
+ new Set(
165
+ projectedNodes
166
+ .filter(
167
+ (node) =>
168
+ componentNameFor(node) === oldName &&
169
+ !node.dataAttributes[COMPONENT_ID_ATTR]?.trim() &&
170
+ !node.dataAttributes[COMPONENT_REF_ATTR]?.trim(),
171
+ )
172
+ .map(
173
+ (node) =>
174
+ `[data-agent-native-node-id="${node.dataAttributes["data-agent-native-node-id"] ?? node.id}"]`,
175
+ ),
176
+ ),
177
+ );
178
+ const batches = liveFiles.map(({ file, live }) => ({
179
+ file: { ...file, content: live.content },
180
+ content: renameLinkedComponentHtml(live.content, componentId, newName)
181
+ .content,
182
+ expectedVersionHash: live.versionHash,
183
+ }));
184
+ const changed = batches.filter(
185
+ ({ file, content }) => file.content !== content,
186
+ );
187
+ if (changed.length === 0) return { designId, componentId, renamed: false };
188
+
189
+ await snapshotDesignBeforeAgentEdit(designId, context);
190
+ const entered = htmlFiles.map((file) => file.id).sort();
191
+ entered.forEach((id) => agentEnterDocument(id));
192
+ try {
193
+ const result = await writeInlineSourceFilesBatch({
194
+ designId,
195
+ files: batches,
196
+ expectedHtmlFileIds: htmlFiles.map((file) => file.id),
197
+ afterFilesPersist: async (tx, updatedAt) => {
198
+ await lockDesignSourceMutation(tx, designId);
199
+ if (newIndexId !== oldIndexId) {
200
+ const destination = await tx.execute({
201
+ sql: "SELECT id FROM component_index WHERE id = ? AND design_id = ? FOR UPDATE",
202
+ args: [newIndexId, designId],
203
+ });
204
+ if (destination.rows.length > 0) {
205
+ throw new ComponentRenameConflictError();
206
+ }
207
+ }
208
+ const moved = await tx.execute({
209
+ sql: "UPDATE component_index SET id = ?, name = ?, runtime_selectors = ?, updated_at = ? WHERE id = ? AND design_id = ? RETURNING id",
210
+ args: [
211
+ newIndexId,
212
+ newName,
213
+ JSON.stringify(componentSelectors),
214
+ updatedAt,
215
+ oldIndexId,
216
+ designId,
217
+ ],
218
+ });
219
+ if (moved.rows.length === 1) {
220
+ if (legacySelectors.length > 0 && newIndexId !== oldIndexId) {
221
+ await tx.execute({
222
+ sql: `INSERT INTO component_index (
223
+ id, design_id, source_ref, name, file_path, export_name,
224
+ props, variants, stories, runtime_selectors, created_at,
225
+ updated_at, owner_email, org_id, visibility
226
+ )
227
+ SELECT ?, design_id, source_ref, ?, file_path, export_name,
228
+ props, variants, stories, ?, created_at, ?, owner_email,
229
+ org_id, visibility
230
+ FROM component_index WHERE id = ? AND design_id = ?`,
231
+ args: [
232
+ oldIndexId,
233
+ oldName,
234
+ JSON.stringify(legacySelectors),
235
+ updatedAt,
236
+ newIndexId,
237
+ designId,
238
+ ],
239
+ });
240
+ }
241
+ } else {
242
+ const designOwner = (access.resource as { ownerEmail?: unknown })
243
+ .ownerEmail;
244
+ const ownerEmail =
245
+ getRequestUserEmail() ??
246
+ (typeof designOwner === "string" && designOwner
247
+ ? designOwner
248
+ : null);
249
+ if (!ownerEmail) throw new Error("no authenticated user");
250
+ await tx.execute({
251
+ sql: `INSERT INTO component_index
252
+ (id, design_id, name, runtime_selectors, owner_email, created_at, updated_at)
253
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
254
+ args: [
255
+ newIndexId,
256
+ designId,
257
+ newName,
258
+ JSON.stringify(componentSelectors),
259
+ ownerEmail,
260
+ updatedAt,
261
+ updatedAt,
262
+ ],
263
+ });
264
+ if (legacySelectors.length > 0 && newIndexId !== oldIndexId) {
265
+ await tx.execute({
266
+ sql: `INSERT INTO component_index
267
+ (id, design_id, name, runtime_selectors, owner_email, created_at, updated_at)
268
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
269
+ args: [
270
+ oldIndexId,
271
+ designId,
272
+ oldName,
273
+ JSON.stringify(legacySelectors),
274
+ ownerEmail,
275
+ updatedAt,
276
+ updatedAt,
277
+ ],
278
+ });
279
+ }
280
+ }
281
+ },
282
+ });
283
+ return {
284
+ designId,
285
+ componentId,
286
+ newName,
287
+ renamed: true,
288
+ files: result.files,
289
+ };
290
+ } finally {
291
+ entered.forEach((id) => agentLeaveDocument(id));
292
+ }
293
+ },
294
+ });
@@ -9,6 +9,10 @@ import { and, eq, isNull, sql } from "drizzle-orm";
9
9
  import { z } from "zod";
10
10
 
11
11
  import { getDb, schema } from "../server/db/index.js";
12
+ import {
13
+ designSourceMutationLockKey,
14
+ lockDesignFilesTable,
15
+ } from "../server/source-workspace.js";
12
16
  import { isProbablyHtmlDocumentContent } from "../shared/html-content.js";
13
17
  import {
14
18
  renameFilenamePreservingExtension,
@@ -180,15 +184,10 @@ export default defineAction({
180
184
  for (let attempt = 0; attempt < MAX_RENAME_ATTEMPTS; attempt += 1) {
181
185
  try {
182
186
  return await db.transaction(async (tx) => {
183
- {
184
- await (
185
- tx as unknown as {
186
- execute: (query: unknown) => Promise<unknown>;
187
- }
188
- ).execute(
189
- sql`LOCK TABLE design_files IN SHARE ROW EXCLUSIVE MODE`,
190
- );
191
- }
187
+ await tx.execute(
188
+ sql`SELECT pg_advisory_xact_lock(hashtextextended(${designSourceMutationLockKey(scopedFile.designId)}, 0::bigint))`,
189
+ );
190
+ await lockDesignFilesTable(tx);
192
191
 
193
192
  const [design] = await tx
194
193
  .select({ updatedAt: schema.designs.updatedAt })
@@ -209,7 +208,8 @@ export default defineAction({
209
208
  updatedAt: schema.designFiles.updatedAt,
210
209
  })
211
210
  .from(schema.designFiles)
212
- .where(eq(schema.designFiles.designId, scopedFile.designId));
211
+ .where(eq(schema.designFiles.designId, scopedFile.designId))
212
+ .for("update");
213
213
  const target = currentFiles.find((file) => file.id === id);
214
214
  if (!target) throw new Error(`Screen not found: ${id}`);
215
215
  if (target.fileType !== "html") {