@agent-native/core 0.80.9 → 0.80.11

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 (148) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +108 -15
  5. package/corpus/core/src/cli/design-connect.ts +28 -2
  6. package/corpus/core/src/cli/skills.ts +45 -24
  7. package/corpus/core/src/client/AssistantChat.tsx +123 -26
  8. package/corpus/core/src/client/sse-event-processor.ts +9 -0
  9. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
  10. package/corpus/templates/assets/AGENTS.md +4 -0
  11. package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
  12. package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
  13. package/corpus/templates/assets/actions/generate-image.ts +118 -67
  14. package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
  15. package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
  16. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
  17. package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
  18. package/corpus/templates/assets/app/i18n-data.ts +61 -0
  19. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  20. package/corpus/templates/assets/app/routes/library.tsx +145 -38
  21. package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
  22. package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
  23. package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
  24. package/corpus/templates/assets/server/lib/generation.ts +135 -11
  25. package/corpus/templates/assets/shared/api.ts +4 -0
  26. package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
  27. package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
  28. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
  29. package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
  30. package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
  31. package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
  32. package/corpus/templates/design/actions/apply-component-prop-edit.ts +441 -0
  33. package/corpus/templates/design/actions/apply-design-state.ts +213 -0
  34. package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
  35. package/corpus/templates/design/actions/apply-motion-edit.ts +413 -0
  36. package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
  37. package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
  38. package/corpus/templates/design/actions/capture-design-state.ts +224 -0
  39. package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
  40. package/corpus/templates/design/actions/create-component.ts +447 -0
  41. package/corpus/templates/design/actions/create-design-branch.ts +263 -0
  42. package/corpus/templates/design/actions/create-design-state.ts +162 -0
  43. package/corpus/templates/design/actions/delete-design-state.ts +55 -0
  44. package/corpus/templates/design/actions/delete-design.ts +20 -0
  45. package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
  46. package/corpus/templates/design/actions/get-component-details.ts +251 -0
  47. package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
  48. package/corpus/templates/design/actions/get-design-review.ts +314 -0
  49. package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
  50. package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
  51. package/corpus/templates/design/actions/index-components.ts +258 -0
  52. package/corpus/templates/design/actions/index-design-tokens.ts +277 -0
  53. package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
  54. package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
  55. package/corpus/templates/design/actions/list-design-states.ts +90 -0
  56. package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
  57. package/corpus/templates/design/actions/open-component-source.ts +242 -0
  58. package/corpus/templates/design/actions/preview-component-prop-edit.ts +260 -0
  59. package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
  60. package/corpus/templates/design/actions/preview-shader-fill.ts +187 -0
  61. package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
  62. package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
  63. package/corpus/templates/design/actions/run-design-audit.ts +436 -0
  64. package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
  65. package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
  66. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1550 -82
  67. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +649 -43
  68. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
  69. package/corpus/templates/design/app/components/design/EditPanel.tsx +1788 -52
  70. package/corpus/templates/design/app/components/design/LayersPanel.tsx +114 -33
  71. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
  72. package/corpus/templates/design/app/components/design/MotionDock.tsx +1071 -0
  73. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1053 -99
  74. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +766 -0
  75. package/corpus/templates/design/app/components/design/StatesPanel.tsx +581 -0
  76. package/corpus/templates/design/app/components/design/TokensPanel.tsx +578 -0
  77. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
  78. package/corpus/templates/design/app/components/design/index.ts +16 -0
  79. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
  80. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
  81. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
  82. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
  83. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  84. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
  85. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
  86. package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
  87. package/corpus/templates/design/app/i18n-data.ts +280 -0
  88. package/corpus/templates/design/app/pages/DesignEditor.tsx +3087 -490
  89. package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
  90. package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
  91. package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
  92. package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
  93. package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
  94. package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
  95. package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
  96. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
  97. package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
  98. package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
  99. package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
  100. package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
  101. package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
  102. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
  103. package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
  104. package/corpus/templates/design/e2e/global-setup.ts +94 -2
  105. package/corpus/templates/design/e2e/helpers.ts +10 -4
  106. package/corpus/templates/design/server/db/schema.ts +123 -0
  107. package/corpus/templates/design/server/plugins/db.ts +90 -0
  108. package/corpus/templates/design/shared/builder-app.ts +297 -0
  109. package/corpus/templates/design/shared/capability-resolver.ts +123 -0
  110. package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
  111. package/corpus/templates/design/shared/code-layer.ts +1016 -71
  112. package/corpus/templates/design/shared/component-model.ts +274 -0
  113. package/corpus/templates/design/shared/design-review.ts +275 -0
  114. package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
  115. package/corpus/templates/design/shared/design-state.ts +112 -0
  116. package/corpus/templates/design/shared/design-surface-index.ts +258 -0
  117. package/corpus/templates/design/shared/motion-compiler.ts +349 -0
  118. package/corpus/templates/design/shared/motion-timeline.ts +193 -0
  119. package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
  120. package/corpus/templates/design/shared/responsive-classes.ts +452 -0
  121. package/corpus/templates/design/shared/shader-fill.ts +323 -0
  122. package/corpus/templates/design/shared/source-mode.ts +245 -0
  123. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
  124. package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
  125. package/dist/agent/production-agent.d.ts.map +1 -1
  126. package/dist/agent/production-agent.js +94 -10
  127. package/dist/agent/production-agent.js.map +1 -1
  128. package/dist/cli/design-connect.d.ts.map +1 -1
  129. package/dist/cli/design-connect.js +28 -2
  130. package/dist/cli/design-connect.js.map +1 -1
  131. package/dist/cli/skills.d.ts.map +1 -1
  132. package/dist/cli/skills.js +37 -21
  133. package/dist/cli/skills.js.map +1 -1
  134. package/dist/client/AssistantChat.d.ts +14 -0
  135. package/dist/client/AssistantChat.d.ts.map +1 -1
  136. package/dist/client/AssistantChat.js +115 -22
  137. package/dist/client/AssistantChat.js.map +1 -1
  138. package/dist/client/sse-event-processor.d.ts.map +1 -1
  139. package/dist/client/sse-event-processor.js +10 -0
  140. package/dist/client/sse-event-processor.js.map +1 -1
  141. package/dist/collab/routes.d.ts +1 -1
  142. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  143. package/dist/notifications/routes.d.ts +2 -2
  144. package/dist/observability/routes.d.ts +8 -8
  145. package/dist/resources/handlers.d.ts +2 -2
  146. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  147. package/dist/server/transcribe-voice.d.ts +1 -1
  148. package/package.json +1 -1
@@ -0,0 +1,275 @@
1
+ /**
2
+ * deploy-design-preview — trigger a preview deploy when the source supports it
3
+ * (§6.6 of DESIGN-STUDIO-PLAN.md).
4
+ *
5
+ * Behaviour:
6
+ * - **Capability gate (server-side):** re-checks `deployPreview` capability.
7
+ * When `unavailable` (inline/localhost), returns a `ctaRequired` response with a
8
+ * "Make it real" CTA — never fakes a deploy call.
9
+ * - **Builder gate:** if `resolveIsBuilderBranchingEnabled()` returns false,
10
+ * returns a `connectRequired` CTA.
11
+ * - **Branch requirement:** a branch must already exist on the design (created
12
+ * via `create-design-branch`). If no branch is found, returns a clear
13
+ * `branchRequired` message rather than silently failing.
14
+ * - **Deploy trigger:** calls `runBuilderAgent()` with a scoped "deploy preview"
15
+ * prompt asking the Builder cloud agent to build and publish a preview URL for
16
+ * the named branch. Builder returns `{ branchName, url, status }`.
17
+ * - **Persistence:** updates the matching branch entry in the design's `data`
18
+ * JSON blob with `previewUrl` and `deployStatus`.
19
+ *
20
+ * The preview URL is the Builder-hosted ephemeral preview (not a permanent
21
+ * production deploy — use the Builder Visual Editor's Publish flow for that).
22
+ *
23
+ * Per DESIGN-STUDIO-PLAN.md §5, `deploy` (production) is the separate step;
24
+ * `deployPreview` is the lighter "preview build" step exposed here.
25
+ */
26
+
27
+ import { defineAction } from "@agent-native/core";
28
+ import {
29
+ runBuilderAgent,
30
+ resolveBuilderBranchProjectId,
31
+ resolveIsBuilderBranchingEnabled,
32
+ } from "@agent-native/core/server";
33
+ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
34
+ import { assertAccess, resolveAccess } from "@agent-native/core/sharing";
35
+ import { eq } from "drizzle-orm";
36
+ import { z } from "zod";
37
+
38
+ import { getDb, schema } from "../server/db/index.js";
39
+ import "../server/db/index.js"; // ensure registerShareableResource runs
40
+ import {
41
+ resolveSourceCapabilities,
42
+ resolveFusionCapabilities,
43
+ } from "../shared/capability-resolver.js";
44
+ import { hasCapability } from "../shared/design-source-capabilities.js";
45
+ import { normalizeDesignSourceType } from "../shared/source-mode.js";
46
+
47
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
48
+
49
+ function parseDesignData(raw: unknown): Record<string, unknown> {
50
+ if (typeof raw !== "string") return {};
51
+ try {
52
+ const parsed: unknown = JSON.parse(raw);
53
+ if (
54
+ parsed !== null &&
55
+ typeof parsed === "object" &&
56
+ !Array.isArray(parsed)
57
+ ) {
58
+ return parsed as Record<string, unknown>;
59
+ }
60
+ } catch {
61
+ // Stale JSON — return empty.
62
+ }
63
+ return {};
64
+ }
65
+
66
+ interface StoredBranchEntry {
67
+ branchName?: string;
68
+ projectId?: string;
69
+ url?: string;
70
+ status?: string;
71
+ purpose?: string | null;
72
+ preSnapshotVersionId?: string | null;
73
+ previewUrl?: string | null;
74
+ deployStatus?: string | null;
75
+ createdAt?: string;
76
+ [key: string]: unknown;
77
+ }
78
+
79
+ function parseBranches(
80
+ designData: Record<string, unknown>,
81
+ ): StoredBranchEntry[] {
82
+ const raw = designData["branches"];
83
+ if (!Array.isArray(raw)) return [];
84
+ return raw.filter(
85
+ (b): b is StoredBranchEntry =>
86
+ b !== null && typeof b === "object" && !Array.isArray(b),
87
+ );
88
+ }
89
+
90
+ /** Build a concise deploy-preview prompt for the Builder cloud agent. */
91
+ function buildDeployPrompt(
92
+ designTitle: string,
93
+ branchName: string,
94
+ projectId: string,
95
+ ): string {
96
+ return [
97
+ `Deploy a preview build for branch "${branchName}" of project "${projectId}".`,
98
+ `Design: "${designTitle}".`,
99
+ "Build the branch and publish a preview URL so the design can be reviewed",
100
+ "in a real browser before merging to production.",
101
+ ].join("\n");
102
+ }
103
+
104
+ // ─── Action ───────────────────────────────────────────────────────────────────
105
+
106
+ export default defineAction({
107
+ description:
108
+ "Trigger a preview deploy for a fusion-backed design branch. " +
109
+ "Requires the design's source to advertise the 'deployPreview' capability " +
110
+ "(fusion tier) AND Builder.io to be connected. " +
111
+ "For inline/localhost designs, returns ctaRequired=true with a Make-it-real " +
112
+ "CTA — never fakes a deploy call. " +
113
+ "A branch must already exist (created via create-design-branch). " +
114
+ "On success, persists previewUrl and deployStatus into the design's branch " +
115
+ "metadata and returns the preview URL. " +
116
+ "Note: this triggers a *preview* deploy, not a production publish. " +
117
+ "Use the Builder Visual Editor's Publish flow for production deploys.",
118
+ schema: z.object({
119
+ designId: z.string().describe("Design project ID to deploy a preview for"),
120
+ branchName: z
121
+ .string()
122
+ .optional()
123
+ .describe(
124
+ "Branch name to deploy. Defaults to the most recently created branch " +
125
+ "when omitted.",
126
+ ),
127
+ }),
128
+ run: async ({ designId, branchName }) => {
129
+ const db = getDb();
130
+
131
+ // ── Access check (editor level required for deploys) ────────────────────
132
+ await assertAccess("design", designId, "editor");
133
+ const access = await resolveAccess("design", designId);
134
+ if (!access) throw new Error("Design not found");
135
+
136
+ const resource = access.resource as {
137
+ title?: string;
138
+ data?: unknown;
139
+ };
140
+
141
+ // ── Source type + capability check ──────────────────────────────────────
142
+ const designData = parseDesignData(resource.data);
143
+ const sourceType =
144
+ normalizeDesignSourceType(designData["sourceType"]) ?? "inline";
145
+
146
+ // For fusion sources, resolve the Builder connection status first so that
147
+ // resolveFusionCapabilities returns the CONNECTED map (with deployPreview
148
+ // available) when Builder is actually wired up. For inline/localhost the
149
+ // generic resolver is sufficient — those sources never have deployPreview.
150
+ const builderEnabled =
151
+ sourceType === "fusion"
152
+ ? await resolveIsBuilderBranchingEnabled()
153
+ : false;
154
+ const caps =
155
+ sourceType === "fusion"
156
+ ? resolveFusionCapabilities(builderEnabled)
157
+ : resolveSourceCapabilities(sourceType);
158
+
159
+ if (!hasCapability(caps, "deployPreview")) {
160
+ // For a disconnected fusion source the connect-builder CTA applies.
161
+ const isFusion = sourceType === "fusion";
162
+ return {
163
+ designId,
164
+ sourceType,
165
+ ctaRequired: true,
166
+ ctaKind: isFusion
167
+ ? ("connect-builder" as const)
168
+ : ("make-it-real" as const),
169
+ ctaMessage: isFusion
170
+ ? "Builder is not yet connected. Connect Builder.io to trigger preview deploys."
171
+ : "Preview deploys require a Builder-hosted app. Use 'Make it real' to upgrade " +
172
+ "this inline design to a real-app source, then deploy previews.",
173
+ previewUrl: null,
174
+ deployStatus: null,
175
+ branch: null,
176
+ };
177
+ }
178
+
179
+ // At this point sourceType === "fusion" and builderEnabled === true,
180
+ // so no separate Builder gate is needed — the capability check above
181
+ // already required a connected Builder to set deployPreview=available.
182
+
183
+ // ── Resolve branch entry ─────────────────────────────────────────────────
184
+ const branches = parseBranches(designData);
185
+
186
+ let branch: StoredBranchEntry | null = null;
187
+ if (branchName) {
188
+ branch =
189
+ branches.find(
190
+ (b) => b.branchName?.toLowerCase() === branchName.toLowerCase(),
191
+ ) ?? null;
192
+ } else {
193
+ branch = branches.length > 0 ? branches[branches.length - 1]! : null;
194
+ }
195
+
196
+ if (!branch || !branch.branchName) {
197
+ return {
198
+ designId,
199
+ sourceType,
200
+ ctaRequired: false,
201
+ ctaKind: null,
202
+ ctaMessage: null,
203
+ note:
204
+ "No branch found for this design. Use create-design-branch to create " +
205
+ "a branch first, then trigger a preview deploy.",
206
+ previewUrl: null,
207
+ deployStatus: null,
208
+ branch: null,
209
+ };
210
+ }
211
+
212
+ // ── Trigger the preview deploy via the Builder cloud agent ───────────────
213
+ const projectId = await resolveBuilderBranchProjectId();
214
+ const userEmail = getRequestUserEmail();
215
+ if (!userEmail) throw new Error("No authenticated user");
216
+
217
+ const designTitle =
218
+ typeof resource.title === "string" && resource.title.trim()
219
+ ? resource.title.trim()
220
+ : "Design";
221
+
222
+ const builderResult = await runBuilderAgent({
223
+ prompt: buildDeployPrompt(designTitle, branch.branchName, projectId),
224
+ projectId,
225
+ branchName: branch.branchName,
226
+ userEmail,
227
+ });
228
+
229
+ // ── Persist preview URL + deploy status into the branch entry ────────────
230
+ const now = new Date().toISOString();
231
+ const updatedBranches = branches.map((b) => {
232
+ if (b.branchName?.toLowerCase() !== branch!.branchName!.toLowerCase()) {
233
+ return b;
234
+ }
235
+ return {
236
+ ...b,
237
+ previewUrl: builderResult.url,
238
+ deployStatus: builderResult.status,
239
+ lastDeployedAt: now,
240
+ };
241
+ });
242
+
243
+ const updatedData = JSON.stringify({
244
+ ...designData,
245
+ branches: updatedBranches,
246
+ });
247
+
248
+ await db
249
+ .update(schema.designs)
250
+ .set({ data: updatedData, updatedAt: now })
251
+ .where(eq(schema.designs.id, designId));
252
+
253
+ return {
254
+ designId,
255
+ sourceType,
256
+ ctaRequired: false,
257
+ ctaKind: null,
258
+ ctaMessage: null,
259
+ previewUrl: builderResult.url,
260
+ deployStatus: builderResult.status,
261
+ branch: {
262
+ branchName: branch.branchName,
263
+ projectId: builderResult.projectId,
264
+ url: branch.url ?? null,
265
+ previewUrl: builderResult.url,
266
+ deployStatus: builderResult.status,
267
+ lastDeployedAt: now,
268
+ },
269
+ note:
270
+ "Preview deploy triggered. The Builder cloud agent is building the branch. " +
271
+ "Visit the previewUrl once status is 'ready' to review the deployed design. " +
272
+ "For production deploys, use the Builder Visual Editor's Publish flow.",
273
+ };
274
+ },
275
+ });
@@ -0,0 +1,251 @@
1
+ /**
2
+ * get-component-details — read action.
3
+ *
4
+ * For a selected component instance, returns the component name, source file
5
+ * (via resolveNodeToFile when the capability is available), props / variants,
6
+ * and the persistent component_index row when one exists.
7
+ *
8
+ * Works across both tiers:
9
+ * - **Alpine / inline** — returns name + observed props from attributes, plus
10
+ * a CTA flag for features that require a real-app source.
11
+ * - **Real app (localhost / fusion)** — returns the full component_index row
12
+ * including parsed TS prop types, cva variants, Storybook stories, and the
13
+ * source file path. The `resolveNodeToFile` capability unlocks the source
14
+ * deep-link returned in `sourceLocation`.
15
+ */
16
+
17
+ import { defineAction } from "@agent-native/core";
18
+ import { getText, hasCollabState } from "@agent-native/core/collab";
19
+ import { accessFilter, resolveAccess } from "@agent-native/core/sharing";
20
+ import { and, eq } from "drizzle-orm";
21
+ import { z } from "zod";
22
+
23
+ import { getDb, schema } from "../server/db/index.js";
24
+ import "../server/db/index.js"; // ensure registerShareableResource runs
25
+ import { resolveSourceCapabilities } from "../shared/capability-resolver.js";
26
+ import { buildCodeLayerProjection } from "../shared/code-layer.js";
27
+ import type { CodeLayerSource } from "../shared/code-layer.js";
28
+ import {
29
+ componentNameFor,
30
+ componentNodeIdMatches,
31
+ extractProps,
32
+ type ComponentInstance,
33
+ } from "../shared/component-model.js";
34
+ import { hasCapability } from "../shared/design-source-capabilities.js";
35
+ import { normalizeDesignSourceType } from "../shared/source-mode.js";
36
+
37
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
38
+
39
+ async function liveContent(
40
+ fileId: string,
41
+ storedContent: string,
42
+ ): Promise<string> {
43
+ try {
44
+ if (await hasCollabState(fileId)) {
45
+ const live = await getText(fileId, "content");
46
+ if (typeof live === "string") return live;
47
+ }
48
+ } catch {
49
+ // Collab reads are best-effort; SQL content is the fallback.
50
+ }
51
+ return storedContent;
52
+ }
53
+
54
+ function parseJson<T>(raw: string | null | undefined, fallback: T): T {
55
+ if (!raw) return fallback;
56
+ try {
57
+ return JSON.parse(raw) as T;
58
+ } catch {
59
+ return fallback;
60
+ }
61
+ }
62
+
63
+ // ─── Action ───────────────────────────────────────────────────────────────────
64
+
65
+ export default defineAction({
66
+ description:
67
+ "Return details for a selected component instance: component name, " +
68
+ "source file (when resolveNodeToFile capability is available), props, " +
69
+ "variants, and the persisted component_index row. " +
70
+ "Provide the design id and node id (data-agent-native-node-id) of the " +
71
+ "selected component root. For Alpine designs the response includes " +
72
+ "lightweight attribute-based props and a CTA flag for full prop controls.",
73
+ schema: z.object({
74
+ designId: z.string().describe("Design project ID"),
75
+ nodeId: z
76
+ .string()
77
+ .describe(
78
+ "The data-agent-native-node-id of the selected component instance root element.",
79
+ ),
80
+ fileId: z
81
+ .string()
82
+ .optional()
83
+ .describe("Design file id. Defaults to index.html."),
84
+ }),
85
+ readOnly: true,
86
+ http: { method: "GET" },
87
+ run: async ({ designId, nodeId, fileId }) => {
88
+ const db = getDb();
89
+
90
+ // ── Access check ────────────────────────────────────────────────────────
91
+ const access = await resolveAccess("design", designId);
92
+ if (!access) throw new Error("Design not found");
93
+
94
+ // ── Source type + capabilities ───────────────────────────────────────────
95
+ let rawSourceType: unknown = "inline";
96
+ const rawData = (access.resource as { data?: unknown }).data;
97
+ if (typeof rawData === "string") {
98
+ try {
99
+ const parsed: unknown = JSON.parse(rawData);
100
+ if (
101
+ parsed !== null &&
102
+ typeof parsed === "object" &&
103
+ "sourceType" in (parsed as object)
104
+ ) {
105
+ rawSourceType = (parsed as { sourceType: unknown }).sourceType;
106
+ }
107
+ } catch {
108
+ // Default to inline.
109
+ }
110
+ }
111
+
112
+ const sourceType = normalizeDesignSourceType(rawSourceType) ?? "inline";
113
+ const caps = resolveSourceCapabilities(sourceType);
114
+ const canResolveToFile = hasCapability(caps, "resolveNodeToFile");
115
+ const hasFullIndex = hasCapability(caps, "indexComponents");
116
+ const canEditProps =
117
+ sourceType === "inline" || hasCapability(caps, "applyEdit");
118
+ const ctaRequired = !hasFullIndex || !canEditProps;
119
+ const ctaMessage = !hasFullIndex
120
+ ? "Full prop controls (TypeScript prop types, cva variants, Storybook stories) require a connected Builder app. Connect Builder to unlock."
121
+ : !canEditProps
122
+ ? "Prop write-back requires the bridge applyEdit capability. Preview controls remain available until source write hardening is enabled."
123
+ : undefined;
124
+
125
+ // ── Fetch design file ────────────────────────────────────────────────────
126
+ const conditions = [
127
+ accessFilter(schema.designs, schema.designShares),
128
+ eq(schema.designFiles.designId, designId),
129
+ fileId
130
+ ? eq(schema.designFiles.id, fileId)
131
+ : eq(schema.designFiles.filename, "index.html"),
132
+ ];
133
+
134
+ const [file] = await db
135
+ .select({
136
+ id: schema.designFiles.id,
137
+ designId: schema.designFiles.designId,
138
+ filename: schema.designFiles.filename,
139
+ content: schema.designFiles.content,
140
+ })
141
+ .from(schema.designFiles)
142
+ .innerJoin(
143
+ schema.designs,
144
+ eq(schema.designFiles.designId, schema.designs.id),
145
+ )
146
+ .where(and(...conditions))
147
+ .limit(1);
148
+
149
+ if (!file) throw new Error("Design HTML file not found.");
150
+
151
+ const html = await liveContent(file.id, file.content ?? "");
152
+
153
+ // ── Projection lookup ────────────────────────────────────────────────────
154
+ const codeLayerSource: CodeLayerSource = {
155
+ kind: "design-file",
156
+ designId: file.designId,
157
+ fileId: file.id,
158
+ filename: file.filename,
159
+ };
160
+
161
+ const projection = buildCodeLayerProjection(html, {
162
+ source: codeLayerSource,
163
+ });
164
+
165
+ const node = projection.nodes.find((n) =>
166
+ componentNodeIdMatches(n, nodeId),
167
+ );
168
+ if (!node) {
169
+ throw new Error(
170
+ `Node "${nodeId}" not found in projection. Run get-code-layer-projection to list current node ids.`,
171
+ );
172
+ }
173
+
174
+ const name = componentNameFor(node);
175
+ if (!name) {
176
+ throw new Error(
177
+ `Node "${nodeId}" does not carry a data-agent-native-component attribute and is not a component root.`,
178
+ );
179
+ }
180
+
181
+ // ── Simple props from attributes ─────────────────────────────────────────
182
+ const observedProps = extractProps(node);
183
+ const alpineData =
184
+ typeof node.attributes["x-data"] === "string"
185
+ ? node.attributes["x-data"]
186
+ : undefined;
187
+
188
+ const instance: ComponentInstance = {
189
+ instanceId: node.id,
190
+ name,
191
+ props: observedProps,
192
+ alpineData,
193
+ selector: node.selector,
194
+ nodeId,
195
+ };
196
+
197
+ // ── Lookup persisted component_index row ─────────────────────────────────
198
+ const [indexRow] = await db
199
+ .select()
200
+ .from(schema.componentIndex)
201
+ .where(
202
+ and(
203
+ eq(schema.componentIndex.designId, designId),
204
+ eq(schema.componentIndex.name, name),
205
+ ),
206
+ )
207
+ .limit(1);
208
+
209
+ const persistedProps = parseJson<unknown[]>(indexRow?.props, []);
210
+ const persistedVariants = parseJson<Record<string, string[]>>(
211
+ indexRow?.variants,
212
+ {},
213
+ );
214
+ const persistedStories = parseJson<unknown[]>(indexRow?.stories, []);
215
+
216
+ // ── Source location (real-app only) ──────────────────────────────────────
217
+ // The bridge `resolveNodeToFile` op maps a node id to a source file + span.
218
+ // For inline designs the capability is `available` but resolves to the
219
+ // design file itself (no external source file). We surface whatever we
220
+ // have from the index row.
221
+ const sourceLocation =
222
+ canResolveToFile && indexRow?.filePath
223
+ ? {
224
+ filePath: indexRow.filePath,
225
+ exportName: indexRow.exportName ?? undefined,
226
+ }
227
+ : undefined;
228
+
229
+ return {
230
+ designId,
231
+ nodeId,
232
+ sourceType,
233
+ instance,
234
+ name,
235
+ // Props: merge observed attribute props with richer persisted prop types
236
+ // when available. Real-app callers get the full TS/cva prop table.
237
+ observedProps,
238
+ persistedProps,
239
+ persistedVariants,
240
+ persistedStories,
241
+ sourceLocation,
242
+ capabilities: {
243
+ canResolveToFile,
244
+ hasFullIndex,
245
+ canEditProps,
246
+ ctaRequired,
247
+ ctaMessage,
248
+ },
249
+ };
250
+ },
251
+ });