@agent-native/core 0.84.30 → 0.84.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +24 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/session-replay.ts +5 -0
  5. package/corpus/core/src/client/use-action.ts +5 -1
  6. package/corpus/core/src/server/ssr-handler.ts +12 -0
  7. package/corpus/core/src/vite/action-types-plugin.ts +6 -2
  8. package/corpus/core/src/vite/client.ts +152 -17
  9. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +13 -1
  10. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-keep-inlined-css-without-live-resource-loa.md +6 -0
  11. package/corpus/templates/design/AGENTS.md +18 -0
  12. package/corpus/templates/design/actions/apply-source-edit.ts +87 -0
  13. package/corpus/templates/design/actions/import-design-source.ts +158 -0
  14. package/corpus/templates/design/actions/list-source-files.ts +52 -0
  15. package/corpus/templates/design/actions/navigate.ts +4 -2
  16. package/corpus/templates/design/actions/preview-source-edit.ts +85 -0
  17. package/corpus/templates/design/actions/read-source-file.ts +55 -0
  18. package/corpus/templates/design/actions/resolve-selection-source.ts +101 -0
  19. package/corpus/templates/design/actions/view-screen.ts +43 -1
  20. package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +630 -0
  21. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +519 -0
  22. package/corpus/templates/design/app/hooks/use-navigation-state.ts +32 -6
  23. package/corpus/templates/design/app/i18n/zh-TW.ts +40 -0
  24. package/corpus/templates/design/app/i18n-data.ts +508 -0
  25. package/corpus/templates/design/app/pages/DesignEditor.tsx +171 -9
  26. package/corpus/templates/design/changelog/2026-07-01-design-can-import-figma-paste-fig-files-and-standalone-html.md +6 -0
  27. package/corpus/templates/design/changelog/2026-07-01-design-code-workspace.md +6 -0
  28. package/corpus/templates/design/package.json +3 -0
  29. package/corpus/templates/design/server/handlers/import-design-file.ts +168 -0
  30. package/corpus/templates/design/server/lib/figma-import/clipboard.ts +122 -0
  31. package/corpus/templates/design/server/lib/figma-import/decode.ts +455 -0
  32. package/corpus/templates/design/server/lib/figma-import/processor.ts +116 -0
  33. package/corpus/templates/design/server/lib/figma-import/render-html.ts +397 -0
  34. package/corpus/templates/design/server/lib/figma-import/types.ts +60 -0
  35. package/corpus/templates/design/server/lib/import-design-files.ts +314 -0
  36. package/corpus/templates/design/server/routes/api/import-design-file.post.ts +1 -0
  37. package/corpus/templates/design/server/source-workspace.ts +215 -0
  38. package/corpus/templates/design/shared/design-source-capabilities.ts +5 -5
  39. package/corpus/templates/design/shared/source-workspace.ts +149 -0
  40. package/dist/client/session-replay.d.ts +1 -0
  41. package/dist/client/session-replay.d.ts.map +1 -1
  42. package/dist/client/session-replay.js +2 -0
  43. package/dist/client/session-replay.js.map +1 -1
  44. package/dist/client/use-action.d.ts +5 -1
  45. package/dist/client/use-action.d.ts.map +1 -1
  46. package/dist/client/use-action.js.map +1 -1
  47. package/dist/collab/awareness.d.ts +2 -2
  48. package/dist/collab/awareness.d.ts.map +1 -1
  49. package/dist/collab/routes.d.ts +1 -1
  50. package/dist/notifications/routes.d.ts +3 -3
  51. package/dist/observability/routes.d.ts +3 -3
  52. package/dist/progress/routes.d.ts +1 -1
  53. package/dist/resources/handlers.d.ts +3 -3
  54. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  55. package/dist/server/ssr-handler.d.ts.map +1 -1
  56. package/dist/server/ssr-handler.js +7 -0
  57. package/dist/server/ssr-handler.js.map +1 -1
  58. package/dist/server/transcribe-voice.d.ts +1 -1
  59. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  60. package/dist/vite/action-types-plugin.js +6 -2
  61. package/dist/vite/action-types-plugin.js.map +1 -1
  62. package/dist/vite/client.d.ts.map +1 -1
  63. package/dist/vite/client.js +132 -11
  64. package/dist/vite/client.js.map +1 -1
  65. package/package.json +1 -1
@@ -0,0 +1,519 @@
1
+ import { appApiPath, useActionMutation, useT } from "@agent-native/core/client";
2
+ import {
3
+ IconBrandFigma,
4
+ IconBrandGithub,
5
+ IconChevronRight,
6
+ IconCircleCheck,
7
+ IconCode,
8
+ IconHtml,
9
+ IconUpload,
10
+ } from "@tabler/icons-react";
11
+ import { useQueryClient } from "@tanstack/react-query";
12
+ import {
13
+ useCallback,
14
+ useRef,
15
+ useState,
16
+ type ClipboardEvent,
17
+ type ReactNode,
18
+ } from "react";
19
+ import { useNavigate } from "react-router";
20
+ import { toast } from "sonner";
21
+
22
+ import { Badge } from "@/components/ui/badge";
23
+ import { Button } from "@/components/ui/button";
24
+ import { Textarea } from "@/components/ui/textarea";
25
+ import { sendToDesignAgentChat } from "@/lib/agent-chat";
26
+ import { cn } from "@/lib/utils";
27
+
28
+ import type { DesignExtensionSlotContext } from "./DesignExtensionsPanel";
29
+
30
+ interface DesignImportPanelProps {
31
+ context: Pick<DesignExtensionSlotContext, "designId" | "viewMode">;
32
+ }
33
+
34
+ interface ImportResult {
35
+ designId?: string;
36
+ files?: Array<{ id: string; filename: string }>;
37
+ warnings?: string[];
38
+ error?: string;
39
+ }
40
+
41
+ type ImportMode = "figma-paste" | "fig-file" | "html";
42
+
43
+ function hasFigmaPayload(html: string): boolean {
44
+ return /\(figmeta\)|\(figma\)|data-metadata=|data-buffer=/i.test(html);
45
+ }
46
+
47
+ function looksLikeHtml(value: string): boolean {
48
+ return /<(html|body|main|section|div|article|header|footer|button|img)\b/i.test(
49
+ value,
50
+ );
51
+ }
52
+
53
+ function resultSummary(result: ImportResult | undefined, fallback: string) {
54
+ const count = result?.files?.length ?? 0;
55
+ if (count === 0) return fallback;
56
+ if (count === 1) return `Imported ${result!.files![0]!.filename}.`;
57
+ return `Imported ${count} screens.`;
58
+ }
59
+
60
+ export function DesignImportPanel({ context }: DesignImportPanelProps) {
61
+ const t = useT();
62
+ const navigate = useNavigate();
63
+ const queryClient = useQueryClient();
64
+ const importSource = useActionMutation("import-design-source");
65
+ const fileInputRef = useRef<HTMLInputElement | null>(null);
66
+ const htmlFileInputRef = useRef<HTMLInputElement | null>(null);
67
+ const [htmlText, setHtmlText] = useState("");
68
+ const [uploading, setUploading] = useState(false);
69
+ const [activeMode, setActiveMode] = useState<ImportMode | null>(null);
70
+ const [activeUploadName, setActiveUploadName] = useState<string | null>(null);
71
+ const [lastResult, setLastResult] = useState<ImportResult | null>(null);
72
+
73
+ const finishImport = useCallback(
74
+ async (result: ImportResult | undefined, fallback: string) => {
75
+ if (result?.error) throw new Error(result.error);
76
+ setLastResult(result ?? null);
77
+ await Promise.all([
78
+ queryClient.invalidateQueries({ queryKey: ["action", "get-design"] }),
79
+ queryClient.invalidateQueries({ queryKey: ["action"] }),
80
+ ]);
81
+ toast.success(resultSummary(result, fallback));
82
+ if (result?.warnings?.length) {
83
+ toast.warning(t("designEditor.import.warningsToast"), {
84
+ description: result.warnings[0],
85
+ });
86
+ }
87
+ navigate(`/design/${result?.designId ?? context.designId}?view=overview`);
88
+ },
89
+ [context.designId, navigate, queryClient, t],
90
+ );
91
+
92
+ const importHtmlString = useCallback(
93
+ (content: string, originalName?: string) => {
94
+ if (!looksLikeHtml(content)) {
95
+ toast.error(t("designEditor.import.errors.notHtml"));
96
+ return;
97
+ }
98
+ importSource.mutate(
99
+ {
100
+ designId: context.designId,
101
+ sourceType: "html-string",
102
+ content,
103
+ originalName,
104
+ },
105
+ {
106
+ onSuccess: (result: unknown) => {
107
+ void finishImport(
108
+ result as ImportResult,
109
+ t("designEditor.import.htmlSuccess"),
110
+ );
111
+ },
112
+ onError: (error: unknown) => {
113
+ toast.error(t("designEditor.import.errors.importFailed"), {
114
+ description:
115
+ error instanceof Error
116
+ ? error.message
117
+ : t("common.genericError"),
118
+ });
119
+ },
120
+ },
121
+ );
122
+ },
123
+ [context.designId, finishImport, importSource, t],
124
+ );
125
+
126
+ const handlePaste = useCallback(
127
+ (event: ClipboardEvent<HTMLDivElement>) => {
128
+ const html = event.clipboardData.getData("text/html");
129
+ const text = event.clipboardData.getData("text/plain");
130
+ const content = html || text;
131
+ if (!content) return;
132
+ if (hasFigmaPayload(content)) {
133
+ event.preventDefault();
134
+ importSource.mutate(
135
+ {
136
+ designId: context.designId,
137
+ sourceType: "figma-paste-html",
138
+ content,
139
+ originalName: "figma-paste.html",
140
+ },
141
+ {
142
+ onSuccess: (result: unknown) => {
143
+ void finishImport(
144
+ result as ImportResult,
145
+ t("designEditor.import.figmaSuccess"),
146
+ );
147
+ },
148
+ onError: (error: unknown) => {
149
+ toast.error(t("designEditor.import.errors.figmaPasteFailed"), {
150
+ description:
151
+ error instanceof Error
152
+ ? error.message
153
+ : t("common.genericError"),
154
+ });
155
+ },
156
+ },
157
+ );
158
+ return;
159
+ }
160
+ if (looksLikeHtml(content)) {
161
+ event.preventDefault();
162
+ importHtmlString(content, "pasted-html.html");
163
+ }
164
+ },
165
+ [context.designId, finishImport, importHtmlString, importSource, t],
166
+ );
167
+
168
+ const uploadFile = useCallback(
169
+ async (file: File) => {
170
+ setUploading(true);
171
+ setActiveUploadName(file.name);
172
+ const body = new FormData();
173
+ body.append("designId", context.designId);
174
+ body.append("file", file);
175
+ try {
176
+ const response = await fetch(
177
+ appApiPath(
178
+ `/api/import-design-file?designId=${encodeURIComponent(context.designId)}`,
179
+ ),
180
+ {
181
+ method: "POST",
182
+ body,
183
+ },
184
+ );
185
+ const result = (await response.json()) as ImportResult;
186
+ if (!response.ok) {
187
+ throw new Error(
188
+ result.error || t("designEditor.import.errors.uploadFailed"),
189
+ );
190
+ }
191
+ await finishImport(result, t("designEditor.import.uploadSuccess"));
192
+ } catch (error) {
193
+ toast.error(t("designEditor.import.errors.uploadFailed"), {
194
+ description:
195
+ error instanceof Error ? error.message : t("common.genericError"),
196
+ });
197
+ } finally {
198
+ setUploading(false);
199
+ setActiveUploadName(null);
200
+ }
201
+ },
202
+ [context.designId, finishImport, t],
203
+ );
204
+
205
+ const handleFigmaFileChange = useCallback(
206
+ (file: File | undefined) => {
207
+ if (!file) return;
208
+ setActiveMode("fig-file");
209
+ void uploadFile(file);
210
+ if (fileInputRef.current) fileInputRef.current.value = "";
211
+ },
212
+ [uploadFile],
213
+ );
214
+
215
+ const handleHtmlFileChange = useCallback(
216
+ async (file: File | undefined) => {
217
+ if (!file) return;
218
+ setActiveMode("html");
219
+ try {
220
+ importHtmlString(await file.text(), file.name);
221
+ } finally {
222
+ if (htmlFileInputRef.current) htmlFileInputRef.current.value = "";
223
+ }
224
+ },
225
+ [importHtmlString],
226
+ );
227
+
228
+ const askVisualEdit = useCallback(() => {
229
+ sendToDesignAgentChat({
230
+ message:
231
+ "Use the visual-edit skill to connect my local app to this Design project. Run the app if needed, call `npx @agent-native/core@latest design connect`, then add URL-backed screens to this design.",
232
+ } as Parameters<typeof sendToDesignAgentChat>[0]);
233
+ toast.success(t("designEditor.import.visualEditSent"));
234
+ }, [t]);
235
+
236
+ const busy = importSource.isPending || uploading;
237
+
238
+ return (
239
+ <div className="flex min-h-0 flex-1 flex-col bg-background">
240
+ <div className="flex h-16 shrink-0 items-center border-b border-border/60 px-4">
241
+ <div className="min-w-0">
242
+ <h3 className="truncate text-xl font-semibold tracking-tight text-foreground">
243
+ {t("designEditor.import.title")}
244
+ </h3>
245
+ <p className="mt-0.5 truncate text-xs text-muted-foreground">
246
+ {"Bring source screens into this design" /* i18n-ignore */}
247
+ </p>
248
+ </div>
249
+ </div>
250
+
251
+ <div className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pb-4 pt-4">
252
+ <div className="space-y-1">
253
+ <ImportSourceRow
254
+ id="figma-paste-import"
255
+ icon={<IconBrandFigma className="size-3.5" />}
256
+ title={t("designEditor.import.figmaPasteTitle")}
257
+ description={
258
+ "Copy a frame in Figma, then paste here." /* i18n-ignore */
259
+ }
260
+ isOpen={activeMode === "figma-paste"}
261
+ onToggle={() =>
262
+ setActiveMode((mode) =>
263
+ mode === "figma-paste" ? null : "figma-paste",
264
+ )
265
+ }
266
+ >
267
+ <div className="p-2.5">
268
+ <div
269
+ role="textbox"
270
+ tabIndex={0}
271
+ aria-label={t("designEditor.import.figmaPasteTarget")}
272
+ onPaste={handlePaste}
273
+ className={cn(
274
+ "flex min-h-24 cursor-text items-center justify-center rounded-md border border-dashed border-border bg-muted/30 px-3 py-4 text-center text-xs text-muted-foreground outline-none transition-colors",
275
+ "focus:border-primary/60 focus:bg-background focus:ring-2 focus:ring-primary/15",
276
+ )}
277
+ >
278
+ {t("designEditor.import.figmaPasteTarget")}
279
+ </div>
280
+ </div>
281
+ </ImportSourceRow>
282
+
283
+ <ImportSourceRow
284
+ id="fig-file-import"
285
+ icon={<IconUpload className="size-3.5" />}
286
+ title={t("designEditor.import.figUploadTitle")}
287
+ description={"Upload exported Figma frames." /* i18n-ignore */}
288
+ isOpen={activeMode === "fig-file"}
289
+ onToggle={() =>
290
+ setActiveMode((mode) => (mode === "fig-file" ? null : "fig-file"))
291
+ }
292
+ >
293
+ <div className="space-y-2 p-2.5">
294
+ <input
295
+ ref={fileInputRef}
296
+ type="file"
297
+ accept=".fig"
298
+ className="hidden"
299
+ onChange={(event) =>
300
+ handleFigmaFileChange(event.target.files?.[0])
301
+ }
302
+ />
303
+ <Button
304
+ size="sm"
305
+ variant="outline"
306
+ className="h-8 w-full justify-center"
307
+ disabled={busy}
308
+ onClick={() => fileInputRef.current?.click()}
309
+ >
310
+ {uploading
311
+ ? "Importing..." /* i18n-ignore */
312
+ : t("designEditor.import.chooseFigFile")}
313
+ </Button>
314
+ <p className="truncate text-[10px] leading-snug text-muted-foreground">
315
+ {
316
+ activeUploadName ??
317
+ "Export only the frames you need." /* i18n-ignore */
318
+ }
319
+ </p>
320
+ </div>
321
+ </ImportSourceRow>
322
+
323
+ <ImportSourceRow
324
+ id="html-import"
325
+ icon={<IconHtml className="size-3.5" />}
326
+ title={t("designEditor.import.htmlTitle")}
327
+ description={"Paste or choose a standalone file." /* i18n-ignore */}
328
+ isOpen={activeMode === "html"}
329
+ onToggle={() =>
330
+ setActiveMode((mode) => (mode === "html" ? null : "html"))
331
+ }
332
+ >
333
+ <div className="space-y-2 p-2.5">
334
+ <Textarea
335
+ value={htmlText}
336
+ onChange={(event) => setHtmlText(event.target.value)}
337
+ placeholder={t("designEditor.import.htmlPlaceholder")}
338
+ className="min-h-24 resize-none text-xs"
339
+ />
340
+ <div className="flex gap-1.5">
341
+ <Button
342
+ size="sm"
343
+ className="h-8 flex-1 px-2"
344
+ disabled={busy || !htmlText.trim()}
345
+ onClick={() => importHtmlString(htmlText, "html-import.html")}
346
+ >
347
+ {t("designEditor.import.importHtml")}
348
+ </Button>
349
+ <input
350
+ ref={htmlFileInputRef}
351
+ type="file"
352
+ accept=".html,.htm"
353
+ className="hidden"
354
+ onChange={(event) =>
355
+ handleHtmlFileChange(event.target.files?.[0])
356
+ }
357
+ />
358
+ <Button
359
+ size="sm"
360
+ variant="outline"
361
+ className="h-8 px-2"
362
+ disabled={busy}
363
+ onClick={() => htmlFileInputRef.current?.click()}
364
+ >
365
+ {t("designEditor.import.chooseHtmlFile")}
366
+ </Button>
367
+ </div>
368
+ </div>
369
+ </ImportSourceRow>
370
+ </div>
371
+
372
+ <div className="mt-5 border-t border-border/60 pt-4">
373
+ <p className="mb-2 text-xs font-medium text-muted-foreground">
374
+ {"More sources" /* i18n-ignore */}
375
+ </p>
376
+ <div className="space-y-1">
377
+ <CompactSourceRow
378
+ icon={<IconBrandGithub className="size-3.5" />}
379
+ title={t("designEditor.import.githubTitle")}
380
+ description={
381
+ "Repository import is coming soon." /* i18n-ignore */
382
+ }
383
+ badge={t("designEditor.import.comingSoon")}
384
+ />
385
+ <CompactSourceRow
386
+ icon={<IconCode className="size-3.5" />}
387
+ title={t("designEditor.import.localTitle")}
388
+ description={
389
+ "Connect a running app with visual-edit." /* i18n-ignore */
390
+ }
391
+ badge={t("designEditor.import.comingSoon")}
392
+ action={
393
+ <Button
394
+ size="sm"
395
+ variant="ghost"
396
+ className="h-7 px-2 text-[11px]"
397
+ onClick={askVisualEdit}
398
+ >
399
+ {t("designEditor.import.useVisualEditNow")}
400
+ </Button>
401
+ }
402
+ />
403
+ </div>
404
+ </div>
405
+
406
+ {lastResult?.files?.length ? (
407
+ <div className="mt-5 rounded-md border border-border/70 bg-muted/30 px-2.5 py-2">
408
+ <div className="flex items-center gap-1.5 text-xs font-medium text-foreground">
409
+ <IconCircleCheck className="size-3.5 text-muted-foreground" />
410
+ {t("designEditor.import.lastImport")}
411
+ </div>
412
+ <ul className="mt-1.5 space-y-0.5 text-[11px] text-muted-foreground">
413
+ {lastResult.files.slice(0, 3).map((file) => (
414
+ <li key={file.id} className="truncate">
415
+ {file.filename}
416
+ </li>
417
+ ))}
418
+ </ul>
419
+ </div>
420
+ ) : null}
421
+ </div>
422
+ </div>
423
+ );
424
+ }
425
+
426
+ function ImportSourceRow({
427
+ id,
428
+ icon,
429
+ title,
430
+ description,
431
+ isOpen,
432
+ onToggle,
433
+ children,
434
+ }: {
435
+ id: string;
436
+ icon: ReactNode;
437
+ title: string;
438
+ description: string;
439
+ isOpen: boolean;
440
+ onToggle: () => void;
441
+ children: ReactNode;
442
+ }) {
443
+ return (
444
+ <div className="overflow-hidden rounded-md">
445
+ <button
446
+ type="button"
447
+ aria-expanded={isOpen}
448
+ aria-controls={id}
449
+ onClick={onToggle}
450
+ className={cn(
451
+ "group flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-2.5 text-left transition-colors hover:bg-accent/60 active:bg-accent",
452
+ isOpen && "bg-accent/45",
453
+ )}
454
+ >
455
+ <span className="flex size-9 shrink-0 items-center justify-center rounded-md border border-border/70 bg-muted/70 text-muted-foreground transition-colors group-hover:border-border group-hover:bg-muted">
456
+ {icon}
457
+ </span>
458
+ <span className="min-w-0 flex-1">
459
+ <span className="block truncate text-sm font-medium leading-tight text-foreground">
460
+ {title}
461
+ </span>
462
+ <span className="mt-0.5 line-clamp-1 text-xs leading-snug text-muted-foreground">
463
+ {description}
464
+ </span>
465
+ </span>
466
+ <IconChevronRight
467
+ className={cn(
468
+ "size-4 shrink-0 text-muted-foreground transition-transform",
469
+ isOpen && "rotate-90",
470
+ )}
471
+ />
472
+ </button>
473
+ {isOpen ? (
474
+ <div
475
+ id={id}
476
+ className="mb-2 mt-1 overflow-hidden rounded-md border border-border/70 bg-background/70"
477
+ >
478
+ {children}
479
+ </div>
480
+ ) : null}
481
+ </div>
482
+ );
483
+ }
484
+
485
+ function CompactSourceRow({
486
+ icon,
487
+ title,
488
+ description,
489
+ badge,
490
+ action,
491
+ }: {
492
+ icon: ReactNode;
493
+ title: string;
494
+ description: string;
495
+ badge: string;
496
+ action?: ReactNode;
497
+ }) {
498
+ return (
499
+ <div className="flex items-center gap-2 rounded-md px-2 py-2 text-left opacity-85">
500
+ <span className="flex size-8 shrink-0 items-center justify-center rounded-md border border-border/60 bg-muted/50 text-muted-foreground">
501
+ {icon}
502
+ </span>
503
+ <span className="min-w-0 flex-1">
504
+ <span className="flex items-center gap-1.5">
505
+ <span className="truncate text-xs font-medium text-foreground">
506
+ {title}
507
+ </span>
508
+ <Badge variant="secondary" className="h-4 px-1 text-[9px]">
509
+ {badge}
510
+ </Badge>
511
+ </span>
512
+ <span className="mt-0.5 block truncate text-[10px] text-muted-foreground">
513
+ {description}
514
+ </span>
515
+ </span>
516
+ {action}
517
+ </div>
518
+ );
519
+ }
@@ -12,8 +12,15 @@ export interface NavigationState {
12
12
  editorView?: "single" | "overview";
13
13
  inspectorTab?: "design" | "tweaks" | "extensions";
14
14
  inspector?: "design" | "tweaks" | "extensions";
15
- leftPanel?: "file" | "agent" | "assets" | "tools" | "tokens";
16
- panel?: "file" | "agent" | "assets" | "tools" | "tokens";
15
+ leftPanel?:
16
+ | "file"
17
+ | "agent"
18
+ | "assets"
19
+ | "tools"
20
+ | "tokens"
21
+ | "import"
22
+ | "code";
23
+ panel?: "file" | "agent" | "assets" | "tools" | "tokens" | "import" | "code";
17
24
  fileId?: string;
18
25
  screenId?: string;
19
26
  filename?: string;
@@ -47,8 +54,15 @@ export interface DesignEditorCommand {
47
54
  viewMode?: "single" | "overview";
48
55
  inspectorTab?: "design" | "tweaks" | "extensions";
49
56
  inspector?: "design" | "tweaks" | "extensions";
50
- leftPanel?: "file" | "agent" | "assets" | "tools" | "tokens";
51
- panel?: "file" | "agent" | "assets" | "tools" | "tokens";
57
+ leftPanel?:
58
+ | "file"
59
+ | "agent"
60
+ | "assets"
61
+ | "tools"
62
+ | "tokens"
63
+ | "import"
64
+ | "code";
65
+ panel?: "file" | "agent" | "assets" | "tools" | "tokens" | "import" | "code";
52
66
  fileId?: string;
53
67
  screenId?: string;
54
68
  filename?: string;
@@ -88,13 +102,23 @@ function normalizeInspectorTab(
88
102
 
89
103
  function normalizeLeftPanel(
90
104
  value: unknown,
91
- ): "file" | "agent" | "assets" | "tools" | "tokens" | undefined {
105
+ ):
106
+ | "file"
107
+ | "agent"
108
+ | "assets"
109
+ | "tools"
110
+ | "tokens"
111
+ | "import"
112
+ | "code"
113
+ | undefined {
92
114
  if (value === "extensions") return "tools";
93
115
  return value === "file" ||
94
116
  value === "agent" ||
95
117
  value === "assets" ||
96
118
  value === "tools" ||
97
- value === "tokens"
119
+ value === "tokens" ||
120
+ value === "import" ||
121
+ value === "code"
98
122
  ? value
99
123
  : undefined;
100
124
  }
@@ -184,6 +208,8 @@ export function useNavigationState(enabled = true) {
184
208
  searchParams.get("inspector"),
185
209
  );
186
210
  if (inspectorTab) state.inspectorTab = inspectorTab;
211
+ const leftPanel = normalizeLeftPanel(searchParams.get("panel"));
212
+ if (leftPanel) state.leftPanel = leftPanel;
187
213
  const screen = searchParams.get("screen");
188
214
  if (screen) state.screen = screen;
189
215
  const fileId = searchParams.get("fileId");
@@ -248,6 +248,7 @@ const messages = {
248
248
  file: "檔案",
249
249
  agent: "代理",
250
250
  assets: "資源",
251
+ import: "匯入",
251
252
  tools: "工具",
252
253
  tokens: "權杖",
253
254
  label: "設計工作區",
@@ -272,6 +273,45 @@ const messages = {
272
273
  emptyHint: "新增設計權杖以重複使用顏色、間距等。",
273
274
  applying: "套用中…",
274
275
  },
276
+ import: {
277
+ title: "匯入",
278
+ description:
279
+ "將 Figma 選取內容、.fig 匯出檔或獨立 HTML 帶入為 Design 螢幕。",
280
+ figmaPasteTitle: "從 Figma 貼上",
281
+ figmaPasteDescription:
282
+ "在 Figma 複製畫框或圖層,然後聚焦此目標並貼上。可用時會直接匯入 Figma 中繼資料。",
283
+ figmaPasteTarget: "在此貼上 Figma 內容",
284
+ figUploadTitle: "上傳 .fig",
285
+ figUploadDescription:
286
+ "只匯出需要的畫框。含有許多嵌入圖片的大型檔案可能超過匯入限制。",
287
+ chooseFigFile: "選擇 .fig 檔案",
288
+ htmlTitle: "匯入 HTML",
289
+ htmlDescription:
290
+ "貼上或上傳獨立 HTML。Design 會將其儲存為新螢幕,不會注入到此編輯器 UI。",
291
+ htmlPlaceholder: "<main>在此貼上獨立 HTML...</main>",
292
+ importHtml: "匯入 HTML",
293
+ chooseHtmlFile: "選擇 HTML 檔案",
294
+ githubTitle: "GitHub",
295
+ githubDescription: "即將推出:直接從儲存庫匯入螢幕和元件。",
296
+ localTitle: "本機 app / VS Code",
297
+ localDescription: "即將推出:連接執行中的本機 app 並匯入 URL 螢幕。",
298
+ visualEditGuidance:
299
+ "目前可請代理使用 visual-edit。它可以執行 app、呼叫 `npx @agent-native/core@latest design connect`,並新增 URL 螢幕。",
300
+ useVisualEditNow: "立即使用 visual-edit",
301
+ comingSoon: "即將推出",
302
+ warningsToast: "匯入完成但有警告",
303
+ figmaSuccess: "已匯入 Figma 貼上內容",
304
+ htmlSuccess: "已匯入 HTML",
305
+ uploadSuccess: "已匯入檔案",
306
+ visualEditSent: "已將 visual-edit 請求傳送給代理",
307
+ lastImport: "上次匯入",
308
+ errors: {
309
+ notHtml: "請貼上或選擇有效的 HTML 以匯入。",
310
+ importFailed: "匯入失敗",
311
+ figmaPasteFailed: "Figma 貼上匯入失敗",
312
+ uploadFailed: "檔案上傳失敗",
313
+ },
314
+ },
275
315
  generationMayHaveStopped:
276
316
  "生成可能在建立檔案前停止。請檢視代理訊息或重試。",
277
317
  generationMayHaveStoppedToast: "生成可能在建立檔案前停止。",