@agent-native/core 0.84.25 → 0.84.28
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 +18 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +4 -2
- package/corpus/core/src/agent/run-manager.ts +4 -3
- package/corpus/core/src/agent/types.ts +9 -2
- package/corpus/core/src/client/AssistantChat.tsx +15 -1
- package/corpus/core/src/client/FeedbackButton.tsx +2 -2
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +17 -2
- package/corpus/core/src/client/agent-chat.ts +118 -0
- package/corpus/core/src/client/components/PresenceBar.tsx +7 -2
- package/corpus/core/src/client/composer/ComposerPlusMenu.tsx +23 -9
- package/corpus/core/src/client/index.ts +4 -0
- package/corpus/core/src/client/sharing/ShareButton.tsx +22 -3
- package/corpus/core/src/client/sharing/ShareDialog.tsx +13 -1
- package/corpus/core/src/client/sse-event-processor.ts +49 -20
- package/corpus/core/src/sharing/actions/set-resource-visibility.ts +3 -3
- package/corpus/templates/design/actions/insert-asset.ts +14 -2
- package/corpus/templates/design/actions/insert-design-native-asset.ts +16 -4
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +14 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +14 -0
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +54 -10
- package/corpus/templates/design/app/components/design/EditPanel.tsx +23 -16
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -2
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +9 -5
- package/corpus/templates/design/app/components/layout/Header.tsx +38 -2
- package/corpus/templates/design/app/pages/DesignEditor.tsx +281 -105
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +15 -1
- package/corpus/templates/design/app/pages/Index.tsx +20 -9
- package/corpus/templates/design/app/routes/settings.tsx +1 -1
- package/corpus/templates/design/changelog/2026-07-01-design-editor-polish-makes-zoom-assets-sharing-and-agent-handoffs-clearer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-qa-fixes-make-exports-permissions-and-tools-clearer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-zoom-menus-in-the-design-editor-now-open-independently-from-.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +4 -4
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/types.d.ts +3 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +16 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/FeedbackButton.js +2 -2
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +14 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +23 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +79 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/components/PresenceBar.d.ts.map +1 -1
- package/dist/client/components/PresenceBar.js +6 -2
- package/dist/client/components/PresenceBar.js.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.js +15 -7
- package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +15 -2
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.js +16 -1
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +42 -16
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/actions/set-resource-visibility.js +3 -3
- package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
- package/package.json +1 -1
|
@@ -150,8 +150,8 @@ export const SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS = 90_000;
|
|
|
150
150
|
|
|
151
151
|
type ActivityTrailEntry = AgentActivityTrailEntry;
|
|
152
152
|
|
|
153
|
-
type
|
|
154
|
-
tool
|
|
153
|
+
type PreparingActionEntry = {
|
|
154
|
+
tool: string;
|
|
155
155
|
startedAt?: number;
|
|
156
156
|
lastProgressBytes?: number;
|
|
157
157
|
/**
|
|
@@ -166,6 +166,10 @@ type PreparingActionState = {
|
|
|
166
166
|
lastProgressAt?: number;
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
+
type PreparingActionState = {
|
|
170
|
+
entries?: Map<string, PreparingActionEntry>;
|
|
171
|
+
};
|
|
172
|
+
|
|
169
173
|
function formatProgressBytes(bytes: number): string {
|
|
170
174
|
if (bytes < 1024) return `${bytes} B`;
|
|
171
175
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -270,21 +274,35 @@ function updatePreparingActionState(
|
|
|
270
274
|
if (ev.type === "activity" && isPreparingActionActivity(ev)) {
|
|
271
275
|
const tool = ev.tool?.trim() || undefined;
|
|
272
276
|
if (!tool) return false;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
const key = ev.id?.trim() || tool;
|
|
278
|
+
const entries = state.entries ?? new Map<string, PreparingActionEntry>();
|
|
279
|
+
state.entries = entries;
|
|
280
|
+
let entry = entries.get(key);
|
|
281
|
+
if (!entry) {
|
|
282
|
+
entry = {
|
|
283
|
+
tool,
|
|
284
|
+
startedAt: now,
|
|
285
|
+
lastProgressAt: undefined,
|
|
286
|
+
lastProgressBytes: undefined,
|
|
287
|
+
};
|
|
288
|
+
entries.set(key, entry);
|
|
278
289
|
}
|
|
279
290
|
const progressBytes = activityProgressBytes(ev);
|
|
280
|
-
const previousBytes =
|
|
291
|
+
const previousBytes = entry.lastProgressBytes ?? 0;
|
|
281
292
|
if (progressBytes !== undefined) {
|
|
282
|
-
|
|
293
|
+
entry.lastProgressBytes = Math.max(previousBytes, progressBytes);
|
|
294
|
+
}
|
|
295
|
+
if (!ev.id?.trim()) {
|
|
296
|
+
if (progressBytes !== undefined && progressBytes > 0) {
|
|
297
|
+
entry.lastProgressAt = now;
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
283
301
|
}
|
|
284
302
|
if (progressBytes !== undefined && progressBytes > previousBytes) {
|
|
285
303
|
// A byte increase is proof the model is still streaming this action's
|
|
286
304
|
// argument. Repeated zero-byte prep activity is only a heartbeat.
|
|
287
|
-
|
|
305
|
+
entry.lastProgressAt = now;
|
|
288
306
|
return true;
|
|
289
307
|
}
|
|
290
308
|
return false;
|
|
@@ -299,10 +317,17 @@ function updatePreparingActionState(
|
|
|
299
317
|
ev.type === "error" ||
|
|
300
318
|
ev.type === "missing_api_key"
|
|
301
319
|
) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
320
|
+
if (ev.type === "tool_start" || ev.type === "tool_done") {
|
|
321
|
+
const tool = ev.tool?.trim();
|
|
322
|
+
const id = ev.id?.trim();
|
|
323
|
+
for (const [key, entry] of state.entries ?? []) {
|
|
324
|
+
if ((id && key === id) || (!id && entry.tool === tool)) {
|
|
325
|
+
state.entries?.delete(key);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
state.entries?.clear();
|
|
330
|
+
}
|
|
306
331
|
}
|
|
307
332
|
return undefined;
|
|
308
333
|
}
|
|
@@ -313,12 +338,16 @@ function hasStalledPreparingAction(state: PreparingActionState, now: number) {
|
|
|
313
338
|
// streaming for a long time. `lastProgressAt` advances on every delta
|
|
314
339
|
// heartbeat, so an actively-streaming large output keeps resetting this and
|
|
315
340
|
// survives; a genuinely stuck prep (keepalive-only, no deltas) trips it.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
341
|
+
for (const entry of state.entries?.values() ?? []) {
|
|
342
|
+
if (
|
|
343
|
+
entry.startedAt !== undefined &&
|
|
344
|
+
now - (entry.lastProgressAt ?? entry.startedAt) >=
|
|
345
|
+
SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS
|
|
346
|
+
) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
322
351
|
}
|
|
323
352
|
|
|
324
353
|
async function readChunkWithProgressTimeout(
|
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
|
|
17
17
|
export default defineAction({
|
|
18
18
|
description:
|
|
19
|
-
"Change the coarse visibility of a shareable resource: 'private' keeps it owner-only, 'org' shares it with all members of the owner's organization, 'public' makes it accessible to anyone with the link.
|
|
20
|
-
// (audit H5) Visibility changes
|
|
21
|
-
//
|
|
19
|
+
"Change the coarse visibility of a shareable resource: 'private' keeps it owner-only, 'org' shares it with all members of the owner's organization, 'public' makes it accessible to anyone with the link. Visibility changes require owner or admin role.",
|
|
20
|
+
// (audit H5) Visibility changes can flip a private resource org-wide or
|
|
21
|
+
// public. Refuse from the tools iframe bridge.
|
|
22
22
|
toolCallable: false,
|
|
23
23
|
mcpApp: {
|
|
24
24
|
compactCatalog: true,
|
|
@@ -57,6 +57,14 @@ function escapeHtml(value: string): string {
|
|
|
57
57
|
.replace(/"/g, """);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function createInsertedNodeId(prefix: string): string {
|
|
61
|
+
const random =
|
|
62
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
63
|
+
? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
|
|
64
|
+
: Math.random().toString(36).slice(2, 14);
|
|
65
|
+
return `inserted-${prefix}-${random}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
function insertBeforeClosingTag(
|
|
61
69
|
html: string,
|
|
62
70
|
closingTag: "main" | "body",
|
|
@@ -70,6 +78,7 @@ function insertBeforeClosingTag(
|
|
|
70
78
|
function appendAssetMarkup(
|
|
71
79
|
html: string,
|
|
72
80
|
args: z.infer<typeof schemaInput>,
|
|
81
|
+
nodeId: string,
|
|
73
82
|
): string {
|
|
74
83
|
const label = args.title?.trim() || args.altText?.trim() || "Generated asset";
|
|
75
84
|
const escapedUrl = escapeHtml(args.assetUrl);
|
|
@@ -82,7 +91,7 @@ function appendAssetMarkup(
|
|
|
82
91
|
? `<video src="${escapedUrl}" controls class="w-full rounded-xl object-cover"></video>`
|
|
83
92
|
: `<img src="${escapedUrl}" alt="${escapeHtml(args.altText?.trim() || label)}" class="w-full rounded-xl object-cover" />`;
|
|
84
93
|
const snippet = `
|
|
85
|
-
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset${assetIdAttr}>
|
|
94
|
+
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset data-agent-native-node-id="${escapeHtml(nodeId)}" data-agent-native-layer-name="${escapedLabel}"${assetIdAttr}>
|
|
86
95
|
<figure class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
|
|
87
96
|
${media}
|
|
88
97
|
<figcaption class="px-4 py-3 text-sm text-slate-600">${escapedLabel}</figcaption>
|
|
@@ -191,7 +200,8 @@ export default defineAction({
|
|
|
191
200
|
// Collab read is best-effort; fall back to stored content.
|
|
192
201
|
}
|
|
193
202
|
|
|
194
|
-
const
|
|
203
|
+
const insertedNodeId = createInsertedNodeId("asset");
|
|
204
|
+
const content = appendAssetMarkup(base, args, insertedNodeId);
|
|
195
205
|
const now = new Date().toISOString();
|
|
196
206
|
await db
|
|
197
207
|
.update(schema.designFiles)
|
|
@@ -213,6 +223,8 @@ export default defineAction({
|
|
|
213
223
|
fileId: file.id,
|
|
214
224
|
filename: file.filename,
|
|
215
225
|
inserted: true,
|
|
226
|
+
insertedNodeId,
|
|
227
|
+
insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
|
|
216
228
|
assetId: args.assetId ?? null,
|
|
217
229
|
assetUrl: args.assetUrl,
|
|
218
230
|
};
|
|
@@ -50,9 +50,17 @@ function insertBeforeClosingTag(
|
|
|
50
50
|
return html.replace(pattern, `${snippet}\n</${closingTag}>`);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function
|
|
53
|
+
function createInsertedNodeId(prefix: string): string {
|
|
54
|
+
const random =
|
|
55
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
56
|
+
? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
|
|
57
|
+
: Math.random().toString(36).slice(2, 14);
|
|
58
|
+
return `inserted-${prefix}-${random}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function nativeSnippet(kind: DesignNativeAssetKind, nodeId: string): string {
|
|
54
62
|
const attrs = (componentName: string) =>
|
|
55
|
-
`data-agent-native-native-asset data-agent-native-component="${componentName}" data-agent-native-layer-name="${componentName}"`;
|
|
63
|
+
`data-agent-native-native-asset data-agent-native-node-id="${nodeId}" data-agent-native-component="${componentName}" data-agent-native-layer-name="${componentName}"`;
|
|
56
64
|
switch (kind) {
|
|
57
65
|
case "section-frame":
|
|
58
66
|
return `
|
|
@@ -137,8 +145,9 @@ function nativeSnippet(kind: DesignNativeAssetKind): string {
|
|
|
137
145
|
function appendNativeAssetMarkup(
|
|
138
146
|
html: string,
|
|
139
147
|
kind: DesignNativeAssetKind,
|
|
148
|
+
nodeId: string,
|
|
140
149
|
): string {
|
|
141
|
-
const snippet = nativeSnippet(kind);
|
|
150
|
+
const snippet = nativeSnippet(kind, nodeId);
|
|
142
151
|
return (
|
|
143
152
|
insertBeforeClosingTag(html, "main", snippet) ??
|
|
144
153
|
insertBeforeClosingTag(html, "body", snippet) ??
|
|
@@ -239,7 +248,8 @@ export default defineAction({
|
|
|
239
248
|
// Collab read is best-effort; fall back to stored content.
|
|
240
249
|
}
|
|
241
250
|
|
|
242
|
-
const
|
|
251
|
+
const insertedNodeId = createInsertedNodeId("native");
|
|
252
|
+
const content = appendNativeAssetMarkup(base, args.kind, insertedNodeId);
|
|
243
253
|
const now = new Date().toISOString();
|
|
244
254
|
await db
|
|
245
255
|
.update(schema.designFiles)
|
|
@@ -261,6 +271,8 @@ export default defineAction({
|
|
|
261
271
|
fileId: file.id,
|
|
262
272
|
filename: file.filename,
|
|
263
273
|
inserted: true,
|
|
274
|
+
insertedNodeId,
|
|
275
|
+
insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
|
|
264
276
|
source: "design-native",
|
|
265
277
|
kind: args.kind,
|
|
266
278
|
};
|
|
@@ -66,6 +66,14 @@ function escapeHtml(value: string): string {
|
|
|
66
66
|
.replace(/"/g, """);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function createInsertedNodeId(prefix: string): string {
|
|
70
|
+
const random =
|
|
71
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
72
|
+
? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
|
|
73
|
+
: Math.random().toString(36).slice(2, 14);
|
|
74
|
+
return `inserted-${prefix}-${random}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
function insertBeforeClosingTag(
|
|
70
78
|
html: string,
|
|
71
79
|
closingTag: "main" | "body",
|
|
@@ -83,6 +91,7 @@ function optionalDataAttribute(name: string, value: string | undefined) {
|
|
|
83
91
|
function appendFigmaAssetMarkup(
|
|
84
92
|
html: string,
|
|
85
93
|
args: z.infer<typeof schemaInput>,
|
|
94
|
+
nodeId: string,
|
|
86
95
|
): string {
|
|
87
96
|
const label = args.name?.trim() || "Figma library asset";
|
|
88
97
|
const description = args.description?.trim();
|
|
@@ -90,7 +99,7 @@ function appendFigmaAssetMarkup(
|
|
|
90
99
|
? `<a href="${escapeHtml(args.sourceUrl)}" target="_blank" rel="noreferrer" class="text-slate-500 underline decoration-slate-300 underline-offset-2 hover:text-slate-800">Open in Figma</a>`
|
|
91
100
|
: "";
|
|
92
101
|
const snippet = `
|
|
93
|
-
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset-source="figma" data-agent-native-figma-asset data-figma-file-key="${escapeHtml(args.fileKey)}"${optionalDataAttribute("data-figma-node-id", args.nodeId)}${optionalDataAttribute("data-figma-component-key", args.componentKey)} data-figma-asset-kind="${escapeHtml(args.kind)}">
|
|
102
|
+
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset-source="figma" data-agent-native-figma-asset data-agent-native-node-id="${escapeHtml(nodeId)}" data-agent-native-layer-name="${escapeHtml(label)}" data-figma-file-key="${escapeHtml(args.fileKey)}"${optionalDataAttribute("data-figma-node-id", args.nodeId)}${optionalDataAttribute("data-figma-component-key", args.componentKey)} data-figma-asset-kind="${escapeHtml(args.kind)}">
|
|
94
103
|
<figure class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
|
|
95
104
|
<img src="${escapeHtml(args.renderUrl)}" alt="${escapeHtml(label)}" class="w-full rounded-t-2xl object-contain" />
|
|
96
105
|
<figcaption class="flex flex-wrap items-center gap-x-3 gap-y-1 px-4 py-3 text-sm text-slate-600">
|
|
@@ -201,7 +210,8 @@ export default defineAction({
|
|
|
201
210
|
// Collab read is best-effort; fall back to stored content.
|
|
202
211
|
}
|
|
203
212
|
|
|
204
|
-
const
|
|
213
|
+
const insertedNodeId = createInsertedNodeId("figma");
|
|
214
|
+
const content = appendFigmaAssetMarkup(base, args, insertedNodeId);
|
|
205
215
|
const now = new Date().toISOString();
|
|
206
216
|
await db
|
|
207
217
|
.update(schema.designFiles)
|
|
@@ -223,6 +233,8 @@ export default defineAction({
|
|
|
223
233
|
fileId: file.id,
|
|
224
234
|
filename: file.filename,
|
|
225
235
|
inserted: true,
|
|
236
|
+
insertedNodeId,
|
|
237
|
+
insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
|
|
226
238
|
source: "figma",
|
|
227
239
|
fileKey: args.fileKey,
|
|
228
240
|
nodeId: args.nodeId ?? null,
|
|
@@ -1775,6 +1775,11 @@ export function DesignCanvas({
|
|
|
1775
1775
|
// explicit viewport width (e.g. 390 / 768 / 1280 side-by-side breakpoints).
|
|
1776
1776
|
const resolvedWidth =
|
|
1777
1777
|
previewWidthPx != null ? `${previewWidthPx}px` : iframeWidth;
|
|
1778
|
+
const focusScrollSurface = useCallback(() => {
|
|
1779
|
+
const surface = scrollContainerRef.current;
|
|
1780
|
+
if (!surface || document.activeElement === surface) return;
|
|
1781
|
+
surface.focus({ preventScroll: true });
|
|
1782
|
+
}, []);
|
|
1778
1783
|
|
|
1779
1784
|
// Wrap the iframe in a positioned container so DrawOverlay /
|
|
1780
1785
|
// CanvasCommentPins can absolutely-position themselves on top of the
|
|
@@ -1901,6 +1906,9 @@ export function DesignCanvas({
|
|
|
1901
1906
|
return (
|
|
1902
1907
|
<div
|
|
1903
1908
|
ref={scrollContainerRef}
|
|
1909
|
+
tabIndex={-1}
|
|
1910
|
+
onPointerEnter={focusScrollSurface}
|
|
1911
|
+
onMouseEnter={focusScrollSurface}
|
|
1904
1912
|
className="relative h-full w-full overflow-hidden"
|
|
1905
1913
|
>
|
|
1906
1914
|
{iframeElement}
|
|
@@ -1915,6 +1923,9 @@ export function DesignCanvas({
|
|
|
1915
1923
|
return (
|
|
1916
1924
|
<div
|
|
1917
1925
|
ref={scrollContainerRef}
|
|
1926
|
+
tabIndex={-1}
|
|
1927
|
+
onPointerEnter={focusScrollSurface}
|
|
1928
|
+
onMouseEnter={focusScrollSurface}
|
|
1918
1929
|
className="relative h-full w-full overflow-hidden"
|
|
1919
1930
|
style={{
|
|
1920
1931
|
width: embeddedFrame.displayWidth,
|
|
@@ -1945,6 +1956,9 @@ export function DesignCanvas({
|
|
|
1945
1956
|
return (
|
|
1946
1957
|
<div
|
|
1947
1958
|
ref={scrollContainerRef}
|
|
1959
|
+
tabIndex={-1}
|
|
1960
|
+
onPointerEnter={focusScrollSurface}
|
|
1961
|
+
onMouseEnter={focusScrollSurface}
|
|
1948
1962
|
className="relative flex-1 h-full overflow-auto"
|
|
1949
1963
|
>
|
|
1950
1964
|
{/* Canvas area. "none" mode fills the canvas (responsive preview);
|
|
@@ -108,6 +108,12 @@ export interface DesignExtensionSlotContext extends Record<string, unknown> {
|
|
|
108
108
|
content: string,
|
|
109
109
|
updatedAt?: string,
|
|
110
110
|
) => void;
|
|
111
|
+
onAssetInserted?: (selection: {
|
|
112
|
+
fileId?: string;
|
|
113
|
+
nodeId?: string;
|
|
114
|
+
selector?: string;
|
|
115
|
+
title?: string;
|
|
116
|
+
}) => void;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
interface DesignExtensionsPanelProps {
|
|
@@ -301,6 +307,7 @@ interface FirstPartyRowProps {
|
|
|
301
307
|
|
|
302
308
|
function FirstPartyExtRow({
|
|
303
309
|
label,
|
|
310
|
+
description,
|
|
304
311
|
icon,
|
|
305
312
|
badge,
|
|
306
313
|
isOpen,
|
|
@@ -321,14 +328,14 @@ function FirstPartyExtRow({
|
|
|
321
328
|
<span className="block truncate text-sm font-medium leading-tight text-foreground">
|
|
322
329
|
{label}
|
|
323
330
|
</span>
|
|
324
|
-
<span className="mt-0.5
|
|
325
|
-
|
|
331
|
+
<span className="mt-0.5 line-clamp-1 text-xs leading-snug text-muted-foreground">
|
|
332
|
+
{description}
|
|
326
333
|
</span>
|
|
327
334
|
</span>
|
|
328
335
|
{badge}
|
|
329
336
|
</button>
|
|
330
337
|
{isOpen && (
|
|
331
|
-
<div className="mb-2
|
|
338
|
+
<div className="mb-2 mt-1 overflow-hidden rounded-md border border-border/70 bg-background/70">
|
|
332
339
|
{children}
|
|
333
340
|
</div>
|
|
334
341
|
)}
|
|
@@ -348,6 +355,12 @@ function ToolFilterMenu<T extends string>({
|
|
|
348
355
|
onChange: (value: T) => void;
|
|
349
356
|
}) {
|
|
350
357
|
const selected = options.find((option) => option.value === value);
|
|
358
|
+
const triggerLabel =
|
|
359
|
+
value === "all"
|
|
360
|
+
? label === "Category"
|
|
361
|
+
? "All categories"
|
|
362
|
+
: `All ${label.toLowerCase()}s`
|
|
363
|
+
: (selected?.label ?? label);
|
|
351
364
|
return (
|
|
352
365
|
<DropdownMenu>
|
|
353
366
|
<DropdownMenuTrigger asChild>
|
|
@@ -358,7 +371,7 @@ function ToolFilterMenu<T extends string>({
|
|
|
358
371
|
className="h-8 cursor-pointer gap-1.5 rounded-md bg-transparent px-2.5 text-sm font-medium"
|
|
359
372
|
>
|
|
360
373
|
<IconAdjustmentsHorizontal className="size-4 text-muted-foreground" />
|
|
361
|
-
<span>{
|
|
374
|
+
<span>{triggerLabel}</span>
|
|
362
375
|
<IconChevronDown className="size-3.5 text-muted-foreground" />
|
|
363
376
|
</Button>
|
|
364
377
|
</DropdownMenuTrigger>
|
|
@@ -463,6 +476,25 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
463
476
|
: undefined,
|
|
464
477
|
{ enabled: Boolean(figmaRequest) },
|
|
465
478
|
);
|
|
479
|
+
const notifyAssetInserted = useCallback(
|
|
480
|
+
(result: unknown, title: string) => {
|
|
481
|
+
if (!result || typeof result !== "object") return;
|
|
482
|
+
const row = result as Record<string, unknown>;
|
|
483
|
+
context.onAssetInserted?.({
|
|
484
|
+
fileId: typeof row.fileId === "string" ? row.fileId : undefined,
|
|
485
|
+
nodeId:
|
|
486
|
+
typeof row.insertedNodeId === "string"
|
|
487
|
+
? row.insertedNodeId
|
|
488
|
+
: undefined,
|
|
489
|
+
selector:
|
|
490
|
+
typeof row.insertedSelector === "string"
|
|
491
|
+
? row.insertedSelector
|
|
492
|
+
: undefined,
|
|
493
|
+
title,
|
|
494
|
+
});
|
|
495
|
+
},
|
|
496
|
+
[context],
|
|
497
|
+
);
|
|
466
498
|
|
|
467
499
|
const handleReady = useCallback(
|
|
468
500
|
(_payload: unknown, _event: MessageEvent, ref: EmbeddedAppRef) => {
|
|
@@ -508,7 +540,8 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
508
540
|
fileId: context.activeFileId || undefined,
|
|
509
541
|
},
|
|
510
542
|
{
|
|
511
|
-
onSuccess: () => {
|
|
543
|
+
onSuccess: (result) => {
|
|
544
|
+
notifyAssetInserted(result, title ?? altText ?? "Asset");
|
|
512
545
|
toast.success("Asset inserted into design.");
|
|
513
546
|
},
|
|
514
547
|
onError: () => {
|
|
@@ -517,7 +550,7 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
517
550
|
},
|
|
518
551
|
);
|
|
519
552
|
},
|
|
520
|
-
[context.designId, context.activeFileId, insertAsset],
|
|
553
|
+
[context.designId, context.activeFileId, insertAsset, notifyAssetInserted],
|
|
521
554
|
);
|
|
522
555
|
|
|
523
556
|
const handleInsertNativeAsset = (asset: DesignNativeAsset) => {
|
|
@@ -528,7 +561,8 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
528
561
|
fileId: context.activeFileId || undefined,
|
|
529
562
|
},
|
|
530
563
|
{
|
|
531
|
-
onSuccess: () => {
|
|
564
|
+
onSuccess: (result) => {
|
|
565
|
+
notifyAssetInserted(result, asset.title);
|
|
532
566
|
toast.success(`${asset.title} inserted into design.`);
|
|
533
567
|
},
|
|
534
568
|
onError: (error) => {
|
|
@@ -566,7 +600,8 @@ export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
566
600
|
fileId: context.activeFileId || undefined,
|
|
567
601
|
},
|
|
568
602
|
{
|
|
569
|
-
onSuccess: () => {
|
|
603
|
+
onSuccess: (result) => {
|
|
604
|
+
notifyAssetInserted(result, asset.name);
|
|
570
605
|
toast.success("Figma asset inserted into design.");
|
|
571
606
|
},
|
|
572
607
|
onError: (error) => {
|
|
@@ -1267,6 +1302,11 @@ export function DesignExtensionsPanel({
|
|
|
1267
1302
|
visibleFirstPartyRows.length > 0 ||
|
|
1268
1303
|
visibleInstalls.length > 0 ||
|
|
1269
1304
|
visibleInstallable.length > 0;
|
|
1305
|
+
const showPluginsEmptyState =
|
|
1306
|
+
!hasAnyVisibleTool &&
|
|
1307
|
+
categoryFilter === "plugins" &&
|
|
1308
|
+
!normalizedSearch &&
|
|
1309
|
+
sourceFilter !== "built-in";
|
|
1270
1310
|
|
|
1271
1311
|
return (
|
|
1272
1312
|
<div className={cn("flex min-h-0 flex-1 flex-col", className)}>
|
|
@@ -1412,10 +1452,14 @@ export function DesignExtensionsPanel({
|
|
|
1412
1452
|
<IconSearch className="size-4 text-muted-foreground" />
|
|
1413
1453
|
</div>
|
|
1414
1454
|
<p className="text-sm font-medium text-foreground">
|
|
1415
|
-
|
|
1455
|
+
{showPluginsEmptyState
|
|
1456
|
+
? "No plugins installed"
|
|
1457
|
+
: "No tools found"}
|
|
1416
1458
|
</p>
|
|
1417
1459
|
<p className="mx-auto mt-1 max-w-52 text-xs leading-5 text-muted-foreground">
|
|
1418
|
-
|
|
1460
|
+
{showPluginsEmptyState
|
|
1461
|
+
? "Create a plugin or clear the Category filter to browse built-in tools."
|
|
1462
|
+
: "Try another search or clear the filters."}
|
|
1419
1463
|
</p>
|
|
1420
1464
|
</div>
|
|
1421
1465
|
) : null}
|
|
@@ -3189,22 +3189,29 @@ function TypographyDetailsPopover({
|
|
|
3189
3189
|
|
|
3190
3190
|
return (
|
|
3191
3191
|
<Popover open={open} onOpenChange={setOpen}>
|
|
3192
|
-
<
|
|
3193
|
-
<
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3192
|
+
<Tooltip>
|
|
3193
|
+
<TooltipTrigger asChild>
|
|
3194
|
+
<PopoverTrigger asChild>
|
|
3195
|
+
<Button
|
|
3196
|
+
type="button"
|
|
3197
|
+
variant="ghost"
|
|
3198
|
+
size="icon"
|
|
3199
|
+
aria-label={"Typography details" /* i18n-ignore design action */}
|
|
3200
|
+
aria-pressed={open}
|
|
3201
|
+
className={cn(
|
|
3202
|
+
"h-6 min-w-6 cursor-pointer rounded-md text-muted-foreground hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground",
|
|
3203
|
+
open &&
|
|
3204
|
+
"bg-[var(--design-editor-accent-color)]/20 text-[var(--design-editor-accent-color)] hover:text-[var(--design-editor-accent-color)]",
|
|
3205
|
+
)}
|
|
3206
|
+
>
|
|
3207
|
+
<IconLayoutSettings className="size-3.5" />
|
|
3208
|
+
</Button>
|
|
3209
|
+
</PopoverTrigger>
|
|
3210
|
+
</TooltipTrigger>
|
|
3211
|
+
<TooltipContent>
|
|
3212
|
+
{"Typography details" /* i18n-ignore design action */}
|
|
3213
|
+
</TooltipContent>
|
|
3214
|
+
</Tooltip>
|
|
3208
3215
|
<PopoverContent
|
|
3209
3216
|
side="left"
|
|
3210
3217
|
align="end"
|
|
@@ -4825,7 +4825,11 @@ export function MultiScreenCanvas({
|
|
|
4825
4825
|
className="relative h-full w-full select-none overflow-hidden outline-none"
|
|
4826
4826
|
onMouseDownCapture={handleMouseDown}
|
|
4827
4827
|
onMouseMove={handleMouseMove}
|
|
4828
|
-
style={{
|
|
4828
|
+
style={{
|
|
4829
|
+
cursor: surfaceCursor,
|
|
4830
|
+
overscrollBehavior: "none",
|
|
4831
|
+
touchAction: "none",
|
|
4832
|
+
}}
|
|
4829
4833
|
>
|
|
4830
4834
|
{showPixelGrid ? (
|
|
4831
4835
|
<div
|
|
@@ -5769,6 +5773,7 @@ const Screen = memo(function Screen({
|
|
|
5769
5773
|
onMouseEnter={() => updateDirectHover(true)}
|
|
5770
5774
|
onMouseLeave={() => updateDirectHover(false)}
|
|
5771
5775
|
style={{
|
|
5776
|
+
width: labelInfoMaxWidth,
|
|
5772
5777
|
maxWidth: labelInfoMaxWidth,
|
|
5773
5778
|
transform: `translateY(-50%) scale(${chromeScale})`,
|
|
5774
5779
|
transformOrigin: "left center",
|
|
@@ -5784,7 +5789,7 @@ const Screen = memo(function Screen({
|
|
|
5784
5789
|
<span
|
|
5785
5790
|
data-frame-title
|
|
5786
5791
|
className={cn(
|
|
5787
|
-
"min-w-0 truncate !text-[11px] font-medium",
|
|
5792
|
+
"min-w-0 flex-1 truncate !text-[11px] font-medium",
|
|
5788
5793
|
emphasized
|
|
5789
5794
|
? "text-[var(--design-editor-accent-color)]"
|
|
5790
5795
|
: activeOrEmphasized
|
|
@@ -63,7 +63,8 @@ export function InspectorAiActions({
|
|
|
63
63
|
routeSourceFile,
|
|
64
64
|
designId,
|
|
65
65
|
};
|
|
66
|
-
const
|
|
66
|
+
const copyDisabled = !request.trim();
|
|
67
|
+
const askDisabled = !canEdit || copyDisabled;
|
|
67
68
|
|
|
68
69
|
return (
|
|
69
70
|
<Collapsible open={open} onOpenChange={setOpen} className="w-full">
|
|
@@ -97,9 +98,12 @@ export function InspectorAiActions({
|
|
|
97
98
|
: t("designEditor.localSourceEdit.describeChange")
|
|
98
99
|
}
|
|
99
100
|
className="min-h-[64px] resize-none text-xs"
|
|
100
|
-
disabled={!canEdit}
|
|
101
101
|
onKeyDown={(e) => {
|
|
102
|
-
if (
|
|
102
|
+
if (
|
|
103
|
+
e.key === "Enter" &&
|
|
104
|
+
(e.metaKey || e.ctrlKey) &&
|
|
105
|
+
!askDisabled
|
|
106
|
+
) {
|
|
103
107
|
e.preventDefault();
|
|
104
108
|
void sendEdit(args);
|
|
105
109
|
setRequest("");
|
|
@@ -112,7 +116,7 @@ export function InspectorAiActions({
|
|
|
112
116
|
size="sm"
|
|
113
117
|
variant="default"
|
|
114
118
|
className="flex-1 gap-1.5 text-xs"
|
|
115
|
-
disabled={
|
|
119
|
+
disabled={askDisabled}
|
|
116
120
|
onClick={() => {
|
|
117
121
|
void sendEdit(args);
|
|
118
122
|
setRequest("");
|
|
@@ -126,7 +130,7 @@ export function InspectorAiActions({
|
|
|
126
130
|
size="sm"
|
|
127
131
|
variant="outline"
|
|
128
132
|
className="gap-1.5 text-xs"
|
|
129
|
-
disabled={
|
|
133
|
+
disabled={copyDisabled}
|
|
130
134
|
onClick={() => {
|
|
131
135
|
void copyPrompt(args);
|
|
132
136
|
}}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
requestAgentChatThreadOpen,
|
|
3
|
+
requestAgentTaskOpen,
|
|
4
|
+
useActionQuery,
|
|
5
|
+
useT,
|
|
6
|
+
} from "@agent-native/core/client";
|
|
2
7
|
import { AgentToggleButton } from "@agent-native/core/client";
|
|
3
8
|
import { RunsTray } from "@agent-native/core/client/progress";
|
|
9
|
+
import { useCallback } from "react";
|
|
4
10
|
import { useLocation } from "react-router";
|
|
5
11
|
|
|
6
12
|
import { useHeaderTitle, useHeaderActions } from "./HeaderActions";
|
|
@@ -12,6 +18,11 @@ const pageTitleKeys: Record<string, string> = {
|
|
|
12
18
|
"/settings": "navigation.settings",
|
|
13
19
|
};
|
|
14
20
|
|
|
21
|
+
type HeaderAgentRun = {
|
|
22
|
+
title?: string;
|
|
23
|
+
metadata?: Record<string, unknown> | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
15
26
|
function DesignTitle({ id }: { id: string }) {
|
|
16
27
|
const { data } = useActionQuery<{ title?: string }>("get-design", { id });
|
|
17
28
|
const title = data?.title ?? "Design";
|
|
@@ -42,6 +53,31 @@ function ResolvedTitle() {
|
|
|
42
53
|
export function Header() {
|
|
43
54
|
const title = useHeaderTitle();
|
|
44
55
|
const actions = useHeaderActions();
|
|
56
|
+
const openRunThread = useCallback(
|
|
57
|
+
(threadId: string, run?: HeaderAgentRun) => {
|
|
58
|
+
const metadata = run?.metadata ?? {};
|
|
59
|
+
const parentThreadId =
|
|
60
|
+
typeof metadata.parentThreadId === "string"
|
|
61
|
+
? metadata.parentThreadId.trim()
|
|
62
|
+
: "";
|
|
63
|
+
const isAgentTeam =
|
|
64
|
+
metadata.kind === "agent-team" || metadata.source === "agent-teams";
|
|
65
|
+
if (isAgentTeam && parentThreadId && parentThreadId !== threadId) {
|
|
66
|
+
requestAgentTaskOpen({
|
|
67
|
+
threadId,
|
|
68
|
+
parentThreadId,
|
|
69
|
+
description:
|
|
70
|
+
typeof metadata.description === "string"
|
|
71
|
+
? metadata.description
|
|
72
|
+
: run?.title || "",
|
|
73
|
+
name: typeof metadata.name === "string" ? metadata.name : "",
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
requestAgentChatThreadOpen({ threadId });
|
|
78
|
+
},
|
|
79
|
+
[],
|
|
80
|
+
);
|
|
45
81
|
|
|
46
82
|
return (
|
|
47
83
|
<header className="flex h-12 items-center gap-3 border-b border-border bg-background px-4 lg:px-6 shrink-0">
|
|
@@ -50,7 +86,7 @@ export function Header() {
|
|
|
50
86
|
</div>
|
|
51
87
|
<div className="flex items-center gap-2 shrink-0">
|
|
52
88
|
{actions}
|
|
53
|
-
<RunsTray pollMs={1500} />
|
|
89
|
+
<RunsTray pollMs={1500} onOpenThread={openRunThread} />
|
|
54
90
|
<AgentToggleButton />
|
|
55
91
|
</div>
|
|
56
92
|
</header>
|