@agent-native/core 0.80.9 → 0.80.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +7 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +28 -2
- package/corpus/core/src/cli/skills.ts +45 -24
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
- package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
- package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +433 -0
- package/corpus/templates/design/actions/apply-design-state.ts +200 -0
- package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +450 -0
- package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
- package/corpus/templates/design/actions/capture-design-state.ts +224 -0
- package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
- package/corpus/templates/design/actions/create-component.ts +447 -0
- package/corpus/templates/design/actions/create-design-branch.ts +263 -0
- package/corpus/templates/design/actions/create-design-state.ts +162 -0
- package/corpus/templates/design/actions/delete-design-state.ts +55 -0
- package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
- package/corpus/templates/design/actions/get-component-details.ts +242 -0
- package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
- package/corpus/templates/design/actions/get-design-review.ts +314 -0
- package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
- package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
- package/corpus/templates/design/actions/index-components.ts +258 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +273 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
- package/corpus/templates/design/actions/list-design-states.ts +72 -0
- package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
- package/corpus/templates/design/actions/open-component-source.ts +237 -0
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +254 -0
- package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
- package/corpus/templates/design/actions/preview-shader-fill.ts +189 -0
- package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
- package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
- package/corpus/templates/design/actions/run-design-audit.ts +421 -0
- package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
- package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1414 -71
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +632 -43
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1391 -52
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +4 -3
- package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1062 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1052 -98
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +759 -0
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +579 -0
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +573 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
- package/corpus/templates/design/app/components/design/index.ts +16 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
- package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +31 -0
- package/corpus/templates/design/app/i18n-data.ts +269 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2478 -386
- package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +10 -4
- package/corpus/templates/design/server/db/schema.ts +123 -0
- package/corpus/templates/design/server/plugins/db.ts +90 -0
- package/corpus/templates/design/shared/builder-app.ts +297 -0
- package/corpus/templates/design/shared/capability-resolver.ts +123 -0
- package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
- package/corpus/templates/design/shared/code-layer.ts +1016 -71
- package/corpus/templates/design/shared/component-model.ts +239 -0
- package/corpus/templates/design/shared/design-review.ts +275 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
- package/corpus/templates/design/shared/design-state.ts +112 -0
- package/corpus/templates/design/shared/design-surface-index.ts +258 -0
- package/corpus/templates/design/shared/motion-compiler.ts +278 -0
- package/corpus/templates/design/shared/motion-timeline.ts +193 -0
- package/corpus/templates/design/shared/responsive-classes.ts +452 -0
- package/corpus/templates/design/shared/shader-fill.ts +323 -0
- package/corpus/templates/design/shared/source-mode.ts +245 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
- package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +28 -2
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +37 -21
- package/dist/cli/skills.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* migrate-inline-design-to-app — generate a real React + Tailwind app branch
|
|
3
|
+
* from an inline Alpine/HTML design via the Builder cloud agent.
|
|
4
|
+
*
|
|
5
|
+
* ## What this action does
|
|
6
|
+
*
|
|
7
|
+
* 1. Requires viewer access to the design and that Builder is fully configured
|
|
8
|
+
* (credentials + branch project ID). When Builder is not configured it
|
|
9
|
+
* returns a connect CTA gracefully, never throws.
|
|
10
|
+
* 2. Reads the current design snapshot (live Yjs content when available).
|
|
11
|
+
* 3. Snapshots the current state into `design_versions` (reversible baseline).
|
|
12
|
+
* 4. Builds a migration seed (HTML + :root CSS vars + Brand Kit tokens) via
|
|
13
|
+
* `buildMigrationSeed` and hands it to `runBuilderAgent`.
|
|
14
|
+
* 5. Returns `{ branchName, url, status, versionId }` so the caller can track
|
|
15
|
+
* the Builder branch and roll back via `versionId` if needed.
|
|
16
|
+
*
|
|
17
|
+
* ## Write gating (per DESIGN-STUDIO-PLAN.md §3 & §5)
|
|
18
|
+
*
|
|
19
|
+
* - `writeFile` / `writeTokens` / `writeMotion` on the inline source stay
|
|
20
|
+
* **planned** until bridge hardening. This action does NOT write to the
|
|
21
|
+
* inline design's source files.
|
|
22
|
+
* - `branch` / `deploy` capabilities are **available** once Builder is
|
|
23
|
+
* configured (fusion source tier). This action is the entry point for that
|
|
24
|
+
* tier — it creates the Builder branch, NOT a local file write.
|
|
25
|
+
* - The `design_versions` snapshot is the only local write: it is additive and
|
|
26
|
+
* never modifies the existing design data.
|
|
27
|
+
*
|
|
28
|
+
* ## Reversibility
|
|
29
|
+
*
|
|
30
|
+
* The snapshot saved as `design_versions` before the migration gives the user
|
|
31
|
+
* a named restore point ("Pre-migration snapshot"). A future `restore-design-
|
|
32
|
+
* version` action can replay it. The original inline design row is unchanged.
|
|
33
|
+
*
|
|
34
|
+
* ## Connect CTA path
|
|
35
|
+
*
|
|
36
|
+
* When Builder is not connected this action returns:
|
|
37
|
+
* ```json
|
|
38
|
+
* { "status": "not-configured", "cta": { "kind": "connect-builder", ... } }
|
|
39
|
+
* ```
|
|
40
|
+
* so the UI can render the "Make it real" upgrade card without throwing.
|
|
41
|
+
* The caller should offer `connect-builder-app` to surface the CTA first.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { defineAction } from "@agent-native/core";
|
|
45
|
+
import {
|
|
46
|
+
runBuilderAgent,
|
|
47
|
+
resolveBuilderBranchProjectId,
|
|
48
|
+
} from "@agent-native/core/server";
|
|
49
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
50
|
+
import { assertAccess, resolveAccess } from "@agent-native/core/sharing";
|
|
51
|
+
import { eq } from "drizzle-orm";
|
|
52
|
+
import { nanoid } from "nanoid";
|
|
53
|
+
import { z } from "zod";
|
|
54
|
+
|
|
55
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
56
|
+
import { buildDesignSnapshot } from "../server/lib/design-snapshot.js";
|
|
57
|
+
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
58
|
+
import {
|
|
59
|
+
resolveBuilderStatus,
|
|
60
|
+
buildMigrationSeed,
|
|
61
|
+
} from "../shared/builder-app.js";
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Helpers
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/** The default Builder app host — mirrors the constant in builder-browser.ts. */
|
|
68
|
+
const DEFAULT_BUILDER_APP_HOST = "https://builder.io";
|
|
69
|
+
|
|
70
|
+
function resolveBuilderAppHost(): string {
|
|
71
|
+
return (
|
|
72
|
+
process.env.BUILDER_APP_HOST ||
|
|
73
|
+
process.env.BUILDER_PUBLIC_APP_HOST ||
|
|
74
|
+
DEFAULT_BUILDER_APP_HOST
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildConnectUrl(origin: string): string {
|
|
79
|
+
const base = origin.replace(/\/+$/, "");
|
|
80
|
+
return `${base}/_agent-native/builder/connect`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Snapshot helpers
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Persist a `design_versions` snapshot row before the migration so the user
|
|
89
|
+
* has a named restore point. Additive only — never modifies existing rows.
|
|
90
|
+
*
|
|
91
|
+
* Returns the new version ID.
|
|
92
|
+
*/
|
|
93
|
+
async function snapshotDesign(
|
|
94
|
+
designId: string,
|
|
95
|
+
label: string,
|
|
96
|
+
): Promise<string> {
|
|
97
|
+
const db = getDb();
|
|
98
|
+
const versionId = nanoid();
|
|
99
|
+
const now = new Date().toISOString();
|
|
100
|
+
|
|
101
|
+
// Read the current file contents for the snapshot (stored content only;
|
|
102
|
+
// we intentionally capture the persisted baseline, not in-flight collab text,
|
|
103
|
+
// so the snapshot is stable and reproducible).
|
|
104
|
+
const files = await db
|
|
105
|
+
.select({
|
|
106
|
+
filename: schema.designFiles.filename,
|
|
107
|
+
content: schema.designFiles.content,
|
|
108
|
+
fileType: schema.designFiles.fileType,
|
|
109
|
+
})
|
|
110
|
+
.from(schema.designFiles)
|
|
111
|
+
.where(eq(schema.designFiles.designId, designId));
|
|
112
|
+
|
|
113
|
+
// Also capture the design data blob (tweaks, source type, etc.).
|
|
114
|
+
// guard:allow-unscoped — the action's run() resolves and asserts editor
|
|
115
|
+
// access on the design (resolveAccess + assertAccess "design", designId)
|
|
116
|
+
// before this snapshot helper runs; reads only the addressed design row by id.
|
|
117
|
+
const [design] = await db
|
|
118
|
+
.select({ data: schema.designs.data, title: schema.designs.title })
|
|
119
|
+
.from(schema.designs)
|
|
120
|
+
.where(eq(schema.designs.id, designId))
|
|
121
|
+
.limit(1);
|
|
122
|
+
|
|
123
|
+
const snapshot = JSON.stringify({
|
|
124
|
+
designId,
|
|
125
|
+
label,
|
|
126
|
+
capturedAt: now,
|
|
127
|
+
designData: design?.data ?? "{}",
|
|
128
|
+
files: files.map((f) => ({
|
|
129
|
+
filename: f.filename,
|
|
130
|
+
fileType: f.fileType,
|
|
131
|
+
content: f.content,
|
|
132
|
+
})),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await db.insert(schema.designVersions).values({
|
|
136
|
+
id: versionId,
|
|
137
|
+
designId,
|
|
138
|
+
label,
|
|
139
|
+
snapshot,
|
|
140
|
+
createdAt: now,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return versionId;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Action
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
export default defineAction({
|
|
151
|
+
description:
|
|
152
|
+
"Migrate an inline Alpine/HTML design to a real React + Tailwind app " +
|
|
153
|
+
"by handing the design's HTML and tokens to the Builder cloud agent. " +
|
|
154
|
+
"Requires Builder.io to be connected (credentials + branch project ID). " +
|
|
155
|
+
"When Builder is NOT configured returns a connect CTA — never throws. " +
|
|
156
|
+
"Snapshots the current design into design_versions before migrating so " +
|
|
157
|
+
"the inline baseline is always recoverable. " +
|
|
158
|
+
"Returns { branchName, url, status, versionId } on success. " +
|
|
159
|
+
"Real-app source writes (writeFile/writeTokens/writeMotion to the inline " +
|
|
160
|
+
"design) remain planned until bridge hardening; this action only creates a " +
|
|
161
|
+
"Builder-hosted branch — it does NOT modify the inline design's files.",
|
|
162
|
+
schema: z.object({
|
|
163
|
+
designId: z.string().describe("Design project ID to migrate to a real app"),
|
|
164
|
+
brandKitSummary: z
|
|
165
|
+
.string()
|
|
166
|
+
.optional()
|
|
167
|
+
.describe(
|
|
168
|
+
"Optional human-readable summary of the linked design system tokens " +
|
|
169
|
+
"(e.g. from index-design-tokens) to include in the migration seed. " +
|
|
170
|
+
"Improves token fidelity in the generated React app.",
|
|
171
|
+
),
|
|
172
|
+
branchName: z
|
|
173
|
+
.string()
|
|
174
|
+
.optional()
|
|
175
|
+
.describe(
|
|
176
|
+
"Optional branch name for the Builder agent to use. " +
|
|
177
|
+
"If omitted, Builder generates one.",
|
|
178
|
+
),
|
|
179
|
+
}),
|
|
180
|
+
run: async ({ designId, brandKitSummary, branchName }) => {
|
|
181
|
+
// ── 1. Access check ────────────────────────────────────────────────────
|
|
182
|
+
const access = await resolveAccess("design", designId);
|
|
183
|
+
if (!access) {
|
|
184
|
+
throw new Error("Design not found");
|
|
185
|
+
}
|
|
186
|
+
const design = access.resource as typeof schema.designs.$inferSelect;
|
|
187
|
+
|
|
188
|
+
// Snapshotting a design_versions row + kicking off a paid Builder cloud run
|
|
189
|
+
// are mutations: require editor access. A read-only (viewer) share must not
|
|
190
|
+
// be able to trigger them.
|
|
191
|
+
await assertAccess("design", designId, "editor");
|
|
192
|
+
|
|
193
|
+
// ── 2. Builder status check ────────────────────────────────────────────
|
|
194
|
+
const builderStatus = await resolveBuilderStatus();
|
|
195
|
+
|
|
196
|
+
if (!builderStatus.connected || !builderStatus.builderEnabled) {
|
|
197
|
+
// Return a graceful CTA; never throw so the UI can render the card.
|
|
198
|
+
const connectUrl = buildConnectUrl(
|
|
199
|
+
process.env.APP_URL ??
|
|
200
|
+
process.env.VITE_APP_URL ??
|
|
201
|
+
process.env.BETTER_AUTH_URL ??
|
|
202
|
+
"",
|
|
203
|
+
);
|
|
204
|
+
const appHost = resolveBuilderAppHost();
|
|
205
|
+
|
|
206
|
+
if (!builderStatus.connected) {
|
|
207
|
+
return {
|
|
208
|
+
status: "not-configured" as const,
|
|
209
|
+
designId,
|
|
210
|
+
cta: {
|
|
211
|
+
kind: "connect-builder" as const,
|
|
212
|
+
label: "Make this a real app",
|
|
213
|
+
description:
|
|
214
|
+
"Connect Builder.io to migrate this design to a real React app " +
|
|
215
|
+
"with components, props, data states, branches, and deploys.",
|
|
216
|
+
primaryAction: "Connect Builder.io",
|
|
217
|
+
connectUrl,
|
|
218
|
+
},
|
|
219
|
+
message:
|
|
220
|
+
"Builder is not connected. Call connect-builder-app to start " +
|
|
221
|
+
"the OAuth flow, then retry migrate-inline-design-to-app.",
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Connected but no project ID.
|
|
226
|
+
return {
|
|
227
|
+
status: "not-configured" as const,
|
|
228
|
+
designId,
|
|
229
|
+
cta: {
|
|
230
|
+
kind: "configure-project" as const,
|
|
231
|
+
label: "Configure Builder project",
|
|
232
|
+
description:
|
|
233
|
+
"Builder credentials are present but no branch project ID is set. " +
|
|
234
|
+
"Set DISPATCH_BUILDER_PROJECT_ID, BUILDER_BRANCH_PROJECT_ID, or " +
|
|
235
|
+
"BUILDER_PROJECT_ID to enable cloud agent migration.",
|
|
236
|
+
primaryAction: "Open Builder settings",
|
|
237
|
+
connectUrl: `${appHost}/account-settings`,
|
|
238
|
+
},
|
|
239
|
+
message:
|
|
240
|
+
"Builder credentials are configured but no branch project ID is set. " +
|
|
241
|
+
"Set DISPATCH_BUILDER_PROJECT_ID to enable cloud agent migration.",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── 3. Resolve branch project ID ──────────────────────────────────────
|
|
246
|
+
const projectId = await resolveBuilderBranchProjectId();
|
|
247
|
+
if (!projectId) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
"Builder branch project ID is not configured. " +
|
|
250
|
+
"Set DISPATCH_BUILDER_PROJECT_ID, BUILDER_BRANCH_PROJECT_ID, or " +
|
|
251
|
+
"BUILDER_PROJECT_ID and try again.",
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── 4. Snapshot the current design (reversible baseline) ──────────────
|
|
256
|
+
const versionId = await snapshotDesign(
|
|
257
|
+
designId,
|
|
258
|
+
`Pre-migration snapshot — ${design.title}`,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// ── 5. Build the live snapshot (Yjs preferred for in-flight edits) ────
|
|
262
|
+
const snapshot = await buildDesignSnapshot(designId, design.data);
|
|
263
|
+
|
|
264
|
+
// ── 6. Build the migration seed prompt ────────────────────────────────
|
|
265
|
+
const seed = buildMigrationSeed({
|
|
266
|
+
title: design.title,
|
|
267
|
+
files: snapshot.files,
|
|
268
|
+
resolvedCssVars: snapshot.resolvedCssVars,
|
|
269
|
+
brandKitSummary,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// ── 7. Hand off to the Builder cloud agent ────────────────────────────
|
|
273
|
+
const ownerEmail = getRequestUserEmail();
|
|
274
|
+
const result = await runBuilderAgent({
|
|
275
|
+
prompt: seed.prompt,
|
|
276
|
+
projectId,
|
|
277
|
+
branchName: branchName?.trim() || undefined,
|
|
278
|
+
userEmail: ownerEmail ?? undefined,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
status: "processing" as const,
|
|
283
|
+
designId,
|
|
284
|
+
versionId,
|
|
285
|
+
snapshotLabel: `Pre-migration snapshot — ${design.title}`,
|
|
286
|
+
branchName: result.branchName,
|
|
287
|
+
projectId: result.projectId,
|
|
288
|
+
url: result.url,
|
|
289
|
+
builderStatus: result.status,
|
|
290
|
+
seedFileCount: seed.fileCount,
|
|
291
|
+
seedTotalBytes: seed.totalBytes,
|
|
292
|
+
message:
|
|
293
|
+
`Migration started. Builder is generating a React app branch ` +
|
|
294
|
+
`"${result.branchName}". Open the url to track progress. ` +
|
|
295
|
+
`The original inline design is preserved as version ${versionId}.`,
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
});
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* open-component-source — navigate / deep-link action.
|
|
3
|
+
*
|
|
4
|
+
* For a selected component instance, resolves the source file path (via
|
|
5
|
+
* `resolveNodeToFile` bridge op when the capability is available) and writes
|
|
6
|
+
* a navigation command to application state so the editor opens the correct
|
|
7
|
+
* file and highlights the component.
|
|
8
|
+
*
|
|
9
|
+
* **Inline / Alpine tier:** the "source file" is the design HTML file itself.
|
|
10
|
+
* The action navigates the editor to the file and selects the component node —
|
|
11
|
+
* there is no external source file to open. The response includes a
|
|
12
|
+
* `ctaRequired` flag pointing to the real-app CTA for full jump-to-source.
|
|
13
|
+
*
|
|
14
|
+
* **Real-app tier (localhost / fusion):** uses the persisted `component_index`
|
|
15
|
+
* row's `filePath` / `exportName` as the target (populated by `index-components`
|
|
16
|
+
* after a bridge handshake or AST parse). The `resolveNodeToFile` capability
|
|
17
|
+
* gate is checked and the response includes the external file path for the IDE
|
|
18
|
+
* to open.
|
|
19
|
+
*
|
|
20
|
+
* Navigation is written via `writeAppState("navigate", ...)` — the same
|
|
21
|
+
* mechanism used by the `navigate` action — so the caller can trigger UI
|
|
22
|
+
* navigation without a round-trip.
|
|
23
|
+
*
|
|
24
|
+
* See DESIGN-STUDIO-PLAN.md §6.1 (jump-to-source) and §7 (action surface).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { defineAction } from "@agent-native/core";
|
|
28
|
+
import { writeAppState } from "@agent-native/core/application-state";
|
|
29
|
+
import { getText, hasCollabState } from "@agent-native/core/collab";
|
|
30
|
+
import { accessFilter, resolveAccess } from "@agent-native/core/sharing";
|
|
31
|
+
import { and, eq } from "drizzle-orm";
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
|
|
34
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
35
|
+
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
36
|
+
import { resolveSourceCapabilities } from "../shared/capability-resolver.js";
|
|
37
|
+
import { buildCodeLayerProjection } from "../shared/code-layer.js";
|
|
38
|
+
import type { CodeLayerSource } from "../shared/code-layer.js";
|
|
39
|
+
import { componentNameFor } from "../shared/component-model.js";
|
|
40
|
+
import { hasCapability } from "../shared/design-source-capabilities.js";
|
|
41
|
+
import { normalizeDesignSourceType } from "../shared/source-mode.js";
|
|
42
|
+
|
|
43
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
async function liveContent(
|
|
46
|
+
fileId: string,
|
|
47
|
+
storedContent: string,
|
|
48
|
+
): Promise<string> {
|
|
49
|
+
try {
|
|
50
|
+
if (await hasCollabState(fileId)) {
|
|
51
|
+
const live = await getText(fileId, "content");
|
|
52
|
+
if (typeof live === "string") return live;
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Collab reads are best-effort; SQL content is the fallback.
|
|
56
|
+
}
|
|
57
|
+
return storedContent;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Action ───────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
export default defineAction({
|
|
63
|
+
description:
|
|
64
|
+
"Navigate to a component's source: for inline/Alpine designs, selects " +
|
|
65
|
+
"the component root in the editor and returns the design file as the " +
|
|
66
|
+
"source location. For real-app sources (localhost / fusion), resolves the " +
|
|
67
|
+
"external source file path + export name via the component_index and the " +
|
|
68
|
+
"resolveNodeToFile bridge capability, and emits a navigation command so the " +
|
|
69
|
+
"IDE can open the file at the correct line.",
|
|
70
|
+
schema: z.object({
|
|
71
|
+
designId: z.string().describe("Design project ID"),
|
|
72
|
+
nodeId: z
|
|
73
|
+
.string()
|
|
74
|
+
.describe("data-agent-native-node-id of the component instance root"),
|
|
75
|
+
fileId: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Design file id; defaults to index.html"),
|
|
79
|
+
}),
|
|
80
|
+
readOnly: true,
|
|
81
|
+
http: { method: "GET" },
|
|
82
|
+
run: async ({ designId, nodeId, fileId }) => {
|
|
83
|
+
const db = getDb();
|
|
84
|
+
|
|
85
|
+
// ── Access check ────────────────────────────────────────────────────────
|
|
86
|
+
const access = await resolveAccess("design", designId);
|
|
87
|
+
if (!access) throw new Error("Design not found");
|
|
88
|
+
|
|
89
|
+
// ── Source type + capabilities ───────────────────────────────────────────
|
|
90
|
+
let rawSourceType: unknown = "inline";
|
|
91
|
+
const rawData = (access.resource as { data?: unknown }).data;
|
|
92
|
+
if (typeof rawData === "string") {
|
|
93
|
+
try {
|
|
94
|
+
const parsed: unknown = JSON.parse(rawData);
|
|
95
|
+
if (
|
|
96
|
+
parsed !== null &&
|
|
97
|
+
typeof parsed === "object" &&
|
|
98
|
+
"sourceType" in (parsed as object)
|
|
99
|
+
) {
|
|
100
|
+
rawSourceType = (parsed as { sourceType: unknown }).sourceType;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// Default to inline.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const sourceType = normalizeDesignSourceType(rawSourceType) ?? "inline";
|
|
108
|
+
const caps = resolveSourceCapabilities(sourceType);
|
|
109
|
+
const canResolveToFile = hasCapability(caps, "resolveNodeToFile");
|
|
110
|
+
|
|
111
|
+
// ── Fetch design file ────────────────────────────────────────────────────
|
|
112
|
+
const conditions = [
|
|
113
|
+
accessFilter(schema.designs, schema.designShares),
|
|
114
|
+
eq(schema.designFiles.designId, designId),
|
|
115
|
+
fileId
|
|
116
|
+
? eq(schema.designFiles.id, fileId)
|
|
117
|
+
: eq(schema.designFiles.filename, "index.html"),
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const [file] = await db
|
|
121
|
+
.select({
|
|
122
|
+
id: schema.designFiles.id,
|
|
123
|
+
designId: schema.designFiles.designId,
|
|
124
|
+
filename: schema.designFiles.filename,
|
|
125
|
+
content: schema.designFiles.content,
|
|
126
|
+
})
|
|
127
|
+
.from(schema.designFiles)
|
|
128
|
+
.innerJoin(
|
|
129
|
+
schema.designs,
|
|
130
|
+
eq(schema.designFiles.designId, schema.designs.id),
|
|
131
|
+
)
|
|
132
|
+
.where(and(...conditions))
|
|
133
|
+
.limit(1);
|
|
134
|
+
|
|
135
|
+
if (!file) throw new Error("Design HTML file not found.");
|
|
136
|
+
|
|
137
|
+
const html = await liveContent(file.id, file.content ?? "");
|
|
138
|
+
|
|
139
|
+
// ── Resolve node ─────────────────────────────────────────────────────────
|
|
140
|
+
const codeLayerSource: CodeLayerSource = {
|
|
141
|
+
kind: "design-file",
|
|
142
|
+
designId: file.designId,
|
|
143
|
+
fileId: file.id,
|
|
144
|
+
filename: file.filename,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const projection = buildCodeLayerProjection(html, {
|
|
148
|
+
source: codeLayerSource,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const node = projection.nodes.find((n) => n.id === nodeId);
|
|
152
|
+
if (!node) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Node "${nodeId}" not found. Run get-code-layer-projection to list current ids.`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const componentName = componentNameFor(node);
|
|
159
|
+
if (!componentName) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Node "${nodeId}" is not a component root (no data-agent-native-component attribute).`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Lookup persisted component_index for real-app source location ────────
|
|
166
|
+
const [indexRow] = await db
|
|
167
|
+
.select({
|
|
168
|
+
id: schema.componentIndex.id,
|
|
169
|
+
filePath: schema.componentIndex.filePath,
|
|
170
|
+
exportName: schema.componentIndex.exportName,
|
|
171
|
+
})
|
|
172
|
+
.from(schema.componentIndex)
|
|
173
|
+
.where(
|
|
174
|
+
and(
|
|
175
|
+
eq(schema.componentIndex.designId, designId),
|
|
176
|
+
eq(schema.componentIndex.name, componentName),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
.limit(1);
|
|
180
|
+
|
|
181
|
+
// ── Build source location ─────────────────────────────────────────────────
|
|
182
|
+
// For inline designs: the design file is the "source".
|
|
183
|
+
// For real-app designs: the external file path from component_index.
|
|
184
|
+
const isRealApp = sourceType !== "inline";
|
|
185
|
+
const externalFilePath =
|
|
186
|
+
isRealApp && canResolveToFile ? (indexRow?.filePath ?? null) : null;
|
|
187
|
+
const exportName =
|
|
188
|
+
isRealApp && canResolveToFile ? (indexRow?.exportName ?? null) : null;
|
|
189
|
+
|
|
190
|
+
const sourceLocation = {
|
|
191
|
+
/** The design file that contains this component instance. */
|
|
192
|
+
designFileId: file.id,
|
|
193
|
+
designFilename: file.filename,
|
|
194
|
+
/** Node id within the design file. */
|
|
195
|
+
nodeId,
|
|
196
|
+
/** CSS selector for the component root in the rendered HTML. */
|
|
197
|
+
selector: node.selector,
|
|
198
|
+
/** External source file for real-app components (null on inline). */
|
|
199
|
+
externalFilePath,
|
|
200
|
+
exportName,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// ── Emit navigation command ──────────────────────────────────────────────
|
|
204
|
+
// Use the same application state key as the `navigate` action so the UI
|
|
205
|
+
// client picks it up and selects the element in the editor.
|
|
206
|
+
await writeAppState("navigate", {
|
|
207
|
+
view: "editor",
|
|
208
|
+
designId,
|
|
209
|
+
editorView: "single",
|
|
210
|
+
fileId: file.id,
|
|
211
|
+
filename: file.filename,
|
|
212
|
+
selectedNodeId: nodeId,
|
|
213
|
+
inspectorTab: "design",
|
|
214
|
+
inspectorSection: "component",
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
designId,
|
|
219
|
+
nodeId,
|
|
220
|
+
componentName,
|
|
221
|
+
sourceType,
|
|
222
|
+
sourceLocation,
|
|
223
|
+
capabilities: {
|
|
224
|
+
canResolveToFile,
|
|
225
|
+
hasExternalSource: Boolean(externalFilePath),
|
|
226
|
+
ctaRequired: isRealApp && !canResolveToFile,
|
|
227
|
+
ctaMessage:
|
|
228
|
+
isRealApp && !canResolveToFile
|
|
229
|
+
? "Jump to external source file requires the resolveNodeToFile bridge capability. Connect Builder and run index-components to enable full jump-to-source."
|
|
230
|
+
: isRealApp && !externalFilePath
|
|
231
|
+
? "Component source file not yet indexed. Run index-components to populate file paths."
|
|
232
|
+
: undefined,
|
|
233
|
+
},
|
|
234
|
+
navigated: true,
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
});
|