@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,260 @@
1
+ /**
2
+ * preview-component-prop-edit — preview-only action.
3
+ *
4
+ * Computes the iframe bridge messages needed to preview a component prop
5
+ * change on the canvas without persisting anything to the database or Yjs.
6
+ *
7
+ * The caller pushes the returned `bridgeMessages` into the iframe via the
8
+ * existing `postMessage` channel (the same mechanism as `tweak-values` and
9
+ * `style-change`). No writes are performed here.
10
+ *
11
+ * Supported prop edit kinds:
12
+ * - `alpineData` — replaces the `x-data` attribute on the component root
13
+ * (Alpine variant / state switch). Returned as a
14
+ * `style-change` bridge message targeting the node selector.
15
+ * - `attribute` — sets any HTML attribute (e.g. `class`, `aria-*`,
16
+ * `data-agent-native-prop-*`).
17
+ * - `classReplace` — replaces one Tailwind utility with another on the root
18
+ * (lightweight responsive / variant preview via the
19
+ * existing `responsive-class` edit kind understood by
20
+ * the bridge).
21
+ *
22
+ * See DESIGN-STUDIO-PLAN.md §6.1 and §7 (preview vs apply contract).
23
+ */
24
+
25
+ import { defineAction } from "@agent-native/core";
26
+ import { getText, hasCollabState } from "@agent-native/core/collab";
27
+ import { accessFilter, resolveAccess } from "@agent-native/core/sharing";
28
+ import { and, eq } from "drizzle-orm";
29
+ import { z } from "zod";
30
+
31
+ import { getDb, schema } from "../server/db/index.js";
32
+ import "../server/db/index.js"; // ensure registerShareableResource runs
33
+ import { buildCodeLayerProjection } from "../shared/code-layer.js";
34
+ import type { CodeLayerSource } from "../shared/code-layer.js";
35
+ import {
36
+ componentNameFor,
37
+ componentNodeIdMatches,
38
+ extractProps,
39
+ } from "../shared/component-model.js";
40
+ import { normalizeDesignSourceType } from "../shared/source-mode.js";
41
+
42
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
43
+
44
+ async function liveContent(
45
+ fileId: string,
46
+ storedContent: string,
47
+ ): Promise<string> {
48
+ try {
49
+ if (await hasCollabState(fileId)) {
50
+ const live = await getText(fileId, "content");
51
+ if (typeof live === "string") return live;
52
+ }
53
+ } catch {
54
+ // Collab reads are best-effort; SQL content is the fallback.
55
+ }
56
+ return storedContent;
57
+ }
58
+
59
+ // ─── Bridge message shapes ────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * A bridge message the client pushes into the canvas iframe via postMessage.
63
+ * The `type` matches the existing parent→iframe message vocabulary so no new
64
+ * message types are introduced.
65
+ */
66
+ export interface ComponentPropPreviewMessage {
67
+ /** Matches the existing parent→iframe postMessage type vocabulary. */
68
+ type: "style-change" | "replace-document-content" | "select-element";
69
+ selector?: string;
70
+ nodeId?: string;
71
+ attributeOverrides?: Record<string, string>;
72
+ classEdit?: {
73
+ kind: "class";
74
+ operation: "replace";
75
+ from: string;
76
+ to: string;
77
+ };
78
+ }
79
+
80
+ // ─── Action ───────────────────────────────────────────────────────────────────
81
+
82
+ export default defineAction({
83
+ description:
84
+ "Preview a component prop edit on the canvas without persisting. " +
85
+ "Returns bridge messages the client pushes into the canvas iframe via " +
86
+ "postMessage to show the change immediately. Supports alpineData (x-data " +
87
+ "attribute replace), attribute (arbitrary HTML attribute set), and " +
88
+ "classReplace (Tailwind utility swap) edit kinds.",
89
+ schema: z.object({
90
+ designId: z.string().describe("Design project ID"),
91
+ nodeId: z
92
+ .string()
93
+ .describe("data-agent-native-node-id of the component root to preview"),
94
+ fileId: z
95
+ .string()
96
+ .optional()
97
+ .describe("Design file id; defaults to index.html"),
98
+ edit: z
99
+ .discriminatedUnion("kind", [
100
+ z.object({
101
+ kind: z.literal("alpineData"),
102
+ value: z
103
+ .string()
104
+ .describe(
105
+ "New x-data expression for the Alpine component root, " +
106
+ "e.g. \"{ variant: 'outline', disabled: false }\"",
107
+ ),
108
+ }),
109
+ z.object({
110
+ kind: z.literal("attribute"),
111
+ attribute: z.string().describe("HTML attribute name to set"),
112
+ value: z.string().describe("New attribute value"),
113
+ }),
114
+ z.object({
115
+ kind: z.literal("classReplace"),
116
+ from: z.string().describe("Existing Tailwind class token to remove"),
117
+ to: z.string().describe("Replacement Tailwind class token to add"),
118
+ }),
119
+ ])
120
+ .describe("The prop edit to preview"),
121
+ }),
122
+ readOnly: true,
123
+ http: { method: "POST" },
124
+ run: async ({ designId, nodeId, fileId, edit }) => {
125
+ const db = getDb();
126
+
127
+ // ── Access check ────────────────────────────────────────────────────────
128
+ const access = await resolveAccess("design", designId);
129
+ if (!access) throw new Error("Design not found");
130
+
131
+ // ── Fetch file ───────────────────────────────────────────────────────────
132
+ const conditions = [
133
+ accessFilter(schema.designs, schema.designShares),
134
+ eq(schema.designFiles.designId, designId),
135
+ fileId
136
+ ? eq(schema.designFiles.id, fileId)
137
+ : eq(schema.designFiles.filename, "index.html"),
138
+ ];
139
+
140
+ const [file] = await db
141
+ .select({
142
+ id: schema.designFiles.id,
143
+ designId: schema.designFiles.designId,
144
+ filename: schema.designFiles.filename,
145
+ content: schema.designFiles.content,
146
+ })
147
+ .from(schema.designFiles)
148
+ .innerJoin(
149
+ schema.designs,
150
+ eq(schema.designFiles.designId, schema.designs.id),
151
+ )
152
+ .where(and(...conditions))
153
+ .limit(1);
154
+
155
+ if (!file) throw new Error("Design HTML file not found.");
156
+
157
+ const html = await liveContent(file.id, file.content ?? "");
158
+
159
+ // ── Resolve node ─────────────────────────────────────────────────────────
160
+ const codeLayerSource: CodeLayerSource = {
161
+ kind: "design-file",
162
+ designId: file.designId,
163
+ fileId: file.id,
164
+ filename: file.filename,
165
+ };
166
+
167
+ const projection = buildCodeLayerProjection(html, {
168
+ source: codeLayerSource,
169
+ });
170
+
171
+ const node = projection.nodes.find((n) =>
172
+ componentNodeIdMatches(n, nodeId),
173
+ );
174
+ if (!node) {
175
+ throw new Error(
176
+ `Node "${nodeId}" not found. Run get-code-layer-projection to list current ids.`,
177
+ );
178
+ }
179
+
180
+ const componentName = componentNameFor(node);
181
+ if (!componentName) {
182
+ throw new Error(
183
+ `Node "${nodeId}" is not a component root (no data-agent-native-component attribute).`,
184
+ );
185
+ }
186
+
187
+ // ── Build bridge messages ────────────────────────────────────────────────
188
+ // All edit kinds map to existing parent→iframe message types so the client
189
+ // can push them without any new bridge surface.
190
+
191
+ const messages: ComponentPropPreviewMessage[] = [];
192
+
193
+ if (edit.kind === "alpineData") {
194
+ // Replace the x-data expression — communicated as a style-change with
195
+ // a special `attributeOverrides` key so the bridge can patch the DOM
196
+ // attribute without a full HTML replace.
197
+ messages.push({
198
+ type: "style-change",
199
+ selector: node.selector,
200
+ nodeId,
201
+ attributeOverrides: { "x-data": edit.value },
202
+ });
203
+ } else if (edit.kind === "attribute") {
204
+ messages.push({
205
+ type: "style-change",
206
+ selector: node.selector,
207
+ nodeId,
208
+ attributeOverrides: { [edit.attribute]: edit.value },
209
+ });
210
+ } else if (edit.kind === "classReplace") {
211
+ // Communicate as a responsive-class intent so the bridge applies it via
212
+ // the existing deterministic class-patch path.
213
+ messages.push({
214
+ type: "style-change",
215
+ selector: node.selector,
216
+ nodeId,
217
+ classEdit: {
218
+ kind: "class",
219
+ operation: "replace",
220
+ from: edit.from,
221
+ to: edit.to,
222
+ },
223
+ });
224
+ }
225
+
226
+ // Also emit a select-element message so the canvas highlights the node.
227
+ messages.push({
228
+ type: "select-element",
229
+ selector: node.selector,
230
+ nodeId,
231
+ });
232
+
233
+ const sourceType =
234
+ normalizeDesignSourceType(
235
+ (() => {
236
+ try {
237
+ const d = JSON.parse(
238
+ (access.resource as { data?: string }).data ?? "{}",
239
+ ) as Record<string, unknown>;
240
+ return d.sourceType ?? "inline";
241
+ } catch {
242
+ return "inline";
243
+ }
244
+ })(),
245
+ ) ?? "inline";
246
+
247
+ return {
248
+ designId,
249
+ nodeId,
250
+ componentName,
251
+ sourceType,
252
+ editKind: edit.kind,
253
+ /** Push each of these messages into the canvas iframe via postMessage. */
254
+ bridgeMessages: messages,
255
+ /** Snapshot of current observed props for before/after display. */
256
+ currentProps: extractProps(node),
257
+ note: "Preview only — no database writes performed. Call apply-component-prop-edit to persist.",
258
+ };
259
+ },
260
+ });
@@ -0,0 +1,113 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { resolveAccess } from "@agent-native/core/sharing";
3
+ import { z } from "zod";
4
+
5
+ import "../server/db/index.js"; // ensure registerShareableResource runs
6
+ import { resolveTweaksToCssVars } from "../shared/resolve-tweaks.js";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Token-edit patch schema
10
+ // ---------------------------------------------------------------------------
11
+
12
+ const tokenEditSchema = z.object({
13
+ /** The CSS custom property to update, e.g. "--primary-color". */
14
+ cssVar: z.string().startsWith("--").describe("CSS custom property to edit"),
15
+ /** New value string, e.g. "#3B82F6" or "0.75rem". */
16
+ value: z.string().describe("New value for the token"),
17
+ });
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Action — preview only, no DB writes
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export default defineAction({
24
+ description:
25
+ "Preview the effect of a design token edit without persisting it. " +
26
+ "Returns the full tweak-values payload (a { '--var': 'value' } map) " +
27
+ "that the client pushes into the iframe via the existing tweak-values " +
28
+ "postMessage so the user sees the change immediately before committing. " +
29
+ "No database writes are performed.",
30
+ schema: z.object({
31
+ designId: z.string().describe("Design project ID"),
32
+ edits: z
33
+ .array(tokenEditSchema)
34
+ .min(1)
35
+ .describe("One or more { cssVar, value } edits to preview"),
36
+ }),
37
+ readOnly: true,
38
+ http: { method: "POST" },
39
+ run: async ({ designId, edits }) => {
40
+ // Requires at least viewer access
41
+ const access = await resolveAccess("design", designId);
42
+ if (!access) {
43
+ throw new Error("Design not found");
44
+ }
45
+
46
+ const design = access.resource;
47
+
48
+ // Load existing tweak definitions and selections from the design's data
49
+ let designData: Record<string, unknown> = {};
50
+ try {
51
+ designData = design.data
52
+ ? (JSON.parse(design.data) as Record<string, unknown>)
53
+ : {};
54
+ } catch {
55
+ // Malformed JSON — treat as empty.
56
+ }
57
+
58
+ type TweakDef = Parameters<typeof resolveTweaksToCssVars>[0][number];
59
+ const tweaks: TweakDef[] = Array.isArray(designData.tweaks)
60
+ ? (designData.tweaks as TweakDef[])
61
+ : [];
62
+
63
+ const existingSelections =
64
+ designData.tweakSelections &&
65
+ typeof designData.tweakSelections === "object" &&
66
+ !Array.isArray(designData.tweakSelections)
67
+ ? (designData.tweakSelections as Record<
68
+ string,
69
+ string | number | boolean
70
+ >)
71
+ : {};
72
+
73
+ // Build the merged selection map with the requested edits applied.
74
+ // If the edit targets a known tweak cssVar we set by tweakId; otherwise
75
+ // we include the raw var in the payload so the client can apply it directly.
76
+ const cssVarToTweakId = new Map<string, string>();
77
+ for (const t of tweaks) {
78
+ if (t.cssVar) cssVarToTweakId.set(t.cssVar, t.id);
79
+ }
80
+
81
+ // Merged selections: existing + requested edits
82
+ const mergedSelections: Record<string, string | number | boolean> = {
83
+ ...existingSelections,
84
+ };
85
+ const directOverrides: Record<string, string> = {};
86
+ for (const { cssVar, value } of edits) {
87
+ const tweakId = cssVarToTweakId.get(cssVar);
88
+ if (tweakId) {
89
+ mergedSelections[tweakId] = value;
90
+ } else {
91
+ // Not a known tweak — include as a direct CSS var override
92
+ directOverrides[cssVar] = value;
93
+ }
94
+ }
95
+
96
+ // Resolve the full tweak set with the merged selections
97
+ const resolvedFromTweaks = resolveTweaksToCssVars(tweaks, mergedSelections);
98
+
99
+ // Merge in any direct overrides (CSS vars not backed by a tweak definition)
100
+ const tweakValues: Record<string, string> = {
101
+ ...resolvedFromTweaks,
102
+ ...directOverrides,
103
+ };
104
+
105
+ return {
106
+ designId,
107
+ /** Full `tweak-values` postMessage payload — push this into the iframe. */
108
+ tweakValues,
109
+ /** The specific edits that were previewed, for confirmation display. */
110
+ previewedEdits: edits,
111
+ };
112
+ },
113
+ });
@@ -0,0 +1,187 @@
1
+ /**
2
+ * preview-shader-fill — preview-only action.
3
+ *
4
+ * Returns the CSS mesh-gradient approximation for a shader fill so the caller
5
+ * can apply it to the selected node via the `shader-fill-preview` bridge
6
+ * message **without persisting anything**.
7
+ *
8
+ * Deliberately lightweight:
9
+ * - No DB access.
10
+ * - No Yjs / collab writes.
11
+ * - No bridge message sent by this action — the client consumes the returned
12
+ * CSS and decides how to forward it to the iframe.
13
+ *
14
+ * Composes with motion: the `speed` field on the descriptor can be keyframed
15
+ * later via `apply-motion-edit` once CSS-animation support is proven; for now
16
+ * the preview is static-CSS-only (no WebGL, no canvas).
17
+ *
18
+ * Plan reference: DESIGN-STUDIO-PLAN.md §6.7 + §7 (`preview-shader-fill`).
19
+ */
20
+
21
+ import { defineAction } from "@agent-native/core";
22
+ import { z } from "zod";
23
+
24
+ import {
25
+ generateShaderFillPreviewCss,
26
+ generateShaderFillFallbackCss,
27
+ buildShaderFillFallbackBlock,
28
+ } from "../shared/shader-fill.js";
29
+ import {
30
+ SHADER_PRESET_MAP,
31
+ type ShaderDescriptor,
32
+ type ShaderPresetName,
33
+ validateDescriptor,
34
+ } from "../shared/shader-presets.js";
35
+
36
+ // ─── Schema ──────────────────────────────────────────────────────────────────
37
+
38
+ const PRESET_NAMES = Object.keys(SHADER_PRESET_MAP) as [
39
+ ShaderPresetName,
40
+ ...ShaderPresetName[],
41
+ ];
42
+
43
+ const descriptorSchema = z.object({
44
+ preset: z
45
+ .enum(PRESET_NAMES)
46
+ .describe("Shader preset name. One of: " + PRESET_NAMES.join(", ")),
47
+ params: z
48
+ .record(z.string(), z.union([z.number(), z.boolean(), z.string()]))
49
+ .optional()
50
+ .default({})
51
+ .describe("Shader-specific params. Merge with preset defaults."),
52
+ colors: z
53
+ .array(z.string())
54
+ .optional()
55
+ .describe("Colour palette override. Falls back to preset defaults."),
56
+ speed: z
57
+ .number()
58
+ .optional()
59
+ .describe(
60
+ "Animation speed multiplier (1 = normal). Stored for future motion keyframe support; no effect on the static CSS preview.",
61
+ ),
62
+ frame: z.number().optional().describe("Static frame time (0–10000)."),
63
+ fit: z.enum(["none", "contain", "cover"]).optional(),
64
+ scale: z.number().optional(),
65
+ rotation: z.number().optional().describe("Rotation in radians."),
66
+ offsetX: z.number().optional(),
67
+ offsetY: z.number().optional(),
68
+ });
69
+
70
+ const targetSchema = z
71
+ .object({
72
+ nodeId: z.string().optional(),
73
+ selector: z.string().optional(),
74
+ })
75
+ .optional()
76
+ .describe(
77
+ "Target element. Provide nodeId or CSS selector. When omitted, the root artboard container is targeted.",
78
+ );
79
+
80
+ // ─── Action ──────────────────────────────────────────────────────────────────
81
+
82
+ export default defineAction({
83
+ description: `
84
+ Preview a CSS mesh-gradient shader fill on the selected design node without persisting anything.
85
+
86
+ Returns:
87
+ - previewCss — a CSS \`background\` value for the live preview (inject via the shader-fill-preview bridge message).
88
+ - fallbackCss — a simpler static CSS \`background\` for export / PDF / SSR contexts.
89
+ - fallbackBlock — a complete CSS rule block (selector + fallback background) ready to embed.
90
+ - descriptor — the resolved and validated ShaderDescriptor.
91
+ - bridgeMessage — a ready-to-use JSON bridge payload for the iframe shader-fill-preview message.
92
+
93
+ Call this before apply-shader-fill. The preview is purely CSS — no WebGL, no canvas, no writes.
94
+
95
+ Motion note: the \`speed\` field is accepted and stored on the descriptor for future motion-keyframe
96
+ support; it has no effect on the static CSS preview output today.
97
+ `.trim(),
98
+ schema: z.object({
99
+ descriptor: descriptorSchema,
100
+ target: targetSchema,
101
+ }),
102
+ readOnly: true,
103
+ run: async ({ descriptor: rawDescriptor, target }) => {
104
+ const descriptor: ShaderDescriptor = {
105
+ preset: rawDescriptor.preset as ShaderPresetName,
106
+ params: rawDescriptor.params ?? {},
107
+ colors: rawDescriptor.colors,
108
+ speed: rawDescriptor.speed,
109
+ frame: rawDescriptor.frame,
110
+ fit: rawDescriptor.fit,
111
+ scale: rawDescriptor.scale,
112
+ rotation: rawDescriptor.rotation,
113
+ offsetX: rawDescriptor.offsetX,
114
+ offsetY: rawDescriptor.offsetY,
115
+ };
116
+
117
+ // Validate against the preset manifest so the caller gets clear errors
118
+ // before wasting a round-trip to the iframe.
119
+ const validation = validateDescriptor(descriptor);
120
+ if (!validation.valid) {
121
+ return {
122
+ ok: false,
123
+ errors: validation.errors,
124
+ descriptor,
125
+ hint: "Fix the descriptor errors and retry. Call get-shader to see the full preset catalog.",
126
+ };
127
+ }
128
+
129
+ const previewCss = generateShaderFillPreviewCss(descriptor);
130
+ const fallbackCss = generateShaderFillFallbackCss(descriptor);
131
+
132
+ // Build a selector string for the fallback block — prefer nodeId selector.
133
+ const selector = target?.selector
134
+ ? target.selector
135
+ : target?.nodeId
136
+ ? `[data-agent-native-node-id="${target.nodeId}"]`
137
+ : ":root";
138
+
139
+ const fallbackBlock = buildShaderFillFallbackBlock(selector, descriptor);
140
+
141
+ // Build a ready-to-post bridge payload for the iframe.
142
+ // The `shader-fill-preview` message type is handled by DesignCanvas.tsx.
143
+ const bridgeMessage = {
144
+ type: "shader-fill-preview",
145
+ selector: target?.selector ?? null,
146
+ nodeId: target?.nodeId ?? null,
147
+ css: previewCss,
148
+ // Metadata so the client can attach a "preview only" badge.
149
+ _preview: true,
150
+ _source: "shader-fill",
151
+ _preset: descriptor.preset,
152
+ };
153
+
154
+ const instructions = [
155
+ `== Preview CSS (set as \`background\` on the target element) ==`,
156
+ previewCss,
157
+ ``,
158
+ `== Static Fallback CSS (for export/SSR — set as \`background\`) ==`,
159
+ fallbackCss,
160
+ ``,
161
+ `== Fallback block (embed in a <style> tag) ==`,
162
+ fallbackBlock,
163
+ ``,
164
+ `== Bridge message (post to the iframe) ==`,
165
+ JSON.stringify(bridgeMessage, null, 2),
166
+ ``,
167
+ `Apply steps:`,
168
+ `1. Inject the previewCss via a bridge "shader-fill-preview" message (no write, no persist).`,
169
+ `2. If the user approves, call apply-shader-fill — but note that action is currently GATED`,
170
+ ` and will return a clear not-yet-available result until runtime rendering +`,
171
+ ` source-write + diff proof are all in place.`,
172
+ `3. For inline/Alpine artboards, apply-shader (the existing planning action) returns`,
173
+ ` a <canvas data-shader=...> element that the design runtime mounts as WebGL.`,
174
+ ].join("\n");
175
+
176
+ return {
177
+ ok: true,
178
+ descriptor,
179
+ previewCss,
180
+ fallbackCss,
181
+ fallbackBlock,
182
+ bridgeMessage,
183
+ instructions,
184
+ note: "This is a preview-only result. Nothing was written to the database or source.",
185
+ };
186
+ },
187
+ });
@@ -0,0 +1,103 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { assertAccess } from "@agent-native/core/sharing";
3
+ import { eq } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb, schema } from "../server/db/index.js";
7
+ import "../server/db/index.js"; // ensure registerShareableResource runs
8
+ import type { BreakpointSet } from "../shared/design-state.js";
9
+
10
+ function readBreakpointSet(designData: string | null): BreakpointSet | null {
11
+ if (!designData) return null;
12
+ try {
13
+ const parsed = JSON.parse(designData) as Record<string, unknown>;
14
+ if (parsed.breakpointSet && typeof parsed.breakpointSet === "object") {
15
+ return parsed.breakpointSet as BreakpointSet;
16
+ }
17
+ } catch {
18
+ // ignore
19
+ }
20
+ return null;
21
+ }
22
+
23
+ function writeBreakpointSet(
24
+ designData: string | null,
25
+ set: BreakpointSet,
26
+ now: string,
27
+ ): string {
28
+ let prev: Record<string, unknown> = {};
29
+ if (designData) {
30
+ try {
31
+ const parsed = JSON.parse(designData);
32
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
33
+ prev = parsed;
34
+ }
35
+ } catch {
36
+ // ignore
37
+ }
38
+ }
39
+ return JSON.stringify({
40
+ ...prev,
41
+ breakpointSet: set,
42
+ breakpointSetUpdatedAt: now,
43
+ });
44
+ }
45
+
46
+ export default defineAction({
47
+ description:
48
+ "Remove a breakpoint frame from the design's breakpoint set by its id. " +
49
+ "If the active breakpoint (stored in application state) matches the removed " +
50
+ "breakpoint, the UI should reset to the first remaining breakpoint or 'auto'.",
51
+ schema: z.object({
52
+ designId: z.string().describe("Design project ID"),
53
+ breakpointId: z
54
+ .string()
55
+ .describe("Id of the BreakpointDefinition to remove."),
56
+ }),
57
+ run: async ({ designId, breakpointId }) => {
58
+ await assertAccess("design", designId, "editor");
59
+
60
+ const db = getDb();
61
+ const [design] = await db
62
+ .select({ data: schema.designs.data })
63
+ .from(schema.designs)
64
+ .where(eq(schema.designs.id, designId))
65
+ .limit(1);
66
+
67
+ if (!design) throw new Error(`Design '${designId}' not found.`);
68
+
69
+ const set = readBreakpointSet(design.data);
70
+ if (!set) {
71
+ return {
72
+ removed: false,
73
+ reason: "No breakpoint set found for this design.",
74
+ };
75
+ }
76
+
77
+ const removed = set.breakpoints.find((bp) => bp.id === breakpointId);
78
+ if (!removed) {
79
+ return {
80
+ removed: false,
81
+ reason: `Breakpoint '${breakpointId}' not found in the set.`,
82
+ };
83
+ }
84
+
85
+ const updatedSet: BreakpointSet = {
86
+ ...set,
87
+ breakpoints: set.breakpoints.filter((bp) => bp.id !== breakpointId),
88
+ };
89
+
90
+ const now = new Date().toISOString();
91
+ const updatedData = writeBreakpointSet(design.data, updatedSet, now);
92
+ await db
93
+ .update(schema.designs)
94
+ .set({ data: updatedData, updatedAt: now })
95
+ .where(eq(schema.designs.id, designId));
96
+
97
+ return {
98
+ removed: true,
99
+ removedBreakpoint: removed,
100
+ breakpointSet: updatedSet,
101
+ };
102
+ },
103
+ });