@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.
Files changed (119) 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 +107 -19
  9. package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +1 -0
  10. package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
  11. package/corpus/templates/design/actions/apply-component-prop-edit.ts +174 -14
  12. package/corpus/templates/design/actions/apply-visual-edit.ts +3 -1
  13. package/corpus/templates/design/actions/create-component.ts +173 -12
  14. package/corpus/templates/design/actions/create-design-from-template.ts +50 -35
  15. package/corpus/templates/design/actions/create-design-version.ts +21 -0
  16. package/corpus/templates/design/actions/create-file.ts +35 -33
  17. package/corpus/templates/design/actions/delete-design.ts +29 -28
  18. package/corpus/templates/design/actions/delete-file.ts +154 -23
  19. package/corpus/templates/design/actions/generate-design.ts +28 -16
  20. package/corpus/templates/design/actions/get-component-details.ts +272 -33
  21. package/corpus/templates/design/actions/get-design-version.ts +9 -1
  22. package/corpus/templates/design/actions/go-to-main-component.ts +37 -35
  23. package/corpus/templates/design/actions/index-components.ts +241 -126
  24. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +12 -9
  25. package/corpus/templates/design/actions/present-design-variants.ts +68 -72
  26. package/corpus/templates/design/actions/rename-component.ts +294 -0
  27. package/corpus/templates/design/actions/rename-screen.ts +10 -10
  28. package/corpus/templates/design/actions/update-file.ts +327 -330
  29. package/corpus/templates/design/actions/update-screen-source.ts +2 -43
  30. package/corpus/templates/design/app/components/design/EditPanel.tsx +17 -1
  31. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +228 -24
  32. package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +17 -1
  33. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +151 -32
  34. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +4 -0
  35. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +1 -1
  36. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +8 -1
  37. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +55 -33
  38. package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
  39. package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +4 -2
  40. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +273 -60
  41. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +9 -2
  42. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
  43. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +13 -0
  44. package/corpus/templates/design/app/components/design/multi-screen/types.ts +14 -0
  45. package/corpus/templates/design/app/components/design/types.ts +9 -1
  46. package/corpus/templates/design/app/hooks/use-design-versions.ts +3 -2
  47. package/corpus/templates/design/app/i18n-breakpoints.ts +0 -110
  48. package/corpus/templates/design/app/i18n-data.ts +0 -10
  49. package/corpus/templates/design/app/pages/DesignEditor.tsx +669 -161
  50. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +20 -0
  51. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +2 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +21 -3
  53. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +2 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/create-component.ts +44 -3
  55. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +69 -47
  56. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +157 -52
  57. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +76 -1
  58. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +152 -44
  59. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +131 -2
  60. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +5 -1
  61. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
  62. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +1 -0
  63. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +58 -8
  64. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
  65. package/corpus/templates/design/app/pages/design-editor/history.ts +3 -0
  66. package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +74 -22
  67. package/corpus/templates/design/app/pages/design-editor/source-publication.ts +14 -2
  68. package/corpus/templates/design/changelog/2026-09-16-design-components-retain-linked-identities-when-edited-and-d.md +6 -0
  69. package/corpus/templates/design/changelog/2026-09-16-fix-design-nested-layer-dragging-and-screen-duplication-pari.md +6 -0
  70. package/corpus/templates/design/changelog/2026-09-17-design-drag-and-drop-keeps-duplicates-cross-screen-moves-and.md +6 -0
  71. package/corpus/templates/design/changelog/2026-09-17-design-preserves-reusable-component-identity-and-props-acros.md +6 -0
  72. package/corpus/templates/design/changelog/2026-09-17-open-vector-paths-no-longer-show-fills-and-interact-dimensio.md +6 -0
  73. package/corpus/templates/design/changelog/2026-09-17-screen-deletion-is-immediate-and-undoable-with-history-that-.md +6 -0
  74. package/corpus/templates/design/e2e/fixtures/design-component-react/index.html +98 -0
  75. package/corpus/templates/design/e2e/fixtures/design-component-react/inline-component.html +65 -0
  76. package/corpus/templates/design/e2e/helpers.ts +6 -1
  77. package/corpus/templates/design/server/lib/design-data-mutation.ts +9 -5
  78. package/corpus/templates/design/server/lib/design-snapshot.ts +28 -16
  79. package/corpus/templates/design/server/lib/design-versions.ts +208 -130
  80. package/corpus/templates/design/server/lib/fusion-screens.ts +46 -20
  81. package/corpus/templates/design/server/lib/import-design-files.ts +5 -1
  82. package/corpus/templates/design/server/lib/localhost-connection.ts +54 -0
  83. package/corpus/templates/design/server/source-workspace.ts +412 -356
  84. package/corpus/templates/design/shared/component-library.ts +36 -6
  85. package/corpus/templates/design/shared/component-model.ts +12 -2
  86. package/corpus/templates/design/shared/local-jsx-visual-edit.ts +231 -5
  87. package/corpus/templates/design/shared/source-mode.ts +22 -0
  88. package/dist/automation/index.d.ts +1 -1
  89. package/dist/client/AssistantChat.d.ts +4 -1
  90. package/dist/client/AssistantChat.js +26 -3
  91. package/dist/client/chat/agentkit-protocol.js +148 -34
  92. package/dist/client/chat/message-components.js +7 -5
  93. package/dist/client/chat/tool-call-display.js +4 -3
  94. package/dist/client/conversation/AgentConversation.js +4 -2
  95. package/dist/client/tool-display.d.ts +9 -0
  96. package/dist/client/tool-display.js +15 -0
  97. package/dist/collab/awareness.d.ts +2 -2
  98. package/dist/collab/routes.d.ts +1 -1
  99. package/dist/localization/core-messages/ar-SA.js +2 -0
  100. package/dist/localization/core-messages/de-DE.js +2 -0
  101. package/dist/localization/core-messages/en-US.d.ts +2 -0
  102. package/dist/localization/core-messages/en-US.js +2 -0
  103. package/dist/localization/core-messages/es-ES.js +2 -0
  104. package/dist/localization/core-messages/fr-FR.js +2 -0
  105. package/dist/localization/core-messages/hi-IN.js +2 -0
  106. package/dist/localization/core-messages/ja-JP.js +2 -0
  107. package/dist/localization/core-messages/ko-KR.js +2 -0
  108. package/dist/localization/core-messages/pt-BR.js +2 -0
  109. package/dist/localization/core-messages/zh-CN.js +2 -0
  110. package/dist/localization/core-messages/zh-TW.js +2 -0
  111. package/dist/localization/core-messages.d.ts +2 -0
  112. package/dist/notifications/routes.d.ts +3 -3
  113. package/dist/observability/routes.d.ts +3 -3
  114. package/dist/resources/handlers.d.ts +1 -1
  115. package/dist/secrets/routes.d.ts +3 -3
  116. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  117. package/dist/server/core-routes-plugin.d.ts +1 -1
  118. package/package.json +3 -3
  119. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +0 -66
@@ -17,6 +17,7 @@ import {
17
17
  } from "../server/lib/design-data-mutation.js";
18
18
  import { snapshotDesignBeforeAgentEdit } from "../server/lib/design-versions.js";
19
19
  import { resolveLocalhostConnectionScope } from "../server/lib/localhost-connection.js";
20
+ import { withDesignSourceMutationTransaction } from "../server/source-workspace.js";
20
21
  import {
21
22
  mergeCanvasFramePlacements,
22
23
  parseCanvasFrameGeometryById,
@@ -938,7 +939,7 @@ export default defineAction({
938
939
  )
939
940
  ? preferredExisting
940
941
  : undefined;
941
- const existing =
942
+ let existing =
942
943
  matchingPreferredExisting ??
943
944
  routeCandidates.find((candidate) => {
944
945
  const frame = existingCanvasFrames[candidate.id];
@@ -997,27 +998,51 @@ export default defineAction({
997
998
  seenRouteRequestKeys.add(routeRequestKey);
998
999
 
999
1000
  if (existing) {
1000
- await db
1001
- .update(schema.designFiles)
1002
- .set({ content: url, fileType: "html", updatedAt: now })
1003
- .where(eq(schema.designFiles.id, existing.id));
1004
- if (await hasCollabState(existing.id)) {
1005
- await applyText(existing.id, url, "content", "agent");
1001
+ const updated = await withDesignSourceMutationTransaction(
1002
+ designId,
1003
+ async (tx) => {
1004
+ const [current] = await tx
1005
+ .select({ id: schema.designFiles.id })
1006
+ .from(schema.designFiles)
1007
+ .where(
1008
+ and(
1009
+ eq(schema.designFiles.id, existing!.id),
1010
+ eq(schema.designFiles.designId, designId),
1011
+ ),
1012
+ )
1013
+ .limit(1);
1014
+ if (!current) return false;
1015
+ await tx
1016
+ .update(schema.designFiles)
1017
+ .set({ content: url, fileType: "html", updatedAt: now })
1018
+ .where(eq(schema.designFiles.id, existing!.id));
1019
+ return true;
1020
+ },
1021
+ );
1022
+ if (updated) {
1023
+ if (await hasCollabState(existing.id)) {
1024
+ await applyText(existing.id, url, "content", "agent");
1025
+ } else {
1026
+ await seedFromText(existing.id, url);
1027
+ }
1006
1028
  } else {
1007
- await seedFromText(existing.id, url);
1029
+ existing = undefined;
1008
1030
  }
1009
- } else {
1031
+ }
1032
+ if (!existing) {
1010
1033
  for (let attempt = 0; ; attempt += 1) {
1011
1034
  try {
1012
- await db.insert(schema.designFiles).values({
1013
- id: fileId,
1014
- designId,
1015
- filename,
1016
- fileType: "html",
1017
- content: url,
1018
- createdAt: now,
1019
- updatedAt: now,
1020
- });
1035
+ await withDesignSourceMutationTransaction(designId, (tx) =>
1036
+ tx.insert(schema.designFiles).values({
1037
+ id: fileId,
1038
+ designId,
1039
+ filename,
1040
+ fileType: "html",
1041
+ content: url,
1042
+ createdAt: now,
1043
+ updatedAt: now,
1044
+ }),
1045
+ );
1021
1046
  await seedFromText(fileId, url);
1022
1047
  break;
1023
1048
  } catch (err) {
@@ -1048,10 +1073,17 @@ export default defineAction({
1048
1073
  routeUrlsMatch(winner.content, url)
1049
1074
  ) {
1050
1075
  fileId = winner.id;
1051
- await db
1052
- .update(schema.designFiles)
1053
- .set({ content: url, fileType: "html", updatedAt: now })
1054
- .where(eq(schema.designFiles.id, winner.id));
1076
+ await withDesignSourceMutationTransaction(designId, (tx) =>
1077
+ tx
1078
+ .update(schema.designFiles)
1079
+ .set({ content: url, fileType: "html", updatedAt: now })
1080
+ .where(
1081
+ and(
1082
+ eq(schema.designFiles.id, winner.id),
1083
+ eq(schema.designFiles.designId, designId),
1084
+ ),
1085
+ ),
1086
+ );
1055
1087
  if (await hasCollabState(winner.id)) {
1056
1088
  await applyText(winner.id, url, "content", "agent");
1057
1089
  } else {
@@ -12,12 +12,9 @@
12
12
  * HTML attribute on the component root.
13
13
  * - `classReplace` — replaces one Tailwind class with another on the root node.
14
14
  *
15
- * **Real-app sources (localhost / fusion):** deliberately fail closed. This
16
- * action's patcher operates on SQL-backed HTML design files; it must never be
17
- * reused for compiled JSX/TSX source. Real-app prop persistence needs a
18
- * dedicated consented, version-guarded bridge transform. Until that exists,
19
- * callers may preview but this action returns `ctaRequired: true` without
20
- * reading or modifying a design file.
15
+ * **Real-app sources:** a literal JSX attribute on a single authored localhost
16
+ * anchor uses the consented, version-guarded local-file CAS path. Transformed,
17
+ * repeated, shared, and fusion sources still fail closed for agent handoff.
21
18
  *
22
19
  * See DESIGN-STUDIO-PLAN.md §6.1, §7 (preview/apply contract), §11 phase 2.
23
20
  */
@@ -76,9 +73,16 @@ import {
76
73
  type ComponentSourceDocument,
77
74
  } from "../shared/component-links.js";
78
75
  import {
76
+ COMPONENT_PROP_PREFIX,
79
77
  componentNameFor,
80
78
  componentNodeIdMatches,
79
+ propNameToDataAttribute,
81
80
  } from "../shared/component-model.js";
81
+ import {
82
+ planLocalJsxVisualEdit,
83
+ readLiteralJsxPropsAtAnchor,
84
+ type LocalJsxSourceAnchor,
85
+ } from "../shared/local-jsx-visual-edit.js";
82
86
  import { designSourceTypeFromData } from "../shared/source-mode.js";
83
87
  import { sourceContentHash } from "../shared/source-workspace.js";
84
88
  import {
@@ -87,6 +91,8 @@ import {
87
91
  restoreComponentMainInDesign,
88
92
  type ComponentArchiveMutationResult,
89
93
  } from "./_component-archive.js";
94
+ import readLocalFileAction from "./read-local-file.js";
95
+ import writeLocalFileAction from "./write-local-file.js";
90
96
 
91
97
  type SupportedStructureIntent =
92
98
  | DeleteNodeEditIntent
@@ -105,6 +111,32 @@ type ComponentStructureEdit =
105
111
  }
106
112
  | { kind: "structure"; intents: SupportedStructureIntent[] };
107
113
 
114
+ interface LocalComponentSource extends LocalJsxSourceAnchor {
115
+ connectionId: string;
116
+ path: string;
117
+ expectedVersionHash?: string;
118
+ expectedValue?: string;
119
+ }
120
+
121
+ function jsxPropNameForComponentAttribute(attribute: string): string {
122
+ if (!attribute.startsWith(COMPONENT_PROP_PREFIX)) return attribute;
123
+ return attribute
124
+ .slice(COMPONENT_PROP_PREFIX.length)
125
+ .replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());
126
+ }
127
+
128
+ function literalJsxPropNameForComponentAttribute(
129
+ content: string,
130
+ anchor: LocalJsxSourceAnchor,
131
+ attribute: string,
132
+ ): string | undefined {
133
+ const literalProps = readLiteralJsxPropsAtAnchor({ content, anchor });
134
+ const matches = literalProps?.filter(
135
+ ({ name }) => propNameToDataAttribute(name) === attribute,
136
+ );
137
+ return matches?.length === 1 ? matches[0]!.name : undefined;
138
+ }
139
+
108
140
  interface LinkedComponentSelection {
109
141
  fileId: string;
110
142
  nodeIds: string[];
@@ -808,8 +840,9 @@ export default defineAction({
808
840
  "For inline/Alpine designs, edits the data-agent-native-prop-* attributes, " +
809
841
  "x-data expression, or class list of the component root via the deterministic " +
810
842
  "HTML-patch path (same seam as apply-visual-edit). " +
811
- "For real-app sources, returns ctaRequired=true without modifying any file; " +
812
- "compiled source requires a dedicated consented bridge transform. " +
843
+ "For a literal JSX attribute on a single authored localhost anchor, uses the " +
844
+ "consented local-file CAS path; transformed, repeated, shared, and fusion " +
845
+ "sources return ctaRequired=true. " +
813
846
  "The structure variant atomically propagates bounded delete, wrap, unwrap, " +
814
847
  "move, and style intents across linked HTML files; unsupported structural " +
815
848
  "operations fail before any file is written. " +
@@ -923,6 +956,31 @@ export default defineAction({
923
956
  .describe(
924
957
  "Exact source version hashes for every HTML file in the Design. Required for linked property and structure edits.",
925
958
  ),
959
+ local: z
960
+ .object({
961
+ connectionId: z.string().min(1),
962
+ path: z.string().min(1),
963
+ line: z.number().int().positive(),
964
+ column: z.number().int().positive(),
965
+ positionPrecision: z
966
+ .enum(["authored", "transformed", "unknown"])
967
+ .optional(),
968
+ runtimeMultiplicity: z.number().int().positive().optional(),
969
+ scope: z
970
+ .enum([
971
+ "single-instance",
972
+ "repeated-render",
973
+ "shared-component-definition",
974
+ "unknown",
975
+ ])
976
+ .optional(),
977
+ expectedVersionHash: z.string().optional(),
978
+ expectedValue: z
979
+ .string()
980
+ .optional()
981
+ .describe("Current literal JSX prop value used as a CAS guard."),
982
+ })
983
+ .optional(),
926
984
  })
927
985
  .optional(),
928
986
  }),
@@ -935,13 +993,115 @@ export default defineAction({
935
993
  const rawData = (access.resource as { data?: unknown }).data;
936
994
  const sourceType = designSourceTypeFromData(rawData);
937
995
 
938
- // Fail closed for every real-app tier even if its generic capability map
939
- // advertises applyEdit. This action only knows how to patch SQL-backed HTML;
940
- // allowing localhost through here could report success for the mirror while
941
- // leaving the real JSX/TSX file untouched. A future compiled-source action
942
- // must perform consent, canonical path resolution, AST anchoring, and an
943
- // expected-version bridge write as one dedicated transaction.
996
+ const localSource = source?.local as LocalComponentSource | undefined;
997
+
998
+ // Literal localhost JSX props reuse the existing authored-anchor planner
999
+ // and consented bridge CAS. All other real-app edits stay fail-closed.
944
1000
  if (sourceType !== "inline") {
1001
+ if (
1002
+ sourceType === "localhost" &&
1003
+ localSource &&
1004
+ edit.kind === "attribute"
1005
+ ) {
1006
+ await assertAccess("design", designId, "editor");
1007
+ if (!localSource.expectedVersionHash) {
1008
+ return {
1009
+ designId,
1010
+ nodeId,
1011
+ sourceType,
1012
+ persisted: false,
1013
+ conflict: true,
1014
+ error:
1015
+ "Component prop edits require the source version captured with the live selection. Refresh the selection and retry.",
1016
+ };
1017
+ }
1018
+ const live = await readLocalFileAction.run({
1019
+ designId,
1020
+ connectionId: localSource.connectionId,
1021
+ path: localSource.path,
1022
+ });
1023
+ if (
1024
+ localSource.expectedVersionHash &&
1025
+ live.versionHash !== localSource.expectedVersionHash
1026
+ ) {
1027
+ return {
1028
+ designId,
1029
+ nodeId,
1030
+ sourceType,
1031
+ persisted: false,
1032
+ conflict: true,
1033
+ error:
1034
+ "The local component source changed while the prop edit was being prepared. Refresh and retry.",
1035
+ source: {
1036
+ kind: "local-file" as const,
1037
+ connectionId: localSource.connectionId,
1038
+ path: localSource.path,
1039
+ versionHash: live.versionHash,
1040
+ },
1041
+ };
1042
+ }
1043
+
1044
+ const jsxPropName =
1045
+ literalJsxPropNameForComponentAttribute(
1046
+ live.content,
1047
+ localSource,
1048
+ edit.attribute,
1049
+ ) ?? jsxPropNameForComponentAttribute(edit.attribute);
1050
+ const planned = planLocalJsxVisualEdit({
1051
+ content: live.content,
1052
+ anchor: localSource,
1053
+ intent: {
1054
+ kind: "attributes",
1055
+ values: {
1056
+ [jsxPropName]: edit.value,
1057
+ [edit.attribute]: edit.value,
1058
+ },
1059
+ expectedValues: {
1060
+ [jsxPropName]: localSource.expectedValue,
1061
+ },
1062
+ },
1063
+ });
1064
+ if (planned.result.status !== "applied") {
1065
+ return {
1066
+ designId,
1067
+ nodeId,
1068
+ sourceType,
1069
+ persisted: false,
1070
+ ctaRequired: planned.result.status === "needsAgent",
1071
+ error: planned.result.message,
1072
+ result: planned.result,
1073
+ };
1074
+ }
1075
+
1076
+ let write: Awaited<ReturnType<typeof writeLocalFileAction.run>> | null =
1077
+ null;
1078
+ if (planned.result.changed) {
1079
+ await snapshotDesignBeforeAgentEdit(designId, context);
1080
+ write = await writeLocalFileAction.run({
1081
+ designId,
1082
+ connectionId: localSource.connectionId,
1083
+ relPath: localSource.path,
1084
+ content: planned.content,
1085
+ expectedVersionHash: live.versionHash,
1086
+ requireExpectedVersionHash: true,
1087
+ });
1088
+ }
1089
+ return {
1090
+ designId,
1091
+ nodeId,
1092
+ sourceType,
1093
+ persisted: write ? write.written : !planned.result.changed,
1094
+ ctaRequired: false,
1095
+ source: {
1096
+ kind: "local-file" as const,
1097
+ connectionId: localSource.connectionId,
1098
+ path: localSource.path,
1099
+ versionHash: write?.versionHash ?? live.versionHash,
1100
+ },
1101
+ content: planned.content,
1102
+ result: planned.result,
1103
+ };
1104
+ }
945
1105
  return {
946
1106
  designId,
947
1107
  nodeId,
@@ -1064,7 +1064,8 @@ export default defineAction({
1064
1064
  if (
1065
1065
  editIntent.kind !== "textContent" &&
1066
1066
  editIntent.kind !== "class" &&
1067
- editIntent.kind !== "style"
1067
+ editIntent.kind !== "style" &&
1068
+ editIntent.kind !== "attribute"
1068
1069
  ) {
1069
1070
  return {
1070
1071
  result: {
@@ -1458,6 +1459,7 @@ export default defineAction({
1458
1459
 
1459
1460
  const workspace = await resolveSourceWorkspace(file.designId, {
1460
1461
  includeContent: true,
1462
+ includeBoard: true,
1461
1463
  });
1462
1464
  if (workspace.sourceType !== "inline") {
1463
1465
  return noWrite(
@@ -20,9 +20,9 @@
20
20
  * **Tier A (Alpine / inline):** always available — the design HTML is the
21
21
  * source of truth.
22
22
  *
23
- * **Tier B (real-app, localhost / fusion):** source writes require the
24
- * `applyEdit` capability (bridge write hardening). Until that lands the action
25
- * returns `ctaRequired: true` without modifying any source.
23
+ * **Tier B (real-app, localhost):** a single authored JSX opening tag can be
24
+ * promoted through the consented local-file CAS path. Transformed, repeated,
25
+ * shared, or fusion sources still return `ctaRequired: true`.
26
26
  *
27
27
  * See DESIGN-STUDIO-PLAN.md §6.1 (component model) and §7 (action surface).
28
28
  */
@@ -68,7 +68,13 @@ import {
68
68
  linkedComponentRootForNode,
69
69
  } from "../shared/component-model.js";
70
70
  import { hasCapability } from "../shared/design-source-capabilities.js";
71
+ import {
72
+ planLocalJsxVisualEdit,
73
+ type LocalJsxSourceAnchor,
74
+ } from "../shared/local-jsx-visual-edit.js";
71
75
  import { designSourceTypeFromData } from "../shared/source-mode.js";
76
+ import readLocalFileAction from "./read-local-file.js";
77
+ import writeLocalFileAction from "./write-local-file.js";
72
78
 
73
79
  // ─── Pure helpers ─────────────────────────────────────────────────────────────
74
80
 
@@ -81,8 +87,22 @@ export interface ComponentAttributeStamp {
81
87
  }
82
88
 
83
89
  export interface CreateComponentSourceExpectation {
84
- currentContent: string;
85
- expectedVersionHash: string;
90
+ currentContent?: string;
91
+ expectedVersionHash?: string;
92
+ }
93
+
94
+ export interface CreateComponentLocalSourceReceipt {
95
+ kind: "local-file";
96
+ connectionId: string;
97
+ path: string;
98
+ versionHash: string;
99
+ }
100
+
101
+ interface LocalComponentSource extends LocalJsxSourceAnchor {
102
+ connectionId: string;
103
+ path: string;
104
+ expectedVersionHash?: string;
105
+ propStamps?: ComponentAttributeStamp[];
86
106
  }
87
107
 
88
108
  /** A linked component owns its descendants; they cannot become nested mains. */
@@ -260,9 +280,9 @@ export default defineAction({
260
280
  'data-agent-native-component="<Name>" plus data-agent-native-prop-* for ' +
261
281
  "obvious variant-like attributes (data-variant/size/state, aria-pressed, " +
262
282
  "etc.). For inline/Alpine designs this writes the HTML directly via the " +
263
- "deterministic patch path. For real-app sources the applyEdit capability " +
264
- "must be available; otherwise returns ctaRequired=true without modifying " +
265
- "any file. After it runs the node is a recognised component instance, so " +
283
+ "deterministic patch path. For localhost React, a single authored JSX " +
284
+ "anchor uses the consented local-file CAS path; transformed, repeated, " +
285
+ "shared, or fusion sources return ctaRequired=true. After it runs the node is a recognised component instance, so " +
266
286
  "component-model detection, the canvas outline, and the Component section " +
267
287
  "all pick it up.",
268
288
  schema: z.object({
@@ -293,10 +313,41 @@ export default defineAction({
293
313
  .object({
294
314
  currentContent: z
295
315
  .string()
316
+ .optional()
296
317
  .describe("Exact source preimage the editor planned against."),
297
318
  expectedVersionHash: z
298
319
  .string()
320
+ .optional()
299
321
  .describe("Hash of the live source preimage."),
322
+ local: z
323
+ .object({
324
+ connectionId: z.string().min(1),
325
+ path: z.string().min(1),
326
+ line: z.number().int().positive(),
327
+ column: z.number().int().positive(),
328
+ positionPrecision: z
329
+ .enum(["authored", "transformed", "unknown"])
330
+ .optional(),
331
+ runtimeMultiplicity: z.number().int().positive().optional(),
332
+ scope: z
333
+ .enum([
334
+ "single-instance",
335
+ "repeated-render",
336
+ "shared-component-definition",
337
+ "unknown",
338
+ ])
339
+ .optional(),
340
+ expectedVersionHash: z.string().optional(),
341
+ propStamps: z
342
+ .array(
343
+ z.object({
344
+ name: z.string().min(1),
345
+ value: z.string(),
346
+ }),
347
+ )
348
+ .optional(),
349
+ })
350
+ .optional(),
300
351
  })
301
352
  .optional()
302
353
  .describe(
@@ -323,9 +374,16 @@ export default defineAction({
323
374
  const rawData = (access.resource as { data?: unknown }).data;
324
375
  const sourceType = designSourceTypeFromData(rawData);
325
376
  const caps = resolveSourceCapabilities(sourceType);
326
-
327
- // Real-app sources gate on `applyEdit` (bridge write hardening).
328
- if (sourceType !== "inline" && !hasCapability(caps, "applyEdit")) {
377
+ const localSource = source?.local as LocalComponentSource | undefined;
378
+
379
+ // Real-app sources gate on `applyEdit` (bridge write hardening). A
380
+ // localhost source with an authored JSX anchor can use the existing
381
+ // consented local-file CAS path for literal component annotations.
382
+ if (
383
+ sourceType !== "inline" &&
384
+ !(sourceType === "localhost" && localSource) &&
385
+ !hasCapability(caps, "applyEdit")
386
+ ) {
329
387
  return {
330
388
  designId,
331
389
  sourceType,
@@ -338,7 +396,109 @@ export default defineAction({
338
396
  }
339
397
 
340
398
  await assertAccess("design", designId, "editor");
341
- await snapshotDesignBeforeAgentEdit(designId, context);
399
+
400
+ if (sourceType !== "inline") {
401
+ if (sourceType !== "localhost" || !localSource) {
402
+ return {
403
+ designId,
404
+ sourceType,
405
+ persisted: false,
406
+ ctaRequired: true,
407
+ ctaMessage:
408
+ "Creating a component from this source requires an authored localhost JSX anchor.",
409
+ };
410
+ }
411
+
412
+ if (!localSource.expectedVersionHash) {
413
+ return {
414
+ designId,
415
+ sourceType,
416
+ persisted: false,
417
+ conflict: true,
418
+ error:
419
+ "Create Component requires the source version captured with the live selection. Refresh the selection and retry.",
420
+ };
421
+ }
422
+
423
+ const live = await readLocalFileAction.run({
424
+ designId,
425
+ connectionId: localSource.connectionId,
426
+ path: localSource.path,
427
+ });
428
+ if (
429
+ localSource.expectedVersionHash &&
430
+ live.versionHash !== localSource.expectedVersionHash
431
+ ) {
432
+ return {
433
+ designId,
434
+ sourceType,
435
+ persisted: false,
436
+ conflict: true,
437
+ error:
438
+ "The local component source changed while Create Component was being prepared. Refresh and retry.",
439
+ source: {
440
+ kind: "local-file" as const,
441
+ connectionId: localSource.connectionId,
442
+ path: localSource.path,
443
+ versionHash: live.versionHash,
444
+ },
445
+ };
446
+ }
447
+
448
+ const componentName = normalizeComponentName(name);
449
+ const values = Object.fromEntries([
450
+ [COMPONENT_NAME_ATTR, componentName],
451
+ ...(localSource.propStamps ?? []).map((stamp) => [
452
+ stamp.name,
453
+ stamp.value,
454
+ ]),
455
+ ]);
456
+ const planned = planLocalJsxVisualEdit({
457
+ content: live.content,
458
+ anchor: localSource,
459
+ intent: { kind: "attributes", values },
460
+ });
461
+ if (planned.result.status !== "applied") {
462
+ return {
463
+ designId,
464
+ sourceType,
465
+ persisted: false,
466
+ ctaRequired: planned.result.status === "needsAgent",
467
+ error: planned.result.message,
468
+ result: planned.result,
469
+ };
470
+ }
471
+
472
+ let write: Awaited<ReturnType<typeof writeLocalFileAction.run>> | null =
473
+ null;
474
+ if (planned.result.changed) {
475
+ await snapshotDesignBeforeAgentEdit(designId, context);
476
+ write = await writeLocalFileAction.run({
477
+ designId,
478
+ connectionId: localSource.connectionId,
479
+ relPath: localSource.path,
480
+ content: planned.content,
481
+ expectedVersionHash: live.versionHash,
482
+ requireExpectedVersionHash: true,
483
+ });
484
+ }
485
+ return {
486
+ designId,
487
+ nodeId,
488
+ componentName,
489
+ sourceType,
490
+ persisted: write ? write.written : !planned.result.changed,
491
+ ctaRequired: false,
492
+ source: {
493
+ kind: "local-file" as const,
494
+ connectionId: localSource.connectionId,
495
+ path: localSource.path,
496
+ versionHash: write?.versionHash ?? live.versionHash,
497
+ },
498
+ content: planned.content,
499
+ result: planned.result,
500
+ };
501
+ }
342
502
 
343
503
  // ── Fetch file ───────────────────────────────────────────────────────────
344
504
  const conditions = [
@@ -490,6 +650,7 @@ export default defineAction({
490
650
  updatedAt: string;
491
651
  } | null = null;
492
652
  if (contentChanged) {
653
+ await snapshotDesignBeforeAgentEdit(designId, context);
493
654
  persistedReceipt = await writeInlineSourceFile({
494
655
  designId: file.designId,
495
656
  file: workspaceFile,