@agent-native/core 0.95.0 → 0.95.1

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 (135) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +10 -0
  3. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +13 -0
  4. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +18 -0
  5. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +16 -0
  6. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +17 -0
  7. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +16 -0
  8. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +17 -0
  9. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +15 -0
  10. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +16 -0
  11. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +11 -0
  12. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +12 -0
  13. package/corpus/core/docs/content/template-design.mdx +16 -0
  14. package/corpus/core/package.json +2 -2
  15. package/corpus/core/src/agent/production-agent.ts +20 -0
  16. package/corpus/core/src/agent/thread-data-builder.ts +41 -17
  17. package/corpus/core/src/client/AssistantChat.tsx +250 -25
  18. package/corpus/core/src/client/chat/tool-call-display.tsx +1 -1
  19. package/corpus/core/src/client/chat/use-reconnect-reader-owner.ts +28 -0
  20. package/corpus/core/src/client/guided-questions.tsx +9 -1
  21. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +6 -7
  22. package/corpus/core/src/client/sse-event-processor.ts +90 -21
  23. package/corpus/core/src/connections/catalog.ts +2 -2
  24. package/corpus/core/src/deploy/build.ts +3 -0
  25. package/corpus/core/src/integrations/catalog.ts +2 -2
  26. package/corpus/core/src/server/auth.ts +1 -1
  27. package/corpus/core/src/server/credential-provider.ts +7 -3
  28. package/corpus/core/src/shared/cache-control.ts +10 -4
  29. package/corpus/templates/analytics/app/lib/data-sources.ts +5 -4
  30. package/corpus/templates/analytics/changelog/2026-07-10-slack-token-setup-marked-legacy.md +6 -0
  31. package/corpus/templates/analytics/server/lib/credential-keys.ts +6 -2
  32. package/corpus/templates/brain/changelog/2026-07-10-slack-backfill-token-marked-legacy.md +6 -0
  33. package/corpus/templates/brain/server/register-secrets.ts +2 -2
  34. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -1
  35. package/corpus/templates/clips/app/components/library/page-header.tsx +19 -3
  36. package/corpus/templates/clips/app/routes/_app.library._index.tsx +16 -12
  37. package/corpus/templates/clips/changelog/2026-07-10-new-recording-appears-once-when-the-library-sidebar-is-open.md +6 -0
  38. package/corpus/templates/clips/changelog/2026-07-10-slack-token-fallback-guidance.md +6 -0
  39. package/corpus/templates/clips/server/register-secrets.ts +1 -1
  40. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +0 -66
  41. package/corpus/templates/content/app/routes/_app.local-files.tsx +7 -47
  42. package/corpus/templates/content/changelog/2026-07-10-local-folders-now-wait-for-an-explicit-pull-before-content-r.md +6 -0
  43. package/corpus/templates/content/package.json +1 -1
  44. package/corpus/templates/design/actions/generate-design.ts +128 -31
  45. package/corpus/templates/design/actions/present-design-variants.ts +37 -9
  46. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +9 -3
  47. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +32 -10
  48. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +2 -2
  49. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -1
  50. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +7 -5
  51. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  52. package/corpus/templates/design/app/global.css +10 -0
  53. package/corpus/templates/design/app/hooks/use-question-flow.ts +9 -2
  54. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  55. package/corpus/templates/design/app/i18n-data.ts +21 -11
  56. package/corpus/templates/design/changelog/2026-07-10-editor-panels-are-easier-to-distinguish-and-design-intake-qu.md +6 -0
  57. package/corpus/templates/design/changelog/2026-07-10-interact-mode-now-stays-on-the-full-canvas-matches-edit-visu.md +6 -0
  58. package/corpus/templates/design/changelog/2026-07-10-new-web-designs-open-at-desktop-size-and-adapt-across-mobile.md +6 -0
  59. package/corpus/templates/design/changelog/2026-07-10-tweaks-now-link-to-full-documentation.md +6 -0
  60. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  61. package/corpus/templates/mail/app/i18n/ar-SA.ts +3 -3
  62. package/corpus/templates/mail/app/i18n/de-DE.ts +3 -3
  63. package/corpus/templates/mail/app/i18n/en-US.ts +4 -3
  64. package/corpus/templates/mail/app/i18n/es-ES.ts +3 -3
  65. package/corpus/templates/mail/app/i18n/fr-FR.ts +3 -3
  66. package/corpus/templates/mail/app/i18n/hi-IN.ts +3 -3
  67. package/corpus/templates/mail/app/i18n/ja-JP.ts +3 -3
  68. package/corpus/templates/mail/app/i18n/ko-KR.ts +3 -3
  69. package/corpus/templates/mail/app/i18n/pt-BR.ts +3 -3
  70. package/corpus/templates/mail/app/i18n/zh-CN.ts +3 -3
  71. package/corpus/templates/mail/app/i18n/zh-TW.ts +3 -3
  72. package/corpus/templates/mail/changelog/2026-07-10-slack-intake-marked-legacy.md +6 -0
  73. package/corpus/templates/mail/server/lib/env-config.ts +5 -4
  74. package/corpus/templates/plan/package.json +1 -1
  75. package/dist/agent/production-agent.d.ts.map +1 -1
  76. package/dist/agent/production-agent.js +20 -0
  77. package/dist/agent/production-agent.js.map +1 -1
  78. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  79. package/dist/agent/thread-data-builder.js +36 -18
  80. package/dist/agent/thread-data-builder.js.map +1 -1
  81. package/dist/client/AssistantChat.d.ts +3 -1
  82. package/dist/client/AssistantChat.d.ts.map +1 -1
  83. package/dist/client/AssistantChat.js +200 -28
  84. package/dist/client/AssistantChat.js.map +1 -1
  85. package/dist/client/chat/tool-call-display.js +1 -1
  86. package/dist/client/chat/tool-call-display.js.map +1 -1
  87. package/dist/client/chat/use-reconnect-reader-owner.d.ts +11 -0
  88. package/dist/client/chat/use-reconnect-reader-owner.d.ts.map +1 -0
  89. package/dist/client/chat/use-reconnect-reader-owner.js +21 -0
  90. package/dist/client/chat/use-reconnect-reader-owner.js.map +1 -0
  91. package/dist/client/guided-questions.d.ts.map +1 -1
  92. package/dist/client/guided-questions.js +5 -1
  93. package/dist/client/guided-questions.js.map +1 -1
  94. package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
  95. package/dist/client/integrations/IntegrationsPanel.js +5 -7
  96. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  97. package/dist/client/sse-event-processor.d.ts.map +1 -1
  98. package/dist/client/sse-event-processor.js +67 -20
  99. package/dist/client/sse-event-processor.js.map +1 -1
  100. package/dist/collab/routes.d.ts +1 -1
  101. package/dist/connections/catalog.d.ts +2 -2
  102. package/dist/connections/catalog.js +2 -2
  103. package/dist/connections/catalog.js.map +1 -1
  104. package/dist/deploy/build.d.ts.map +1 -1
  105. package/dist/deploy/build.js +3 -1
  106. package/dist/deploy/build.js.map +1 -1
  107. package/dist/integrations/catalog.js +2 -2
  108. package/dist/integrations/catalog.js.map +1 -1
  109. package/dist/notifications/routes.d.ts +2 -2
  110. package/dist/observability/routes.d.ts +1 -1
  111. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  112. package/dist/resources/handlers.d.ts +1 -1
  113. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  114. package/dist/server/auth.js +1 -1
  115. package/dist/server/auth.js.map +1 -1
  116. package/dist/server/credential-provider.d.ts +5 -3
  117. package/dist/server/credential-provider.d.ts.map +1 -1
  118. package/dist/server/credential-provider.js +7 -3
  119. package/dist/server/credential-provider.js.map +1 -1
  120. package/dist/shared/cache-control.d.ts +8 -6
  121. package/dist/shared/cache-control.d.ts.map +1 -1
  122. package/dist/shared/cache-control.js +8 -3
  123. package/dist/shared/cache-control.js.map +1 -1
  124. package/docs/content/locales/ar-SA/template-design.mdx +13 -0
  125. package/docs/content/locales/de-DE/template-design.mdx +18 -0
  126. package/docs/content/locales/es-ES/template-design.mdx +16 -0
  127. package/docs/content/locales/fr-FR/template-design.mdx +17 -0
  128. package/docs/content/locales/hi-IN/template-design.mdx +16 -0
  129. package/docs/content/locales/ja-JP/template-design.mdx +17 -0
  130. package/docs/content/locales/ko-KR/template-design.mdx +15 -0
  131. package/docs/content/locales/pt-BR/template-design.mdx +16 -0
  132. package/docs/content/locales/zh-CN/template-design.mdx +11 -0
  133. package/docs/content/locales/zh-TW/template-design.mdx +12 -0
  134. package/docs/content/template-design.mdx +16 -0
  135. package/package.json +2 -2
@@ -33,6 +33,7 @@ import {
33
33
  updateGenerationSessionWithSavedFiles,
34
34
  } from "../shared/generation-session.js";
35
35
  import { assertLockedLayersPreserved } from "../shared/locked-layers.js";
36
+ import { widthToPrefix } from "../shared/responsive-classes.js";
36
37
  import { annotateScreenHtmlForPersist } from "../shared/screen-annotation.js";
37
38
 
38
39
  /** Editor deep link so external agents can surface "Open design". */
@@ -54,6 +55,43 @@ function isRenderableDesignFile(file: {
54
55
  );
55
56
  }
56
57
 
58
+ type GenerationViewport = "mobile" | "tablet" | "desktop";
59
+
60
+ const DEFAULT_GENERATION_VIEWPORT: GenerationViewport = "desktop";
61
+ const GENERATION_VIEWPORT_SIZES: Record<
62
+ GenerationViewport,
63
+ { width: number; height: number }
64
+ > = {
65
+ mobile: { width: 390, height: 844 },
66
+ tablet: { width: 768, height: 1024 },
67
+ desktop: { width: 1440, height: 1024 },
68
+ };
69
+ const GENERATED_FRAME_GAP = 96;
70
+ const DEFAULT_RESPONSIVE_BREAKPOINTS = [390, 768, 1440].map((widthPx) => ({
71
+ id: `generated-${widthPx}`,
72
+ label: widthPx === 390 ? "Mobile" : widthPx === 768 ? "Tablet" : "Desktop",
73
+ widthPx,
74
+ prefix: widthToPrefix(widthPx),
75
+ }));
76
+
77
+ function hasBreakpointSet(value: unknown): boolean {
78
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
79
+ return false;
80
+ }
81
+ const breakpoints = (value as { breakpoints?: unknown }).breakpoints;
82
+ return Array.isArray(breakpoints) && breakpoints.length > 0;
83
+ }
84
+
85
+ function nextGeneratedFrameX(
86
+ frames: ReturnType<typeof parseCanvasFrameGeometryById>,
87
+ ): number {
88
+ return Object.values(frames).reduce((furthestRight, frame) => {
89
+ const x = frame.x ?? 0;
90
+ const width = frame.width ?? 0;
91
+ return Math.max(furthestRight, x + width + GENERATED_FRAME_GAP);
92
+ }, 0);
93
+ }
94
+
57
95
  function jsonValuesEqual(left: unknown, right: unknown): boolean {
58
96
  return JSON.stringify(left) === JSON.stringify(right);
59
97
  }
@@ -149,7 +187,15 @@ const generateDesignAgentParameters = {
149
187
  canvasFrames: {
150
188
  type: "string",
151
189
  description:
152
- "Optional JSON array of overview-canvas placements keyed by filename or fileId.",
190
+ "Optional JSON array of overview-canvas placements keyed by filename or fileId. " +
191
+ "Pass explicit x/y/width/height for every generated screen; desktop is 1440x1024.",
192
+ },
193
+ primaryViewport: {
194
+ type: "string",
195
+ enum: ["mobile", "tablet", "desktop"],
196
+ description:
197
+ "The requested primary form factor. Defaults to desktop (1440x1024). " +
198
+ "Set this from the intake answer when no explicit canvasFrames placement is supplied.",
153
199
  },
154
200
  },
155
201
  required: ["designId", "prompt", "files"],
@@ -171,6 +217,9 @@ const generateDesignAction = defineAction({
171
217
  "When `designSystemId` is provided, first use `get-design-system` and apply " +
172
218
  "its `agentContext` tokens/docs before writing the file content; do not " +
173
219
  "treat the id alone as enough design-system context. " +
220
+ "Every web design must be responsive. Use a desktop 1440x1024 primary " +
221
+ "canvas by default, or set `primaryViewport` for an explicitly mobile- or " +
222
+ "tablet-primary design; this action adds responsive editor breakpoints. " +
174
223
  "Do not report a design as ready until this action succeeds. " +
175
224
  "When adding multiple screens or states, pass canvasFrames with filenames " +
176
225
  "and x/y/width/height so the new screens appear placed on the overview canvas.",
@@ -304,6 +353,14 @@ const generateDesignAction = defineAction({
304
353
  "Reference each screen by filename or fileId and include x/y/width/height " +
305
354
  "from generate-screens regions or your planned canvas layout.",
306
355
  ),
356
+ primaryViewport: z
357
+ .enum(["mobile", "tablet", "desktop"])
358
+ .optional()
359
+ .default(DEFAULT_GENERATION_VIEWPORT)
360
+ .describe(
361
+ "Primary generated viewport. Defaults to desktop (1440x1024); set " +
362
+ "mobile or tablet only when that is the requested form factor.",
363
+ ),
307
364
  }),
308
365
  mcpApp: {
309
366
  compactCatalog: true,
@@ -323,6 +380,7 @@ const generateDesignAction = defineAction({
323
380
  projectType,
324
381
  tweaks,
325
382
  canvasFrames,
383
+ primaryViewport,
326
384
  }) => {
327
385
  await assertAccess("design", designId, "editor");
328
386
  if (designSystemId) {
@@ -515,7 +573,7 @@ const generateDesignAction = defineAction({
515
573
  }));
516
574
  await mutateDesignData({
517
575
  designId,
518
- mutate: (prevData) => {
576
+ mutate: (prevData, { updatedAt }) => {
519
577
  const mergedData: Record<string, unknown> = {
520
578
  ...prevData,
521
579
  lastPrompt: prompt,
@@ -525,34 +583,74 @@ const generateDesignAction = defineAction({
525
583
  if (normalizedTweaks !== undefined) {
526
584
  mergedData.tweaks = normalizedTweaks;
527
585
  }
528
- if (canvasFrames !== undefined) {
529
- const savedByFileId = new Map(
530
- savedFiles.map((file) => [file.id, file]),
531
- );
532
- const savedByFilename = new Map(
533
- savedFiles.map((file) => [file.filename, file]),
534
- );
535
- const existingByFileId = new Map(
536
- existingFiles.map((file) => [file.id, file]),
537
- );
538
- const merged = mergeCanvasFramePlacements({
539
- existing: prevData.canvasFrames,
540
- placements: canvasFrames,
541
- resolveFileId: (placement) => {
542
- if (placement.fileId) {
543
- return savedByFileId.has(placement.fileId) ||
544
- existingByFileId.has(placement.fileId)
545
- ? placement.fileId
546
- : undefined;
547
- }
548
- return placement.filename
549
- ? (savedByFilename.get(placement.filename)?.id ??
550
- existingByName.get(placement.filename)?.id)
586
+ const savedByFileId = new Map(
587
+ savedFiles.map((file) => [file.id, file]),
588
+ );
589
+ const savedByFilename = new Map(
590
+ savedFiles.map((file) => [file.filename, file]),
591
+ );
592
+ const existingByFileId = new Map(
593
+ existingFiles.map((file) => [file.id, file]),
594
+ );
595
+ const merged = mergeCanvasFramePlacements({
596
+ existing: prevData.canvasFrames,
597
+ placements: canvasFrames ?? [],
598
+ resolveFileId: (placement) => {
599
+ if (placement.fileId) {
600
+ return savedByFileId.has(placement.fileId) ||
601
+ existingByFileId.has(placement.fileId)
602
+ ? placement.fileId
551
603
  : undefined;
552
- },
604
+ }
605
+ return placement.filename
606
+ ? (savedByFilename.get(placement.filename)?.id ??
607
+ existingByName.get(placement.filename)?.id)
608
+ : undefined;
609
+ },
610
+ });
611
+ const viewport = GENERATION_VIEWPORT_SIZES[primaryViewport];
612
+ let nextX = nextGeneratedFrameX(merged.canvasFrames);
613
+ const generationFrames = [...merged.placedFrames];
614
+ for (const file of savedFiles) {
615
+ const source = files.find(
616
+ (candidate) => candidate.filename === file.filename,
617
+ );
618
+ if (!source || !isRenderableDesignFile(source)) continue;
619
+ const current = merged.canvasFrames[file.id] ?? {};
620
+ if (
621
+ current.x !== undefined &&
622
+ current.y !== undefined &&
623
+ current.width !== undefined &&
624
+ current.height !== undefined
625
+ ) {
626
+ continue;
627
+ }
628
+ const frame = {
629
+ x: current.x ?? nextX,
630
+ y: current.y ?? 0,
631
+ width: current.width ?? viewport.width,
632
+ height: current.height ?? viewport.height,
633
+ z: current.z ?? generationFrames.length,
634
+ ...(current.rotation === undefined
635
+ ? {}
636
+ : { rotation: current.rotation }),
637
+ };
638
+ merged.canvasFrames[file.id] = frame;
639
+ generationFrames.push({
640
+ fileId: file.id,
641
+ filename: file.filename,
642
+ frame,
553
643
  });
554
- mergedData.canvasFrames = merged.canvasFrames;
555
- placedFrames = merged.placedFrames;
644
+ nextX = frame.x + frame.width + GENERATED_FRAME_GAP;
645
+ }
646
+ mergedData.canvasFrames = merged.canvasFrames;
647
+ placedFrames = generationFrames;
648
+ if (!hasBreakpointSet(mergedData.breakpointSet)) {
649
+ mergedData.breakpointSet = {
650
+ id: "generated-responsive",
651
+ breakpoints: DEFAULT_RESPONSIVE_BREAKPOINTS,
652
+ };
653
+ mergedData.breakpointSetUpdatedAt = updatedAt;
556
654
  }
557
655
  return mergedData;
558
656
  },
@@ -566,12 +664,10 @@ const generateDesignAction = defineAction({
566
664
  ) {
567
665
  return false;
568
666
  }
569
- if (canvasFrames === undefined) return true;
570
-
571
667
  const currentFrames = parseCanvasFrameGeometryById(
572
668
  current.canvasFrames,
573
669
  );
574
- return Boolean(
670
+ const framesApplied = Boolean(
575
671
  placedFrames?.every(({ fileId, frame }) => {
576
672
  const currentFrame = currentFrames[fileId];
577
673
  return (
@@ -583,6 +679,7 @@ const generateDesignAction = defineAction({
583
679
  );
584
680
  }),
585
681
  );
682
+ return framesApplied && hasBreakpointSet(current.breakpointSet);
586
683
  },
587
684
  });
588
685
 
@@ -19,6 +19,7 @@ import {
19
19
  type CanvasFramePlacement,
20
20
  } from "../shared/canvas-frames.js";
21
21
  import { isUniqueConstraintViolation } from "../shared/db-conflict.js";
22
+ import { widthToPrefix } from "../shared/responsive-classes.js";
22
23
  import { annotateScreenHtmlForPersist } from "../shared/screen-annotation.js";
23
24
 
24
25
  const VARIANT_GAP = 96;
@@ -27,8 +28,22 @@ const MOBILE_WIDTH = 390;
27
28
  const MOBILE_HEIGHT = 844;
28
29
  const TABLET_WIDTH = 768;
29
30
  const TABLET_HEIGHT = 1024;
30
- const DESKTOP_WIDTH = 1280;
31
- const DESKTOP_HEIGHT = 900;
31
+ const DESKTOP_WIDTH = 1440;
32
+ const DESKTOP_HEIGHT = 1024;
33
+ const DEFAULT_RESPONSIVE_BREAKPOINTS = [390, 768, 1440].map((widthPx) => ({
34
+ id: `generated-${widthPx}`,
35
+ label: widthPx === 390 ? "Mobile" : widthPx === 768 ? "Tablet" : "Desktop",
36
+ widthPx,
37
+ prefix: widthToPrefix(widthPx),
38
+ }));
39
+
40
+ function hasBreakpointSet(value: unknown): boolean {
41
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
42
+ return false;
43
+ }
44
+ const breakpoints = (value as { breakpoints?: unknown }).breakpoints;
45
+ return Array.isArray(breakpoints) && breakpoints.length > 0;
46
+ }
32
47
 
33
48
  function designDeepLink(designId: string): string {
34
49
  return buildDeepLink({
@@ -951,6 +966,15 @@ export default defineAction({
951
966
  canvasFrames: mergedFrames.canvasFrames,
952
967
  screenMetadata: previousMetadata,
953
968
  designVariantSets: previousVariantSets,
969
+ ...(hasBreakpointSet(current.breakpointSet)
970
+ ? {}
971
+ : {
972
+ breakpointSet: {
973
+ id: "generated-responsive",
974
+ breakpoints: DEFAULT_RESPONSIVE_BREAKPOINTS,
975
+ },
976
+ breakpointSetUpdatedAt: updatedAt,
977
+ }),
954
978
  updatedAt,
955
979
  };
956
980
  },
@@ -968,13 +992,17 @@ export default defineAction({
968
992
  ? variantSets[variantSetId]
969
993
  : null;
970
994
  const persistedScreens = Array.isArray(set?.screens) ? set.screens : [];
971
- return screens.every(
972
- (screen) =>
973
- isRecord(canvasFrames[screen.id]) &&
974
- isRecord(metadata[screen.id]) &&
975
- persistedScreens.some(
976
- (persisted) => isRecord(persisted) && persisted.id === screen.id,
977
- ),
995
+ return (
996
+ hasBreakpointSet(current.breakpointSet) &&
997
+ screens.every(
998
+ (screen) =>
999
+ isRecord(canvasFrames[screen.id]) &&
1000
+ isRecord(metadata[screen.id]) &&
1001
+ persistedScreens.some(
1002
+ (persisted) =>
1003
+ isRecord(persisted) && persisted.id === screen.id,
1004
+ ),
1005
+ )
978
1006
  );
979
1007
  },
980
1008
  });
@@ -249,9 +249,6 @@ const EDITOR_BRIDGE_VAR_NAMES = [
249
249
  "--design-editor-component-strong-color",
250
250
  "--design-editor-component-contrast-color",
251
251
  "--design-editor-measure-color",
252
- "--background",
253
- "--foreground",
254
- "--border",
255
252
  ];
256
253
 
257
254
  function readEditorBridgeThemeVars(): Record<string, string> {
@@ -1949,6 +1946,15 @@ export function DesignCanvas({
1949
1946
  // iframe document here; the bridge replays inspector state after that load.
1950
1947
  }, [content, contentKey, runtimeReplacementContent, runtimeReplacementKey]);
1951
1948
 
1949
+ useEffect(() => {
1950
+ if (!interactMode) return;
1951
+ // Interact renders the authoritative persisted source because the editor
1952
+ // bridge is intentionally absent. Keep that same source as the next
1953
+ // edit-mode baseline so leaving Interact cannot resurrect the older
1954
+ // bridge-managed snapshot and make the design visibly jump backward.
1955
+ setRenderedContent(content);
1956
+ }, [content, interactMode]);
1957
+
1952
1958
  usePinchZoom({
1953
1959
  containerRef: scrollContainerRef,
1954
1960
  zoom,
@@ -42,6 +42,7 @@ import {
42
42
  type PenPath,
43
43
  } from "@shared/pen-path";
44
44
  import {
45
+ IconArrowsMaximize,
45
46
  IconCopy,
46
47
  IconDots,
47
48
  IconHandClick,
@@ -393,6 +394,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
393
394
  hiddenScreenIds = EMPTY_SCREEN_IDS,
394
395
  lockedScreenIds = EMPTY_SCREEN_IDS,
395
396
  fullViewScreenIds,
397
+ interactMode = false,
396
398
  activeScreenHasHoveredChild = false,
397
399
  hoveredChildScreenId,
398
400
  directlyHoveredScreenId,
@@ -7531,8 +7533,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
7531
7533
  }}
7532
7534
  editorChromeScaleX={canvasZoom / 100}
7533
7535
  editorChromeScaleY={canvasZoom / 100}
7534
- editMode={boardEditMode}
7535
- interactMode={false}
7536
+ editMode={boardEditMode && !interactMode}
7537
+ interactMode={interactMode}
7536
7538
  scaleMode={boardIsActive && effectiveTool === "scale"}
7537
7539
  clearSelectionRequest={boardClearSelectionRequest}
7538
7540
  selectedSelector={
@@ -7602,6 +7604,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
7602
7604
  !isBreakpointSelectionTarget(screen)
7603
7605
  }
7604
7606
  showFullView={fullViewIdSet.has(screen.id)}
7607
+ interactMode={interactMode}
7605
7608
  isDirectlyHovered={screen.id === directlyHoveredScreenId}
7606
7609
  isFileDragOver={
7607
7610
  fileDragOverFrameId !== null &&
@@ -8859,6 +8862,7 @@ interface ScreenProps {
8859
8862
  isSelected: boolean;
8860
8863
  isTopScreen: boolean;
8861
8864
  showFullView: boolean;
8865
+ interactMode: boolean;
8862
8866
  isDirectlyHovered: boolean;
8863
8867
  /** True while a native OS file drag is hovering this frame (Figma parity §1). */
8864
8868
  isFileDragOver: boolean;
@@ -8926,6 +8930,7 @@ const Screen = memo(function Screen({
8926
8930
  isSelected,
8927
8931
  isTopScreen,
8928
8932
  showFullView,
8933
+ interactMode,
8929
8934
  isDirectlyHovered,
8930
8935
  isFileDragOver,
8931
8936
  hasHoveredChild,
@@ -9022,9 +9027,12 @@ const Screen = memo(function Screen({
9022
9027
  const frameLabelHeight = FRAME_LABEL_HEIGHT * chromeScale;
9023
9028
  const frameScreenWidth = geometry.width / Math.max(chromeScale, 0.001);
9024
9029
  // Keep frame actions inside their own frame so closely spaced screens cannot
9025
- // cover one another. Narrow frames collapse Interact to its familiar icon;
9030
+ // cover one another. Narrow frames collapse the action to its familiar icon;
9026
9031
  // the accessible name and native tooltip preserve the action's meaning.
9027
9032
  const compactFullView = frameScreenWidth < FRAME_HEADER_BUTTON_COMPACT_WIDTH;
9033
+ const frameActionLabel = interactMode
9034
+ ? t("multiScreenCanvas.fullView")
9035
+ : t("designEditor.modes.interact");
9028
9036
  const labelInfoMaxWidth = Math.max(
9029
9037
  64,
9030
9038
  frameScreenWidth -
@@ -9157,8 +9165,8 @@ const Screen = memo(function Screen({
9157
9165
  transformOrigin: "right center",
9158
9166
  transition: getChromeLabelTransition(chromeSettling),
9159
9167
  }}
9160
- aria-label={t("designEditor.modes.interact")}
9161
- title={t("designEditor.modes.interact")}
9168
+ aria-label={frameActionLabel}
9169
+ title={frameActionLabel}
9162
9170
  onClick={(event) => onEdit(screen.id, event)}
9163
9171
  onMouseDown={(event) => {
9164
9172
  event.preventDefault();
@@ -9167,9 +9175,13 @@ const Screen = memo(function Screen({
9167
9175
  onMouseEnter={() => updateDirectHover(true)}
9168
9176
  onMouseLeave={() => updateDirectHover(false)}
9169
9177
  >
9170
- <IconHandClick className="size-3 shrink-0" />
9178
+ {interactMode ? (
9179
+ <IconArrowsMaximize className="size-3 shrink-0" />
9180
+ ) : (
9181
+ <IconHandClick className="size-3 shrink-0" />
9182
+ )}
9171
9183
  <span className={cn("truncate", compactFullView && "sr-only")}>
9172
- {t("designEditor.modes.interact")}
9184
+ {frameActionLabel}
9173
9185
  </span>
9174
9186
  </button>
9175
9187
  </div>
@@ -9394,6 +9406,7 @@ const Screen = memo(function Screen({
9394
9406
  renderBreakpointContent={renderBreakpointContent}
9395
9407
  activeBreakpointWidth={screen.activeBreakpointWidth}
9396
9408
  isScreenSelected={isSelected}
9409
+ interactMode={interactMode}
9397
9410
  penActive={penActive}
9398
9411
  creationToolActive={creationToolActive}
9399
9412
  cullTier={cullTier}
@@ -9518,6 +9531,7 @@ function BreakpointPreviewRow({
9518
9531
  renderBreakpointContent,
9519
9532
  activeBreakpointWidth,
9520
9533
  isScreenSelected,
9534
+ interactMode,
9521
9535
  penActive,
9522
9536
  creationToolActive,
9523
9537
  cullTier,
@@ -9559,6 +9573,7 @@ function BreakpointPreviewRow({
9559
9573
  * mirrors `Screen`'s own `isSelected`, used so a breakpoint frame's chrome
9560
9574
  * reads as "part of a selected group" the same way the base frame does. */
9561
9575
  isScreenSelected: boolean;
9576
+ interactMode: boolean;
9562
9577
  penActive: boolean;
9563
9578
  creationToolActive: boolean;
9564
9579
  /** Uses the owning screen's exact culling lifecycle: never-seen/evicted
@@ -9595,6 +9610,9 @@ function BreakpointPreviewRow({
9595
9610
  canEdit?: boolean;
9596
9611
  }) {
9597
9612
  const t = useT();
9613
+ const frameActionLabel = interactMode
9614
+ ? t("multiScreenCanvas.fullView")
9615
+ : t("designEditor.modes.interact");
9598
9616
  const breakpointWidths = screen.breakpointWidths ?? [];
9599
9617
  // Place additional frames to the right of the primary, starting after the gap
9600
9618
  let offsetX = primaryGeometry.width + BREAKPOINT_FRAME_GAP;
@@ -9884,8 +9902,8 @@ function BreakpointPreviewRow({
9884
9902
  transform: `scale(${chromeScale})`,
9885
9903
  transformOrigin: "right center",
9886
9904
  }}
9887
- aria-label={t("designEditor.modes.interact")}
9888
- title={t("designEditor.modes.interact")}
9905
+ aria-label={frameActionLabel}
9906
+ title={frameActionLabel}
9889
9907
  onClick={(e) => {
9890
9908
  e.stopPropagation();
9891
9909
  activateThisFrame(e);
@@ -9896,7 +9914,11 @@ function BreakpointPreviewRow({
9896
9914
  e.stopPropagation();
9897
9915
  }}
9898
9916
  >
9899
- <IconHandClick className="size-3" />
9917
+ {interactMode ? (
9918
+ <IconArrowsMaximize className="size-3" />
9919
+ ) : (
9920
+ <IconHandClick className="size-3" />
9921
+ )}
9900
9922
  </button>
9901
9923
  ) : null}
9902
9924
  <span
@@ -340,7 +340,7 @@ function OptionButton({
340
340
  compact ? "px-2.5 py-1.5" : "px-3 py-2",
341
341
  selected
342
342
  ? "border-[var(--design-editor-accent-color)] bg-[var(--design-editor-selection-color)] text-foreground"
343
- : "border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-foreground hover:bg-[var(--design-editor-panel-raised-bg)]",
343
+ : "border-[var(--design-editor-control-border)] bg-[var(--design-editor-question-option-bg)] text-foreground hover:bg-[var(--design-editor-control-bg)]",
344
344
  )}
345
345
  >
346
346
  <span
@@ -418,7 +418,7 @@ function ColorOptions({
418
418
  "group inline-flex min-h-8 min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-md border px-2.5 py-1.5 text-start transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)] focus-visible:ring-offset-0",
419
419
  selected
420
420
  ? "border-[var(--design-editor-accent-color)] bg-[var(--design-editor-selection-color)] text-foreground"
421
- : "border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-foreground hover:bg-[var(--design-editor-panel-raised-bg)]",
421
+ : "border-[var(--design-editor-control-border)] bg-[var(--design-editor-question-option-bg)] text-foreground hover:bg-[var(--design-editor-control-bg)]",
422
422
  )}
423
423
  >
424
424
  <span
@@ -77,7 +77,15 @@ export function TweaksPanelContent({
77
77
  data-tweaks-help
78
78
  className="mt-2 border-t border-border/60 pt-2 !text-[10px] leading-relaxed text-muted-foreground/70"
79
79
  >
80
- {t("designEditor.tweaksHelp")}
80
+ {t("designEditor.tweaksHelp")}{" "}
81
+ <a
82
+ href="/docs/template-design#tweaks"
83
+ target="_blank"
84
+ rel="noreferrer"
85
+ className="font-medium text-foreground/80 underline-offset-2 hover:text-foreground hover:underline"
86
+ >
87
+ {t("designEditor.tweaksDocs")}
88
+ </a>
81
89
  </p>
82
90
  </div>
83
91
  );
@@ -910,10 +910,12 @@ declare var __RUNTIME_LAYER_SNAPSHOT_ENABLED__: boolean;
910
910
 
911
911
  function selectionTargetForHit(hit: Element | null): Element | null {
912
912
  if (!hit || isDocumentRootElement(hit)) return hit;
913
- if (selectedEl && hit !== selectedEl && selectedEl.contains(hit))
914
- return hit;
915
- if (hasStableOwnSource(hit)) return hit;
916
- return closestStableSourceElement(hit) || hit;
913
+ // Select the deepest element under the pointer on the first click. The
914
+ // bridge can mint a pending node id and build a source-equivalent selector
915
+ // for id-less descendants, so climbing to the nearest tagged ancestor is
916
+ // no longer necessary and makes ordinary list labels select their parent
917
+ // container instead.
918
+ return hit;
917
919
  }
918
920
 
919
921
  function freshRuntimeNodeId(prefix: string): string {
@@ -1929,7 +1931,7 @@ declare var __RUNTIME_LAYER_SNAPSHOT_ENABLED__: boolean;
1929
1931
  var transformBadge = document.createElement("div");
1930
1932
  transformBadge.setAttribute("data-agent-native-transform-badge", "");
1931
1933
  transformBadge.style.cssText =
1932
- "position:fixed;z-index:100000;display:none;pointer-events:none;border:1px solid hsl(var(--border));border-radius:4px;background:hsl(var(--background) / 0.96);color:hsl(var(--foreground));font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;padding:3px 5px;box-shadow:0 8px 20px color-mix(in srgb, hsl(var(--foreground)) 16%, transparent);";
1934
+ "position:fixed;z-index:100000;display:none;pointer-events:none;border:1px solid rgba(255,255,255,0.16);border-radius:4px;background:rgba(24,24,27,0.96);color:rgba(255,255,255,0.96);font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;padding:3px 5px;box-shadow:0 8px 20px rgba(0,0,0,0.28);";
1933
1935
  document.body.appendChild(transformBadge);
1934
1936
 
1935
1937
  var spacingBadge = document.createElement("div");
@@ -130,6 +130,9 @@ export interface MultiScreenCanvasProps {
130
130
  * transformed directly from the overview canvas. */
131
131
  lockedScreenIds?: ReadonlySet<string> | readonly string[];
132
132
  fullViewScreenIds?: string[];
133
+ /** Lets every live frame receive native pointer interaction while the
134
+ * overview camera and frame chrome remain available. */
135
+ interactMode?: boolean;
133
136
  activeScreenHasHoveredChild?: boolean;
134
137
  hoveredChildScreenId?: string | null;
135
138
  directlyHoveredScreenId?: string | null;
@@ -84,6 +84,11 @@
84
84
  --design-editor-panel-raised-bg: hsl(var(--secondary));
85
85
  --design-editor-control-bg: hsl(var(--muted));
86
86
  --design-editor-control-border: hsl(var(--border));
87
+ --design-editor-question-option-bg: color-mix(
88
+ in srgb,
89
+ var(--design-editor-control-bg) 58%,
90
+ transparent
91
+ );
87
92
  --design-editor-canvas-bg: hsl(0 0% 92%);
88
93
  --design-editor-skeleton-canvas-bg: hsl(0 0% 92%);
89
94
  --design-editor-skeleton-panel-ghost-bg: color-mix(
@@ -162,6 +167,11 @@
162
167
  --design-editor-panel-raised-bg: hsl(0 0% 18%);
163
168
  --design-editor-control-bg: hsl(0 0% 18%);
164
169
  --design-editor-control-border: hsl(0 0% 24%);
170
+ --design-editor-question-option-bg: color-mix(
171
+ in srgb,
172
+ var(--design-editor-control-bg) 58%,
173
+ transparent
174
+ );
165
175
  --design-editor-canvas-bg: hsl(0 0% 10%);
166
176
  --design-editor-skeleton-canvas-bg: hsl(0 0% 11%);
167
177
  }
@@ -16,6 +16,9 @@ function designQuestionsStateKey(designId: string | undefined): string {
16
16
  return designId ? `show-questions:${designId}` : "show-questions";
17
17
  }
18
18
 
19
+ const RESPONSIVE_GENERATION_REQUIREMENTS =
20
+ 'Responsive behavior is mandatory for every web design. Read the form-factor answer above: for Desktop or Both/responsive, call generate-design with `primaryViewport: "desktop"` and a 1440x1024 canvas frame; use `primaryViewport: "mobile"` only for an explicitly mobile-primary choice. Use mobile-first responsive CSS, then take desktop and mobile screenshots and fix any overflow before reporting the design complete.';
21
+
19
22
  /**
20
23
  * Polls design-scoped question state. When the agent writes structured
21
24
  * questions, the editor surfaces a full-canvas overlay for only this design.
@@ -40,6 +43,8 @@ export function useQuestionFlow(
40
43
  "Answers:",
41
44
  formattedAnswers,
42
45
  "",
46
+ RESPONSIVE_GENERATION_REQUIREMENTS,
47
+ "",
43
48
  designId
44
49
  ? 'Now continue the design. Honor any answer about variations: if the user asked to explore options, call present-design-variants with 2-5 concise directions using label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens - wait for their chat pick, delete each unchosen variant screen at most once, call get-design-snapshot exactly once with fileId for the kept screen, then call edit-design exactly once on that same fileId in a bounded pass. Use mode "replace-file" when expanding the representative placeholder into a complete but compact product UI in the chosen direction. Prioritize the primary workflow and render secondary details as visible controls, states, or affordances if the feature list is too large for one reliable edit. Do not repeat delete/snapshot cycles. Do not call generate-design after a variant pick. Stop after the first successful edit-design save. Otherwise call generate-design with one complete, renderable index.html first. Do not ask another question unless a required decision is still genuinely missing.'
45
50
  : "Now continue the design. Honor any answer about variations: use variants only if requested; otherwise generate one polished direction.",
@@ -89,6 +94,8 @@ export function useQuestionFlow(
89
94
  "Answers:",
90
95
  formattedAnswers,
91
96
  "",
97
+ RESPONSIVE_GENERATION_REQUIREMENTS,
98
+ "",
92
99
  designId
93
100
  ? 'Now continue the design. Honor any answer about variations: if the user asked to explore options, call present-design-variants with 2-5 concise directions using label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens - wait for their chat pick, delete each unchosen variant screen at most once, call get-design-snapshot exactly once with fileId for the kept screen, then call edit-design exactly once on that same fileId in a bounded pass. Use mode "replace-file" when expanding the representative placeholder into a complete but compact product UI in the chosen direction. Prioritize the primary workflow and render secondary details as visible controls, states, or affordances if the feature list is too large for one reliable edit. Do not repeat delete/snapshot cycles. Do not call generate-design after a variant pick. Stop after the first successful edit-design save. Otherwise call generate-design with one complete, renderable index.html first. Do not ask another question unless a required decision is still genuinely missing.'
94
101
  : "Now continue the design. Honor any answer about variations: use variants only if requested; otherwise generate one polished direction.",
@@ -105,8 +112,8 @@ export function useQuestionFlow(
105
112
  sendContinuation(
106
113
  "Skip the questions — decide for me.",
107
114
  designId
108
- ? `The user skipped the pre-generation questions for design ${designId}. Proceed with reasonable defaults. Generate one polished first direction unless the original prompt explicitly requested options.`
109
- : "The user skipped the pre-generation questions. Proceed with reasonable defaults. Generate one polished first direction unless the original prompt explicitly requested options.",
115
+ ? `The user skipped the pre-generation questions for design ${designId}. Proceed with reasonable defaults. ${RESPONSIVE_GENERATION_REQUIREMENTS} Generate one polished first direction unless the original prompt explicitly requested options.`
116
+ : `The user skipped the pre-generation questions. Proceed with reasonable defaults. ${RESPONSIVE_GENERATION_REQUIREMENTS} Generate one polished first direction unless the original prompt explicitly requested options.`,
110
117
  );
111
118
  }, [designId, sendContinuation]);
112
119
 
@@ -699,6 +699,7 @@ const messages = {
699
699
  closeTweaks: "關閉調整",
700
700
  tweaksHelp:
701
701
  "調整項是疊加在基礎設計上的中斷點與狀態專屬視覺覆寫。重設控制項可回到繼承值。",
702
+ tweaksDocs: "深入瞭解。",
702
703
  noTweakControls: "還沒有調整控制項。",
703
704
  extensions: "擴充功能",
704
705
  addExtension: "擴充功能",