@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
|
@@ -71,12 +71,9 @@ import { normalizeDesignSourceType } from "@shared/source-mode";
|
|
|
71
71
|
import {
|
|
72
72
|
IconArrowLeft,
|
|
73
73
|
IconArrowUpRight,
|
|
74
|
-
IconArrowsMaximize,
|
|
75
74
|
IconPencil,
|
|
76
75
|
IconMessage,
|
|
77
76
|
IconBrush,
|
|
78
|
-
IconZoomIn,
|
|
79
|
-
IconZoomOut,
|
|
80
77
|
IconDeviceDesktop,
|
|
81
78
|
IconDeviceTablet,
|
|
82
79
|
IconDeviceMobile,
|
|
@@ -118,6 +115,7 @@ import {
|
|
|
118
115
|
IconCircleCheck,
|
|
119
116
|
IconTerminal2,
|
|
120
117
|
IconLink,
|
|
118
|
+
IconLock,
|
|
121
119
|
} from "@tabler/icons-react";
|
|
122
120
|
import { useQueryClient } from "@tanstack/react-query";
|
|
123
121
|
import {
|
|
@@ -306,6 +304,8 @@ const BOARD_SURFACE_SIZE = 131_072;
|
|
|
306
304
|
const LOCALHOST_WRITE_EXTENSIONS = new Set([".html", ".htm", ".css"]);
|
|
307
305
|
const NO_LOCALHOST_WRITE_CONTENT_MESSAGE =
|
|
308
306
|
"No content to write. Open the screen first." /* i18n-ignore */;
|
|
307
|
+
const TWEAK_CONTROLS_EDIT_ACCESS_MESSAGE =
|
|
308
|
+
"You need edit access to add tweak controls." /* i18n-ignore */;
|
|
309
309
|
const FOCUSED_SCREEN_ZOOM = 100;
|
|
310
310
|
export const DEFAULT_OVERVIEW_ZOOM = 60;
|
|
311
311
|
const KEEPALIVE_FILE_SAVE_MAX_BYTES = 60_000;
|
|
@@ -857,6 +857,16 @@ function removeEditorChromeOverlays(root: ParentNode): void {
|
|
|
857
857
|
.forEach((element) => element.remove());
|
|
858
858
|
}
|
|
859
859
|
|
|
860
|
+
function sanitizeSerializedXmlForSvg(value: string): string {
|
|
861
|
+
// SVG opened as XML only knows the five predefined entities. HTML serializers
|
|
862
|
+
// can leave named entities or bare ampersands in foreignObject content; escape
|
|
863
|
+
// those so the downloaded SVG parses cleanly in browsers and editors.
|
|
864
|
+
return value.replace(
|
|
865
|
+
/&(?!(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);)/g,
|
|
866
|
+
"&",
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
860
870
|
/**
|
|
861
871
|
* Resolve the document-space rect of the currently selected element inside the
|
|
862
872
|
* preview iframe so image exports (PNG/SVG) can crop to just that frame instead
|
|
@@ -3741,10 +3751,17 @@ function DesignCollaboratorsMenu({
|
|
|
3741
3751
|
return (
|
|
3742
3752
|
<DropdownMenuItem
|
|
3743
3753
|
key={user.email}
|
|
3744
|
-
onSelect={() => {
|
|
3745
|
-
if (
|
|
3754
|
+
onSelect={(event) => {
|
|
3755
|
+
if (collaborator.isCurrent) {
|
|
3756
|
+
event.preventDefault();
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
onAvatarClick?.(user);
|
|
3746
3760
|
}}
|
|
3747
|
-
className=
|
|
3761
|
+
className={cn(
|
|
3762
|
+
"gap-2",
|
|
3763
|
+
collaborator.isCurrent && "cursor-default",
|
|
3764
|
+
)}
|
|
3748
3765
|
>
|
|
3749
3766
|
<DesignCollaboratorAvatar collaborator={collaborator} />
|
|
3750
3767
|
<span className="min-w-0 flex-1">
|
|
@@ -3755,9 +3772,17 @@ function DesignCollaboratorsMenu({
|
|
|
3755
3772
|
{user.email}
|
|
3756
3773
|
</span>
|
|
3757
3774
|
</span>
|
|
3758
|
-
{
|
|
3775
|
+
{collaborator.isCurrent ? (
|
|
3776
|
+
<span className="text-xs text-muted-foreground">
|
|
3777
|
+
{"You" /* i18n-ignore collaborator row */}
|
|
3778
|
+
</span>
|
|
3779
|
+
) : isFollowing ? (
|
|
3759
3780
|
<IconCheck className="size-3.5 text-[var(--design-editor-accent-color)]" />
|
|
3760
|
-
) :
|
|
3781
|
+
) : (
|
|
3782
|
+
<span className="text-xs text-muted-foreground">
|
|
3783
|
+
{"Follow" /* i18n-ignore collaborator row */}
|
|
3784
|
+
</span>
|
|
3785
|
+
)}
|
|
3761
3786
|
</DropdownMenuItem>
|
|
3762
3787
|
);
|
|
3763
3788
|
})}
|
|
@@ -3766,6 +3791,26 @@ function DesignCollaboratorsMenu({
|
|
|
3766
3791
|
);
|
|
3767
3792
|
}
|
|
3768
3793
|
|
|
3794
|
+
function ReadOnlyEditorPanel({
|
|
3795
|
+
title,
|
|
3796
|
+
description,
|
|
3797
|
+
}: {
|
|
3798
|
+
title: string;
|
|
3799
|
+
description: string;
|
|
3800
|
+
}) {
|
|
3801
|
+
return (
|
|
3802
|
+
<div className="flex min-h-0 flex-1 flex-col items-center justify-center px-5 text-center">
|
|
3803
|
+
<div className="mb-3 flex size-10 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
|
3804
|
+
<IconLock className="size-5" />
|
|
3805
|
+
</div>
|
|
3806
|
+
<p className="text-sm font-medium text-foreground">{title}</p>
|
|
3807
|
+
<p className="mt-1 max-w-56 text-xs leading-5 text-muted-foreground">
|
|
3808
|
+
{description}
|
|
3809
|
+
</p>
|
|
3810
|
+
</div>
|
|
3811
|
+
);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3769
3814
|
function externalPreviewUrlForContent(content: string): string | null {
|
|
3770
3815
|
const trimmed = content.trim();
|
|
3771
3816
|
if (!/^https?:\/\//i.test(trimmed)) return null;
|
|
@@ -5821,12 +5866,17 @@ export default function DesignEditor() {
|
|
|
5821
5866
|
const next = titleDraft.trim();
|
|
5822
5867
|
if (!next || next === design?.title) return;
|
|
5823
5868
|
|
|
5869
|
+
const designQueryKey = ["action", "get-design", { id }];
|
|
5870
|
+
const previousDesign = queryClient.getQueryData(designQueryKey);
|
|
5871
|
+
const previousListDesignsQueries = queryClient.getQueriesData({
|
|
5872
|
+
queryKey: ["action", "list-designs"],
|
|
5873
|
+
});
|
|
5824
5874
|
queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
|
|
5825
5875
|
if (!old || typeof old !== "object") return old;
|
|
5826
5876
|
return { ...old, title: next };
|
|
5827
5877
|
});
|
|
5828
|
-
queryClient.
|
|
5829
|
-
["action", "list-designs"
|
|
5878
|
+
queryClient.setQueriesData(
|
|
5879
|
+
{ queryKey: ["action", "list-designs"] },
|
|
5830
5880
|
(old: any) => {
|
|
5831
5881
|
if (!old) return old;
|
|
5832
5882
|
return {
|
|
@@ -5840,7 +5890,12 @@ export default function DesignEditor() {
|
|
|
5840
5890
|
|
|
5841
5891
|
updateDesignMutation.mutate({ id, title: next } as any, {
|
|
5842
5892
|
onError: () => {
|
|
5893
|
+
queryClient.setQueryData(designQueryKey, previousDesign);
|
|
5894
|
+
for (const [queryKey, data] of previousListDesignsQueries) {
|
|
5895
|
+
queryClient.setQueryData(queryKey, data);
|
|
5896
|
+
}
|
|
5843
5897
|
queryClient.invalidateQueries({ queryKey: ["action", "get-design"] });
|
|
5898
|
+
queryClient.invalidateQueries({ queryKey: ["action", "list-designs"] });
|
|
5844
5899
|
},
|
|
5845
5900
|
});
|
|
5846
5901
|
}, [
|
|
@@ -8976,8 +9031,11 @@ export default function DesignEditor() {
|
|
|
8976
9031
|
files: UploadedFile[],
|
|
8977
9032
|
options: PromptComposerSubmitOptions,
|
|
8978
9033
|
) => {
|
|
8979
|
-
if (!canEditDesign
|
|
9034
|
+
if (!canEditDesign) {
|
|
9035
|
+
toast.error(TWEAK_CONTROLS_EDIT_ACCESS_MESSAGE);
|
|
8980
9036
|
return;
|
|
9037
|
+
}
|
|
9038
|
+
if (!design) return;
|
|
8981
9039
|
const trimmed = prompt.trim();
|
|
8982
9040
|
if (!trimmed) return;
|
|
8983
9041
|
const fileContext = formatUploadedFileContext(files);
|
|
@@ -9010,6 +9068,7 @@ export default function DesignEditor() {
|
|
|
9010
9068
|
context,
|
|
9011
9069
|
submit: true,
|
|
9012
9070
|
openSidebar: true,
|
|
9071
|
+
newTab: true,
|
|
9013
9072
|
model: options.model,
|
|
9014
9073
|
engine: options.engine,
|
|
9015
9074
|
effort: options.effort,
|
|
@@ -9021,10 +9080,8 @@ export default function DesignEditor() {
|
|
|
9021
9080
|
activeFile,
|
|
9022
9081
|
canEditDesign,
|
|
9023
9082
|
design,
|
|
9024
|
-
generating,
|
|
9025
9083
|
handleTweakPromptOpenChange,
|
|
9026
9084
|
id,
|
|
9027
|
-
pendingGenerationActive,
|
|
9028
9085
|
tweakSelections,
|
|
9029
9086
|
tweaks,
|
|
9030
9087
|
],
|
|
@@ -9214,6 +9271,45 @@ export default function DesignEditor() {
|
|
|
9214
9271
|
});
|
|
9215
9272
|
}, [activeFile, design?.title, id, selectedCodeLayerNode, selectedElement]);
|
|
9216
9273
|
|
|
9274
|
+
const handleAssetInserted = useCallback(
|
|
9275
|
+
(selection: {
|
|
9276
|
+
fileId?: string;
|
|
9277
|
+
nodeId?: string;
|
|
9278
|
+
selector?: string;
|
|
9279
|
+
title?: string;
|
|
9280
|
+
}) => {
|
|
9281
|
+
if (selection.fileId) {
|
|
9282
|
+
setActiveFileId(selection.fileId);
|
|
9283
|
+
if (viewModeRef.current === "overview") {
|
|
9284
|
+
setOverviewSelectedScreenIds([selection.fileId]);
|
|
9285
|
+
}
|
|
9286
|
+
}
|
|
9287
|
+
if (selection.nodeId) {
|
|
9288
|
+
setSelectedLayerIdsState([selection.nodeId]);
|
|
9289
|
+
}
|
|
9290
|
+
if (selection.selector || selection.nodeId) {
|
|
9291
|
+
setSelectedElement({
|
|
9292
|
+
tagName: "section",
|
|
9293
|
+
sourceId: selection.nodeId,
|
|
9294
|
+
selector:
|
|
9295
|
+
selection.selector ??
|
|
9296
|
+
`[data-agent-native-node-id="${selection.nodeId}"]`,
|
|
9297
|
+
classes: [],
|
|
9298
|
+
computedStyles: {},
|
|
9299
|
+
boundingRect: { x: 0, y: 0, width: 0, height: 0 },
|
|
9300
|
+
textContent: selection.title,
|
|
9301
|
+
isFlexChild: false,
|
|
9302
|
+
isFlexContainer: false,
|
|
9303
|
+
});
|
|
9304
|
+
}
|
|
9305
|
+
setHoveredElement(null);
|
|
9306
|
+
setHoveredElementScreenId(null);
|
|
9307
|
+
setActiveTool("move");
|
|
9308
|
+
setMode("edit");
|
|
9309
|
+
},
|
|
9310
|
+
[],
|
|
9311
|
+
);
|
|
9312
|
+
|
|
9217
9313
|
const designExtensionContext = useMemo<DesignExtensionSlotContext>(
|
|
9218
9314
|
() => ({
|
|
9219
9315
|
designId: id ?? "",
|
|
@@ -9250,6 +9346,7 @@ export default function DesignEditor() {
|
|
|
9250
9346
|
updatedAt,
|
|
9251
9347
|
});
|
|
9252
9348
|
},
|
|
9349
|
+
onAssetInserted: handleAssetInserted,
|
|
9253
9350
|
}),
|
|
9254
9351
|
[
|
|
9255
9352
|
activeContent,
|
|
@@ -9262,6 +9359,7 @@ export default function DesignEditor() {
|
|
|
9262
9359
|
clearShaderFillPreview,
|
|
9263
9360
|
design?.title,
|
|
9264
9361
|
files,
|
|
9362
|
+
handleAssetInserted,
|
|
9265
9363
|
id,
|
|
9266
9364
|
mode,
|
|
9267
9365
|
overviewSelectedScreenIds,
|
|
@@ -13326,22 +13424,44 @@ export default function DesignEditor() {
|
|
|
13326
13424
|
);
|
|
13327
13425
|
// When an element is selected, crop the export to just that frame.
|
|
13328
13426
|
const cropRect = resolveExportCropRect(doc, selectedElement);
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13338
|
-
|
|
13339
|
-
|
|
13340
|
-
|
|
13341
|
-
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13427
|
+
let canvas: HTMLCanvasElement;
|
|
13428
|
+
try {
|
|
13429
|
+
canvas = await html2canvas(doc.documentElement, {
|
|
13430
|
+
width,
|
|
13431
|
+
height,
|
|
13432
|
+
windowWidth: width,
|
|
13433
|
+
windowHeight: height,
|
|
13434
|
+
scale: exportScale,
|
|
13435
|
+
useCORS: true,
|
|
13436
|
+
foreignObjectRendering: true,
|
|
13437
|
+
backgroundColor: null,
|
|
13438
|
+
onclone: (clonedDocument) => {
|
|
13439
|
+
// Sanitize colors first: it aligns source/clone elements by index,
|
|
13440
|
+
// so remove the editor-chrome overlays only afterwards.
|
|
13441
|
+
sanitizeHtml2CanvasClone(doc, clonedDocument);
|
|
13442
|
+
removeEditorChromeOverlays(clonedDocument);
|
|
13443
|
+
},
|
|
13444
|
+
});
|
|
13445
|
+
} catch (primaryError) {
|
|
13446
|
+
console.warn(
|
|
13447
|
+
"PNG export failed with foreignObjectRendering; retrying canvas renderer:",
|
|
13448
|
+
primaryError,
|
|
13449
|
+
);
|
|
13450
|
+
canvas = await html2canvas(doc.documentElement, {
|
|
13451
|
+
width,
|
|
13452
|
+
height,
|
|
13453
|
+
windowWidth: width,
|
|
13454
|
+
windowHeight: height,
|
|
13455
|
+
scale: exportScale,
|
|
13456
|
+
useCORS: true,
|
|
13457
|
+
foreignObjectRendering: false,
|
|
13458
|
+
backgroundColor: null,
|
|
13459
|
+
onclone: (clonedDocument) => {
|
|
13460
|
+
sanitizeHtml2CanvasClone(doc, clonedDocument);
|
|
13461
|
+
removeEditorChromeOverlays(clonedDocument);
|
|
13462
|
+
},
|
|
13463
|
+
});
|
|
13464
|
+
}
|
|
13345
13465
|
// Render the whole page first, then crop, so ancestor backgrounds show
|
|
13346
13466
|
// through the selected frame exactly as they do on screen.
|
|
13347
13467
|
const outputCanvas = cropRect
|
|
@@ -13465,7 +13585,9 @@ export default function DesignEditor() {
|
|
|
13465
13585
|
body.style.minHeight = `${height}px`;
|
|
13466
13586
|
}
|
|
13467
13587
|
|
|
13468
|
-
const serializedHtml =
|
|
13588
|
+
const serializedHtml = sanitizeSerializedXmlForSvg(
|
|
13589
|
+
new XMLSerializer().serializeToString(clone),
|
|
13590
|
+
);
|
|
13469
13591
|
const safeTitle =
|
|
13470
13592
|
design?.title
|
|
13471
13593
|
?.replace(/&/g, "&")
|
|
@@ -13661,7 +13783,7 @@ ${serializedHtml}
|
|
|
13661
13783
|
type="button"
|
|
13662
13784
|
onClick={selectedShareExportOption.onDownload}
|
|
13663
13785
|
disabled={selectedShareExportOption.disabled}
|
|
13664
|
-
className="h-8 gap-1.5 rounded-md px-3 text-[12px]"
|
|
13786
|
+
className="h-8 gap-1.5 rounded-md bg-[var(--design-editor-accent-color)] px-3 text-[12px] text-[var(--design-editor-accent-contrast-color)] shadow-none hover:bg-[var(--design-editor-accent-hover-color)] hover:text-[var(--design-editor-accent-contrast-color)] disabled:bg-muted disabled:text-muted-foreground"
|
|
13665
13787
|
>
|
|
13666
13788
|
<IconDownload className="size-3.5" />
|
|
13667
13789
|
{"Download" /* i18n-ignore share export action */}
|
|
@@ -15495,6 +15617,22 @@ ${serializedHtml}
|
|
|
15495
15617
|
);
|
|
15496
15618
|
|
|
15497
15619
|
const zoomLabel = `${Math.round(zoom)}%`;
|
|
15620
|
+
const [openZoomControl, setOpenZoomControl] = useState<
|
|
15621
|
+
"toolbar" | "inspector" | null
|
|
15622
|
+
>(null);
|
|
15623
|
+
const [zoomInputValue, setZoomInputValue] = useState(zoomLabel);
|
|
15624
|
+
useEffect(() => {
|
|
15625
|
+
if (!openZoomControl) setZoomInputValue(zoomLabel);
|
|
15626
|
+
}, [zoomLabel, openZoomControl]);
|
|
15627
|
+
const commitZoomInput = useCallback(() => {
|
|
15628
|
+
const next = Number(zoomInputValue.replace("%", "").trim());
|
|
15629
|
+
if (!Number.isFinite(next)) {
|
|
15630
|
+
setZoomInputValue(zoomLabel);
|
|
15631
|
+
return;
|
|
15632
|
+
}
|
|
15633
|
+
setZoom(Math.max(10, Math.min(500, next)));
|
|
15634
|
+
setOpenZoomControl(null);
|
|
15635
|
+
}, [setZoom, zoomInputValue, zoomLabel]);
|
|
15498
15636
|
|
|
15499
15637
|
const handleTokensApplied = useCallback(
|
|
15500
15638
|
(resolvedCssVars: Record<string, string>) => {
|
|
@@ -15834,8 +15972,20 @@ ${serializedHtml}
|
|
|
15834
15972
|
</span>
|
|
15835
15973
|
);
|
|
15836
15974
|
|
|
15837
|
-
const
|
|
15838
|
-
<DropdownMenu
|
|
15975
|
+
const renderZoomControl = (controlId: "toolbar" | "inspector") => (
|
|
15976
|
+
<DropdownMenu
|
|
15977
|
+
open={openZoomControl === controlId}
|
|
15978
|
+
onOpenChange={(open) => {
|
|
15979
|
+
if (open) {
|
|
15980
|
+
setZoomInputValue(zoomLabel);
|
|
15981
|
+
setOpenZoomControl(controlId);
|
|
15982
|
+
return;
|
|
15983
|
+
}
|
|
15984
|
+
setOpenZoomControl((current) =>
|
|
15985
|
+
current === controlId ? null : current,
|
|
15986
|
+
);
|
|
15987
|
+
}}
|
|
15988
|
+
>
|
|
15839
15989
|
<Tooltip>
|
|
15840
15990
|
<TooltipTrigger asChild>
|
|
15841
15991
|
<DropdownMenuTrigger asChild>
|
|
@@ -15851,29 +16001,66 @@ ${serializedHtml}
|
|
|
15851
16001
|
</TooltipTrigger>
|
|
15852
16002
|
<TooltipContent>{t("designEditor.zoom")}</TooltipContent>
|
|
15853
16003
|
</Tooltip>
|
|
15854
|
-
<DropdownMenuContent
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
16004
|
+
<DropdownMenuContent
|
|
16005
|
+
align="end"
|
|
16006
|
+
className="w-72 overflow-hidden rounded-xl border-[var(--design-editor-control-border)] bg-[var(--design-editor-panel-bg)] p-0 shadow-2xl"
|
|
16007
|
+
>
|
|
16008
|
+
<div className="p-3">
|
|
16009
|
+
<Input
|
|
16010
|
+
autoFocus
|
|
16011
|
+
value={zoomInputValue}
|
|
16012
|
+
onChange={(event) => setZoomInputValue(event.target.value)}
|
|
16013
|
+
onFocus={(event) => event.currentTarget.select()}
|
|
16014
|
+
onKeyDown={(event) => {
|
|
16015
|
+
event.stopPropagation();
|
|
16016
|
+
if (event.key === "Enter") {
|
|
16017
|
+
event.preventDefault();
|
|
16018
|
+
commitZoomInput();
|
|
16019
|
+
} else if (event.key === "Escape") {
|
|
16020
|
+
event.preventDefault();
|
|
16021
|
+
setZoomInputValue(zoomLabel);
|
|
16022
|
+
setOpenZoomControl(null);
|
|
16023
|
+
}
|
|
16024
|
+
}}
|
|
16025
|
+
className="h-10 rounded-md border-[var(--design-editor-accent-color)] bg-[var(--design-editor-control-bg)] px-3 text-base font-medium tabular-nums text-foreground shadow-none focus-visible:ring-2 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
16026
|
+
aria-label={"Zoom percentage" /* i18n-ignore zoom field */}
|
|
16027
|
+
/>
|
|
16028
|
+
</div>
|
|
16029
|
+
<DropdownMenuSeparator />
|
|
16030
|
+
<DropdownMenuItem
|
|
16031
|
+
onClick={handleZoomIn}
|
|
16032
|
+
className="h-12 px-12 text-[15px]"
|
|
16033
|
+
>
|
|
16034
|
+
<span className="flex-1">{"Zoom in" /* i18n-ignore */}</span>
|
|
16035
|
+
<DropdownMenuShortcut>⌘+</DropdownMenuShortcut>
|
|
15858
16036
|
</DropdownMenuItem>
|
|
15859
|
-
<DropdownMenuItem
|
|
15860
|
-
|
|
15861
|
-
|
|
16037
|
+
<DropdownMenuItem
|
|
16038
|
+
onClick={handleZoomOut}
|
|
16039
|
+
className="h-12 px-12 text-[15px]"
|
|
16040
|
+
>
|
|
16041
|
+
<span className="flex-1">{"Zoom out" /* i18n-ignore */}</span>
|
|
16042
|
+
<DropdownMenuShortcut>⌘−</DropdownMenuShortcut>
|
|
15862
16043
|
</DropdownMenuItem>
|
|
15863
|
-
<DropdownMenuItem
|
|
15864
|
-
|
|
15865
|
-
|
|
16044
|
+
<DropdownMenuItem
|
|
16045
|
+
onClick={handleZoomToFit}
|
|
16046
|
+
className="h-12 px-12 text-[15px]"
|
|
16047
|
+
>
|
|
16048
|
+
<span className="flex-1">{"Zoom to fit" /* i18n-ignore */}</span>
|
|
15866
16049
|
<DropdownMenuShortcut>⇧1</DropdownMenuShortcut>
|
|
15867
16050
|
</DropdownMenuItem>
|
|
15868
|
-
|
|
15869
|
-
{ZOOM_PRESETS.map((preset) => (
|
|
16051
|
+
{[50, 100, 200].map((preset) => (
|
|
15870
16052
|
<DropdownMenuItem
|
|
15871
16053
|
key={preset}
|
|
15872
16054
|
onClick={() => setZoom(preset)}
|
|
15873
|
-
className="
|
|
16055
|
+
className="h-12 px-12 text-[15px]"
|
|
15874
16056
|
>
|
|
15875
|
-
<span>
|
|
15876
|
-
|
|
16057
|
+
<span className="flex-1">
|
|
16058
|
+
{"Zoom to " /* i18n-ignore */}
|
|
16059
|
+
{preset}%
|
|
16060
|
+
</span>
|
|
16061
|
+
{preset === 100 ? (
|
|
16062
|
+
<DropdownMenuShortcut>⌘0</DropdownMenuShortcut>
|
|
16063
|
+
) : null}
|
|
15877
16064
|
</DropdownMenuItem>
|
|
15878
16065
|
))}
|
|
15879
16066
|
</DropdownMenuContent>
|
|
@@ -16390,52 +16577,7 @@ ${serializedHtml}
|
|
|
16390
16577
|
</DropdownMenuContent>
|
|
16391
16578
|
</DropdownMenu>
|
|
16392
16579
|
|
|
16393
|
-
{
|
|
16394
|
-
<DropdownMenu>
|
|
16395
|
-
<Tooltip>
|
|
16396
|
-
<TooltipTrigger asChild>
|
|
16397
|
-
<DropdownMenuTrigger asChild>
|
|
16398
|
-
<Button
|
|
16399
|
-
variant="ghost"
|
|
16400
|
-
size="sm"
|
|
16401
|
-
className="h-7 gap-1 px-2 text-xs tabular-nums text-muted-foreground cursor-pointer"
|
|
16402
|
-
>
|
|
16403
|
-
{zoomLabel}
|
|
16404
|
-
<IconChevronDown className="w-3 h-3 opacity-60" />
|
|
16405
|
-
</Button>
|
|
16406
|
-
</DropdownMenuTrigger>
|
|
16407
|
-
</TooltipTrigger>
|
|
16408
|
-
<TooltipContent>{t("designEditor.zoom")}</TooltipContent>
|
|
16409
|
-
</Tooltip>
|
|
16410
|
-
<DropdownMenuContent align="end" className="w-40">
|
|
16411
|
-
<DropdownMenuItem onClick={handleZoomOut}>
|
|
16412
|
-
<IconZoomOut className="mr-2 h-4 w-4" />
|
|
16413
|
-
{t("designEditor.zoomOut")}
|
|
16414
|
-
</DropdownMenuItem>
|
|
16415
|
-
<DropdownMenuItem onClick={handleZoomIn}>
|
|
16416
|
-
<IconZoomIn className="mr-2 h-4 w-4" />
|
|
16417
|
-
{t("designEditor.zoomIn")}
|
|
16418
|
-
</DropdownMenuItem>
|
|
16419
|
-
<DropdownMenuItem onClick={handleZoomToFit}>
|
|
16420
|
-
<IconArrowsMaximize className="mr-2 h-4 w-4" />
|
|
16421
|
-
{"Fit to screen" /* i18n-ignore zoom option */}
|
|
16422
|
-
<DropdownMenuShortcut>⇧1</DropdownMenuShortcut>
|
|
16423
|
-
</DropdownMenuItem>
|
|
16424
|
-
<DropdownMenuSeparator />
|
|
16425
|
-
{ZOOM_PRESETS.map((preset) => (
|
|
16426
|
-
<DropdownMenuItem
|
|
16427
|
-
key={preset}
|
|
16428
|
-
onClick={() => setZoom(preset)}
|
|
16429
|
-
className="justify-between"
|
|
16430
|
-
>
|
|
16431
|
-
<span>{preset}%</span>
|
|
16432
|
-
{Math.round(zoom) === preset && (
|
|
16433
|
-
<IconCheck className="h-4 w-4" />
|
|
16434
|
-
)}
|
|
16435
|
-
</DropdownMenuItem>
|
|
16436
|
-
))}
|
|
16437
|
-
</DropdownMenuContent>
|
|
16438
|
-
</DropdownMenu>
|
|
16580
|
+
{renderZoomControl("toolbar")}
|
|
16439
16581
|
|
|
16440
16582
|
<div className="mx-1 h-5 w-px bg-border" />
|
|
16441
16583
|
</>
|
|
@@ -16565,7 +16707,16 @@ ${serializedHtml}
|
|
|
16565
16707
|
showScopeBadge={false}
|
|
16566
16708
|
browserTabId={browserTabId}
|
|
16567
16709
|
/>
|
|
16568
|
-
) :
|
|
16710
|
+
) : (
|
|
16711
|
+
<ReadOnlyEditorPanel
|
|
16712
|
+
title={
|
|
16713
|
+
"Agent chat requires editor access" /* i18n-ignore */
|
|
16714
|
+
}
|
|
16715
|
+
description={
|
|
16716
|
+
"Ask an owner for edit access before using the agent to change this design." /* i18n-ignore */
|
|
16717
|
+
}
|
|
16718
|
+
/>
|
|
16719
|
+
)}
|
|
16569
16720
|
</div>
|
|
16570
16721
|
<div
|
|
16571
16722
|
className={cn(
|
|
@@ -16578,7 +16729,16 @@ ${serializedHtml}
|
|
|
16578
16729
|
{t("designEditor.leftRail.assets")}
|
|
16579
16730
|
</h3>
|
|
16580
16731
|
</div>
|
|
16581
|
-
|
|
16732
|
+
{canEditDesign ? (
|
|
16733
|
+
<AssetLibraryPanel context={designExtensionContext} />
|
|
16734
|
+
) : (
|
|
16735
|
+
<ReadOnlyEditorPanel
|
|
16736
|
+
title={"Assets require editor access" /* i18n-ignore */}
|
|
16737
|
+
description={
|
|
16738
|
+
"Ask an owner for edit access before inserting assets into this design." /* i18n-ignore */
|
|
16739
|
+
}
|
|
16740
|
+
/>
|
|
16741
|
+
)}
|
|
16582
16742
|
</div>
|
|
16583
16743
|
<div
|
|
16584
16744
|
className={cn(
|
|
@@ -16586,11 +16746,20 @@ ${serializedHtml}
|
|
|
16586
16746
|
activeLeftPanel === "tools" ? "flex" : "hidden",
|
|
16587
16747
|
)}
|
|
16588
16748
|
>
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16749
|
+
{canEditDesign ? (
|
|
16750
|
+
<DesignExtensionsPanel
|
|
16751
|
+
context={designExtensionContext}
|
|
16752
|
+
hideAssetLibrary
|
|
16753
|
+
title={t("designEditor.leftRail.tools")}
|
|
16754
|
+
/>
|
|
16755
|
+
) : (
|
|
16756
|
+
<ReadOnlyEditorPanel
|
|
16757
|
+
title={"Tools require editor access" /* i18n-ignore */}
|
|
16758
|
+
description={
|
|
16759
|
+
"Ask an owner for edit access before running tools or creating extensions for this design." /* i18n-ignore */
|
|
16760
|
+
}
|
|
16761
|
+
/>
|
|
16762
|
+
)}
|
|
16594
16763
|
</div>
|
|
16595
16764
|
<div
|
|
16596
16765
|
className={cn(
|
|
@@ -16598,7 +16767,7 @@ ${serializedHtml}
|
|
|
16598
16767
|
activeLeftPanel === "tokens" ? "flex" : "hidden",
|
|
16599
16768
|
)}
|
|
16600
16769
|
>
|
|
16601
|
-
{id ? (
|
|
16770
|
+
{id && canEditDesign ? (
|
|
16602
16771
|
<>
|
|
16603
16772
|
<div className="flex min-h-8 shrink-0 items-center border-b border-border/60 px-3">
|
|
16604
16773
|
<h3 className="min-w-0 flex-1 truncate text-xs font-semibold text-foreground">
|
|
@@ -16612,7 +16781,14 @@ ${serializedHtml}
|
|
|
16612
16781
|
/>
|
|
16613
16782
|
</div>
|
|
16614
16783
|
</>
|
|
16615
|
-
) :
|
|
16784
|
+
) : (
|
|
16785
|
+
<ReadOnlyEditorPanel
|
|
16786
|
+
title={"Tokens require editor access" /* i18n-ignore */}
|
|
16787
|
+
description={
|
|
16788
|
+
"Ask an owner for edit access before importing, creating, or applying tokens." /* i18n-ignore */
|
|
16789
|
+
}
|
|
16790
|
+
/>
|
|
16791
|
+
)}
|
|
16616
16792
|
</div>
|
|
16617
16793
|
</div>
|
|
16618
16794
|
<div
|
|
@@ -17475,7 +17651,7 @@ ${serializedHtml}
|
|
|
17475
17651
|
selectedElements={selectedInspectorElements}
|
|
17476
17652
|
pageStyles={pageStyles}
|
|
17477
17653
|
zoom={zoom}
|
|
17478
|
-
headerTrailing={
|
|
17654
|
+
headerTrailing={renderZoomControl("inspector")}
|
|
17479
17655
|
width={rightSidebarWidth}
|
|
17480
17656
|
activeTab={activeInspectorTab}
|
|
17481
17657
|
onActiveTabChange={setActiveInspectorTab}
|
|
@@ -17735,7 +17911,7 @@ ${serializedHtml}
|
|
|
17735
17911
|
title={t("designEditor.tweaksPromptTitle")}
|
|
17736
17912
|
placeholder={t("designEditor.tweaksPlaceholder")}
|
|
17737
17913
|
onSubmit={handleTweakPromptSubmit}
|
|
17738
|
-
loading={
|
|
17914
|
+
loading={false}
|
|
17739
17915
|
anchorRef={tweakPromptAnchorRef}
|
|
17740
17916
|
/>
|
|
17741
17917
|
|
|
@@ -59,6 +59,20 @@ interface BuilderIndexResult {
|
|
|
59
59
|
instructions?: string;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
async function readJsonResponse(res: Response): Promise<any> {
|
|
63
|
+
const text = await res.text();
|
|
64
|
+
if (!text.trim()) return {};
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(text);
|
|
67
|
+
} catch {
|
|
68
|
+
return {
|
|
69
|
+
error: res.ok
|
|
70
|
+
? "The server returned an invalid response."
|
|
71
|
+
: text.slice(0, 240),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
62
76
|
export default function DesignSystemSetup() {
|
|
63
77
|
const t = useT();
|
|
64
78
|
const navigate = useNavigate();
|
|
@@ -127,7 +141,7 @@ export default function DesignSystemSetup() {
|
|
|
127
141
|
body,
|
|
128
142
|
},
|
|
129
143
|
);
|
|
130
|
-
const json = await res
|
|
144
|
+
const json = await readJsonResponse(res);
|
|
131
145
|
if (!res.ok || json?.error) {
|
|
132
146
|
throw new Error(json?.error || `Upload failed (${res.status})`);
|
|
133
147
|
}
|
|
@@ -189,6 +189,13 @@ export default function Index() {
|
|
|
189
189
|
});
|
|
190
190
|
}, [filtered]);
|
|
191
191
|
|
|
192
|
+
const handleSearchChange = useCallback((query: string) => {
|
|
193
|
+
setSearch(query);
|
|
194
|
+
setSelectedDesignIds((current) =>
|
|
195
|
+
current.size === 0 ? current : new Set(),
|
|
196
|
+
);
|
|
197
|
+
}, []);
|
|
198
|
+
|
|
192
199
|
const clearSelection = useCallback(() => {
|
|
193
200
|
setSelectedDesignIds(new Set());
|
|
194
201
|
}, []);
|
|
@@ -359,14 +366,18 @@ export default function Index() {
|
|
|
359
366
|
setRenameId(null);
|
|
360
367
|
if (!next) return;
|
|
361
368
|
|
|
362
|
-
queryClient.
|
|
363
|
-
["action", "list-designs"
|
|
364
|
-
(old: any) =>
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
369
|
+
queryClient.setQueriesData(
|
|
370
|
+
{ queryKey: ["action", "list-designs"] },
|
|
371
|
+
(old: any) => {
|
|
372
|
+
if (!old || typeof old !== "object") return old;
|
|
373
|
+
return {
|
|
374
|
+
...old,
|
|
375
|
+
count: old.count ?? (old.designs ?? []).length,
|
|
376
|
+
designs: (old.designs ?? []).map((d: Design) =>
|
|
377
|
+
d.id === id ? { ...d, title: next } : d,
|
|
378
|
+
),
|
|
379
|
+
};
|
|
380
|
+
},
|
|
370
381
|
);
|
|
371
382
|
|
|
372
383
|
updateMutation.mutate({ id, title: next } as any, {
|
|
@@ -397,7 +408,7 @@ export default function Index() {
|
|
|
397
408
|
<IconSearch className="absolute start-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground/70" />
|
|
398
409
|
<Input
|
|
399
410
|
value={search}
|
|
400
|
-
onChange={(e) =>
|
|
411
|
+
onChange={(e) => handleSearchChange(e.target.value)}
|
|
401
412
|
placeholder={t("home.searchPlaceholder")}
|
|
402
413
|
className="ps-8 h-8 w-48 bg-accent/50 border-border text-sm text-foreground/90 placeholder:text-muted-foreground/70"
|
|
403
414
|
/>
|