@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +13 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +108 -15
- package/corpus/core/src/cli/design-connect.ts +28 -2
- package/corpus/core/src/cli/skills.ts +45 -24
- package/corpus/core/src/client/AssistantChat.tsx +123 -26
- package/corpus/core/src/client/sse-event-processor.ts +9 -0
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
- package/corpus/templates/assets/actions/generate-image.ts +118 -67
- package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
- package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
- package/corpus/templates/assets/app/i18n-data.ts +61 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +145 -38
- package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +135 -11
- package/corpus/templates/assets/shared/api.ts +4 -0
- package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
- 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 +441 -0
- package/corpus/templates/design/actions/apply-design-state.ts +213 -0
- package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +413 -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/delete-design.ts +20 -0
- package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
- package/corpus/templates/design/actions/get-component-details.ts +251 -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 +277 -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 +90 -0
- package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
- package/corpus/templates/design/actions/open-component-source.ts +242 -0
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +260 -0
- package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
- package/corpus/templates/design/actions/preview-shader-fill.ts +187 -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 +436 -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 +1550 -82
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +649 -43
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1788 -52
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +114 -33
- package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1071 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1053 -99
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +766 -0
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +581 -0
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +578 -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 +32 -0
- package/corpus/templates/design/app/i18n-data.ts +280 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +3087 -490
- 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-design-token-edits-now-stay-visible-in-previews-and-token-li.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/global-setup.ts +94 -2
- 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 +274 -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 +349 -0
- package/corpus/templates/design/shared/motion-timeline.ts +193 -0
- package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
- 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/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +94 -10
- package/dist/agent/production-agent.js.map +1 -1
- 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/client/AssistantChat.d.ts +14 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -22
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/sse-event-processor.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 +8 -8
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- 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
|
+
});
|