@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
- package/corpus/templates/analytics/app/global.css +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +37 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
- package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +274 -137
- package/corpus/templates/clips/desktop/src/styles.css +35 -0
- package/corpus/templates/content/app/global.css +1 -0
- package/corpus/templates/crm/app/global.css +1 -1
- package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -0
- package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
- package/corpus/templates/slides/AGENTS.md +11 -10
- package/corpus/templates/slides/actions/add-deck.ts +2 -0
- package/corpus/templates/slides/actions/add-slide.ts +15 -1
- package/corpus/templates/slides/actions/create-deck.ts +18 -6
- package/corpus/templates/slides/actions/get-deck.ts +29 -1
- package/corpus/templates/slides/actions/patch-deck.ts +139 -10
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +10 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +74 -63
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +29 -7
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
- package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
- package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
- package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
- package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +16 -27
- package/corpus/templates/slides/app/pages/Index.tsx +256 -98
- package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
- package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
- package/corpus/templates/slides/shared/api.ts +2 -1
- package/corpus/templates/slides/shared/deck-title.ts +135 -0
- package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
- package/dist/agent/engine/ai-sdk-engine.js +1 -1
- package/dist/agent/production-agent.d.ts +21 -1
- package/dist/agent/production-agent.js +129 -10
- package/dist/agent/tool-approval-store.d.ts +18 -0
- package/dist/agent/tool-approval-store.js +122 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +11 -2
- package/dist/client/AgentPanel.js +31 -6
- package/dist/client/AssistantChat.d.ts +3 -2
- package/dist/client/AssistantChat.js +7 -20
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +11 -0
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/chat/markdown-renderer.js +14 -2
- package/dist/client/chat/message-components.d.ts +6 -2
- package/dist/client/chat/message-components.js +25 -47
- package/dist/client/chat/tool-call-display.d.ts +3 -5
- package/dist/client/chat/tool-call-display.js +7 -212
- package/dist/client/chat-first/apps-rail.js +3 -3
- package/dist/client/chat-first/browser-pane.js +4 -1
- package/dist/client/chat-first/chat-history.d.ts +3 -1
- package/dist/client/chat-first/chat-history.js +2 -2
- package/dist/client/chat-first/surface-tabs.d.ts +1 -1
- package/dist/client/chat-first/surface-tabs.js +52 -41
- package/dist/client/chat-first/types.d.ts +3 -0
- package/dist/client/chat-first.d.ts +10 -0
- package/dist/client/chat-first.js +37 -2
- package/dist/client/components/ui/message-scroller.js +4 -1
- package/dist/client/i18n.js +3 -1
- package/dist/client/progress/RunsTray.js +14 -1
- package/dist/client/resources/McpConnectionSuggestion.js +9 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/use-chat-models.js +156 -80
- package/dist/integrations/webhook-handler.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/auth.js +262 -31
- package/dist/server/onboarding-html.js +56 -9
- package/dist/shared/reasoning-effort.d.ts +8 -8
- package/dist/shared/reasoning-effort.js +10 -10
- package/dist/shared/streaming-text-smoothing.js +41 -11
- package/dist/styles/agent-native.css +19 -32
- package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +2 -2
- 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(
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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)
|
|
196
|
-
|
|
197
|
-
|
|
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
|
|
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
|
|
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={
|
|
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={
|
|
1374
|
+
anchorRef={historyButtonRef}
|
|
1386
1375
|
onSelectAsset={
|
|
1387
1376
|
replaceImageSrc
|
|
1388
1377
|
? (newUrl) => {
|