@agent-native/core 0.84.57 → 0.84.58

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 (69) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +8 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/action.ts +11 -0
  5. package/corpus/core/src/agent/production-agent.ts +24 -46
  6. package/corpus/core/src/client/AssistantChat.tsx +69 -12
  7. package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
  8. package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +45 -2
  9. package/corpus/core/src/coding-tools/run-code.ts +1 -0
  10. package/corpus/core/src/integrations/webhook-handler.ts +10 -9
  11. package/corpus/core/src/server/action-discovery.ts +3 -0
  12. package/corpus/core/src/server/credential-provider.ts +11 -6
  13. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +29 -0
  14. package/corpus/templates/analytics/server/plugins/agent-chat.ts +9 -1
  15. package/corpus/templates/design/actions/list-design-extensions.ts +2 -2
  16. package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +11 -89
  17. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +314 -181
  18. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +92 -134
  19. package/corpus/templates/design/app/components/design/EditPanel.tsx +93 -19
  20. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -26
  21. package/corpus/templates/design/app/components/design/TokensPanel.tsx +220 -192
  22. package/corpus/templates/design/app/components/design/code-workbench-theme.ts +150 -0
  23. package/corpus/templates/design/app/i18n/zh-TW.ts +5 -7
  24. package/corpus/templates/design/app/i18n-data.ts +61 -77
  25. package/corpus/templates/design/app/lib/design-import.ts +4 -1
  26. package/corpus/templates/design/app/pages/DesignEditor.tsx +97 -48
  27. package/corpus/templates/design/changelog/2026-07-02-design-inspector-and-canvas-controls-stay-consistent-when-se.md +6 -0
  28. package/corpus/templates/design/changelog/2026-07-02-fixed-a-crash-when-opening-the-code-editor.md +6 -0
  29. package/corpus/templates/design/changelog/2026-07-02-fixed-motion-drawer-open-transition.md +6 -0
  30. package/corpus/templates/design/changelog/2026-07-02-improved-design-assets-panel.md +6 -0
  31. package/dist/action.d.ts +8 -0
  32. package/dist/action.d.ts.map +1 -1
  33. package/dist/action.js +3 -0
  34. package/dist/action.js.map +1 -1
  35. package/dist/agent/production-agent.d.ts +10 -11
  36. package/dist/agent/production-agent.d.ts.map +1 -1
  37. package/dist/agent/production-agent.js +21 -44
  38. package/dist/agent/production-agent.js.map +1 -1
  39. package/dist/client/AssistantChat.d.ts +1 -0
  40. package/dist/client/AssistantChat.d.ts.map +1 -1
  41. package/dist/client/AssistantChat.js +61 -11
  42. package/dist/client/AssistantChat.js.map +1 -1
  43. package/dist/client/MultiTabAssistantChat.js +1 -1
  44. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  45. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
  46. package/dist/client/conversation/use-near-bottom-autoscroll.js +43 -2
  47. package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
  48. package/dist/coding-tools/run-code.d.ts.map +1 -1
  49. package/dist/coding-tools/run-code.js +1 -0
  50. package/dist/coding-tools/run-code.js.map +1 -1
  51. package/dist/collab/awareness.d.ts +2 -2
  52. package/dist/collab/awareness.d.ts.map +1 -1
  53. package/dist/collab/routes.d.ts +1 -1
  54. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  55. package/dist/integrations/webhook-handler.d.ts +1 -0
  56. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  57. package/dist/integrations/webhook-handler.js +9 -9
  58. package/dist/integrations/webhook-handler.js.map +1 -1
  59. package/dist/notifications/routes.d.ts +3 -3
  60. package/dist/observability/routes.d.ts +5 -5
  61. package/dist/server/action-discovery.d.ts.map +1 -1
  62. package/dist/server/action-discovery.js +3 -0
  63. package/dist/server/action-discovery.js.map +1 -1
  64. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  65. package/dist/server/credential-provider.d.ts +4 -0
  66. package/dist/server/credential-provider.d.ts.map +1 -1
  67. package/dist/server/credential-provider.js +13 -7
  68. package/dist/server/credential-provider.js.map +1 -1
  69. package/package.json +1 -1
@@ -10,25 +10,18 @@ import {
10
10
  IconUpload,
11
11
  } from "@tabler/icons-react";
12
12
  import { useQueryClient } from "@tanstack/react-query";
13
- import {
14
- useCallback,
15
- useRef,
16
- useState,
17
- type ClipboardEvent,
18
- type ReactNode,
19
- } from "react";
13
+ import { useCallback, useRef, useState, type ReactNode } from "react";
20
14
  import { useNavigate } from "react-router";
21
15
  import { toast } from "sonner";
22
16
 
23
17
  import { Badge } from "@/components/ui/badge";
24
18
  import { Button } from "@/components/ui/button";
25
19
  import { Textarea } from "@/components/ui/textarea";
26
- import { sendToDesignAgentChat } from "@/lib/agent-chat";
27
20
  import {
28
- getFigmaClipboardContent,
29
21
  importResultSummary,
30
22
  looksLikeStandaloneHtml,
31
23
  VISUAL_EDIT_CONNECT_COMMAND,
24
+ VISUAL_EDIT_INSTALL_COMMAND,
32
25
  type ImportResult,
33
26
  } from "@/lib/design-import";
34
27
  import { cn } from "@/lib/utils";
@@ -107,49 +100,6 @@ export function DesignImportPanel({ context }: DesignImportPanelProps) {
107
100
  [context.designId, finishImport, importSource, t],
108
101
  );
109
102
 
110
- const handlePaste = useCallback(
111
- (event: ClipboardEvent<HTMLDivElement>) => {
112
- const html = event.clipboardData.getData("text/html");
113
- const text = event.clipboardData.getData("text/plain");
114
- const content = html || text;
115
- if (!content) return;
116
- const figmaContent = getFigmaClipboardContent(event.clipboardData);
117
- if (figmaContent) {
118
- event.preventDefault();
119
- importSource.mutate(
120
- {
121
- designId: context.designId,
122
- sourceType: "figma-paste-html",
123
- content: figmaContent,
124
- originalName: "figma-paste.html",
125
- },
126
- {
127
- onSuccess: (result: unknown) => {
128
- void finishImport(
129
- result as ImportResult,
130
- t("designEditor.import.figmaSuccess"),
131
- );
132
- },
133
- onError: (error: unknown) => {
134
- toast.error(t("designEditor.import.errors.figmaPasteFailed"), {
135
- description:
136
- error instanceof Error
137
- ? error.message
138
- : t("common.genericError"),
139
- });
140
- },
141
- },
142
- );
143
- return;
144
- }
145
- if (looksLikeStandaloneHtml(content)) {
146
- event.preventDefault();
147
- importHtmlString(content, "pasted-html.html");
148
- }
149
- },
150
- [context.designId, finishImport, importHtmlString, importSource, t],
151
- );
152
-
153
103
  const uploadFile = useCallback(
154
104
  async (file: File) => {
155
105
  setUploading(true);
@@ -210,46 +160,36 @@ export function DesignImportPanel({ context }: DesignImportPanelProps) {
210
160
  [importHtmlString],
211
161
  );
212
162
 
213
- const askVisualEdit = useCallback(() => {
214
- sendToDesignAgentChat({
215
- message:
216
- "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.",
217
- } as Parameters<typeof sendToDesignAgentChat>[0]);
218
- toast.success(t("designEditor.import.visualEditSent"));
219
- }, [t]);
220
-
221
- const copyVisualEditCommand = useCallback(async () => {
222
- try {
223
- await navigator.clipboard.writeText(VISUAL_EDIT_CONNECT_COMMAND);
224
- toast.success(t("designEditor.copied"));
225
- } catch {
226
- toast.error(t("designEditor.toasts.clipboardBlocked"));
227
- }
228
- }, [t]);
163
+ const copyVisualEditCommand = useCallback(
164
+ async (command: string) => {
165
+ try {
166
+ await navigator.clipboard.writeText(command);
167
+ toast.success(t("designEditor.copied"));
168
+ } catch {
169
+ toast.error(t("designEditor.toasts.clipboardBlocked"));
170
+ }
171
+ },
172
+ [t],
173
+ );
229
174
 
230
175
  const busy = importSource.isPending || uploading;
231
176
 
232
177
  return (
233
178
  <div className="flex min-h-0 flex-1 flex-col bg-background">
234
- <div className="flex h-14 shrink-0 items-center border-b border-border/60 px-3.5">
235
- <div className="min-w-0">
236
- <h3 className="truncate text-base font-semibold tracking-tight text-foreground">
237
- {t("designEditor.import.title")}
238
- </h3>
239
- <p className="mt-0.5 truncate text-[11px] text-muted-foreground">
240
- {"Bring source screens into this design" /* i18n-ignore */}
241
- </p>
242
- </div>
179
+ <div className="flex min-h-8 shrink-0 items-center border-b border-border/60 px-3">
180
+ <h3 className="min-w-0 flex-1 truncate text-xs font-semibold text-foreground">
181
+ {t("designEditor.import.title")}
182
+ </h3>
243
183
  </div>
244
184
 
245
- <div className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain px-3.5 pb-4 pt-3">
185
+ <div className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain px-3 pb-4 pt-3">
246
186
  <div className="space-y-0.5">
247
187
  <ImportSourceRow
248
188
  id="figma-paste-import"
249
189
  icon={<IconBrandFigma className="size-3.5" />}
250
190
  title={t("designEditor.import.figmaPasteTitle")}
251
191
  description={
252
- "Copy a frame in Figma, then paste here." /* i18n-ignore */
192
+ "Copy a frame in Figma, then paste into the canvas." /* i18n-ignore */
253
193
  }
254
194
  isOpen={activeMode === "figma-paste"}
255
195
  onToggle={() =>
@@ -258,20 +198,13 @@ export function DesignImportPanel({ context }: DesignImportPanelProps) {
258
198
  )
259
199
  }
260
200
  >
261
- <div className="p-2">
262
- <div
263
- role="textbox"
264
- tabIndex={0}
265
- data-hotkeys-scope="text"
266
- aria-label={t("designEditor.import.figmaPasteTarget")}
267
- onPaste={handlePaste}
268
- className={cn(
269
- "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",
270
- "focus:border-primary/60 focus:bg-background focus:ring-2 focus:ring-primary/15",
271
- )}
272
- >
273
- {t("designEditor.import.figmaPasteTarget")}
274
- </div>
201
+ <div className="space-y-1.5 p-2 text-[11px] leading-snug text-muted-foreground">
202
+ <p>{t("designEditor.import.figmaPasteDescription")}</p>
203
+ <p>
204
+ {
205
+ "Click the canvas first, then paste with the same shortcut you use for copied Design content." /* i18n-ignore */
206
+ }
207
+ </p>
275
208
  </div>
276
209
  </ImportSourceRow>
277
210
 
@@ -362,6 +295,46 @@ export function DesignImportPanel({ context }: DesignImportPanelProps) {
362
295
  </div>
363
296
  </div>
364
297
  </ImportSourceRow>
298
+
299
+ <ImportSourceRow
300
+ id="local-app-import"
301
+ icon={<IconCode className="size-3.5" />}
302
+ title={t("designEditor.import.localTitle")}
303
+ description={t("designEditor.import.localDescription")}
304
+ isOpen={activeMode === "local-app"}
305
+ onToggle={() =>
306
+ setActiveMode((mode) =>
307
+ mode === "local-app" ? null : "local-app",
308
+ )
309
+ }
310
+ >
311
+ <div className="space-y-2 p-2">
312
+ <p className="text-[11px] leading-snug text-muted-foreground">
313
+ {t("designEditor.import.visualEditGuidance")}{" "}
314
+ <a
315
+ href="/docs/template-design"
316
+ target="_blank"
317
+ rel="noreferrer"
318
+ className="font-medium text-foreground underline-offset-2 hover:underline"
319
+ >
320
+ {"Read the visual-edit docs." /* i18n-ignore */}
321
+ </a>
322
+ </p>
323
+ <VisualEditCommandRow
324
+ command={VISUAL_EDIT_INSTALL_COMMAND}
325
+ onCopy={copyVisualEditCommand}
326
+ />
327
+ <VisualEditCommandRow
328
+ command={VISUAL_EDIT_CONNECT_COMMAND}
329
+ onCopy={copyVisualEditCommand}
330
+ />
331
+ <p className="text-[10px] leading-snug text-muted-foreground">
332
+ {
333
+ "Replace <port> with the running app's local port." /* i18n-ignore */
334
+ }
335
+ </p>
336
+ </div>
337
+ </ImportSourceRow>
365
338
  </div>
366
339
 
367
340
  <div className="mt-4 border-t border-border/60 pt-3">
@@ -377,47 +350,6 @@ export function DesignImportPanel({ context }: DesignImportPanelProps) {
377
350
  }
378
351
  badge={t("designEditor.import.comingSoon")}
379
352
  />
380
- <ImportSourceRow
381
- id="local-app-import"
382
- icon={<IconCode className="size-3.5" />}
383
- title={t("designEditor.import.localTitle")}
384
- description={t("designEditor.import.localDescription")}
385
- isOpen={activeMode === "local-app"}
386
- onToggle={() =>
387
- setActiveMode((mode) =>
388
- mode === "local-app" ? null : "local-app",
389
- )
390
- }
391
- >
392
- <div className="space-y-2 p-2">
393
- <p className="text-[11px] leading-snug text-muted-foreground">
394
- {t("designEditor.import.visualEditGuidance")}
395
- </p>
396
- <div className="flex items-center gap-1.5 rounded-md border border-border/70 bg-muted/40 p-1.5">
397
- <code className="min-w-0 flex-1 truncate font-mono text-[10px] leading-5 text-foreground/80">
398
- {VISUAL_EDIT_CONNECT_COMMAND}
399
- </code>
400
- <Button
401
- type="button"
402
- size="sm"
403
- variant="ghost"
404
- className="h-6 shrink-0 px-1.5 text-[10px]"
405
- onClick={copyVisualEditCommand}
406
- >
407
- <IconCopy className="size-3" />
408
- {"Copy" /* i18n-ignore */}
409
- </Button>
410
- </div>
411
- <Button
412
- size="sm"
413
- variant="outline"
414
- className="h-7 w-full justify-center text-[11px]"
415
- onClick={askVisualEdit}
416
- >
417
- {t("designEditor.import.useVisualEditNow")}
418
- </Button>
419
- </div>
420
- </ImportSourceRow>
421
353
  </div>
422
354
  </div>
423
355
 
@@ -441,6 +373,32 @@ export function DesignImportPanel({ context }: DesignImportPanelProps) {
441
373
  );
442
374
  }
443
375
 
376
+ function VisualEditCommandRow({
377
+ command,
378
+ onCopy,
379
+ }: {
380
+ command: string;
381
+ onCopy: (command: string) => void;
382
+ }) {
383
+ return (
384
+ <div className="flex items-center gap-1.5 rounded-md border border-border/70 bg-muted/40 p-1.5">
385
+ <code className="min-w-0 flex-1 truncate font-mono text-[10px] leading-5 text-foreground/80">
386
+ {command}
387
+ </code>
388
+ <Button
389
+ type="button"
390
+ size="sm"
391
+ variant="ghost"
392
+ aria-label={"Copy command" /* i18n-ignore */}
393
+ className="h-6 shrink-0 px-1.5 text-[10px]"
394
+ onClick={() => onCopy(command)}
395
+ >
396
+ <IconCopy className="size-3" />
397
+ </Button>
398
+ </div>
399
+ );
400
+ }
401
+
444
402
  function ImportSourceRow({
445
403
  id,
446
404
  icon,
@@ -138,7 +138,7 @@ import type { StatesPanelProps } from "./StatesPanel";
138
138
  import { TweaksPanelContent } from "./TweaksPanel";
139
139
  import type { ElementInfo } from "./types";
140
140
 
141
- export type InspectorTab = "design" | "tweaks" | "extensions";
141
+ export type InspectorTab = "design" | "tweaks";
142
142
 
143
143
  const MIXED_VALUE = "Mixed";
144
144
 
@@ -206,6 +206,7 @@ function mixedElementFromSelection(
206
206
  interface EditPanelProps {
207
207
  selectedElement: ElementInfo | null;
208
208
  selectedElements?: ElementInfo[];
209
+ selectedScreenGeometry?: ScreenGeometrySelection | null;
209
210
  pageStyles?: Record<string, string>;
210
211
  zoom?: number;
211
212
  headerTrailing?: ReactNode;
@@ -216,7 +217,6 @@ interface EditPanelProps {
216
217
  tweakValues?: Record<string, string | number | boolean>;
217
218
  onTweakChange?: (id: string, value: string | number | boolean) => void;
218
219
  onRequestTweaks?: (anchor: HTMLElement) => void;
219
- extensionsPanel?: ReactNode;
220
220
  onStyleChange: (property: string, value: string) => void;
221
221
  onStylesChange?: (styles: Record<string, string>) => void;
222
222
  onExport?: (settings: ExportSettingsValue[]) => void;
@@ -288,6 +288,15 @@ interface EditPanelProps {
288
288
  aiActions?: ReactNode;
289
289
  }
290
290
 
291
+ export interface ScreenGeometrySelection {
292
+ id: string;
293
+ title: string;
294
+ x: number;
295
+ y: number;
296
+ width: number;
297
+ height: number;
298
+ }
299
+
291
300
  /**
292
301
  * Data backing the "Inspect code" popover. The parent resolves the selected
293
302
  * node's HTML and (for real-app sources) its source file location.
@@ -2959,16 +2968,87 @@ function SelectionHeader({
2959
2968
  );
2960
2969
  }
2961
2970
 
2971
+ function ScreenSelectionHeader({
2972
+ screen,
2973
+ }: {
2974
+ screen: ScreenGeometrySelection;
2975
+ }) {
2976
+ return (
2977
+ <div className="flex min-h-8 shrink-0 items-center justify-between gap-2 border-b border-border/90 px-3">
2978
+ <div className="flex min-w-0 items-center gap-1.5 text-left text-[13px] font-semibold text-foreground">
2979
+ <IconFrame className="size-3.5 shrink-0 text-muted-foreground" />
2980
+ <span className="truncate">{screen.title}</span>
2981
+ </div>
2982
+ </div>
2983
+ );
2984
+ }
2985
+
2986
+ function ScreenGeometryProperties({
2987
+ screen,
2988
+ }: {
2989
+ screen: ScreenGeometrySelection;
2990
+ }) {
2991
+ const t = useT();
2992
+ const noop = useCallback(() => {}, []);
2993
+
2994
+ return (
2995
+ <PanelSection title={t("editPanel.sections.positionLayout")}>
2996
+ <div className="space-y-1.5">
2997
+ <SubsectionLabel>{t("editPanel.labels.position")}</SubsectionLabel>
2998
+ <div className="grid grid-cols-2 gap-2">
2999
+ <ScrubInput
3000
+ label="X"
3001
+ value={Math.round(screen.x)}
3002
+ onChange={noop}
3003
+ unit="px"
3004
+ disabled
3005
+ inputClassName="h-6"
3006
+ />
3007
+ <ScrubInput
3008
+ label="Y"
3009
+ value={Math.round(screen.y)}
3010
+ onChange={noop}
3011
+ unit="px"
3012
+ disabled
3013
+ inputClassName="h-6"
3014
+ />
3015
+ </div>
3016
+ </div>
3017
+ <div className="space-y-1.5">
3018
+ <SubsectionLabel>
3019
+ {"Size" /* i18n-ignore design inspector label */}
3020
+ </SubsectionLabel>
3021
+ <div className="grid grid-cols-2 gap-2">
3022
+ <ScrubInput
3023
+ label="W"
3024
+ value={Math.round(screen.width)}
3025
+ onChange={noop}
3026
+ unit="px"
3027
+ disabled
3028
+ inputClassName="h-6"
3029
+ />
3030
+ <ScrubInput
3031
+ label="H"
3032
+ value={Math.round(screen.height)}
3033
+ onChange={noop}
3034
+ unit="px"
3035
+ disabled
3036
+ inputClassName="h-6"
3037
+ />
3038
+ </div>
3039
+ </div>
3040
+ </PanelSection>
3041
+ );
3042
+ }
3043
+
2962
3044
  function InspectorTabsHeader({
2963
3045
  activeTab,
2964
3046
  onActiveTabChange,
2965
3047
  trailing,
2966
- showExtensions,
2967
3048
  }: {
2968
3049
  activeTab: InspectorTab;
2969
3050
  onActiveTabChange: (tab: InspectorTab) => void;
2970
3051
  trailing?: ReactNode;
2971
- showExtensions?: boolean;
2972
3052
  }) {
2973
3053
  const t = useT();
2974
3054
 
@@ -2991,14 +3071,6 @@ function InspectorTabsHeader({
2991
3071
  >
2992
3072
  {t("designEditor.tweaks")}
2993
3073
  </TabsTrigger>
2994
- {showExtensions ? (
2995
- <TabsTrigger
2996
- value="extensions"
2997
- className="h-6 rounded-md px-1.5 !text-[11px] font-semibold text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-[var(--design-editor-panel-raised-bg)] data-[state=active]:text-foreground data-[state=active]:shadow-none"
2998
- >
2999
- {"Extensions" /* i18n-ignore design inspector tab */}
3000
- </TabsTrigger>
3001
- ) : null}
3002
3074
  </TabsList>
3003
3075
  </Tabs>
3004
3076
  {trailing ? <div className="shrink-0">{trailing}</div> : null}
@@ -6859,6 +6931,7 @@ export function ComponentSection({
6859
6931
  export function EditPanel({
6860
6932
  selectedElement,
6861
6933
  selectedElements,
6934
+ selectedScreenGeometry,
6862
6935
  pageStyles = {},
6863
6936
  headerTrailing,
6864
6937
  width = 256,
@@ -6868,7 +6941,6 @@ export function EditPanel({
6868
6941
  tweakValues = {},
6869
6942
  onTweakChange,
6870
6943
  onRequestTweaks,
6871
- extensionsPanel,
6872
6944
  onStyleChange,
6873
6945
  onStylesChange,
6874
6946
  onExport,
@@ -6978,7 +7050,6 @@ export function EditPanel({
6978
7050
  activeTab={activeTab}
6979
7051
  onActiveTabChange={handleActiveTabChange}
6980
7052
  trailing={headerTrailing}
6981
- showExtensions={!!extensionsPanel}
6982
7053
  />
6983
7054
 
6984
7055
  {activeTab === "design" ? (
@@ -6999,6 +7070,9 @@ export function EditPanel({
6999
7070
  : undefined
7000
7071
  }
7001
7072
  />
7073
+ {!inspectorElement && selectedScreenGeometry ? (
7074
+ <ScreenSelectionHeader screen={selectedScreenGeometry} />
7075
+ ) : null}
7002
7076
 
7003
7077
  <div
7004
7078
  className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain"
@@ -7093,7 +7167,11 @@ export function EditPanel({
7093
7167
  </div>
7094
7168
  ) : null}
7095
7169
 
7096
- {!inspectorElement && (
7170
+ {!inspectorElement && selectedScreenGeometry ? (
7171
+ <ScreenGeometryProperties screen={selectedScreenGeometry} />
7172
+ ) : null}
7173
+
7174
+ {!inspectorElement && !selectedScreenGeometry && (
7097
7175
  <PageProperties
7098
7176
  styles={pageStyles}
7099
7177
  onStyleChange={onStyleChange}
@@ -7263,10 +7341,6 @@ export function EditPanel({
7263
7341
  />
7264
7342
  </div>
7265
7343
  </div>
7266
- ) : activeTab === "extensions" && extensionsPanel ? (
7267
- <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
7268
- {extensionsPanel}
7269
- </div>
7270
7344
  ) : null}
7271
7345
  </div>
7272
7346
  );
@@ -409,10 +409,6 @@ const DRAG_THRESHOLD = 3;
409
409
  const FRAME_LABEL_HEIGHT = 28;
410
410
  const FRAME_HEADER_BUTTON_OUTSIDE_WIDTH = 260;
411
411
  const FRAME_HEADER_BUTTON_RESERVE = 116;
412
- const FRAME_HEADER_DIMENSIONS_WIDTH = 72;
413
- const FRAME_HEADER_TITLE_CHAR_WIDTH = 6.5;
414
- const FRAME_HEADER_MIN_TITLE_WIDTH = 28;
415
- const FRAME_HEADER_MAX_TITLE_WIDTH = 180;
416
412
  const TRANSFORM_BADGE_OFFSET = 12;
417
413
  const TRANSFORM_BADGE_EDGE_PADDING = 8;
418
414
  const TRANSFORM_BADGE_HEIGHT = 28;
@@ -433,7 +429,7 @@ const CHROME_OPACITY_TRANSITION = "opacity 150ms ease-out";
433
429
  const CHROME_BORDER_SETTLE_TRANSITION = `inset ${CHROME_SETTLE_MS}ms ease-out, border-width ${CHROME_SETTLE_MS}ms ease-out, border-radius ${CHROME_SETTLE_MS}ms ease-out, ${CHROME_OPACITY_TRANSITION}`;
434
430
  const SELECTION_BOX_SETTLE_TRANSITION = `border-width ${CHROME_SETTLE_MS}ms ease-out, border-radius ${CHROME_SETTLE_MS}ms ease-out, ${CHROME_OPACITY_TRANSITION}`;
435
431
  const CHROME_HANDLE_SETTLE_TRANSITION = `width ${CHROME_SETTLE_MS}ms ease-out, height ${CHROME_SETTLE_MS}ms ease-out, border-width ${CHROME_SETTLE_MS}ms ease-out, top ${CHROME_SETTLE_MS}ms ease-out, bottom ${CHROME_SETTLE_MS}ms ease-out, left ${CHROME_SETTLE_MS}ms ease-out, right ${CHROME_SETTLE_MS}ms ease-out, ${CHROME_OPACITY_TRANSITION}`;
436
- // Frame header (name + dimensions + "Full view" button) is counter-scaled via
432
+ // Frame header (name + "Full view" button) is counter-scaled via
437
433
  // transform to stay a fixed screen size; ease that scale on zoom-settle. opacity
438
434
  // is included so the button's hover-fade (transition-opacity) keeps working.
439
435
  const CHROME_LABEL_SETTLE_TRANSITION = `transform ${CHROME_SETTLE_MS}ms ease-out, ${CHROME_OPACITY_TRANSITION}`;
@@ -474,6 +470,14 @@ export function shouldBoardSurfaceCapturePointerEvents(args: {
474
470
  );
475
471
  }
476
472
 
473
+ export function shouldShowFrameFullViewButton(args: {
474
+ emphasized: boolean;
475
+ showFullView?: boolean;
476
+ childHoverActive?: boolean;
477
+ }) {
478
+ return args.emphasized || !!args.showFullView || !!args.childHoverActive;
479
+ }
480
+
477
481
  export function getBoardSurfaceLayerStyle(args: {
478
482
  geometry: FrameGeometry;
479
483
  interactive: boolean;
@@ -5676,10 +5680,16 @@ const Screen = memo(function Screen({
5676
5680
  (directlyHovered || isDirectlyHovered) &&
5677
5681
  !creationToolActive &&
5678
5682
  !canvasGestureActive;
5683
+ const childHoverActive =
5684
+ hasHoveredChild && !creationToolActive && !canvasGestureActive;
5679
5685
  const suppressFrameChromeForChild =
5680
5686
  hasHoveredChild && !directlyHovered && !isDirectlyHovered;
5681
5687
  const emphasized = isSelected || frameDirectlyHovered;
5682
- const fullViewVisible = emphasized || showFullView;
5688
+ const fullViewVisible = shouldShowFrameFullViewButton({
5689
+ emphasized,
5690
+ showFullView,
5691
+ childHoverActive,
5692
+ });
5683
5693
  const activeOrEmphasized = isActive || emphasized;
5684
5694
  const selectionOutlined = isSelected && !groupSelected;
5685
5695
  const showHoverChrome =
@@ -5712,16 +5722,6 @@ const Screen = memo(function Screen({
5712
5722
  64,
5713
5723
  frameScreenWidth - (fullViewOutsideFrame ? 8 : FRAME_HEADER_BUTTON_RESERVE),
5714
5724
  );
5715
- const estimatedTitleWidth = clamp(
5716
- display.length * FRAME_HEADER_TITLE_CHAR_WIDTH,
5717
- FRAME_HEADER_MIN_TITLE_WIDTH,
5718
- FRAME_HEADER_MAX_TITLE_WIDTH,
5719
- );
5720
- const showDimensions =
5721
- frameScreenWidth >=
5722
- estimatedTitleWidth +
5723
- FRAME_HEADER_DIMENSIONS_WIDTH +
5724
- (fullViewOutsideFrame ? 16 : FRAME_HEADER_BUTTON_RESERVE);
5725
5725
  const fullViewMaxWidth = fullViewOutsideFrame
5726
5726
  ? 120
5727
5727
  : Math.max(84, Math.min(180, frameScreenWidth * 0.46));
@@ -5806,14 +5806,6 @@ const Screen = memo(function Screen({
5806
5806
  >
5807
5807
  {display}
5808
5808
  </span>
5809
- {showDimensions ? (
5810
- <span
5811
- data-frame-dimensions
5812
- className="hidden shrink-0 text-[10px] tabular-nums text-muted-foreground/70 sm:inline"
5813
- >
5814
- {previewViewport.displayWidth} x {previewViewport.displayHeight}
5815
- </span>
5816
- ) : null}
5817
5809
  </div>
5818
5810
  <button
5819
5811
  type="button"
@@ -6551,12 +6543,12 @@ interface BoundsRect {
6551
6543
  bottom: number;
6552
6544
  }
6553
6545
 
6554
- interface ScreenViewportSize {
6546
+ export interface ScreenViewportSize {
6555
6547
  width: number;
6556
6548
  height: number;
6557
6549
  }
6558
6550
 
6559
- function getInitialFrameGeometry(
6551
+ export function getInitialFrameGeometry(
6560
6552
  index: number,
6561
6553
  metadata?: ScreenViewportSize,
6562
6554
  ): FrameGeometry {