@agent-native/core 0.149.0 → 0.149.2

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 (134) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
  3. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
  4. package/corpus/templates/analytics/app/global.css +1 -1
  5. package/corpus/templates/analytics/app/i18n-data.ts +37 -0
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
  7. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
  8. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
  9. package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  11. package/corpus/templates/assets/app/global.css +1 -1
  12. package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  13. package/corpus/templates/brain/app/global.css +1 -1
  14. package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  15. package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  16. package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
  17. package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
  18. package/corpus/templates/clips/desktop/src/app.tsx +274 -137
  19. package/corpus/templates/clips/desktop/src/styles.css +35 -0
  20. package/corpus/templates/content/app/global.css +1 -0
  21. package/corpus/templates/crm/app/global.css +1 -1
  22. package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  23. package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  24. package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  25. package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  26. package/corpus/templates/plan/app/global.css +1 -0
  27. package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  28. package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
  29. package/corpus/templates/slides/AGENTS.md +11 -10
  30. package/corpus/templates/slides/actions/add-deck.ts +2 -0
  31. package/corpus/templates/slides/actions/add-slide.ts +15 -1
  32. package/corpus/templates/slides/actions/create-deck.ts +18 -6
  33. package/corpus/templates/slides/actions/get-deck.ts +29 -1
  34. package/corpus/templates/slides/actions/patch-deck.ts +139 -10
  35. package/corpus/templates/slides/actions/save-deck.ts +24 -1
  36. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
  37. package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
  38. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +10 -1
  39. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
  40. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +74 -63
  41. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
  42. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
  43. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
  44. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
  45. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +29 -7
  46. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
  47. package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
  48. package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
  49. package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
  50. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
  51. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
  52. package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
  53. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  54. package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
  55. package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
  56. package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
  57. package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
  58. package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
  59. package/corpus/templates/slides/app/pages/DeckEditor.tsx +16 -27
  60. package/corpus/templates/slides/app/pages/Index.tsx +256 -98
  61. package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
  62. package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
  63. package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
  64. package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
  65. package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
  66. package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
  67. package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
  68. package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
  69. package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
  70. package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
  71. package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
  72. package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
  73. package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
  74. package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
  75. package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
  76. package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
  77. package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
  78. package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
  79. package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
  80. package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
  81. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  82. package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
  83. package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
  84. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
  85. package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
  86. package/corpus/templates/slides/shared/api.ts +2 -1
  87. package/corpus/templates/slides/shared/deck-title.ts +135 -0
  88. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
  89. package/dist/agent/engine/ai-sdk-engine.js +1 -1
  90. package/dist/agent/production-agent.d.ts +21 -1
  91. package/dist/agent/production-agent.js +129 -10
  92. package/dist/agent/tool-approval-store.d.ts +18 -0
  93. package/dist/agent/tool-approval-store.js +122 -0
  94. package/dist/agent/types.d.ts +1 -1
  95. package/dist/cli/code-agent-executor.js +11 -2
  96. package/dist/client/AgentPanel.js +31 -6
  97. package/dist/client/AssistantChat.d.ts +3 -2
  98. package/dist/client/AssistantChat.js +7 -20
  99. package/dist/client/agent-chat/index.d.ts +1 -1
  100. package/dist/client/agent-chat-adapter.js +11 -0
  101. package/dist/client/agent-chat.d.ts +1 -1
  102. package/dist/client/chat/markdown-renderer.js +14 -2
  103. package/dist/client/chat/message-components.d.ts +6 -2
  104. package/dist/client/chat/message-components.js +25 -47
  105. package/dist/client/chat/tool-call-display.d.ts +3 -5
  106. package/dist/client/chat/tool-call-display.js +7 -212
  107. package/dist/client/chat-first/apps-rail.js +3 -3
  108. package/dist/client/chat-first/browser-pane.js +4 -1
  109. package/dist/client/chat-first/chat-history.d.ts +3 -1
  110. package/dist/client/chat-first/chat-history.js +2 -2
  111. package/dist/client/chat-first/surface-tabs.d.ts +1 -1
  112. package/dist/client/chat-first/surface-tabs.js +52 -41
  113. package/dist/client/chat-first/types.d.ts +3 -0
  114. package/dist/client/chat-first.d.ts +10 -0
  115. package/dist/client/chat-first.js +37 -2
  116. package/dist/client/components/ui/message-scroller.js +4 -1
  117. package/dist/client/i18n.js +3 -1
  118. package/dist/client/progress/RunsTray.js +14 -1
  119. package/dist/client/resources/McpConnectionSuggestion.js +9 -1
  120. package/dist/client/sse-event-processor.js +10 -0
  121. package/dist/client/use-chat-models.js +156 -80
  122. package/dist/integrations/webhook-handler.js +1 -1
  123. package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
  124. package/dist/secrets/routes.d.ts +6 -6
  125. package/dist/server/auth.js +262 -31
  126. package/dist/server/onboarding-html.js +56 -9
  127. package/dist/shared/reasoning-effort.d.ts +8 -8
  128. package/dist/shared/reasoning-effort.js +10 -10
  129. package/dist/shared/streaming-text-smoothing.js +41 -11
  130. package/dist/styles/agent-native.css +19 -32
  131. package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  132. package/docs/content/template-chat.mdx +1 -1
  133. package/package.json +2 -2
  134. package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
@@ -10,9 +10,16 @@
10
10
  * largest rendered element so a thumbnail's transform: scale(0.25)
11
11
  * doesn't shrink the captured pixels.
12
12
  */
13
+ import { appBasePath } from "@agent-native/core/client/api-path";
14
+
13
15
  import { type AspectRatio, getAspectRatioDims } from "./aspect-ratios";
14
16
  import { importExportModule } from "./dynamic-import";
15
17
 
18
+ /** Same-origin URL that re-serves a remote image, bypassing its missing CORS. */
19
+ export function imageProxyUrl(src: string): string {
20
+ return `${appBasePath()}/api/image-proxy?url=${encodeURIComponent(src)}`;
21
+ }
22
+
16
23
  /**
17
24
  * Cross-origin <img> elements without an explicit `crossOrigin="anonymous"`
18
25
  * attribute taint the canvas when rasterized via <foreignObject>, producing
@@ -21,8 +28,15 @@ import { importExportModule } from "./dynamic-import";
21
28
  * setting the attribute and re-assigning the same src. This is the root
22
29
  * cause of the "blank images in exported PDF" bug Rochkind reported.
23
30
  */
24
- export async function preloadImagesWithCors(root: HTMLElement): Promise<void> {
31
+ export async function preloadImagesWithCors(
32
+ root: HTMLElement,
33
+ ): Promise<() => void> {
25
34
  const imgs = Array.from(root.querySelectorAll<HTMLImageElement>("img"));
35
+ // The slide DOM is the live editor canvas. Anything rewritten here would
36
+ // otherwise be picked up by the next save and persisted into the deck, so
37
+ // every mutation is recorded and undone once the capture is done.
38
+ const restores: Array<() => void> = [];
39
+
26
40
  await Promise.all(
27
41
  imgs.map(async (img) => {
28
42
  const src = img.currentSrc || img.src;
@@ -35,30 +49,59 @@ export async function preloadImagesWithCors(root: HTMLElement): Promise<void> {
35
49
  isCrossOrigin = false;
36
50
  }
37
51
  if (!isCrossOrigin) return;
52
+
53
+ const originalCrossOrigin = img.getAttribute("crossorigin");
54
+ const originalSrc = img.getAttribute("src");
55
+ const restore = () => {
56
+ if (originalCrossOrigin === null) img.removeAttribute("crossorigin");
57
+ else img.setAttribute("crossorigin", originalCrossOrigin);
58
+ if (originalSrc === null) img.removeAttribute("src");
59
+ else img.setAttribute("src", originalSrc);
60
+ };
61
+
38
62
  if (img.crossOrigin === "anonymous") {
39
63
  // Already CORS-enabled; just make sure it's decoded.
40
64
  try {
41
65
  await img.decode();
66
+ return;
42
67
  } catch {
43
- /* ignore */
68
+ // coercion-ok: a failed direct load is the signal to try the proxy,
69
+ // and the proxy attempt below reports its own failure.
70
+ }
71
+ } else {
72
+ img.crossOrigin = "anonymous";
73
+ // Re-set src to retrigger the load with the new CORS attribute.
74
+ img.src = src;
75
+ restores.push(restore);
76
+ try {
77
+ await img.decode();
78
+ return;
79
+ } catch {
80
+ // coercion-ok: same as above — this is the CORS probe, not the
81
+ // final outcome.
44
82
  }
45
- return;
46
83
  }
84
+
85
+ // The host does not send Access-Control-Allow-Origin, and no client-side
86
+ // flag can override that. Re-serve the image from our own origin so the
87
+ // canvas stays clean instead of rasterizing a blank rect.
88
+ if (!restores.includes(restore)) restores.push(restore);
47
89
  img.crossOrigin = "anonymous";
48
- // Re-set src to retrigger the load with the new CORS attribute.
49
- img.src = src;
90
+ img.src = imageProxyUrl(src);
50
91
  try {
51
92
  await img.decode();
52
93
  } catch (err) {
53
- // Server didn't return Access-Control-Allow-Origin. The screenshot
54
- // will be blank for this image — log so the user can swap the host.
55
94
  console.warn(
56
- `[export-pdf] CORS-tainted image likely caused blank render: ${src}`,
95
+ `[export-pdf] image could not be loaded for export: ${src}`,
57
96
  err,
58
97
  );
59
98
  }
60
99
  }),
61
100
  );
101
+
102
+ return () => {
103
+ for (const restore of restores) restore();
104
+ };
62
105
  }
63
106
 
64
107
  export function findSlideExportSource(
@@ -132,23 +175,39 @@ export async function exportDeckAsPdf(
132
175
  // Force CORS-enabled re-fetch on every cross-origin <img> before
133
176
  // capture — otherwise the canvas tainting check inside modern-screenshot
134
177
  // produces a blank rect for the image.
135
- await preloadImagesWithCors(source);
136
-
137
- const dataUrl = await domToJpeg(source, {
138
- width: dims.width,
139
- height: dims.height,
140
- scale: 2, // 2x for crisp text
141
- backgroundColor: "#000000",
142
- quality: 0.92,
143
- // Pair with the in-DOM CORS preload above. modern-screenshot's
144
- // internal image fetcher needs no-cache so re-issued requests don't
145
- // get served the original tainted (no-CORS) response from the HTTP
146
- // cache, and an anonymous-CORS request mode so the response itself
147
- // is usable on a clean canvas.
148
- fetch: {
149
- requestInit: { cache: "no-cache", mode: "cors", credentials: "omit" },
150
- },
151
- });
178
+ const restoreImages = await preloadImagesWithCors(source);
179
+
180
+ let dataUrl: string;
181
+ try {
182
+ dataUrl = await domToJpeg(source, {
183
+ width: dims.width,
184
+ height: dims.height,
185
+ scale: 2, // 2x for crisp text
186
+ // guard:allow-raw-color — a PDF page has no theme to follow.
187
+ backgroundColor: "#000000",
188
+ quality: 0.92,
189
+ // Pair with the in-DOM CORS preload above. modern-screenshot's
190
+ // internal image fetcher needs no-cache so re-issued requests don't
191
+ // get served the original tainted (no-CORS) response from the HTTP
192
+ // cache, and an anonymous-CORS request mode so the response itself
193
+ // is usable on a clean canvas.
194
+ //
195
+ // `same-origin` rather than `omit`: images the preload rewrote to
196
+ // /api/image-proxy are same-origin and that route needs the session
197
+ // cookie, so omitting credentials would 401 exactly the images this
198
+ // is meant to rescue. Cross-origin requests still go out anonymously,
199
+ // which is what CORS mode requires.
200
+ fetch: {
201
+ requestInit: {
202
+ cache: "no-cache",
203
+ mode: "cors",
204
+ credentials: "same-origin",
205
+ },
206
+ },
207
+ });
208
+ } finally {
209
+ restoreImages();
210
+ }
152
211
 
153
212
  if (i > 0) pdf.addPage([dims.width, dims.height], orientation);
154
213
  pdf.addImage(dataUrl, "JPEG", 0, 0, dims.width, dims.height);
@@ -9,6 +9,16 @@ export type ImportedSourceDeck = {
9
9
  imagesSkipped: number;
10
10
  };
11
11
 
12
+ /**
13
+ * PDF/PPTX import renders every page (image extraction, per-page fidelity
14
+ * parsing) and can run well past the client's default 60s action timeout on
15
+ * large or image-heavy files. A timeout here only aborts the client's wait —
16
+ * the server keeps importing and the deck still ends up with slides — so the
17
+ * default made the editor silently fail on a deck that had, or was about to
18
+ * have, real content.
19
+ */
20
+ export const IMPORT_ACTION_TIMEOUT_MS = 5 * 60 * 1000;
21
+
12
22
  function sourceFormat(file: UploadedFile): "pdf" | "pptx" | null {
13
23
  const name = file.originalName.toLowerCase();
14
24
  if (name.endsWith(".pptx")) return "pptx";
@@ -57,6 +67,7 @@ export async function importUploadedDeckIntoDeck(
57
67
  deckId,
58
68
  importIntoDeck: true,
59
69
  },
70
+ { timeoutMs: IMPORT_ACTION_TIMEOUT_MS },
60
71
  ),
61
72
  );
62
73
  if (result.imported !== true || result.deckId !== deckId) {
@@ -48,7 +48,10 @@ export function readRecentReferences(
48
48
  const parsed: unknown = JSON.parse(raw);
49
49
  if (!Array.isArray(parsed)) return { items: [], readable: false };
50
50
  return {
51
- items: parsed.filter(isRecentReference).slice(0, MAX_RECENT_REFERENCES),
51
+ items: parsed
52
+ .filter(isRecentReference)
53
+ .sort((first, second) => second.lastUsedAt - first.lastUsedAt)
54
+ .slice(0, MAX_RECENT_REFERENCES),
52
55
  readable: true,
53
56
  };
54
57
  } catch {
@@ -56,6 +59,25 @@ export function readRecentReferences(
56
59
  }
57
60
  }
58
61
 
62
+ export function forgetRecentReference(
63
+ kind: RecentReferenceKind,
64
+ storage:
65
+ | Pick<Storage, "getItem" | "setItem">
66
+ | null
67
+ | undefined = typeof window === "undefined" ? null : window.localStorage,
68
+ ): RecentReferencesResult {
69
+ const current = readRecentReferences(storage);
70
+ if (!storage || !current.readable) return current;
71
+
72
+ const next = current.items.filter((item) => item.kind !== kind);
73
+ try {
74
+ storage.setItem(RECENT_REFERENCES_STORAGE_KEY, JSON.stringify(next));
75
+ return { items: next, readable: true };
76
+ } catch {
77
+ return { items: current.items, readable: false };
78
+ }
79
+ }
80
+
59
81
  export function rememberRecentReference(
60
82
  reference: Omit<RecentReference, "lastUsedAt">,
61
83
  storage:
@@ -9,6 +9,14 @@ export interface AnimationTarget {
9
9
  elementPath?: number[];
10
10
  }
11
11
 
12
+ export interface ResolvedAnimationTarget<
13
+ T extends AnimationTarget = AnimationTarget,
14
+ > {
15
+ target: T;
16
+ element: Element;
17
+ key: string;
18
+ }
19
+
12
20
  const INLINE_TAGS = new Set([
13
21
  "a",
14
22
  "abbr",
@@ -192,15 +200,45 @@ export function resolveSlideAnimationElement(
192
200
  root: Element,
193
201
  target: AnimationTarget,
194
202
  ): Element | null {
195
- if (Array.isArray(target.elementPath) && target.elementPath.length > 0) {
196
- const pathTarget = resolveElementPath(root, target.elementPath);
197
- if (pathTarget) return pathTarget;
203
+ if (Array.isArray(target.elementPath)) {
204
+ // A supplied path is the identity of the target. Falling back to a
205
+ // legacy index after it goes stale can animate a different element while
206
+ // leaving the intended element visible.
207
+ return target.elementPath.length > 0
208
+ ? resolveElementPath(root, target.elementPath)
209
+ : null;
198
210
  }
199
211
 
200
212
  const legacyContainer = findLegacyAnimationContainer(root);
201
213
  return legacyContainer?.children.item(target.elementIndex) ?? null;
202
214
  }
203
215
 
216
+ /**
217
+ * Resolve an ordered animation list as one validated unit. A null result
218
+ * means the list cannot be rendered faithfully - a missing or duplicate
219
+ * target must not become a phantom click step in the presentation player.
220
+ */
221
+ export function resolveSlideAnimationTargets<T extends AnimationTarget>(
222
+ root: Element,
223
+ targets: readonly T[],
224
+ ): ResolvedAnimationTarget<T>[] | null {
225
+ const seen = new Set<string>();
226
+ const resolved: ResolvedAnimationTarget<T>[] = [];
227
+
228
+ for (const target of targets) {
229
+ const element = resolveSlideAnimationElement(root, target);
230
+ if (!element) return null;
231
+ const path = getElementPath(root, element);
232
+ if (!path) return null;
233
+ const key = animationElementKey(path);
234
+ if (seen.has(key)) return null;
235
+ seen.add(key);
236
+ resolved.push({ target, element, key });
237
+ }
238
+
239
+ return resolved;
240
+ }
241
+
204
242
  export function getSlideAnimationTargetKey(
205
243
  html: string,
206
244
  target: AnimationTarget,
@@ -5,7 +5,7 @@ import {
5
5
  emailToColor,
6
6
  emailToName,
7
7
  } from "@agent-native/core/client/collab";
8
- import { useSession, callAction } from "@agent-native/core/client/hooks";
8
+ import { useSession } from "@agent-native/core/client/hooks";
9
9
  import { useT } from "@agent-native/core/client/i18n";
10
10
  import { useOrg } from "@agent-native/core/client/org";
11
11
  import {
@@ -54,7 +54,7 @@ import {
54
54
  useSlideComments,
55
55
  type CommentThread,
56
56
  } from "@/hooks/use-slide-comments";
57
- import { getAspectRatioDims, type AspectRatio } from "@/lib/aspect-ratios";
57
+ import { getAspectRatioDims } from "@/lib/aspect-ratios";
58
58
  import {
59
59
  deckAccessCheckKey,
60
60
  shouldShowDeckEditorSkeleton,
@@ -238,9 +238,6 @@ export default function DeckEditor() {
238
238
  const [pendingComment, setPendingComment] = useState<{
239
239
  quotedText: string;
240
240
  } | null>(null);
241
- const imageGenButtonRef = useRef<HTMLButtonElement>(null);
242
- const assetsButtonRef = useRef<HTMLButtonElement>(null);
243
-
244
241
  // Track which image src to replace
245
242
  const [replaceImageSrc, setReplaceImageSrc] = useState<string | null>(null);
246
243
 
@@ -345,6 +342,18 @@ export default function DeckEditor() {
345
342
  if (!generating) setAddSlideGenerating(false);
346
343
  }, [generating]);
347
344
 
345
+ // Below `md` the rail is a drawer behind a full-viewport dimming scrim; at
346
+ // `md` and up it's docked with no scrim. `sidebarOpen` is seeded from the
347
+ // width at mount only, so a window that starts wide and is then narrowed
348
+ // (or an editor opened in a resizable preview pane) keeps `sidebarOpen`
349
+ // true while the scrim stops being `md:hidden` — dimming the whole editor
350
+ // with no way to dismiss it.
351
+ useEffect(() => {
352
+ const onResize = () => setSidebarOpen(window.innerWidth >= 768);
353
+ window.addEventListener("resize", onResize);
354
+ return () => window.removeEventListener("resize", onResize);
355
+ }, []);
356
+
348
357
  const previousSlideIdsRef = useRef<string[]>([]);
349
358
  useEffect(() => {
350
359
  const currentSlideIds = deck?.slides.map((slide) => slide.id) ?? [];
@@ -1023,16 +1032,9 @@ export default function DeckEditor() {
1023
1032
  setReplaceImageSrc(null);
1024
1033
  setAssetLibraryOpen(true);
1025
1034
  }}
1026
- imageGenButtonRef={imageGenButtonRef}
1027
- assetsButtonRef={assetsButtonRef}
1028
- historyOpen={historyOpen}
1029
1035
  onShowHistory={() => setHistoryOpen(!historyOpen)}
1030
1036
  historyButtonRef={historyButtonRef}
1031
1037
  currentSlide={currentSlide}
1032
- onUpdateSlide={(updates, slideIdOverride) => {
1033
- const targetId = slideIdOverride ?? currentSlide?.id;
1034
- if (targetId) updateSlide(id, targetId, updates);
1035
- }}
1036
1038
  activeUsers={slideActiveUsers.filter((u) => u.email !== session?.email)}
1037
1039
  agentPresent={agentPresent}
1038
1040
  agentActive={agentActive}
@@ -1106,19 +1108,6 @@ export default function DeckEditor() {
1106
1108
  }
1107
1109
  return exportDeckToGoogleSlides(deck.title, slides, deck.aspectRatio);
1108
1110
  }}
1109
- aspectRatio={deck.aspectRatio}
1110
- onSetAspectRatio={(ratio: AspectRatio) => {
1111
- const previous = deck.aspectRatio;
1112
- // Optimistic UI: update local cache immediately so canvas resizes.
1113
- updateDeck(id, { aspectRatio: ratio });
1114
- callAction("update-deck-aspect-ratio", {
1115
- deckId: id,
1116
- aspectRatio: ratio,
1117
- }).catch((err) => {
1118
- console.error("Failed to set aspect ratio:", err);
1119
- updateDeck(id, { aspectRatio: previous });
1120
- });
1121
- }}
1122
1111
  currentSlideId={currentSlide?.id}
1123
1112
  addSlideGenerating={addSlideGenerating}
1124
1113
  onAddSlideGeneratingChange={setAddSlideGenerating}
@@ -1364,7 +1353,7 @@ export default function DeckEditor() {
1364
1353
  <ImageGenPanel
1365
1354
  open={imageGenOpen}
1366
1355
  onOpenChange={setImageGenOpen}
1367
- anchorRef={imageGenButtonRef}
1356
+ anchorRef={historyButtonRef}
1368
1357
  referenceImageUrls={imageStyleReferenceUrls}
1369
1358
  slideContext={
1370
1359
  currentSlide
@@ -1382,7 +1371,7 @@ export default function DeckEditor() {
1382
1371
  <AssetLibraryPanel
1383
1372
  open={assetLibraryOpen}
1384
1373
  onOpenChange={setAssetLibraryOpen}
1385
- anchorRef={assetsButtonRef}
1374
+ anchorRef={historyButtonRef}
1386
1375
  onSelectAsset={
1387
1376
  replaceImageSrc
1388
1377
  ? (newUrl) => {