@agent-native/core 0.153.10 → 0.154.0

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 (128) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/AGENTS.md +20 -30
  3. package/corpus/templates/analytics/actions/search-dashboard-references.ts +58 -0
  4. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  5. package/corpus/templates/analytics/app/lib/utils.ts +1 -1
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewSqlPopover.tsx +1 -1
  7. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -0
  8. package/corpus/templates/analytics/server/lib/dashboards-store.ts +366 -3
  9. package/corpus/templates/analytics/server/lib/real-data-actions.ts +3 -1
  10. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -2
  11. package/corpus/templates/calendar/app/components/calendar/EventDialog.tsx +1 -1
  12. package/corpus/templates/calendar/app/lib/utils.ts +1 -1
  13. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +32 -5
  14. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +15 -15
  15. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  16. package/corpus/templates/clips/app/lib/utils.ts +5 -6
  17. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +43 -8
  18. package/corpus/templates/clips/desktop/src/app.tsx +26 -12
  19. package/corpus/templates/clips/desktop/src/lib/recorder.ts +23 -9
  20. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +261 -11
  21. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +21 -3
  22. package/corpus/templates/clips/shared/mic-audio-cleanup.ts +350 -0
  23. package/corpus/templates/content/app/i18n-data.ts +10 -10
  24. package/corpus/templates/content/scripts/check-native-deps.mjs +181 -15
  25. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +79 -7
  26. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +14 -2
  27. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +2 -2
  28. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +35 -11
  29. package/corpus/templates/design/actions/get-design-system.ts +103 -3
  30. package/corpus/templates/design/actions/import-design-tokens.ts +37 -8
  31. package/corpus/templates/design/actions/import-github.ts +32 -138
  32. package/corpus/templates/design/actions/index-design-system-with-builder.ts +49 -16
  33. package/corpus/templates/design/actions/present-design-variants.ts +93 -4
  34. package/corpus/templates/design/actions/run-design-audit.ts +229 -0
  35. package/corpus/templates/design/actions/sync-design-system-with-builder.ts +64 -0
  36. package/corpus/templates/design/app/components/design/code-workbench/commands.ts +2 -2
  37. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +17 -4
  38. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +5 -1
  39. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +5 -1
  40. package/corpus/templates/design/app/hooks/use-question-flow.ts +81 -5
  41. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  42. package/corpus/templates/design/app/i18n-data.ts +32 -1
  43. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +10 -10
  44. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +83 -11
  45. package/corpus/templates/design/app/pages/DesignSystems.tsx +14 -0
  46. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +22 -0
  47. package/corpus/templates/design/changelog/2026-08-13-a-screen-that-would-render-behind-an-invisible-alpine-overla.md +6 -0
  48. package/corpus/templates/design/changelog/2026-08-13-attach-a-ui-screenshot-and-the-design-is-reproduced-not-rein.md +6 -0
  49. package/corpus/templates/design/changelog/2026-08-13-linking-a-design-system-means-you-never-get-generic-directio.md +6 -0
  50. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-reports-when-a-screen-ignores-its-linke.md +6 -0
  51. package/corpus/templates/design/changelog/2026-08-13-the-design-system-preview-now-shows-how-many-named-tokens-we.md +6 -0
  52. package/corpus/templates/design/changelog/2026-08-13-your-design-system-prompt-and-reference-screenshots-now-reac.md +6 -0
  53. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +18 -2
  54. package/corpus/templates/design/shared/design-review.ts +1 -0
  55. package/corpus/templates/design/shared/html-integrity.ts +171 -0
  56. package/corpus/templates/mail/app/lib/utils.ts +1 -1
  57. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +10 -6
  58. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +41 -3
  59. package/corpus/templates/slides/actions/sync-design-system-with-builder.ts +64 -0
  60. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  61. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +193 -20
  62. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +58 -33
  63. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +28 -41
  64. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +119 -91
  65. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +16 -0
  66. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +158 -31
  67. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +30 -14
  68. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +2 -2
  69. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -1
  70. package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +14 -0
  71. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +24 -0
  72. package/corpus/templates/slides/app/global.css +24 -0
  73. package/corpus/templates/slides/app/hooks/use-navigation-state.ts +5 -1
  74. package/corpus/templates/slides/app/i18n/en-US.ts +19 -10
  75. package/corpus/templates/slides/app/lib/deck-filter.ts +47 -0
  76. package/corpus/templates/slides/app/lib/export-google-slides-client.ts +15 -4
  77. package/corpus/templates/slides/app/lib/export-pptx-client.ts +637 -1
  78. package/corpus/templates/slides/app/lib/image-download.ts +84 -0
  79. package/corpus/templates/slides/app/lib/utils.ts +1 -1
  80. package/corpus/templates/slides/app/pages/Index.tsx +25 -9
  81. package/corpus/templates/slides/netlify.toml +1 -0
  82. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +14 -2
  83. package/dist/agent/production-agent.js +1 -1
  84. package/dist/brand-kit/types.d.ts +10 -2
  85. package/dist/cli/code.js +1 -1
  86. package/dist/cli/index.js +30 -1
  87. package/dist/cli/native-dependencies.d.ts +24 -0
  88. package/dist/cli/native-dependencies.js +264 -0
  89. package/dist/cli/plan-local.js +1 -1
  90. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  91. package/dist/cli/skills-content/design-visual-edit-skill.js +2 -2
  92. package/dist/cli/visualize-repo.js +1 -1
  93. package/dist/client/FeedbackButton.js +12 -12
  94. package/dist/client/agent-chat-adapter.js +3 -4
  95. package/dist/client/assistant-ui-recovery.d.ts +1 -1
  96. package/dist/client/assistant-ui-recovery.js +4 -0
  97. package/dist/client/chat/message-components.d.ts +11 -3
  98. package/dist/client/chat/message-components.js +31 -5
  99. package/dist/client/chat/tool-call-display.d.ts +2 -1
  100. package/dist/client/chat/tool-call-display.js +33 -15
  101. package/dist/client/db-admin/SqlEditor.js +1 -1
  102. package/dist/client/dev-overlay/DevOverlay.js +2 -2
  103. package/dist/client/extensions/portable-extension.js +4 -0
  104. package/dist/client/guided-questions.d.ts +8 -0
  105. package/dist/client/guided-questions.js +12 -3
  106. package/dist/client/sharing/ShareButton.js +16 -24
  107. package/dist/client/sse-event-processor.js +35 -15
  108. package/dist/client/tool-display.d.ts +9 -0
  109. package/dist/client/tool-display.js +39 -0
  110. package/dist/client/vite-dev-recovery-script.js +1 -1
  111. package/dist/collab/awareness.d.ts +2 -2
  112. package/dist/collab/routes.d.ts +1 -1
  113. package/dist/collab/struct-routes.d.ts +1 -1
  114. package/dist/extensions/html-shell.js +5 -0
  115. package/dist/notifications/routes.d.ts +1 -1
  116. package/dist/observability/routes.d.ts +3 -3
  117. package/dist/resources/handlers.d.ts +1 -1
  118. package/dist/secrets/routes.d.ts +3 -3
  119. package/dist/server/agent-chat/prompt-resources.js +17 -16
  120. package/dist/server/agent-chat-plugin.d.ts +9 -0
  121. package/dist/server/agent-chat-plugin.js +29 -7
  122. package/dist/server/builder-design-systems.d.ts +33 -1
  123. package/dist/server/builder-design-systems.js +293 -11
  124. package/dist/server/design-token-utils.d.ts +11 -0
  125. package/dist/server/design-token-utils.js +68 -16
  126. package/dist/server/index.d.ts +1 -1
  127. package/dist/server/index.js +1 -1
  128. package/package.json +3 -3
@@ -6,6 +6,7 @@ import {
6
6
  type CollabUser,
7
7
  } from "@agent-native/core/client/collab";
8
8
  import {
9
+ readClientAppState,
9
10
  setClientAppState,
10
11
  usePinchZoom,
11
12
  useAvatarUrl,
@@ -17,15 +18,7 @@ import {
17
18
  } from "@agent-native/toolkit/collab-ui";
18
19
  import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
19
20
  import { hashSlideContent } from "@shared/slide-fit";
20
- import {
21
- IconBrush,
22
- IconClipboard,
23
- IconCopy,
24
- IconStackBack,
25
- IconStackFront,
26
- IconTrash,
27
- IconX,
28
- } from "@tabler/icons-react";
21
+ import { IconX } from "@tabler/icons-react";
29
22
  import {
30
23
  useState,
31
24
  useCallback,
@@ -73,6 +66,7 @@ import {
73
66
  MAX_CANVAS_ZOOM,
74
67
  MIN_CANVAS_ZOOM,
75
68
  } from "@/lib/canvas-zoom";
69
+ import { downloadImage } from "@/lib/image-download";
76
70
  import { extractMermaidBlocks } from "@/lib/mermaid-blocks";
77
71
  import { publishSlidesSelection } from "@/lib/slide-agent-context";
78
72
  import {
@@ -113,6 +107,7 @@ import {
113
107
  getInlineTextStyleSnapshot,
114
108
  getInlineTextStyleSnapshotForRange,
115
109
  restoreEditableTextRange,
110
+ selectAllEditableText,
116
111
  snapshotEditableTextRange,
117
112
  type InlineTextStylePatch,
118
113
  type InlineTextStyleSnapshot,
@@ -167,6 +162,7 @@ import { type SlideStylePatch, type SlideStyleSnapshot } from "./slide-style";
167
162
  import {
168
163
  findSmartBlock,
169
164
  isInlineTextElement,
165
+ isSlideTextEditingTarget,
170
166
  isTextLeaf,
171
167
  shouldStampBuilderId,
172
168
  } from "./slide-text-targets";
@@ -1064,6 +1060,13 @@ function syncOverflowToAppState(
1064
1060
  }
1065
1061
  }
1066
1062
 
1063
+ function layoutWarningDismissalStateKey(
1064
+ deckId: string | undefined,
1065
+ slideId: string,
1066
+ ): string {
1067
+ return `slides-layout-warning-dismissed:${deckId ?? "local"}:${slideId}`;
1068
+ }
1069
+
1067
1070
  export default function SlideEditor({
1068
1071
  slide,
1069
1072
  onUpdateSlide,
@@ -1222,12 +1225,54 @@ export default function SlideEditor({
1222
1225
  const repairRequestTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
1223
1226
  null,
1224
1227
  );
1225
- const [dismissedOverflowWarningKey, setDismissedOverflowWarningKey] =
1228
+ const overflowWarningStateKey = layoutWarningDismissalStateKey(
1229
+ deckId,
1230
+ slide.id,
1231
+ );
1232
+ const overflowContentHash = hashSlideContent(slide.content);
1233
+ const [dismissedOverflowWarningHash, setDismissedOverflowWarningHash] =
1226
1234
  useState<string | null>(null);
1227
- const overflowWarningKey = `${slide.id}:${hashSlideContent(slide.content)}`;
1228
- const warningVisible = dismissedOverflowWarningKey !== overflowWarningKey;
1229
- const dismissWarning = () =>
1230
- setDismissedOverflowWarningKey(overflowWarningKey);
1235
+ const [overflowWarningDismissalStatus, setOverflowWarningDismissalStatus] =
1236
+ useState<"loading" | "loaded" | "error">("loading");
1237
+ useEffect(() => {
1238
+ let cancelled = false;
1239
+ setDismissedOverflowWarningHash(null);
1240
+ setOverflowWarningDismissalStatus("loading");
1241
+ void readClientAppState<unknown>(overflowWarningStateKey)
1242
+ .then((value) => {
1243
+ if (cancelled) return;
1244
+ setDismissedOverflowWarningHash(
1245
+ typeof value === "string" ? value : null,
1246
+ );
1247
+ setOverflowWarningDismissalStatus("loaded");
1248
+ })
1249
+ .catch((error: unknown) => {
1250
+ if (cancelled) return;
1251
+ console.error("Could not read slide overflow warning dismissal", error);
1252
+ setDismissedOverflowWarningHash(null);
1253
+ setOverflowWarningDismissalStatus("error");
1254
+ });
1255
+ return () => {
1256
+ cancelled = true;
1257
+ };
1258
+ }, [overflowWarningStateKey]);
1259
+ const warningVisible =
1260
+ overflowWarningDismissalStatus === "error" ||
1261
+ (overflowWarningDismissalStatus === "loaded" &&
1262
+ dismissedOverflowWarningHash !== overflowContentHash);
1263
+ const dismissWarning = useCallback(() => {
1264
+ setDismissedOverflowWarningHash(overflowContentHash);
1265
+ setOverflowWarningDismissalStatus("loaded");
1266
+ void setClientAppState(overflowWarningStateKey, overflowContentHash, {
1267
+ keepalive: true,
1268
+ requestSource: TAB_ID,
1269
+ }).catch((error: unknown) => {
1270
+ console.error(
1271
+ "Could not persist slide overflow warning dismissal",
1272
+ error,
1273
+ );
1274
+ });
1275
+ }, [overflowContentHash, overflowWarningStateKey]);
1231
1276
  const dims = getAspectRatioDims(aspectRatio);
1232
1277
  const [fitCanvasZoom, setFitCanvasZoom] = useState(100);
1233
1278
  const userSetCanvasZoomRef = useRef(false);
@@ -1930,6 +1975,17 @@ export default function SlideEditor({
1930
1975
  return () => editingEl.removeEventListener("input", handleInput);
1931
1976
  }, [captureInlineEditDraft, editingEl, slide.id]);
1932
1977
 
1978
+ // Keep canvas gesture handlers from stealing the browser's native text
1979
+ // selection stream once an inline edit has started.
1980
+ useEffect(() => {
1981
+ if (!editingEl) return;
1982
+ const onPointerDown = (event: PointerEvent) => {
1983
+ if (event.button === 0) event.stopPropagation();
1984
+ };
1985
+ editingEl.addEventListener("pointerdown", onPointerDown);
1986
+ return () => editingEl.removeEventListener("pointerdown", onPointerDown);
1987
+ }, [editingEl]);
1988
+
1933
1989
  useEffect(() => {
1934
1990
  if (!editingEl) return;
1935
1991
 
@@ -1981,6 +2037,17 @@ export default function SlideEditor({
1981
2037
  (isTextLeaf(editingEl) && RICH_BLOCK_TAGS.has(editingEl.tagName)) ||
1982
2038
  isBulletList(editingEl);
1983
2039
  const onKey = (e: KeyboardEvent) => {
2040
+ const isSelectAll =
2041
+ (e.metaKey || e.ctrlKey) && !e.altKey && e.key.toLowerCase() === "a";
2042
+ if (isSelectAll) {
2043
+ const editing = editingElRef.current;
2044
+ if (!editing || !editing.isConnected) return;
2045
+ e.preventDefault();
2046
+ e.stopPropagation();
2047
+ selectAllEditableText(editing);
2048
+ return;
2049
+ }
2050
+
1984
2051
  // Ignore keys from outside the slide (e.g. the style dock's font-size
1985
2052
  // input). Guard against the LIVE slide content, not `editingEl`, which a
1986
2053
  // re-render may have detached — a stale ref would wrongly bail here and
@@ -2036,6 +2103,7 @@ export default function SlideEditor({
2036
2103
  exitInlineEdit,
2037
2104
  editingEl,
2038
2105
  captureInlineEditDraft,
2106
+ selectAllEditableText,
2039
2107
  slide.id,
2040
2108
  getSlideContent,
2041
2109
  ]);
@@ -4123,10 +4191,12 @@ export default function SlideEditor({
4123
4191
 
4124
4192
  useEffect(() => {
4125
4193
  if (readOnly || editingEl) return;
4126
- if (multiSelection.size === 0 && !selectedElementSelector) return;
4194
+ if (multiSelection.size === 0 && !selectedElementSelector && !selectedImg)
4195
+ return;
4127
4196
  const onKey = (e: KeyboardEvent) => {
4128
4197
  if (!e.key.startsWith("Arrow")) return;
4129
4198
  const active = document.activeElement;
4199
+ if (isSlideTextEditingTarget(e.target, active, editingEl)) return;
4130
4200
  if (
4131
4201
  active?.tagName === "INPUT" ||
4132
4202
  active?.tagName === "TEXTAREA" ||
@@ -4139,7 +4209,7 @@ export default function SlideEditor({
4139
4209
  if (!nudge) return;
4140
4210
  const { x: dx, y: dy } = nudge.delta;
4141
4211
 
4142
- if (multiSelection.size > 0 && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
4212
+ if (multiSelection.size > 0 && !e.metaKey && !e.ctrlKey) {
4143
4213
  const slideContent = getSlideContent();
4144
4214
  if (!slideContent) return;
4145
4215
  const elements = Array.from(multiSelection)
@@ -4188,17 +4258,51 @@ export default function SlideEditor({
4188
4258
  return;
4189
4259
  }
4190
4260
 
4191
- const element = resolveSelectedElement();
4192
- if (!element || !isPersistedFreeformObject(element)) return;
4261
+ const slideContent = getSlideContent();
4262
+ const element =
4263
+ resolveSelectedElement() ??
4264
+ (selectedImg && slideContent
4265
+ ? (findPersistedImageObject(selectedImg, slideContent) ?? selectedImg)
4266
+ : null);
4267
+ if (!element) return;
4268
+
4269
+ // Arrow nudging is also a first-class way to move flow-layout text and
4270
+ // images. Promote those elements using the same reversible freeform
4271
+ // boundary as a real drag, then persist the exact resulting HTML.
4272
+ const frozen = isPersistedFreeformObject(element)
4273
+ ? { element }
4274
+ : freezeElementForFreeformSelection(element);
4275
+ if (!frozen || !isPersistedFreeformObject(frozen.element)) {
4276
+ frozen?.restoreMarkdownTree?.();
4277
+ return;
4278
+ }
4193
4279
  e.preventDefault();
4194
- const geometry = getObjectGeometry(element);
4280
+ const geometry = getObjectGeometry(frozen.element);
4195
4281
  geometry.x += dx;
4196
4282
  geometry.y += dy;
4197
- applyObjectGeometry(element, geometry);
4283
+ applyObjectGeometry(frozen.element, geometry);
4198
4284
  const html = readCurrentSlideContentHtml();
4285
+
4286
+ if (frozen.restoreMarkdownTree) {
4287
+ const objectId = frozen.element.getAttribute("data-slide-object-id");
4288
+ if (objectId) {
4289
+ const owner =
4290
+ frozen.element.parentElement ?? frozen.element.ownerDocument;
4291
+ owner
4292
+ .querySelectorAll<HTMLElement>("[data-slide-layout-spacer-for]")
4293
+ .forEach((spacer) => {
4294
+ if (
4295
+ spacer.getAttribute("data-slide-layout-spacer-for") === objectId
4296
+ ) {
4297
+ spacer.remove();
4298
+ }
4299
+ });
4300
+ }
4301
+ frozen.restoreMarkdownTree();
4302
+ }
4199
4303
  if (html !== null) onUpdateSlideRef.current({ content: html });
4200
- const selector = getBuilderSelector(element);
4201
- if (selector) selectElementForStyling(element, selector);
4304
+ const selector = getBuilderSelector(frozen.element);
4305
+ if (selector) selectElementForStyling(frozen.element, selector);
4202
4306
  };
4203
4307
  window.addEventListener("keydown", onKey);
4204
4308
  return () => window.removeEventListener("keydown", onKey);
@@ -4206,6 +4310,8 @@ export default function SlideEditor({
4206
4310
  applyObjectGeometry,
4207
4311
  commitMultiObjectChange,
4208
4312
  editingEl,
4313
+ findPersistedImageObject,
4314
+ freezeElementForFreeformSelection,
4209
4315
  getObjectGeometry,
4210
4316
  getSlideContent,
4211
4317
  multiSelection,
@@ -4214,6 +4320,7 @@ export default function SlideEditor({
4214
4320
  refreshMultiSelectionRects,
4215
4321
  resolveSelectedElement,
4216
4322
  selectElementForStyling,
4323
+ selectedImg,
4217
4324
  selectedElementSelector,
4218
4325
  ]);
4219
4326
 
@@ -4268,7 +4375,11 @@ export default function SlideEditor({
4268
4375
  onExitTextBoxMode?.();
4269
4376
  return;
4270
4377
  }
4271
- if (editingEl) return; // avoid interfering with an active inline edit
4378
+ if (
4379
+ isSlideTextEditingTarget(e.target, document.activeElement, editingEl)
4380
+ ) {
4381
+ return;
4382
+ }
4272
4383
 
4273
4384
  // Pointer-down on a member of the current multi-selection drags the
4274
4385
  // whole group instead of the single-object flow below.
@@ -5185,6 +5296,28 @@ export default function SlideEditor({
5185
5296
  horizontalOverflow={
5186
5297
  overflowInfo.horizontalOverflow
5187
5298
  }
5299
+ warningLabel={t(
5300
+ "deckEditor.layoutOverflowWarning",
5301
+ )}
5302
+ overflowDetails={[
5303
+ overflowInfo.verticalOverflow > 0
5304
+ ? t("deckEditor.layoutOverflowVertical", {
5305
+ pixels: overflowInfo.verticalOverflow,
5306
+ })
5307
+ : null,
5308
+ overflowInfo.horizontalOverflow > 0
5309
+ ? t("deckEditor.layoutOverflowHorizontal", {
5310
+ pixels: overflowInfo.horizontalOverflow,
5311
+ })
5312
+ : null,
5313
+ ]
5314
+ .filter((detail): detail is string =>
5315
+ Boolean(detail),
5316
+ )
5317
+ .join(" · ")}
5318
+ overflowDetailsLabel={t(
5319
+ "deckEditor.layoutOverflowDetails",
5320
+ )}
5188
5321
  isAskingAgentToFix={isAskingAgentToFix}
5189
5322
  dismissLabel={t(
5190
5323
  "deckEditor.dismissLayoutWarning",
@@ -5200,7 +5333,6 @@ export default function SlideEditor({
5200
5333
  disabled={!hasObjectSelection}
5201
5334
  onSelect={copySelectedObjects}
5202
5335
  >
5203
- <IconCopy className="me-2 h-4 w-4" />
5204
5336
  {t("styleInspector.copy")}
5205
5337
  <ContextMenuShortcut>
5206
5338
  {shortcutLabel("cmd+c")}
@@ -5210,7 +5342,6 @@ export default function SlideEditor({
5210
5342
  disabled={!hasObjectSelection}
5211
5343
  onSelect={pasteSelectedObjects}
5212
5344
  >
5213
- <IconClipboard className="me-2 h-4 w-4" />
5214
5345
  {t("styleInspector.paste")}
5215
5346
  <ContextMenuShortcut>
5216
5347
  {shortcutLabel("cmd+v")}
@@ -5220,7 +5351,6 @@ export default function SlideEditor({
5220
5351
  disabled={!hasObjectSelection}
5221
5352
  onSelect={duplicateSelectedObjects}
5222
5353
  >
5223
- <IconCopy className="me-2 h-4 w-4" />
5224
5354
  {t("editorSidebar.duplicate")}
5225
5355
  <ContextMenuShortcut>
5226
5356
  {shortcutLabel("cmd+d")}
@@ -5230,7 +5360,6 @@ export default function SlideEditor({
5230
5360
  disabled={!hasObjectSelection}
5231
5361
  onSelect={deleteSelectedElements}
5232
5362
  >
5233
- <IconTrash className="me-2 h-4 w-4" />
5234
5363
  {t("editorSidebar.delete")}
5235
5364
  <ContextMenuShortcut>⌫</ContextMenuShortcut>
5236
5365
  </ContextMenuItem>
@@ -5239,7 +5368,6 @@ export default function SlideEditor({
5239
5368
  disabled={getStyleTargets().length === 0}
5240
5369
  onSelect={copySelectedElementStyle}
5241
5370
  >
5242
- <IconBrush className="me-2 h-4 w-4" />
5243
5371
  {t("styleInspector.copyStyle")}
5244
5372
  <ContextMenuShortcut>
5245
5373
  {shortcutLabel("cmd+alt+c")}
@@ -5252,7 +5380,6 @@ export default function SlideEditor({
5252
5380
  }
5253
5381
  onSelect={pasteCopiedElementStyle}
5254
5382
  >
5255
- <IconBrush className="me-2 h-4 w-4" />
5256
5383
  {t("styleInspector.pasteStyle")}
5257
5384
  <ContextMenuShortcut>
5258
5385
  {shortcutLabel("cmd+alt+v")}
@@ -5263,14 +5390,12 @@ export default function SlideEditor({
5263
5390
  disabled={!selectedElementSelector}
5264
5391
  onSelect={() => handleArrangeSelected("front")}
5265
5392
  >
5266
- <IconStackFront className="me-2 h-4 w-4" />
5267
5393
  {t("styleInspector.bringToFront")}
5268
5394
  </ContextMenuItem>
5269
5395
  <ContextMenuItem
5270
5396
  disabled={!selectedElementSelector}
5271
5397
  onSelect={() => handleArrangeSelected("back")}
5272
5398
  >
5273
- <IconStackBack className="me-2 h-4 w-4" />
5274
5399
  {t("styleInspector.sendToBack")}
5275
5400
  </ContextMenuItem>
5276
5401
  </ContextMenuContent>
@@ -5357,10 +5482,12 @@ export default function SlideEditor({
5357
5482
  {imageOverlay && (
5358
5483
  <ImageOverlay
5359
5484
  anchorRect={imageOverlay.rect}
5485
+ src={imageOverlay.src}
5360
5486
  objectFit={imageOverlay.objectFit}
5361
5487
  onGenerate={onGenerateImage}
5362
5488
  onLibrary={() => onOpenAssetLibrary(imageOverlay.src)}
5363
5489
  onUpload={() => onUploadImage(imageOverlay.src)}
5490
+ onDownload={() => void downloadImage(imageOverlay.src)}
5364
5491
  onToggleObjectFit={() => {
5365
5492
  const newFit =
5366
5493
  imageOverlay.objectFit === "cover" ? "contain" : "cover";
@@ -1,10 +1,19 @@
1
- import { IconAlertTriangle, IconX } from "@tabler/icons-react";
1
+ import { IconAlertTriangle, IconInfoCircle, IconX } from "@tabler/icons-react";
2
2
 
3
3
  import { Button } from "@/components/ui/button";
4
+ import {
5
+ Tooltip,
6
+ TooltipContent,
7
+ TooltipProvider,
8
+ TooltipTrigger,
9
+ } from "@/components/ui/tooltip";
4
10
 
5
11
  interface SlideOverflowWarningProps {
6
12
  verticalOverflow: number;
7
13
  horizontalOverflow?: number;
14
+ warningLabel: string;
15
+ overflowDetails: string;
16
+ overflowDetailsLabel: string;
8
17
  isAskingAgentToFix: boolean;
9
18
  dismissLabel: string;
10
19
  onFix: () => void;
@@ -12,21 +21,14 @@ interface SlideOverflowWarningProps {
12
21
  }
13
22
 
14
23
  export function SlideOverflowWarning({
15
- verticalOverflow,
16
- horizontalOverflow = 0,
24
+ warningLabel,
25
+ overflowDetails,
26
+ overflowDetailsLabel,
17
27
  isAskingAgentToFix,
18
28
  dismissLabel,
19
29
  onFix,
20
30
  onDismiss,
21
31
  }: SlideOverflowWarningProps) {
22
- const overflowLabel = [
23
- verticalOverflow > 0 ? `vertical ${verticalOverflow}px` : "",
24
- horizontalOverflow > 0 ? `horizontal ${horizontalOverflow}px` : "",
25
- ]
26
- .filter(Boolean)
27
- .join(", ");
28
- const visibleOverflowLabel =
29
- horizontalOverflow > 0 ? overflowLabel : `${verticalOverflow}px`;
30
32
  return (
31
33
  <div
32
34
  role="status"
@@ -42,9 +44,23 @@ export function SlideOverflowWarning({
42
44
  className="h-3.5 w-3.5 flex-shrink-0 text-foreground"
43
45
  stroke={2}
44
46
  />
45
- <span className="leading-tight">
46
- Layout overflows by {visibleOverflowLabel}
47
- </span>
47
+ <span className="leading-tight">{warningLabel}</span>
48
+ <TooltipProvider delayDuration={200}>
49
+ <Tooltip>
50
+ <TooltipTrigger asChild>
51
+ <Button
52
+ type="button"
53
+ size="icon"
54
+ variant="ghost"
55
+ className="size-6 cursor-help text-muted-foreground hover:bg-transparent hover:text-foreground"
56
+ aria-label={overflowDetailsLabel}
57
+ >
58
+ <IconInfoCircle className="size-3.5" />
59
+ </Button>
60
+ </TooltipTrigger>
61
+ <TooltipContent side="top">{overflowDetails}</TooltipContent>
62
+ </Tooltip>
63
+ </TooltipProvider>
48
64
  <Button
49
65
  size="sm"
50
66
  variant="ghost"
@@ -85,9 +85,9 @@ export function SpeakerNotesPanel({
85
85
  )}
86
86
  <button
87
87
  onClick={toggle}
88
- className="flex w-full cursor-pointer items-center justify-between px-4 py-1.5"
88
+ className="group flex w-full cursor-pointer items-center justify-between px-4 py-1.5"
89
89
  >
90
- <span className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider">
90
+ <span className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider transition-colors group-hover:text-foreground/80">
91
91
  {t("raw.speakerNotes")}
92
92
  </span>
93
93
  {expanded ? (
@@ -151,8 +151,11 @@ export function resolveSlidesCanvasPointerIntent({
151
151
  ) {
152
152
  return "move-object-perimeter";
153
153
  }
154
+ // The interior of an editable text object belongs to native text selection.
155
+ // Only the measured outer edge is reserved for moving the selected object.
156
+ if (targetIsEditableText) return "edit-text";
154
157
  if (hasSelectedObject && targetWithinSelectedObject) {
155
158
  return "move-object-body";
156
159
  }
157
- return targetIsEditableText ? "edit-text" : "none";
160
+ return "none";
158
161
  }
@@ -151,6 +151,20 @@ export function snapshotEditableTextRange(
151
151
  return getEditableTextRange(editable, selection)?.cloneRange() ?? null;
152
152
  }
153
153
 
154
+ /** Selects the complete contents of one live slide text block. */
155
+ export function selectAllEditableText(
156
+ editable: HTMLElement,
157
+ selection: Selection | null = window.getSelection(),
158
+ ) {
159
+ if (!selection) return false;
160
+ editable.focus({ preventScroll: true });
161
+ const range = document.createRange();
162
+ range.selectNodeContents(editable);
163
+ selection.removeAllRanges();
164
+ selection.addRange(range);
165
+ return true;
166
+ }
167
+
154
168
  /** Restores a captured range only while it is still safe to use in this block. */
155
169
  export function restoreEditableTextRange(
156
170
  editable: HTMLElement,
@@ -1,5 +1,7 @@
1
1
  import { findEnclosingList } from "./bullet-editing";
2
2
 
3
+ type EditingTarget = EventTarget | Element | null;
4
+
3
5
  /** Inline markup changes presentation, but does not create a canvas target. */
4
6
  const INLINE_TEXT_TAGS = new Set([
5
7
  "SPAN",
@@ -23,6 +25,28 @@ export function isInlineTextElement(element: Element): boolean {
23
25
  return INLINE_TEXT_TAGS.has(element.tagName);
24
26
  }
25
27
 
28
+ function isEditingTarget(target: EditingTarget): boolean {
29
+ if (!(target instanceof Element)) return false;
30
+ return (
31
+ (target instanceof HTMLElement && target.isContentEditable) ||
32
+ target.closest('[contenteditable="true"], [data-editing-block="true"]') !==
33
+ null
34
+ );
35
+ }
36
+
37
+ /** Whether a keyboard/pointer event belongs to an active slide text edit. */
38
+ export function isSlideTextEditingTarget(
39
+ target: EditingTarget,
40
+ activeElement: Element | null = null,
41
+ editingElement: Element | null = null,
42
+ ): boolean {
43
+ return (
44
+ isEditingTarget(target) ||
45
+ isEditingTarget(activeElement) ||
46
+ isEditingTarget(editingElement)
47
+ );
48
+ }
49
+
26
50
  export function shouldStampBuilderId(element: HTMLElement): boolean {
27
51
  return (
28
52
  !element.classList.contains("fmd-layout-spacer") &&
@@ -69,6 +69,7 @@ button[title="Open agent sidebar"] {
69
69
  --sidebar-ring: 0 0% 50%;
70
70
 
71
71
  --accent-cyan: 187 100% 50%;
72
+ --slides-thumbnail-shadow: 0 0% 0%;
72
73
  }
73
74
 
74
75
  .light {
@@ -115,6 +116,29 @@ button[title="Open agent sidebar"] {
115
116
  --accent-cyan: 193 82% 31%;
116
117
  }
117
118
 
119
+ [data-slides-thumbnail-scroll]::before {
120
+ position: absolute;
121
+ z-index: 20;
122
+ inset: 0.5rem 0.5rem auto;
123
+ height: 1rem;
124
+ pointer-events: none;
125
+ content: "";
126
+ opacity: 0;
127
+ background: radial-gradient(
128
+ ellipse 82% 125% at 50% 0%,
129
+ hsl(var(--slides-thumbnail-shadow) / 0.18) 0%,
130
+ hsl(var(--slides-thumbnail-shadow) / 0.08) 48%,
131
+ transparent 100%
132
+ );
133
+ box-shadow: 0 0.2rem 0.45rem -0.25rem
134
+ hsl(var(--slides-thumbnail-shadow) / 0.64);
135
+ transition: opacity 150ms ease-out;
136
+ }
137
+
138
+ [data-slides-thumbnail-scroll="scrolled"]::before {
139
+ opacity: 1;
140
+ }
141
+
118
142
  /* Slides keeps the shared visual inspector behavior, but the dock and context toolbar need the
119
143
  flatter, filled-control treatment used by the slide editor. */
120
144
  .slide-style-inspector,
@@ -4,6 +4,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useEffect, useRef } from "react";
5
5
  import { useLocation, useNavigate } from "react-router";
6
6
 
7
+ import { readStoredDeckFilter, resolveDeckFilter } from "@/lib/deck-filter";
7
8
  import { TAB_ID } from "@/lib/tab-id";
8
9
 
9
10
  export interface NavigationState {
@@ -59,7 +60,10 @@ export function useNavigationState() {
59
60
  } else {
60
61
  const params = new URLSearchParams(location.search);
61
62
  state.deckFilter =
62
- params.get("createdBy") === "me" ? "created-by-me" : "all";
63
+ resolveDeckFilter(params.get("createdBy"), readStoredDeckFilter()) ===
64
+ "mine"
65
+ ? "created-by-me"
66
+ : "all";
63
67
  }
64
68
 
65
69
  const write = (key: string) =>
@@ -242,8 +242,7 @@ const messages = {
242
242
  code: "Code",
243
243
  comments: "Comments",
244
244
  editorLink: "Editor link",
245
- editorLinkDescription:
246
- "Opens the deck in the editor. Anyone with access can use this link.",
245
+ editorLinkDescription: "Opens the deck in the editor",
247
246
  presentationLink: "Presentation link",
248
247
  presentationLinkDescription:
249
248
  "Opens directly in fullscreen presentation mode.",
@@ -251,7 +250,7 @@ const messages = {
251
250
  more: "More",
252
251
  importing: "Importing...",
253
252
  importFile: "Import file",
254
- savedVersions: "Saved versions",
253
+ savedVersions: "History",
255
254
  lightTheme: "Light theme",
256
255
  darkTheme: "Dark theme",
257
256
  },
@@ -273,8 +272,8 @@ const messages = {
273
272
  exportPdf: "Export as PDF",
274
273
  exportPptx: "Export as PPTX",
275
274
  connectGoogle: "Connect Google",
276
- openInGoogleSlides: "Open in Google Slides",
277
- googleSlidesCreated: "Opened in Google Slides",
275
+ openInGoogleSlides: "Export to Google Slides",
276
+ googleSlidesCreated: "Exported to Google Slides",
278
277
  googleSlidesCreatedHint:
279
278
  "A copy of this deck was created in your Google Drive.",
280
279
  duplicateDeck: "Duplicate deck",
@@ -494,17 +493,17 @@ const messages = {
494
493
  "The deck was rolled back to the selected snapshot.",
495
494
  restoreFailed: "Restore failed",
496
495
  restoreFailedDescription: "Could not restore this deck version.",
497
- backToSavedVersions: "Back to saved versions",
498
- savedVersions: "Saved versions",
499
- description: "Browse saved deck versions and restore a previous snapshot.",
496
+ backToSavedVersions: "Back to history",
497
+ savedVersions: "History",
498
+ description: "Browse deck history and restore a previous snapshot.",
500
499
  untitled: "Untitled",
501
500
  snapshotUnavailable: "Snapshot unavailable",
502
501
  slideNumber: "Slide {{number}}",
503
502
  noSlidesInSnapshot: "No slides in this snapshot.",
504
503
  restoreThisVersion: "Restore this version",
505
- noSavedVersions: "No saved versions yet",
504
+ noSavedVersions: "No history yet",
506
505
  noSavedVersionsDescription:
507
- "Versions are saved automatically before future deck edits.",
506
+ "History is saved automatically before future deck edits.",
508
507
  },
509
508
  editorSidebar: {
510
509
  selectSlide: "Select slide {{number}}",
@@ -561,6 +560,10 @@ const messages = {
561
560
  deckHasNoSlides: "Deck has no slides.",
562
561
  pdfRenderFailed: "Could not render PDF.",
563
562
  buildingDeck: "Building deck",
563
+ layoutOverflowWarning: "Layout overflows",
564
+ layoutOverflowDetails: "Show overflow details",
565
+ layoutOverflowVertical: "Vertical overflow: {{pixels}}px",
566
+ layoutOverflowHorizontal: "Horizontal overflow: {{pixels}}px",
564
567
  dismissLayoutWarning: "Dismiss layout warning",
565
568
  slidesAdded_one: "{{count}} slide added",
566
569
  slidesAdded_other: "{{count}} slides added",
@@ -606,6 +609,8 @@ const messages = {
606
609
  websitePlaceholder: "example.com or Nike",
607
610
  add: "Add",
608
611
  githubRepository: "Connect Code: GitHub repository",
612
+ githubRef: "Branch, tag, or commit (optional)",
613
+ githubPaths: "Files or folders, comma-separated (optional)",
609
614
  codeFiles: "Connect Code Files",
610
615
  codeFilesDrop:
611
616
  "CSS, Tailwind config, theme files, design.md — drop or click",
@@ -639,6 +644,10 @@ const messages = {
639
644
  sourceUnavailableDescription:
640
645
  "Builder could not load the latest source results.",
641
646
  sourceOpenInBuilder: "Open in Builder",
647
+ syncSource: "Sync source",
648
+ syncingSource: "Syncing source...",
649
+ syncStarted: "Design system sync started",
650
+ syncFailed: "Could not sync design system",
642
651
  loading: "Loading design system",
643
652
  loadFailed: "We couldn't load this design system. Close and try again.",
644
653
  cancel: "Cancel",