@agent-native/core 0.80.9 → 0.80.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +7 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +28 -2
- package/corpus/core/src/cli/skills.ts +45 -24
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
- package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
- package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +433 -0
- package/corpus/templates/design/actions/apply-design-state.ts +200 -0
- package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +450 -0
- package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
- package/corpus/templates/design/actions/capture-design-state.ts +224 -0
- package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
- package/corpus/templates/design/actions/create-component.ts +447 -0
- package/corpus/templates/design/actions/create-design-branch.ts +263 -0
- package/corpus/templates/design/actions/create-design-state.ts +162 -0
- package/corpus/templates/design/actions/delete-design-state.ts +55 -0
- package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
- package/corpus/templates/design/actions/get-component-details.ts +242 -0
- package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
- package/corpus/templates/design/actions/get-design-review.ts +314 -0
- package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
- package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
- package/corpus/templates/design/actions/index-components.ts +258 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +273 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
- package/corpus/templates/design/actions/list-design-states.ts +72 -0
- package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
- package/corpus/templates/design/actions/open-component-source.ts +237 -0
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +254 -0
- package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
- package/corpus/templates/design/actions/preview-shader-fill.ts +189 -0
- package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
- package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
- package/corpus/templates/design/actions/run-design-audit.ts +421 -0
- package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
- package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1414 -71
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +632 -43
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1391 -52
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +4 -3
- package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1062 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1052 -98
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +759 -0
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +579 -0
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +573 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
- package/corpus/templates/design/app/components/design/index.ts +16 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
- package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +31 -0
- package/corpus/templates/design/app/i18n-data.ts +269 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2478 -386
- package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +10 -4
- package/corpus/templates/design/server/db/schema.ts +123 -0
- package/corpus/templates/design/server/plugins/db.ts +90 -0
- package/corpus/templates/design/shared/builder-app.ts +297 -0
- package/corpus/templates/design/shared/capability-resolver.ts +123 -0
- package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
- package/corpus/templates/design/shared/code-layer.ts +1016 -71
- package/corpus/templates/design/shared/component-model.ts +239 -0
- package/corpus/templates/design/shared/design-review.ts +275 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
- package/corpus/templates/design/shared/design-state.ts +112 -0
- package/corpus/templates/design/shared/design-surface-index.ts +258 -0
- package/corpus/templates/design/shared/motion-compiler.ts +278 -0
- package/corpus/templates/design/shared/motion-timeline.ts +193 -0
- package/corpus/templates/design/shared/responsive-classes.ts +452 -0
- package/corpus/templates/design/shared/shader-fill.ts +323 -0
- package/corpus/templates/design/shared/source-mode.ts +245 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
- package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +28 -2
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +37 -21
- package/dist/cli/skills.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for canvas-primitive visual styles.
|
|
3
|
+
*
|
|
4
|
+
* Both the board draft preview (DraftPrimitiveContent in MultiScreenCanvas.tsx)
|
|
5
|
+
* and the committed HTML renderer (appendCanvasPrimitiveToHtml in
|
|
6
|
+
* DesignEditor.tsx) must use these helpers so that what you see while drawing
|
|
7
|
+
* (preview) is pixel-identical to what gets committed — fixing the B5 color
|
|
8
|
+
* jump and the B6 ellipse border-radius jump.
|
|
9
|
+
*
|
|
10
|
+
* Design decisions:
|
|
11
|
+
* - Fill: `hsl(var(--primary) / 0.12)` — the calm, theme-adaptive tint already
|
|
12
|
+
* used by the draft preview. NOT the saturated blue previously hard-coded in
|
|
13
|
+
* appendCanvasPrimitiveToHtml (`rgba(37,99,235,0.16)`).
|
|
14
|
+
* - Stroke: `hsl(var(--primary) / 0.7)` — same CSS-var approach, adapts to
|
|
15
|
+
* light/dark mode automatically.
|
|
16
|
+
* - Stroke width: 1px for div-based shapes.
|
|
17
|
+
* - Ellipse: borderRadius "50%" in both paths — no more "oval on commit" jump.
|
|
18
|
+
* - Rect: borderRadius "2px" (small, matches the previous committed value; the
|
|
19
|
+
* preview used Tailwind `rounded-sm` which resolves to 2px).
|
|
20
|
+
* - Frame: dashed border, slightly lighter fill (matches preview semantics).
|
|
21
|
+
* - Text: inherits current color; dashed outline as placeholder affordance.
|
|
22
|
+
*
|
|
23
|
+
* CSS custom properties (`hsl(var(--primary) / …)`) work in the committed HTML
|
|
24
|
+
* because the iframe inherits the design-editor stylesheet that defines
|
|
25
|
+
* `--primary`. When a `fill` / `stroke` override is already present on the
|
|
26
|
+
* primitive those are passed through unchanged so user-chosen colors are never
|
|
27
|
+
* clobbered.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type * as React from "react";
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Types
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
export type CanvasPrimitiveKind =
|
|
37
|
+
| "rect"
|
|
38
|
+
| "rectangle"
|
|
39
|
+
| "ellipse"
|
|
40
|
+
| "text"
|
|
41
|
+
| "frame";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Canonical visual properties for a div-based canvas primitive.
|
|
45
|
+
* All values are plain CSS strings (usable verbatim in both React CSSProperties
|
|
46
|
+
* and HTML style attributes).
|
|
47
|
+
*/
|
|
48
|
+
export interface CanvasPrimitiveVisual {
|
|
49
|
+
background: string;
|
|
50
|
+
border: string;
|
|
51
|
+
borderRadius: string;
|
|
52
|
+
/** Only set for text primitives */
|
|
53
|
+
color?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Canonical tokens
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/** Default fill tint — calm, theme-adaptive, not saturated. */
|
|
61
|
+
const DEFAULT_FILL = "hsl(var(--primary) / 0.12)";
|
|
62
|
+
|
|
63
|
+
/** Default stroke — uses the same hue at higher opacity for clarity. */
|
|
64
|
+
const DEFAULT_STROKE = "hsl(var(--primary) / 0.7)";
|
|
65
|
+
|
|
66
|
+
/** Stroke width in pixels for div-based shapes. */
|
|
67
|
+
const DEFAULT_STROKE_WIDTH_PX = 1;
|
|
68
|
+
|
|
69
|
+
/** Border shorthand shared by rect + ellipse. */
|
|
70
|
+
const DEFAULT_BORDER = `${DEFAULT_STROKE_WIDTH_PX}px solid ${DEFAULT_STROKE}`;
|
|
71
|
+
|
|
72
|
+
/** Frame gets a dashed border to signal it is a layout container. */
|
|
73
|
+
const FRAME_BORDER = `${DEFAULT_STROKE_WIDTH_PX}px dashed ${DEFAULT_STROKE}`;
|
|
74
|
+
|
|
75
|
+
/** Very faint fill for frames so the interior is readable. */
|
|
76
|
+
const FRAME_FILL = "hsl(var(--primary) / 0.05)";
|
|
77
|
+
|
|
78
|
+
/** Small radius matching Tailwind `rounded-sm` (2 px). */
|
|
79
|
+
const RECT_RADIUS = "2px";
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// canvasPrimitiveVisual
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns the canonical CanvasPrimitiveVisual for the given kind.
|
|
87
|
+
*
|
|
88
|
+
* Usage (React preview):
|
|
89
|
+
* const v = canvasPrimitiveVisual("ellipse");
|
|
90
|
+
* <div style={{ background: v.background, border: v.border, borderRadius: v.borderRadius }} />
|
|
91
|
+
*
|
|
92
|
+
* Usage (committed HTML):
|
|
93
|
+
* const v = canvasPrimitiveVisual("ellipse");
|
|
94
|
+
* el.style.background = v.background;
|
|
95
|
+
* el.style.border = v.border;
|
|
96
|
+
* el.style.borderRadius = v.borderRadius;
|
|
97
|
+
*/
|
|
98
|
+
export function canvasPrimitiveVisual(
|
|
99
|
+
kind: CanvasPrimitiveKind,
|
|
100
|
+
): CanvasPrimitiveVisual {
|
|
101
|
+
switch (kind) {
|
|
102
|
+
case "ellipse":
|
|
103
|
+
return {
|
|
104
|
+
background: DEFAULT_FILL,
|
|
105
|
+
border: DEFAULT_BORDER,
|
|
106
|
+
borderRadius: "50%",
|
|
107
|
+
};
|
|
108
|
+
case "frame":
|
|
109
|
+
return {
|
|
110
|
+
background: FRAME_FILL,
|
|
111
|
+
border: FRAME_BORDER,
|
|
112
|
+
borderRadius: RECT_RADIUS,
|
|
113
|
+
};
|
|
114
|
+
case "text":
|
|
115
|
+
return {
|
|
116
|
+
background: "transparent",
|
|
117
|
+
border: `${DEFAULT_STROKE_WIDTH_PX}px dashed hsl(var(--primary) / 0.6)`,
|
|
118
|
+
borderRadius: RECT_RADIUS,
|
|
119
|
+
color: "currentColor",
|
|
120
|
+
};
|
|
121
|
+
case "rect":
|
|
122
|
+
case "rectangle":
|
|
123
|
+
default:
|
|
124
|
+
return {
|
|
125
|
+
background: DEFAULT_FILL,
|
|
126
|
+
border: DEFAULT_BORDER,
|
|
127
|
+
borderRadius: RECT_RADIUS,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// canvasPrimitiveStyleString
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Returns an inline CSS style string for the given kind, ready to assign to
|
|
138
|
+
* `element.setAttribute("style", …)` or to splice into generated HTML.
|
|
139
|
+
*
|
|
140
|
+
* Override parameters let callers pass a user-chosen fill/stroke instead of
|
|
141
|
+
* the defaults — if undefined the canonical tokens are used.
|
|
142
|
+
*
|
|
143
|
+
* Example output for "ellipse":
|
|
144
|
+
* "background:hsl(var(--primary) / 0.12);border:1px solid hsl(var(--primary) / 0.7);border-radius:50%"
|
|
145
|
+
*/
|
|
146
|
+
export function canvasPrimitiveStyleString(
|
|
147
|
+
kind: CanvasPrimitiveKind,
|
|
148
|
+
overrides?: { fill?: string; stroke?: string; strokeWidth?: number },
|
|
149
|
+
): string {
|
|
150
|
+
const v = canvasPrimitiveVisual(kind);
|
|
151
|
+
|
|
152
|
+
// Apply caller overrides so user-chosen colours are preserved.
|
|
153
|
+
let background = v.background;
|
|
154
|
+
let border = v.border;
|
|
155
|
+
|
|
156
|
+
if (overrides?.fill) {
|
|
157
|
+
background = overrides.fill;
|
|
158
|
+
}
|
|
159
|
+
if (overrides?.stroke || overrides?.strokeWidth !== undefined) {
|
|
160
|
+
const stroke = overrides.stroke ?? DEFAULT_STROKE;
|
|
161
|
+
const width = overrides.strokeWidth ?? DEFAULT_STROKE_WIDTH_PX;
|
|
162
|
+
const style = kind === "frame" || kind === "text" ? "dashed" : "solid";
|
|
163
|
+
border = `${width}px ${style} ${stroke}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const parts: string[] = [
|
|
167
|
+
`background:${background}`,
|
|
168
|
+
`border:${border}`,
|
|
169
|
+
`border-radius:${v.borderRadius}`,
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
if (v.color) {
|
|
173
|
+
parts.push(`color:${v.color}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return parts.join(";");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// canvasPrimitiveReactStyle
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Returns a React CSSProperties object for the given kind, ready to spread
|
|
185
|
+
* onto a JSX `style` prop.
|
|
186
|
+
*
|
|
187
|
+
* Override parameters preserve user-chosen fill/stroke colours.
|
|
188
|
+
*
|
|
189
|
+
* Example:
|
|
190
|
+
* <div style={canvasPrimitiveReactStyle("ellipse")} />
|
|
191
|
+
*/
|
|
192
|
+
export function canvasPrimitiveReactStyle(
|
|
193
|
+
kind: CanvasPrimitiveKind,
|
|
194
|
+
overrides?: { fill?: string; stroke?: string; strokeWidth?: number },
|
|
195
|
+
): React.CSSProperties {
|
|
196
|
+
const v = canvasPrimitiveVisual(kind);
|
|
197
|
+
|
|
198
|
+
let background = v.background as string | undefined;
|
|
199
|
+
let borderColor: string | undefined;
|
|
200
|
+
let borderWidth: number | string | undefined = DEFAULT_STROKE_WIDTH_PX;
|
|
201
|
+
let borderStyle: string | undefined = "solid";
|
|
202
|
+
|
|
203
|
+
if (kind === "frame" || kind === "text") {
|
|
204
|
+
borderStyle = "dashed";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (overrides?.fill) {
|
|
208
|
+
background = overrides.fill;
|
|
209
|
+
}
|
|
210
|
+
if (overrides?.stroke) {
|
|
211
|
+
borderColor = overrides.stroke;
|
|
212
|
+
} else {
|
|
213
|
+
// Extract from canonical border shorthand: "Npx style color"
|
|
214
|
+
const borderParts = v.border.split(" ");
|
|
215
|
+
// e.g. ["1px", "solid", "hsl(...)"] — colour may have spaces inside parens
|
|
216
|
+
borderColor = borderParts.slice(2).join(" ");
|
|
217
|
+
}
|
|
218
|
+
if (overrides?.strokeWidth !== undefined) {
|
|
219
|
+
borderWidth = overrides.strokeWidth;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const style: React.CSSProperties = {
|
|
223
|
+
background,
|
|
224
|
+
border: undefined,
|
|
225
|
+
borderColor,
|
|
226
|
+
borderWidth,
|
|
227
|
+
borderStyle,
|
|
228
|
+
borderRadius: v.borderRadius,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if (v.color) {
|
|
232
|
+
style.color = v.color;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// For text kind, clear fill-as-background since text uses `color`
|
|
236
|
+
if (kind === "text") {
|
|
237
|
+
style.background = "transparent";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return style;
|
|
241
|
+
}
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
export { DesignCanvas } from "./DesignCanvas";
|
|
2
|
+
export type {
|
|
3
|
+
MotionTrackWire,
|
|
4
|
+
IframeHotkeyPayload,
|
|
5
|
+
IframeContextMenuPayload,
|
|
6
|
+
} from "./DesignCanvas";
|
|
2
7
|
export { DeviceFrame } from "./DeviceFrame";
|
|
3
8
|
export { DrawOverlay } from "./DrawOverlay";
|
|
4
9
|
export { EditPanel } from "./EditPanel";
|
|
10
|
+
export type { InspectorTab } from "./EditPanel";
|
|
11
|
+
export {
|
|
12
|
+
MotionDock,
|
|
13
|
+
type MotionDockProps,
|
|
14
|
+
type MotionDockTrack,
|
|
15
|
+
} from "./MotionDock";
|
|
5
16
|
export { QuestionFlow } from "./QuestionFlow";
|
|
17
|
+
export { ReviewPanel } from "./ReviewPanel";
|
|
18
|
+
export type { ReviewPanelProps, ReviewVersionOption } from "./ReviewPanel";
|
|
19
|
+
export { StatesPanel } from "./StatesPanel";
|
|
20
|
+
export type { StatesPanelProps } from "./StatesPanel";
|
|
21
|
+
export { TokensPanel, type TokensPanelProps } from "./TokensPanel";
|
|
6
22
|
export { TweaksPanel } from "./TweaksPanel";
|
|
7
23
|
export type {
|
|
8
24
|
DrawAnnotation,
|
|
@@ -147,6 +147,7 @@ export interface AutoLayoutMatrixProps {
|
|
|
147
147
|
onPaddingLinkedChange: (linked: boolean) => void;
|
|
148
148
|
onClipContentChange?: (clipContent: boolean) => void;
|
|
149
149
|
onDistribute?: (axis: DistributionAxis) => void;
|
|
150
|
+
onGapModeChange?: (mode: "fixed" | "auto", axis: DistributionAxis) => void;
|
|
150
151
|
onChildSizingChange: (
|
|
151
152
|
axis: AutoLayoutSizingAxis,
|
|
152
153
|
sizing: AutoLayoutSizing,
|
|
@@ -246,6 +247,7 @@ export function AutoLayoutMatrix({
|
|
|
246
247
|
onPaddingLinkedChange,
|
|
247
248
|
onClipContentChange,
|
|
248
249
|
onDistribute,
|
|
250
|
+
onGapModeChange,
|
|
249
251
|
onChildSizingChange,
|
|
250
252
|
onChildMinMaxChange,
|
|
251
253
|
onApplyVariable,
|
|
@@ -455,6 +457,7 @@ export function AutoLayoutMatrix({
|
|
|
455
457
|
value={value.gap}
|
|
456
458
|
onGapChange={onGapChange}
|
|
457
459
|
onDistribute={onDistribute}
|
|
460
|
+
onGapModeChange={onGapModeChange}
|
|
458
461
|
label={copy.gap}
|
|
459
462
|
disabled={disabled}
|
|
460
463
|
direction={value.direction}
|
|
@@ -858,6 +861,7 @@ function GapField({
|
|
|
858
861
|
value,
|
|
859
862
|
onGapChange,
|
|
860
863
|
onDistribute,
|
|
864
|
+
onGapModeChange,
|
|
861
865
|
label,
|
|
862
866
|
disabled,
|
|
863
867
|
direction,
|
|
@@ -866,6 +870,7 @@ function GapField({
|
|
|
866
870
|
value: number;
|
|
867
871
|
onGapChange: (gap: number) => void;
|
|
868
872
|
onDistribute?: (axis: DistributionAxis) => void;
|
|
873
|
+
onGapModeChange?: (mode: "fixed" | "auto", axis: DistributionAxis) => void;
|
|
869
874
|
label: string;
|
|
870
875
|
disabled: boolean;
|
|
871
876
|
direction: AutoLayoutDirection;
|
|
@@ -928,16 +933,20 @@ function GapField({
|
|
|
928
933
|
<DropdownMenuCheckboxItem
|
|
929
934
|
checked={gapMode !== "auto"}
|
|
930
935
|
className="text-[12px]"
|
|
931
|
-
onSelect={() =>
|
|
932
|
-
/* Fixed gap: keep current numeric value (already numeric). */
|
|
933
|
-
}}
|
|
936
|
+
onSelect={() => onGapModeChange?.("fixed", direction)}
|
|
934
937
|
>
|
|
935
938
|
{"Fixed" /* i18n-ignore design gap mode label */}
|
|
936
939
|
</DropdownMenuCheckboxItem>
|
|
937
940
|
<DropdownMenuCheckboxItem
|
|
938
941
|
checked={gapMode === "auto"}
|
|
939
942
|
className="text-[12px]"
|
|
940
|
-
onSelect={() =>
|
|
943
|
+
onSelect={() => {
|
|
944
|
+
if (onGapModeChange) {
|
|
945
|
+
onGapModeChange("auto", direction);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
onDistribute?.(direction);
|
|
949
|
+
}}
|
|
941
950
|
>
|
|
942
951
|
{"Auto" /* i18n-ignore design gap mode label */}
|
|
943
952
|
</DropdownMenuCheckboxItem>
|
|
@@ -1227,9 +1236,9 @@ export function SizingField({
|
|
|
1227
1236
|
tooltipLabel={`${axis} size`}
|
|
1228
1237
|
icon={null}
|
|
1229
1238
|
value={Math.round(resolvedSize ?? 0)}
|
|
1230
|
-
onChange={(next) => onSizeChange!(Math.max(
|
|
1239
|
+
onChange={(next) => onSizeChange!(Math.max(0, Math.round(next)))}
|
|
1231
1240
|
unit="px"
|
|
1232
|
-
min={
|
|
1241
|
+
min={0}
|
|
1233
1242
|
step={1}
|
|
1234
1243
|
precision={0}
|
|
1235
1244
|
disabled={disabled}
|
|
@@ -28,6 +28,13 @@ import {
|
|
|
28
28
|
PopoverContent,
|
|
29
29
|
PopoverTrigger,
|
|
30
30
|
} from "@/components/ui/popover";
|
|
31
|
+
import {
|
|
32
|
+
Select,
|
|
33
|
+
SelectContent,
|
|
34
|
+
SelectItem,
|
|
35
|
+
SelectTrigger,
|
|
36
|
+
SelectValue,
|
|
37
|
+
} from "@/components/ui/select";
|
|
31
38
|
import {
|
|
32
39
|
Tooltip,
|
|
33
40
|
TooltipContent,
|
|
@@ -651,6 +658,24 @@ const NOISE_FALLBACK_CSS =
|
|
|
651
658
|
"repeating-conic-gradient(#0000 0% 25%, #00000010 0% 50%) 0 0 / 6px 6px, #8a8a8a";
|
|
652
659
|
const PATTERN_FALLBACK_CSS =
|
|
653
660
|
"repeating-linear-gradient(45deg, #00000014 0 6px, #ffffff14 6px 12px), #9aa0a6";
|
|
661
|
+
const BLEND_MODE_OPTIONS = [
|
|
662
|
+
{ value: "normal", label: "Normal" },
|
|
663
|
+
{ value: "multiply", label: "Multiply" },
|
|
664
|
+
{ value: "screen", label: "Screen" },
|
|
665
|
+
{ value: "overlay", label: "Overlay" },
|
|
666
|
+
{ value: "darken", label: "Darken" },
|
|
667
|
+
{ value: "lighten", label: "Lighten" },
|
|
668
|
+
{ value: "color-dodge", label: "Color dodge" }, // i18n-ignore design blend mode label
|
|
669
|
+
{ value: "color-burn", label: "Color burn" }, // i18n-ignore design blend mode label
|
|
670
|
+
{ value: "hard-light", label: "Hard light" }, // i18n-ignore design blend mode label
|
|
671
|
+
{ value: "soft-light", label: "Soft light" }, // i18n-ignore design blend mode label
|
|
672
|
+
{ value: "difference", label: "Difference" },
|
|
673
|
+
{ value: "exclusion", label: "Exclusion" },
|
|
674
|
+
{ value: "hue", label: "Hue" },
|
|
675
|
+
{ value: "saturation", label: "Saturation" },
|
|
676
|
+
{ value: "color", label: "Color" },
|
|
677
|
+
{ value: "luminosity", label: "Luminosity" },
|
|
678
|
+
] as const;
|
|
654
679
|
|
|
655
680
|
// ─── Main component ────────────────────────────────────────────────────────────
|
|
656
681
|
|
|
@@ -662,9 +687,9 @@ export function DesignColorPicker({
|
|
|
662
687
|
label: _label,
|
|
663
688
|
opacity,
|
|
664
689
|
onOpacityChange,
|
|
665
|
-
blendMode
|
|
666
|
-
onBlendModeChange
|
|
667
|
-
showBlendMode
|
|
690
|
+
blendMode,
|
|
691
|
+
onBlendModeChange,
|
|
692
|
+
showBlendMode = false,
|
|
668
693
|
fillRows: _fillRows,
|
|
669
694
|
selectedFillId: _selectedFillId,
|
|
670
695
|
onFillSelect: _onFillSelect,
|
|
@@ -693,6 +718,11 @@ export function DesignColorPicker({
|
|
|
693
718
|
const hsv = rgbaToHsv(color);
|
|
694
719
|
const hsl = rgbaToHsl(color);
|
|
695
720
|
const effectiveOpacity = opacity ?? alphaToOpacity(color.a);
|
|
721
|
+
const blendModeValue = BLEND_MODE_OPTIONS.some(
|
|
722
|
+
(option) => option.value === blendMode,
|
|
723
|
+
)
|
|
724
|
+
? blendMode
|
|
725
|
+
: "normal";
|
|
696
726
|
|
|
697
727
|
const [mode, setMode] = useState<DesignColorMode>("hex");
|
|
698
728
|
const [hexDraft, setHexDraft] = useState(() => toDisplayHex(color));
|
|
@@ -1506,6 +1536,39 @@ export function DesignColorPicker({
|
|
|
1506
1536
|
</div>
|
|
1507
1537
|
)}
|
|
1508
1538
|
|
|
1539
|
+
{showBlendMode && onBlendModeChange && (
|
|
1540
|
+
<div className="border-t border-border/70 px-3 py-2.5">
|
|
1541
|
+
<div className="flex items-center gap-1.5">
|
|
1542
|
+
<span className="min-w-0 flex-1 text-[11px] text-muted-foreground">
|
|
1543
|
+
{copy.blendMode}
|
|
1544
|
+
</span>
|
|
1545
|
+
<Select
|
|
1546
|
+
value={blendModeValue}
|
|
1547
|
+
disabled={disabled}
|
|
1548
|
+
onValueChange={onBlendModeChange}
|
|
1549
|
+
>
|
|
1550
|
+
<SelectTrigger
|
|
1551
|
+
aria-label={copy.blendMode}
|
|
1552
|
+
className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]"
|
|
1553
|
+
>
|
|
1554
|
+
<SelectValue />
|
|
1555
|
+
</SelectTrigger>
|
|
1556
|
+
<SelectContent>
|
|
1557
|
+
{BLEND_MODE_OPTIONS.map((option) => (
|
|
1558
|
+
<SelectItem
|
|
1559
|
+
key={option.value}
|
|
1560
|
+
value={option.value}
|
|
1561
|
+
className="text-[11px]"
|
|
1562
|
+
>
|
|
1563
|
+
{option.label}
|
|
1564
|
+
</SelectItem>
|
|
1565
|
+
))}
|
|
1566
|
+
</SelectContent>
|
|
1567
|
+
</Select>
|
|
1568
|
+
</div>
|
|
1569
|
+
</div>
|
|
1570
|
+
)}
|
|
1571
|
+
|
|
1509
1572
|
{/* ── Document colors ──────────────────────────────────────── */}
|
|
1510
1573
|
{/* Renders the palette of colors already used in the design.
|
|
1511
1574
|
When `documentColors` is provided, those swatches are shown;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client";
|
|
2
|
+
import {
|
|
3
|
+
IconClipboard,
|
|
4
|
+
IconChevronDown,
|
|
5
|
+
IconSparkles,
|
|
6
|
+
} from "@tabler/icons-react";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
|
|
9
|
+
import { Button } from "@/components/ui/button";
|
|
10
|
+
import {
|
|
11
|
+
Collapsible,
|
|
12
|
+
CollapsibleContent,
|
|
13
|
+
CollapsibleTrigger,
|
|
14
|
+
} from "@/components/ui/collapsible";
|
|
15
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
16
|
+
import { useAgentEditRequest } from "@/hooks/useAgentEditRequest";
|
|
17
|
+
import { cn } from "@/lib/utils";
|
|
18
|
+
|
|
19
|
+
export interface InspectorAiActionsProps {
|
|
20
|
+
/** CSS selector for the currently-selected element, if any. */
|
|
21
|
+
selector?: string;
|
|
22
|
+
/** Source id (data-code-layer-id) of the selected element, if any. */
|
|
23
|
+
sourceId?: string;
|
|
24
|
+
/** Active file id. */
|
|
25
|
+
fileId?: string;
|
|
26
|
+
/** Active file name (e.g. "index.html"). */
|
|
27
|
+
filename?: string;
|
|
28
|
+
/** The design id. */
|
|
29
|
+
designId?: string;
|
|
30
|
+
/** When false, the controls are disabled. */
|
|
31
|
+
canEdit: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compact inspector block that lets the user type a freeform AI edit request
|
|
36
|
+
* and either route it to the agent chat sidebar ("Apply with AI") or copy the
|
|
37
|
+
* prompt to the clipboard ("Copy prompt").
|
|
38
|
+
*
|
|
39
|
+
* Collapsed by default to keep the inspector clean.
|
|
40
|
+
*/
|
|
41
|
+
export function InspectorAiActions({
|
|
42
|
+
selector,
|
|
43
|
+
sourceId,
|
|
44
|
+
fileId,
|
|
45
|
+
filename,
|
|
46
|
+
designId,
|
|
47
|
+
canEdit,
|
|
48
|
+
}: InspectorAiActionsProps) {
|
|
49
|
+
const t = useT();
|
|
50
|
+
const { sendEdit, copyPrompt } = useAgentEditRequest();
|
|
51
|
+
const [open, setOpen] = useState(false);
|
|
52
|
+
const [request, setRequest] = useState("");
|
|
53
|
+
|
|
54
|
+
const args = {
|
|
55
|
+
message: request,
|
|
56
|
+
selector,
|
|
57
|
+
sourceId,
|
|
58
|
+
fileId,
|
|
59
|
+
filename,
|
|
60
|
+
designId,
|
|
61
|
+
};
|
|
62
|
+
const disabled = !canEdit || !request.trim();
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Collapsible open={open} onOpenChange={setOpen} className="w-full">
|
|
66
|
+
<CollapsibleTrigger asChild>
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
className={cn(
|
|
70
|
+
"flex w-full items-center justify-between gap-1 rounded px-2 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground transition-colors",
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
<span className="flex items-center gap-1.5">
|
|
74
|
+
<IconSparkles className="size-3.5 shrink-0" />
|
|
75
|
+
{t("designEditor.localSourceEdit.askAi")}
|
|
76
|
+
</span>
|
|
77
|
+
<IconChevronDown
|
|
78
|
+
className={cn(
|
|
79
|
+
"size-3.5 shrink-0 transition-transform duration-150",
|
|
80
|
+
open && "rotate-180",
|
|
81
|
+
)}
|
|
82
|
+
/>
|
|
83
|
+
</button>
|
|
84
|
+
</CollapsibleTrigger>
|
|
85
|
+
<CollapsibleContent className="overflow-hidden data-[state=closed]:animate-none">
|
|
86
|
+
<div className="flex flex-col gap-2 px-1 pb-2 pt-1">
|
|
87
|
+
<Textarea
|
|
88
|
+
value={request}
|
|
89
|
+
onChange={(e) => setRequest(e.target.value)}
|
|
90
|
+
placeholder={
|
|
91
|
+
selector
|
|
92
|
+
? t("designEditor.localSourceEdit.describeElementChange")
|
|
93
|
+
: t("designEditor.localSourceEdit.describeChange")
|
|
94
|
+
}
|
|
95
|
+
className="min-h-[64px] resize-none text-xs"
|
|
96
|
+
disabled={!canEdit}
|
|
97
|
+
onKeyDown={(e) => {
|
|
98
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !disabled) {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
void sendEdit(args);
|
|
101
|
+
setRequest("");
|
|
102
|
+
setOpen(false);
|
|
103
|
+
}
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
<div className="flex gap-1.5">
|
|
107
|
+
<Button
|
|
108
|
+
size="sm"
|
|
109
|
+
variant="default"
|
|
110
|
+
className="flex-1 gap-1.5 text-xs"
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
onClick={() => {
|
|
113
|
+
void sendEdit(args);
|
|
114
|
+
setRequest("");
|
|
115
|
+
setOpen(false);
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
<IconSparkles className="size-3.5 shrink-0" />
|
|
119
|
+
{t("designEditor.localSourceEdit.applyWithAi")}
|
|
120
|
+
</Button>
|
|
121
|
+
<Button
|
|
122
|
+
size="sm"
|
|
123
|
+
variant="outline"
|
|
124
|
+
className="gap-1.5 text-xs"
|
|
125
|
+
disabled={disabled}
|
|
126
|
+
onClick={() => {
|
|
127
|
+
void copyPrompt(args);
|
|
128
|
+
}}
|
|
129
|
+
title={t("designEditor.localSourceEdit.copyPromptTooltip")}
|
|
130
|
+
>
|
|
131
|
+
<IconClipboard className="size-3.5 shrink-0" />
|
|
132
|
+
{t("designEditor.localSourceEdit.copyPrompt")}
|
|
133
|
+
</Button>
|
|
134
|
+
</div>
|
|
135
|
+
{selector && (
|
|
136
|
+
<p className="text-[10px] leading-tight text-muted-foreground truncate">
|
|
137
|
+
{t("designEditor.localSourceEdit.targeting")}{" "}
|
|
138
|
+
<code className="font-mono">{selector}</code>
|
|
139
|
+
</p>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
</CollapsibleContent>
|
|
143
|
+
</Collapsible>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -6,6 +6,30 @@ right spot plus copy-pasteable code to insert or replace.
|
|
|
6
6
|
|
|
7
7
|
Do NOT rely on line numbers — they drift. Use the search strings instead.
|
|
8
8
|
|
|
9
|
+
## Status: shader fills are user-reachable and applyable
|
|
10
|
+
|
|
11
|
+
The **Shader Fills** card in `DesignExtensionsPanel.tsx` (inspector slot
|
|
12
|
+
`design.editor.inspector`) is the live entry point: open it, **Browse Shaders**,
|
|
13
|
+
pick a preset, tune it with `ShaderControls`, and the iframe previews the fill as
|
|
14
|
+
a CSS gradient (`preview-shader-fill`). With an element selected on the canvas, the
|
|
15
|
+
card's **Apply fill** button persists that fill via the `apply-shader-fill` action.
|
|
16
|
+
|
|
17
|
+
`apply-shader-fill` is **no longer gated** — it is SAFETY-gated, not Builder-gated:
|
|
18
|
+
|
|
19
|
+
- It asserts **editor** access on the target design (`accessFilter` read +
|
|
20
|
+
`assertAccess("design", id, "editor")`) before any write.
|
|
21
|
+
- It only persists **HTML design-file** sources. localhost / fusion / inline-html
|
|
22
|
+
sources return the preview value and write nothing.
|
|
23
|
+
- It writes a single inline-style `background` onto the target node through the
|
|
24
|
+
deterministic HTML editor (`applyVisualEdit`, the same persist path as
|
|
25
|
+
`apply-visual-edit`), reusing the strict CSS-colour allowlist from
|
|
26
|
+
`shared/shader-fill.ts` so `descriptor.colors` can never inject CSS.
|
|
27
|
+
|
|
28
|
+
Sections (a), (b), (d), and (f) below remain optional ways to surface shaders in
|
|
29
|
+
_other_ inspector seams (the colour picker fill type, the effects panel, the
|
|
30
|
+
artboard WebGL bridge, design-system tokens) and are not required for the
|
|
31
|
+
card-based apply path that ships today.
|
|
32
|
+
|
|
9
33
|
---
|
|
10
34
|
|
|
11
35
|
## (a) DesignColorPicker.tsx — Add `"shader"` fill type
|
|
@@ -351,20 +375,17 @@ if (fill.type === "shader") {
|
|
|
351
375
|
|
|
352
376
|
---
|
|
353
377
|
|
|
354
|
-
## (e) Changelog entry
|
|
355
|
-
|
|
356
|
-
The shader feature is not yet user-reachable — it still needs all seam files wired.
|
|
357
|
-
Do NOT add a changelog entry yet.
|
|
378
|
+
## (e) Changelog entry
|
|
358
379
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
panel), run:
|
|
380
|
+
The shader-fills card is now user-reachable and the apply path persists, so a
|
|
381
|
+
user-facing changelog entry is warranted. From the `templates/design/` directory:
|
|
362
382
|
|
|
363
383
|
```bash
|
|
364
|
-
agent-native changelog add "Add GPU shader fills
|
|
384
|
+
agent-native changelog add "Add GPU shader fills — 8 presets (MeshGradient, GrainGradient, Voronoi, Metaballs, Warp, GodRays, Dithering, PaperTexture) with live preview and one-click apply to the selected element" --type added
|
|
365
385
|
```
|
|
366
386
|
|
|
367
|
-
from the
|
|
387
|
+
(The effects-panel surface from section (b) is still optional; scope the changelog
|
|
388
|
+
line to what actually ships.)
|
|
368
389
|
|
|
369
390
|
---
|
|
370
391
|
|
|
@@ -407,14 +428,23 @@ shader token picker.
|
|
|
407
428
|
|
|
408
429
|
## Integration Checklist
|
|
409
430
|
|
|
431
|
+
Shipped (card-based preview + apply path):
|
|
432
|
+
|
|
433
|
+
- [x] `DesignExtensionsPanel.tsx` — Shader Fills card opens `ShaderFillsPanel`
|
|
434
|
+
- [x] `DesignExtensionsPanel.tsx` — "Apply fill" persists via `apply-shader-fill`
|
|
435
|
+
- [x] `actions/apply-shader-fill.ts` — persists fill as CSS background (editor-gated)
|
|
436
|
+
- [x] `actions/preview-shader-fill.ts` — preview-only CSS (unchanged)
|
|
437
|
+
- [x] `actions/apply-shader.ts` exists (planning/codegen snippet)
|
|
438
|
+
- [x] `actions/get-shader.ts` exists
|
|
439
|
+
|
|
440
|
+
Optional follow-ups (other inspector seams):
|
|
441
|
+
|
|
410
442
|
- [ ] `DesignColorPicker.tsx` — `DesignFillType` includes `"shader"`
|
|
411
443
|
- [ ] `DesignColorPicker.tsx` — `DesignPaintType` includes `"shader"`
|
|
412
444
|
- [ ] `DesignColorPicker.tsx` — shader button renders in paint-type row
|
|
413
445
|
- [ ] `DesignColorPicker.tsx` — `ShaderControls` renders when `paintType === "shader"`
|
|
414
446
|
- [ ] `EditPanel.tsx` — `ShaderEffectRow` renders in `EffectsProperties`
|
|
415
447
|
- [ ] `EditPanel.tsx` — "Add Shader" option in effects dropdown
|
|
416
|
-
- [ ] `actions/apply-shader.ts` exists
|
|
417
|
-
- [ ] `actions/get-shader.ts` exists
|
|
418
448
|
- [ ] `DesignCanvas.tsx` — shader bridge script injected into artboard
|
|
419
449
|
- [ ] `DesignCanvas.tsx` — `data-shader` attribute serialized for shader fills
|
|
420
|
-
- [ ] Changelog entry added (
|
|
450
|
+
- [ ] Changelog entry added (see section (e))
|
|
@@ -453,7 +453,7 @@ export function DrawOverlay({
|
|
|
453
453
|
const toolbar = (
|
|
454
454
|
<div
|
|
455
455
|
data-draw-toolbar
|
|
456
|
-
className="pointer-events-auto fixed bottom-
|
|
456
|
+
className="pointer-events-auto fixed bottom-20 left-1/2 z-[110] flex max-w-[calc(100vw-1rem)] -translate-x-1/2 flex-wrap items-center justify-center gap-2 rounded-xl border border-border bg-popover px-3 py-2 shadow-2xl"
|
|
457
457
|
>
|
|
458
458
|
{/* Color picker */}
|
|
459
459
|
<div className="flex gap-1">
|