@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.
Files changed (111) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +7 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/design-connect.ts +28 -2
  5. package/corpus/core/src/cli/skills.ts +45 -24
  6. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
  7. package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
  8. package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
  9. package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
  10. package/corpus/templates/design/actions/apply-component-prop-edit.ts +433 -0
  11. package/corpus/templates/design/actions/apply-design-state.ts +200 -0
  12. package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
  13. package/corpus/templates/design/actions/apply-motion-edit.ts +450 -0
  14. package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
  15. package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
  16. package/corpus/templates/design/actions/capture-design-state.ts +224 -0
  17. package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
  18. package/corpus/templates/design/actions/create-component.ts +447 -0
  19. package/corpus/templates/design/actions/create-design-branch.ts +263 -0
  20. package/corpus/templates/design/actions/create-design-state.ts +162 -0
  21. package/corpus/templates/design/actions/delete-design-state.ts +55 -0
  22. package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
  23. package/corpus/templates/design/actions/get-component-details.ts +242 -0
  24. package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
  25. package/corpus/templates/design/actions/get-design-review.ts +314 -0
  26. package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
  27. package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
  28. package/corpus/templates/design/actions/index-components.ts +258 -0
  29. package/corpus/templates/design/actions/index-design-tokens.ts +273 -0
  30. package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
  31. package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
  32. package/corpus/templates/design/actions/list-design-states.ts +72 -0
  33. package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
  34. package/corpus/templates/design/actions/open-component-source.ts +237 -0
  35. package/corpus/templates/design/actions/preview-component-prop-edit.ts +254 -0
  36. package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
  37. package/corpus/templates/design/actions/preview-shader-fill.ts +189 -0
  38. package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
  39. package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
  40. package/corpus/templates/design/actions/run-design-audit.ts +421 -0
  41. package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
  42. package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
  43. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1414 -71
  44. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +632 -43
  45. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
  46. package/corpus/templates/design/app/components/design/EditPanel.tsx +1391 -52
  47. package/corpus/templates/design/app/components/design/LayersPanel.tsx +4 -3
  48. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
  49. package/corpus/templates/design/app/components/design/MotionDock.tsx +1062 -0
  50. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1052 -98
  51. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +759 -0
  52. package/corpus/templates/design/app/components/design/StatesPanel.tsx +579 -0
  53. package/corpus/templates/design/app/components/design/TokensPanel.tsx +573 -0
  54. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
  55. package/corpus/templates/design/app/components/design/index.ts +16 -0
  56. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
  57. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
  58. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
  59. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
  60. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  61. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
  62. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
  63. package/corpus/templates/design/app/i18n/zh-TW.ts +31 -0
  64. package/corpus/templates/design/app/i18n-data.ts +269 -0
  65. package/corpus/templates/design/app/pages/DesignEditor.tsx +2478 -386
  66. package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
  67. package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
  68. package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
  69. package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
  70. package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
  71. package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
  72. package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
  73. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
  74. package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
  75. package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
  76. package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
  77. package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
  78. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
  79. package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
  80. package/corpus/templates/design/e2e/helpers.ts +10 -4
  81. package/corpus/templates/design/server/db/schema.ts +123 -0
  82. package/corpus/templates/design/server/plugins/db.ts +90 -0
  83. package/corpus/templates/design/shared/builder-app.ts +297 -0
  84. package/corpus/templates/design/shared/capability-resolver.ts +123 -0
  85. package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
  86. package/corpus/templates/design/shared/code-layer.ts +1016 -71
  87. package/corpus/templates/design/shared/component-model.ts +239 -0
  88. package/corpus/templates/design/shared/design-review.ts +275 -0
  89. package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
  90. package/corpus/templates/design/shared/design-state.ts +112 -0
  91. package/corpus/templates/design/shared/design-surface-index.ts +258 -0
  92. package/corpus/templates/design/shared/motion-compiler.ts +278 -0
  93. package/corpus/templates/design/shared/motion-timeline.ts +193 -0
  94. package/corpus/templates/design/shared/responsive-classes.ts +452 -0
  95. package/corpus/templates/design/shared/shader-fill.ts +323 -0
  96. package/corpus/templates/design/shared/source-mode.ts +245 -0
  97. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
  98. package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
  99. package/dist/cli/design-connect.d.ts.map +1 -1
  100. package/dist/cli/design-connect.js +28 -2
  101. package/dist/cli/design-connect.js.map +1 -1
  102. package/dist/cli/skills.d.ts.map +1 -1
  103. package/dist/cli/skills.js +37 -21
  104. package/dist/cli/skills.js.map +1 -1
  105. package/dist/collab/routes.d.ts +1 -1
  106. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  107. package/dist/notifications/routes.d.ts +2 -2
  108. package/dist/observability/routes.d.ts +7 -7
  109. package/dist/resources/handlers.d.ts +2 -2
  110. package/dist/server/transcribe-voice.d.ts +1 -1
  111. package/package.json +1 -1
@@ -0,0 +1,323 @@
1
+ /**
2
+ * shader-fill.ts — pure CSS mesh-gradient "shader fill" helpers.
3
+ *
4
+ * No DB, no side effects. Deterministic output: same descriptor → same CSS.
5
+ *
6
+ * Two exports:
7
+ * - `generateShaderFillPreviewCss` — a CSS mesh-gradient that approximates the
8
+ * shader fill visually; safe to inject into the iframe via a bridge
9
+ * `tweak-values`-style message without any write commit.
10
+ * - `generateShaderFillFallbackCss` — a static CSS fallback that can be placed
11
+ * in `<style>` blocks when the WebGL runtime is absent (export, SSR, PDF).
12
+ *
13
+ * The fallback is intentionally simpler than the preview (no animation, no
14
+ * WebGL) so it stays safe at the container level. Both return the string value
15
+ * of a CSS `background` property — the caller wraps it in a rule.
16
+ */
17
+
18
+ import type { ShaderDescriptor, ShaderPresetName } from "./shader-presets.js";
19
+ import { SHADER_PRESET_MAP } from "./shader-presets.js";
20
+
21
+ // ─── CSS-injection safety ────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Neutral fallback used when a caller-supplied colour fails validation. Keeps
25
+ * the rendered gradient deterministic and visually sane without ever echoing
26
+ * untrusted text into a CSS `background` / gradient string.
27
+ */
28
+ const SAFE_FALLBACK_COLOR = "#808080";
29
+
30
+ /**
31
+ * Characters that must never appear inside a colour token or selector: they
32
+ * can terminate the current declaration/rule (`;` `}`), open a new rule (`{`),
33
+ * break out of `<style>` (`<` `>`), or pull in a remote resource (`url(`).
34
+ */
35
+ const CSS_BREAKOUT_RE = /[;{}<>]|url\(/i;
36
+
37
+ /**
38
+ * Strict allowlist for a single CSS colour token. Accepts only:
39
+ * - hex: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`
40
+ * - `rgb()` / `rgba()`
41
+ * - `hsl()` / `hsla()`
42
+ * - `oklch()`
43
+ * - bare named colours (e.g. `rebeccapurple`, `transparent`)
44
+ *
45
+ * Anything containing CSS-breakout characters, whitespace that could break out
46
+ * of the property value, or `url(` is rejected.
47
+ */
48
+ function isSafeCssColor(value: unknown): value is string {
49
+ if (typeof value !== "string") return false;
50
+ const trimmed = value.trim();
51
+ if (!trimmed) return false;
52
+ // Reject declaration/rule/style/`url(` breakout characters outright.
53
+ if (CSS_BREAKOUT_RE.test(trimmed)) return false;
54
+ return (
55
+ // Hex: 3, 4, 6, or 8 digits.
56
+ /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(trimmed) ||
57
+ // rgb() / rgba(): digits, %, commas, dots, slash, whitespace, sign only.
58
+ /^rgba?\(\s*[0-9%,.\s/+-]+\)$/.test(trimmed) ||
59
+ // hsl() / hsla(): same numeric charset plus an optional `deg` unit.
60
+ /^hsla?\(\s*[0-9%,.\s/+-]+(?:deg)?[0-9%,.\s/+-]*\)$/.test(trimmed) ||
61
+ // oklch(): digits, %, dots, slash, whitespace, sign only.
62
+ /^oklch\(\s*[0-9%.\s/+-]+\)$/.test(trimmed) ||
63
+ // Bare named colour (alphabetic only — `transparent`, `rebeccapurple`, …).
64
+ /^[a-zA-Z]+$/.test(trimmed)
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Coerce a caller-supplied colour to a safe value: returns the trimmed colour
70
+ * if it passes the strict allowlist, otherwise a neutral fallback. This is the
71
+ * single choke point every colour passes through before being interpolated
72
+ * into a CSS string.
73
+ */
74
+ function safeColor(value: string): string {
75
+ return isSafeCssColor(value) ? value.trim() : SAFE_FALLBACK_COLOR;
76
+ }
77
+
78
+ /** Sanitise a whole palette, dropping nothing but neutralising unsafe entries. */
79
+ function safePalette(colors: string[]): string[] {
80
+ return colors.map(safeColor);
81
+ }
82
+
83
+ /**
84
+ * Validate a CSS selector before it is interpolated into a `selector { … }`
85
+ * rule. Rejects anything containing declaration/rule/style/`url(` breakout
86
+ * characters; falls back to a harmless `:root` when invalid so callers never
87
+ * inject an attacker-controlled rule head.
88
+ */
89
+ function safeSelector(selector: string): string {
90
+ const trimmed = typeof selector === "string" ? selector.trim() : "";
91
+ if (!trimmed || CSS_BREAKOUT_RE.test(trimmed)) return ":root";
92
+ return trimmed;
93
+ }
94
+
95
+ // ─── Internal colour helpers ────────────────────────────────────────────────
96
+
97
+ /**
98
+ * Resolve the primary colour palette for a descriptor.
99
+ * Falls back to the preset defaults when the descriptor carries no colours.
100
+ *
101
+ * Every returned colour is run through the strict CSS-colour allowlist so the
102
+ * downstream gradient/`background` strings can never carry a CSS-injection
103
+ * payload from `descriptor.colors`.
104
+ */
105
+ function resolveColors(descriptor: ShaderDescriptor): string[] {
106
+ if (descriptor.colors && descriptor.colors.length > 0) {
107
+ return safePalette(descriptor.colors);
108
+ }
109
+ const presetDef = SHADER_PRESET_MAP[descriptor.preset];
110
+ if (presetDef?.defaultColors && presetDef.defaultColors.length > 0) {
111
+ return safePalette(presetDef.defaultColors);
112
+ }
113
+ // Last-resort neutral palette.
114
+ return ["#e0e0e0", "#a0a0c0"];
115
+ }
116
+
117
+ /**
118
+ * Resolve the first "single back" color if the preset defines one, otherwise
119
+ * fall back to the last entry in the palette.
120
+ */
121
+ function resolveBackColor(
122
+ descriptor: ShaderDescriptor,
123
+ palette: string[],
124
+ ): string {
125
+ const presetDef = SHADER_PRESET_MAP[descriptor.preset];
126
+ if (presetDef?.defaultColorBack) return safeColor(presetDef.defaultColorBack);
127
+ return palette[palette.length - 1] ?? "#000000";
128
+ }
129
+
130
+ // ─── Gradient stop generators (per-preset family) ────────────────────────────
131
+
132
+ /**
133
+ * Build a conic-gradient string that loosely mimics MeshGradient / GrainGradient
134
+ * colour placement. The stops are evenly distributed.
135
+ */
136
+ function buildConicGradient(colors: string[], rotation = 0): string {
137
+ const deg = Math.round((rotation * 180) / Math.PI);
138
+ const n = colors.length;
139
+ const stops = colors
140
+ .map((c, i) => `${c} ${Math.round((i / n) * 360)}deg`)
141
+ .join(", ");
142
+ return `conic-gradient(from ${deg}deg at 50% 50%, ${stops}, ${colors[0]} 360deg)`;
143
+ }
144
+
145
+ /**
146
+ * Build a radial-gradient with the supplied colours — good approximation of
147
+ * Voronoi / Metaballs / GodRays where colour pools radiate from the centre.
148
+ */
149
+ function buildRadialGradient(colors: string[], back: string): string {
150
+ const n = colors.length;
151
+ const stops = colors
152
+ .map((c, i) => {
153
+ const pct = Math.round(((i + 0.5) / n) * 100);
154
+ return `${c} ${pct}%`;
155
+ })
156
+ .join(", ");
157
+ return `radial-gradient(ellipse at 50% 50%, ${stops}, ${back} 100%)`;
158
+ }
159
+
160
+ /**
161
+ * Build a linear-gradient — used for Warp / Dithering / PaperTexture
162
+ * where a flat gradient is the closest safe approximation.
163
+ */
164
+ function buildLinearGradient(colors: string[], angleDeg = 135): string {
165
+ const n = colors.length;
166
+ const stops = colors
167
+ .map((c, i) => `${c} ${Math.round((i / Math.max(n - 1, 1)) * 100)}%`)
168
+ .join(", ");
169
+ return `linear-gradient(${angleDeg}deg, ${stops})`;
170
+ }
171
+
172
+ // ─── Public API ──────────────────────────────────────────────────────────────
173
+
174
+ /**
175
+ * Generate a deterministic CSS `background` value that previews the shader
176
+ * fill without any WebGL runtime.
177
+ *
178
+ * The value is a multi-stop CSS gradient that approximates the shader's colour
179
+ * scheme and general shape. It is safe to inject into the iframe canvas via a
180
+ * bridge message without persisting anything.
181
+ *
182
+ * Callers set this as the `background` property on the target element.
183
+ *
184
+ * @param descriptor - The ShaderDescriptor defining preset + params + colours.
185
+ * @returns A CSS `background` property value string.
186
+ */
187
+ export function generateShaderFillPreviewCss(
188
+ descriptor: ShaderDescriptor,
189
+ ): string {
190
+ const palette = resolveColors(descriptor);
191
+ const back = resolveBackColor(descriptor, palette);
192
+ const preset: ShaderPresetName = descriptor.preset;
193
+ const rotation = descriptor.rotation ?? 0;
194
+
195
+ switch (preset) {
196
+ case "MeshGradient":
197
+ case "GrainGradient":
198
+ // Conic gradient best captures the swirling multi-colour look.
199
+ return buildConicGradient(palette, rotation);
200
+
201
+ case "Voronoi":
202
+ case "Metaballs":
203
+ // Radial gradient captures the pooling/bubbling character.
204
+ return buildRadialGradient(palette, back);
205
+
206
+ case "GodRays": {
207
+ // Radial for the central bloom, bias toward darker back colour.
208
+ const godColors =
209
+ palette.length > 0 ? palette : ["#6200ff", "#ffffff", "#a600ff"];
210
+ return buildRadialGradient(godColors, back);
211
+ }
212
+
213
+ case "Warp":
214
+ case "Dithering":
215
+ case "PaperTexture":
216
+ default:
217
+ // Linear gradient is the safest approximation for these.
218
+ return buildLinearGradient(
219
+ palette,
220
+ Math.round((rotation * 180) / Math.PI) + 135,
221
+ );
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Generate a static CSS `background` value intended as a permanent fallback
227
+ * when the WebGL runtime is absent (export, PDF, SSR, e-mail).
228
+ *
229
+ * Deliberately simpler than the preview:
230
+ * - No animation keyframes (would be stripped anyway).
231
+ * - Single gradient stop per colour.
232
+ * - Safe at any container size.
233
+ *
234
+ * Callers set this as the `background` property in a `<style>` fallback rule
235
+ * that is overridden at runtime when the shader canvas mounts.
236
+ *
237
+ * @param descriptor - The ShaderDescriptor defining preset + params + colours.
238
+ * @returns A CSS `background` property value string, suitable for a `<style>` block.
239
+ */
240
+ export function generateShaderFillFallbackCss(
241
+ descriptor: ShaderDescriptor,
242
+ ): string {
243
+ const palette = resolveColors(descriptor);
244
+ const back = resolveBackColor(descriptor, palette);
245
+
246
+ if (palette.length === 1) {
247
+ return palette[0];
248
+ }
249
+
250
+ // For all presets: a simple linear-gradient is the most universally
251
+ // compatible fallback. 135 deg gives a diagonal that reads as "designed".
252
+ const stops = palette
253
+ .map(
254
+ (c, i) =>
255
+ `${c} ${Math.round((i / Math.max(palette.length - 1, 1)) * 100)}%`,
256
+ )
257
+ .join(", ");
258
+
259
+ // If the preset has a distinct back colour, bookend with it.
260
+ const hasDistinctBack =
261
+ SHADER_PRESET_MAP[descriptor.preset]?.defaultColorBack != null &&
262
+ !palette.includes(back);
263
+
264
+ if (hasDistinctBack) {
265
+ return `linear-gradient(135deg, ${back} 0%, ${stops}, ${back} 100%)`;
266
+ }
267
+
268
+ return `linear-gradient(135deg, ${stops})`;
269
+ }
270
+
271
+ /**
272
+ * Build the complete CSS block for injecting a shader-fill fallback on a
273
+ * selector. The block includes:
274
+ * 1. A `background` fallback rule (static gradient).
275
+ * 2. A CSS comment marking it as a shader-fill fallback so tooling can detect
276
+ * and replace it.
277
+ *
278
+ * @param selector - The CSS selector to target (e.g. `#hero`, `.card`).
279
+ * @param descriptor - The ShaderDescriptor.
280
+ * @returns A complete CSS block string, ready to embed in a `<style>` element.
281
+ */
282
+ export function buildShaderFillFallbackBlock(
283
+ selector: string,
284
+ descriptor: ShaderDescriptor,
285
+ ): string {
286
+ const bg = generateShaderFillFallbackCss(descriptor);
287
+ const safeSel = safeSelector(selector);
288
+ return [
289
+ `/* shader-fill-fallback: ${descriptor.preset} */`,
290
+ `${safeSel} {`,
291
+ ` background: ${bg};`,
292
+ `}`,
293
+ ].join("\n");
294
+ }
295
+
296
+ /**
297
+ * Resolve the CSS `background` value to persist into the design source for a
298
+ * shader fill.
299
+ *
300
+ * This is the single value `apply-shader-fill` writes onto the target element's
301
+ * inline `style.background`. It is identical to the value the live preview
302
+ * renders (`generateShaderFillPreviewCss`) so "what you previewed is what you
303
+ * get", and — like every other output of this module — every colour passes
304
+ * through the strict CSS-colour allowlist before being interpolated, so a
305
+ * `descriptor.colors` payload can never carry a CSS-injection string into the
306
+ * persisted source.
307
+ *
308
+ * Returns the gradient string plus the sanitised palette that produced it so
309
+ * callers can surface proof-of-persist (which colours were written, which were
310
+ * neutralised) without re-deriving them.
311
+ *
312
+ * @param descriptor - The ShaderDescriptor defining preset + params + colours.
313
+ * @returns `{ background, colors }` — the value to write and the safe palette.
314
+ */
315
+ export function buildShaderFillBackground(descriptor: ShaderDescriptor): {
316
+ background: string;
317
+ colors: string[];
318
+ } {
319
+ return {
320
+ background: generateShaderFillPreviewCss(descriptor),
321
+ colors: resolveColors(descriptor),
322
+ };
323
+ }
@@ -1,5 +1,92 @@
1
+ /**
2
+ * Source modes, bridge operations, and capability-aware source descriptors for
3
+ * the Design Studio.
4
+ *
5
+ * This module is the single canonical home for:
6
+ * - `DesignSourceType` — the three runtime tiers (inline | localhost | fusion).
7
+ * - `DesignBridgeOperation` — the low-level bridge RPC surface.
8
+ * - `DesignSourceDescriptor` variants — now optionally carry a proven
9
+ * `DesignSourceCapabilities` map so callers never infer write ability from
10
+ * `sourceType` alone (see §1.1 of DESIGN-STUDIO-PLAN.md).
11
+ * - `resolveDescriptorCapabilities()` — preferred helper: returns the proven
12
+ * capability set when present on the descriptor, otherwise falls back to the
13
+ * `resolveSourceCapabilities()` tier defaults from `capability-resolver.ts`.
14
+ *
15
+ * Relation to `design-source-capabilities.ts`:
16
+ * - `DesignBridgeOperation` / `DesignBridgeOperationStatus` describe the
17
+ * low-level bridge RPC surface (used in `DesignBridgeCapability` and
18
+ * `LocalhostDesignConnectionConfig.capabilities`).
19
+ * - `DesignCapabilityName` / `DesignSourceCapabilities` (defined in
20
+ * `design-source-capabilities.ts`) are the higher-level vocabulary that UI
21
+ * panels and server actions gate on. They extend
22
+ * `DesignBridgeOperationStatus` with `"unavailable"` to support the
23
+ * migration-CTA pattern.
24
+ */
25
+
26
+ // Circular-safe imports from `design-source-capabilities.ts`.
27
+ //
28
+ // `design-source-capabilities.ts` imports only via `import type` from this
29
+ // module, so the runtime module graph has NO cycle. TypeScript's type checker
30
+ // handles the bidirectional type reference correctly; `tsc --noEmit` passes.
31
+ //
32
+ // - Type-only import: used for the `capabilities?` fields on source descriptors
33
+ // and `LocalhostDesignConnectionConfig.sourceCapabilities`.
34
+ // - Value import: the canonical default maps consumed by
35
+ // `resolveDescriptorCapabilities()`.
36
+ import type { DesignSourceCapabilities } from "./design-source-capabilities";
37
+ import {
38
+ FUSION_DISCONNECTED_CAPABILITIES,
39
+ FUSION_CONNECTED_CAPABILITIES,
40
+ INLINE_DEFAULT_CAPABILITIES,
41
+ LOCALHOST_DEFAULT_CAPABILITIES,
42
+ } from "./design-source-capabilities";
43
+
1
44
  export const DESIGN_SOURCE_TYPES = ["inline", "localhost", "fusion"] as const;
2
45
 
46
+ /**
47
+ * Source-level provenance for a selected DOM element, populated from
48
+ * data attributes emitted by the connected app's build-time transform
49
+ * (e.g. @vitejs/plugin-react jsxDEV source maps or a Babel source plugin).
50
+ *
51
+ * - data-source-file / data-loc "file:line:col" → sourceFile
52
+ * - data-source-line / data-loc → line
53
+ * - data-source-column / data-loc → column
54
+ * - data-component-name → component
55
+ *
56
+ * All fields are optional because cross-origin localhost iframes cannot be
57
+ * read (same-origin policy), and inline screens may not carry these attrs.
58
+ */
59
+ export interface ElementProvenance {
60
+ sourceFile?: string;
61
+ line?: number;
62
+ column?: number;
63
+ component?: string;
64
+ }
65
+
66
+ export function parseDataLocProvenance(
67
+ dataLoc: string,
68
+ ): Pick<ElementProvenance, "sourceFile" | "line" | "column"> | null {
69
+ const lastColonIndex = dataLoc.lastIndexOf(":");
70
+ if (lastColonIndex < 0) return null;
71
+ const lastPart = dataLoc.slice(lastColonIndex + 1);
72
+ if (!/^\d+$/.test(lastPart)) return null;
73
+
74
+ const beforeLastPart = dataLoc.slice(0, lastColonIndex);
75
+ const previousColonIndex = beforeLastPart.lastIndexOf(":");
76
+ const previousPart =
77
+ previousColonIndex >= 0 ? beforeLastPart.slice(previousColonIndex + 1) : "";
78
+ const hasColumn = /^\d+$/.test(previousPart);
79
+ const sourceFile = (
80
+ hasColumn ? beforeLastPart.slice(0, previousColonIndex) : beforeLastPart
81
+ ).trim();
82
+ const line = Number(hasColumn ? previousPart : lastPart);
83
+ const column = hasColumn ? Number(lastPart) : undefined;
84
+
85
+ if (!sourceFile || !Number.isFinite(line)) return null;
86
+ if (column !== undefined && !Number.isFinite(column)) return null;
87
+ return { sourceFile, line, column };
88
+ }
89
+
3
90
  export type DesignSourceType = (typeof DESIGN_SOURCE_TYPES)[number];
4
91
 
5
92
  export const DESIGN_BRIDGE_OPERATIONS = [
@@ -22,6 +109,10 @@ export interface DesignBridgeCapability {
22
109
  reason?: string;
23
110
  }
24
111
 
112
+ // Re-export `DesignSourceCapabilities` so callers that import from
113
+ // `source-mode` continue to resolve the type without an extra import.
114
+ export type { DesignSourceCapabilities };
115
+
25
116
  export interface LocalhostDesignRoute {
26
117
  id: string;
27
118
  path: string;
@@ -49,7 +140,25 @@ export interface LocalhostDesignConnectionConfig {
49
140
  bridgeUrl?: string;
50
141
  rootPath?: string;
51
142
  routeManifest: LocalhostDesignRouteManifest;
143
+ /**
144
+ * Low-level bridge operation capabilities (legacy shape).
145
+ *
146
+ * Kept for backward compatibility with existing persistence and consumers
147
+ * (`connect-localhost`, `list-localhost-connections`). New code should read
148
+ * `sourceCapabilities` for the higher-level capability vocabulary that UI
149
+ * panels and agent actions gate on.
150
+ */
52
151
  capabilities: DesignBridgeCapability[];
152
+ /**
153
+ * High-level capability map for this connection (the preferred gate).
154
+ *
155
+ * Derived from and/or overrides `LOCALHOST_DEFAULT_CAPABILITIES`. Absent
156
+ * means the caller should fall back to the tier defaults via
157
+ * `resolveDescriptorCapabilities()`. Populated by the bridge handshake and
158
+ * persisted alongside the connection so capability checks remain correct
159
+ * across reconnects.
160
+ */
161
+ sourceCapabilities?: DesignSourceCapabilities;
53
162
  status: "connected" | "detected" | "manual" | "error";
54
163
  lastSeenAt?: string;
55
164
  createdAt?: string;
@@ -62,6 +171,14 @@ export interface InlineDesignSource {
62
171
  fileId?: string;
63
172
  filename?: string;
64
173
  revision?: string;
174
+ /**
175
+ * Optional proven capability set for this source.
176
+ *
177
+ * When present, UI panels and actions MUST read capabilities from here
178
+ * rather than inferring them from `sourceType`. Absent means "use the
179
+ * tier defaults from `resolveDescriptorCapabilities()`".
180
+ */
181
+ capabilities?: DesignSourceCapabilities;
65
182
  }
66
183
 
67
184
  export interface LocalhostDesignSource {
@@ -72,6 +189,15 @@ export interface LocalhostDesignSource {
72
189
  url?: string;
73
190
  bridgeUrl?: string;
74
191
  revision?: string;
192
+ /**
193
+ * Optional proven capability set for this source.
194
+ *
195
+ * Populated after a successful bridge handshake; overrides the conservative
196
+ * `LOCALHOST_DEFAULT_CAPABILITIES` for capabilities the bridge has verified.
197
+ * Use `resolveDescriptorCapabilities(source)` to merge defaults with proven
198
+ * overrides.
199
+ */
200
+ capabilities?: DesignSourceCapabilities;
75
201
  }
76
202
 
77
203
  export interface FusionDesignSource {
@@ -80,6 +206,35 @@ export interface FusionDesignSource {
80
206
  url?: string;
81
207
  revision?: string;
82
208
  metadata?: Record<string, unknown>;
209
+ /**
210
+ * Whether Builder credentials are configured and a branch project is set for
211
+ * this fusion source.
212
+ *
213
+ * When `true`, `resolveDescriptorCapabilities()` returns
214
+ * `FUSION_CONNECTED_CAPABILITIES` (indexComponents + branch + deployPreview +
215
+ * deploy available; source writes still planned until bridge hardening).
216
+ *
217
+ * When `false` or absent, returns `FUSION_DISCONNECTED_CAPABILITIES`
218
+ * (preview-only — no real-app write or branch operations).
219
+ *
220
+ * Set this field after verifying Builder connection status via
221
+ * `resolveIsBuilderBranchingEnabled()` from `@agent-native/core/server`.
222
+ * Callers that need the capability map without a descriptor can use
223
+ * `resolveFusionCapabilities(connected)` from `capability-resolver.ts`.
224
+ */
225
+ connected?: boolean;
226
+ /**
227
+ * Optional proven capability set for this source.
228
+ *
229
+ * When present, UI panels and actions MUST read capabilities from here
230
+ * rather than inferring them from `sourceType` or `connected`. Absent means
231
+ * "use the connection-aware tier defaults from
232
+ * `resolveDescriptorCapabilities()`".
233
+ *
234
+ * Populated once the Builder-hosted bridge has proven additional capabilities
235
+ * beyond the defaults (e.g. specific `writeFile` or `applyEdit` readiness).
236
+ */
237
+ capabilities?: DesignSourceCapabilities;
83
238
  }
84
239
 
85
240
  export type DesignSourceDescriptor =
@@ -244,3 +399,93 @@ export function titleFromRoutePath(path: string): string {
244
399
  .replace(/\b\w/g, (char) => char.toUpperCase()) || "Screen"
245
400
  );
246
401
  }
402
+
403
+ /**
404
+ * Return the effective `DesignSourceCapabilities` for a source descriptor.
405
+ *
406
+ * **Preferred over reading `sourceType` directly.** The function honours the
407
+ * three-level capability contract from DESIGN-STUDIO-PLAN.md §1.1:
408
+ *
409
+ * 1. If the descriptor already carries a proven `capabilities` map (populated
410
+ * after a bridge handshake or capability verification), return that map.
411
+ * 2. For **fusion** sources, honour the `connected` flag on the descriptor:
412
+ * - `connected === true` → `FUSION_CONNECTED_CAPABILITIES`: `indexComponents`,
413
+ * `branch`, `deployPreview`, `deploy` are **available**; source writes
414
+ * (`writeFile`, `writeTokens`, `writeMotion`) remain **planned** until
415
+ * bridge hardening.
416
+ * - `connected === false` / absent → `FUSION_DISCONNECTED_CAPABILITIES`:
417
+ * preview-only; no real-app write or branch operations.
418
+ * 3. Otherwise fall back to the conservative tier defaults
419
+ * (`INLINE_DEFAULT_CAPABILITIES` / `LOCALHOST_DEFAULT_CAPABILITIES`).
420
+ *
421
+ * Usage:
422
+ * ```ts
423
+ * import { resolveDescriptorCapabilities } from "./source-mode";
424
+ * import { hasCapability } from "./design-source-capabilities";
425
+ *
426
+ * const caps = resolveDescriptorCapabilities(source);
427
+ * if (hasCapability(caps, "branch")) { ... }
428
+ * ```
429
+ *
430
+ * To set the fusion connection state on a descriptor before resolving:
431
+ * ```ts
432
+ * import { resolveIsBuilderBranchingEnabled } from "@agent-native/core/server";
433
+ *
434
+ * const connected = await resolveIsBuilderBranchingEnabled();
435
+ * const source: FusionDesignSource = { ...existing, connected };
436
+ * const caps = resolveDescriptorCapabilities(source);
437
+ * ```
438
+ *
439
+ * To record proven capabilities discovered after a bridge handshake, spread
440
+ * the defaults and override the specific entries before storing on the
441
+ * descriptor:
442
+ * ```ts
443
+ * import { resolveFusionCapabilities, available } from "./capability-resolver";
444
+ *
445
+ * const caps = {
446
+ * ...resolveFusionCapabilities(true),
447
+ * writeFile: available("Bridge write hardening complete"),
448
+ * };
449
+ * const source: FusionDesignSource = { ...existing, capabilities: caps };
450
+ * ```
451
+ *
452
+ * Note: this module cannot import from `capability-resolver.ts` at runtime
453
+ * because `capability-resolver.ts` already imports `DesignSourceType` from
454
+ * here (circular). The helper is therefore implemented inline using the same
455
+ * canonical default maps re-imported from `design-source-capabilities.ts`.
456
+ */
457
+ export function resolveDescriptorCapabilities(
458
+ source: DesignSourceDescriptor,
459
+ ): DesignSourceCapabilities {
460
+ // If the descriptor carries a proven capability map, use it as-is.
461
+ // (A proven map overrides both sourceType and connected state.)
462
+ if (source.capabilities) return source.capabilities;
463
+
464
+ switch (source.sourceType) {
465
+ case "inline":
466
+ return INLINE_DEFAULT_CAPABILITIES;
467
+ case "localhost":
468
+ return LOCALHOST_DEFAULT_CAPABILITIES;
469
+ case "fusion":
470
+ // Honour the connection status flag on the descriptor.
471
+ //
472
+ // - `connected === true` → `FUSION_CONNECTED_CAPABILITIES`:
473
+ // indexComponents, branch, deployPreview, deploy are available;
474
+ // source writes (writeFile/writeTokens/writeMotion) remain planned.
475
+ // - `connected === false` or absent → `FUSION_DISCONNECTED_CAPABILITIES`:
476
+ // preview-only; no real-app write or branch operations.
477
+ //
478
+ // Callers should set `source.connected` after verifying Builder status
479
+ // via `resolveIsBuilderBranchingEnabled()`. When the status is unknown
480
+ // (e.g. a stale descriptor without the field), the conservative
481
+ // disconnected default is returned.
482
+ return source.connected
483
+ ? FUSION_CONNECTED_CAPABILITIES
484
+ : FUSION_DISCONNECTED_CAPABILITIES;
485
+ default: {
486
+ const _exhaustive: never = source;
487
+ void _exhaustive;
488
+ return INLINE_DEFAULT_CAPABILITIES;
489
+ }
490
+ }
491
+ }
@@ -14,6 +14,7 @@ import { IconMinus, IconPlus } from "@tabler/icons-react";
14
14
  import {
15
15
  useCallback,
16
16
  useEffect,
17
+ useLayoutEffect,
17
18
  useMemo,
18
19
  useRef,
19
20
  useState,
@@ -29,6 +30,9 @@ import { Textarea } from "@/components/ui/textarea";
29
30
 
30
31
  import { Wireframe, type DesignElementSelection } from "./wireframe/Wireframe";
31
32
 
33
+ const useIsomorphicLayoutEffect =
34
+ typeof window === "undefined" ? useEffect : useLayoutEffect;
35
+
32
36
  /* -------------------------------------------------------------------------- */
33
37
  /* Pan / zoom feel — recovered from the on-main hardcoded renderer */
34
38
  /* (server/ui-plan-html.ts UI_PLAN_JS) + claude.ai/designs design-canvas.jsx. */
@@ -313,7 +317,7 @@ export function CanvasArea({
313
317
  canvas.viewport?.pan?.x ?? ""
314
318
  }:${canvas.viewport?.pan?.y ?? ""}`;
315
319
  const lastAppliedSavedViewportKeyRef = useRef<string | null>(null);
316
- useEffect(() => {
320
+ useIsomorphicLayoutEffect(() => {
317
321
  if (!hasSavedViewport) return;
318
322
  if (lastAppliedSavedViewportKeyRef.current === savedViewportKey) return;
319
323
  lastAppliedSavedViewportKeyRef.current = savedViewportKey;
@@ -344,7 +348,7 @@ export function CanvasArea({
344
348
  boardRef.current = board;
345
349
 
346
350
  const lastAutoFitKeyRef = useRef<string | null>(null);
347
- useEffect(() => {
351
+ useIsomorphicLayoutEffect(() => {
348
352
  if (hasSavedViewport) return;
349
353
  if (lastAutoFitKeyRef.current === frameLayoutKey) return;
350
354
  const element = viewportRef.current;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-29
4
+ ---
5
+
6
+ Plan canvases open without an initial pan-and-zoom flicker while the first view settles.
@@ -1 +1 @@
1
- {"version":3,"file":"design-connect.d.ts","sourceRoot":"","sources":["../../src/cli/design-connect.ts"],"names":[],"mappings":"AAEA,OAAa,EAEX,KAAK,MAAM,EAEZ,MAAM,WAAW,CAAC;AAcnB,QAAA,MAAM,iBAAiB,YACrB,QAAQ,EACR,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,cAAc,CACN,CAAC;AAEX,KAAK,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,6BAA6B,CAAC;IACtC,UAAU,EAAE,WAAW,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,KAAK,CAAC;QAClB,SAAS,EAAE,eAAe,CAAC;QAC3B,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,CAAC;QAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,qBAAqB,CAAC;CACjC;AAyBD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAwDxE;AAiED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CA+CvE;AA6DD,wBAAsB,4BAA4B,CAChD,OAAO,GAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3D,OAAO,CAAC,qBAAqB,CAAC,CA0ChC;AAiBD,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CAwC9B;AAgBD,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,mBA0C7C"}
1
+ {"version":3,"file":"design-connect.d.ts","sourceRoot":"","sources":["../../src/cli/design-connect.ts"],"names":[],"mappings":"AAEA,OAAa,EAEX,KAAK,MAAM,EAEZ,MAAM,WAAW,CAAC;AAcnB,QAAA,MAAM,iBAAiB,YACrB,QAAQ,EACR,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,cAAc,CACN,CAAC;AAEX,KAAK,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,6BAA6B,CAAC;IACtC,UAAU,EAAE,WAAW,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,KAAK,CAAC;QAClB,SAAS,EAAE,eAAe,CAAC;QAC3B,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,CAAC;QAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,qBAAqB,CAAC;CACjC;AAyBD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAwDxE;AAiED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CA+CvE;AA6DD,wBAAsB,4BAA4B,CAChD,OAAO,GAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3D,OAAO,CAAC,qBAAqB,CAAC,CAiDhC;AAiBD,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CAwC9B;AAmCD,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,mBA0C7C"}