@agent-native/core 0.145.8 → 0.146.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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/analytics/actions/compose-dashboard.ts +2 -27
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +18 -3
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +75 -24
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +16 -24
- package/corpus/templates/analytics/actions/rename-dashboard.ts +2 -24
- package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +2 -24
- package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +2 -25
- package/corpus/templates/analytics/actions/update-dashboard.ts +4 -32
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +1 -40
- package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-comm.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
- package/corpus/templates/analytics/netlify.toml +7 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +450 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +2 -2
- package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
- package/corpus/templates/analytics/server/lib/dashboard-collab-sync.ts +77 -0
- package/corpus/templates/analytics/server/lib/demo-dashboards.ts +2 -23
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
- package/corpus/templates/analytics/server/plugins/db.ts +42 -0
- package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +18 -4
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/brain/server/lib/connectors.ts +38 -14
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-08-07-fixed-trimmed-clips-durations-so-the-main-player-display-mat.md +6 -0
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +1 -1
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/dispatch/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-disp.md +6 -0
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +59 -22
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +10 -1
- package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +10 -3
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/slides/actions/generate-image-api.ts +123 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -2
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
- package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
- package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
- package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
- package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
- package/corpus/templates/slides/app/routes/slide.tsx +10 -5
- package/corpus/templates/slides/changelog/2026-08-07-deleting-a-deck-no-longer-freezes-the-slides-browser.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-07-slides-verify-generated-images-are-persisted-on-the-target-s.md +6 -0
- package/corpus/templates/slides/server/db/schema.ts +1 -0
- package/corpus/templates/slides/server/handlers/share.ts +51 -3
- package/corpus/templates/slides/server/lib/slide-image-insertion.ts +141 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/slides/server/plugins/db.ts +5 -0
- package/corpus/templates/slides/shared/api.ts +2 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/cli/code-agent-executor.js +8 -3
- package/dist/client/AgentPanel.d.ts +10 -4
- package/dist/client/AgentPanel.js +29 -82
- package/dist/client/ConnectBuilderCard.d.ts +7 -7
- package/dist/client/ConnectBuilderCard.js +45 -20
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/config.d.ts +9 -2
- package/dist/config.js +38 -2
- package/dist/db/client.d.ts +13 -1
- package/dist/db/client.js +76 -2
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/migrations.js +22 -4
- package/dist/deploy/build.d.ts +1 -0
- package/dist/deploy/build.js +32 -23
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/scripts/docs/search.js +3 -2
- package/dist/server/agent-chat/prompt-resources.js +4 -3
- package/dist/server/agents-bundle.d.ts +20 -3
- package/dist/server/agents-bundle.js +57 -30
- package/dist/server/better-auth-instance.js +2 -2
- package/dist/server/builder-browser.d.ts +34 -0
- package/dist/server/builder-browser.js +179 -0
- package/dist/server/core-routes-plugin.d.ts +5 -0
- package/dist/server/core-routes-plugin.js +24 -8
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/shared/cache-control.d.ts +2 -2
- package/dist/shared/cache-control.js +2 -2
- package/dist/shared/runtime-config.js +1 -1
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/vite/agent-native-config-loader.d.ts +10 -0
- package/dist/vite/agent-native-config-loader.js +56 -0
- package/dist/vite/agents-bundle-plugin.d.ts +5 -1
- package/dist/vite/agents-bundle-plugin.js +29 -6
- package/dist/vite/client.d.ts +4 -2
- package/dist/vite/client.js +17 -50
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +1 -1
- package/docs/content/agent-native-config.mdx +43 -17
- package/docs/content/drop-in-agent.mdx +3 -3
- package/docs/content/environment-variables.mdx +1 -1
- package/docs/content/getting-started-actions.mdx +1 -2
- package/docs/content/getting-started-pages.mdx +1 -2
- package/docs/content/getting-started.mdx +0 -4
- package/docs/content/what-is-agent-native.mdx +4 -4
- package/package.json +1 -1
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/docs/content/getting-started-database.mdx +0 -173
|
@@ -38,6 +38,8 @@ import type { Slide } from "@/context/DeckContext";
|
|
|
38
38
|
import type { AspectRatio } from "@/lib/aspect-ratios";
|
|
39
39
|
import { TAB_ID } from "@/lib/tab-id";
|
|
40
40
|
|
|
41
|
+
import type { DesignSystemData } from "../../../shared/api";
|
|
42
|
+
|
|
41
43
|
interface EditorSidebarProps {
|
|
42
44
|
slides: Slide[];
|
|
43
45
|
activeSlideId: string;
|
|
@@ -53,6 +55,8 @@ interface EditorSidebarProps {
|
|
|
53
55
|
recentEdits?: AttributedRecentEdit[];
|
|
54
56
|
/** Deck aspect ratio (defaults to 16:9 when omitted) */
|
|
55
57
|
aspectRatio?: AspectRatio;
|
|
58
|
+
/** Active deck design system used by slide content tokens. */
|
|
59
|
+
designSystem?: DesignSystemData;
|
|
56
60
|
/** The next slide while the agent is preparing its HTML. */
|
|
57
61
|
generatingSlide?: { index: number; content?: string | null };
|
|
58
62
|
generatingSlideSelected?: boolean;
|
|
@@ -154,6 +158,7 @@ function SortableSlideThumb({
|
|
|
154
158
|
registerButtonRef,
|
|
155
159
|
presenceUsers = [],
|
|
156
160
|
aspectRatio,
|
|
161
|
+
designSystem,
|
|
157
162
|
onOverflowChange,
|
|
158
163
|
readOnly = false,
|
|
159
164
|
}: {
|
|
@@ -167,6 +172,7 @@ function SortableSlideThumb({
|
|
|
167
172
|
registerButtonRef: (slideId: string, node: HTMLButtonElement | null) => void;
|
|
168
173
|
presenceUsers?: CollabUser[];
|
|
169
174
|
aspectRatio?: AspectRatio;
|
|
175
|
+
designSystem?: DesignSystemData;
|
|
170
176
|
onOverflowChange: (info: SlideOverflowInfo) => void;
|
|
171
177
|
}) {
|
|
172
178
|
const t = useT();
|
|
@@ -234,6 +240,7 @@ function SortableSlideThumb({
|
|
|
234
240
|
<SlideRenderer
|
|
235
241
|
slide={slide}
|
|
236
242
|
aspectRatio={aspectRatio}
|
|
243
|
+
designSystem={designSystem}
|
|
237
244
|
onOverflowChange={onOverflowChange}
|
|
238
245
|
/>
|
|
239
246
|
</div>
|
|
@@ -290,12 +297,14 @@ function SortableSlideThumb({
|
|
|
290
297
|
function GeneratingSlideSkeleton({
|
|
291
298
|
index,
|
|
292
299
|
aspectRatio,
|
|
300
|
+
designSystem,
|
|
293
301
|
content,
|
|
294
302
|
selected,
|
|
295
303
|
onSelect,
|
|
296
304
|
}: {
|
|
297
305
|
index: number;
|
|
298
306
|
aspectRatio?: AspectRatio;
|
|
307
|
+
designSystem?: DesignSystemData;
|
|
299
308
|
content?: string | null;
|
|
300
309
|
selected?: boolean;
|
|
301
310
|
onSelect?: () => void;
|
|
@@ -319,6 +328,7 @@ function GeneratingSlideSkeleton({
|
|
|
319
328
|
<GeneratingSlidePreview
|
|
320
329
|
content={content}
|
|
321
330
|
aspectRatio={aspectRatio}
|
|
331
|
+
designSystem={designSystem}
|
|
322
332
|
thumbnail
|
|
323
333
|
/>
|
|
324
334
|
</div>
|
|
@@ -338,6 +348,7 @@ export default function EditorSidebar({
|
|
|
338
348
|
slidePresence,
|
|
339
349
|
recentEdits,
|
|
340
350
|
aspectRatio,
|
|
351
|
+
designSystem,
|
|
341
352
|
generatingSlide,
|
|
342
353
|
generatingSlideSelected = false,
|
|
343
354
|
onSelectGeneratingSlide,
|
|
@@ -508,6 +519,7 @@ export default function EditorSidebar({
|
|
|
508
519
|
registerButtonRef={registerSlideButton}
|
|
509
520
|
presenceUsers={slidePresence?.get(slide.id) ?? []}
|
|
510
521
|
aspectRatio={aspectRatio}
|
|
522
|
+
designSystem={designSystem}
|
|
511
523
|
onOverflowChange={(info) =>
|
|
512
524
|
handleSlideOverflowChange(slide, info)
|
|
513
525
|
}
|
|
@@ -518,6 +530,7 @@ export default function EditorSidebar({
|
|
|
518
530
|
<GeneratingSlideSkeleton
|
|
519
531
|
index={generatingSlide.index}
|
|
520
532
|
aspectRatio={aspectRatio}
|
|
533
|
+
designSystem={designSystem}
|
|
521
534
|
content={generatingSlide.content}
|
|
522
535
|
selected={generatingSlideSelected}
|
|
523
536
|
onSelect={onSelectGeneratingSlide}
|
|
@@ -7,6 +7,8 @@ import type { Slide } from "@/context/DeckContext";
|
|
|
7
7
|
import type { AspectRatio } from "@/lib/aspect-ratios";
|
|
8
8
|
import { cn } from "@/lib/utils";
|
|
9
9
|
|
|
10
|
+
import type { DesignSystemData } from "../../../shared/api";
|
|
11
|
+
|
|
10
12
|
function SlideLoadingArtwork() {
|
|
11
13
|
return (
|
|
12
14
|
<div className="absolute inset-0 flex flex-col justify-center gap-4 bg-muted/30 p-[14%]">
|
|
@@ -25,11 +27,13 @@ export default function GeneratingSlidePreview({
|
|
|
25
27
|
aspectRatio,
|
|
26
28
|
thumbnail = true,
|
|
27
29
|
className,
|
|
30
|
+
designSystem,
|
|
28
31
|
}: {
|
|
29
32
|
content?: string | null;
|
|
30
33
|
aspectRatio?: AspectRatio;
|
|
31
34
|
thumbnail?: boolean;
|
|
32
35
|
className?: string;
|
|
36
|
+
designSystem?: DesignSystemData;
|
|
33
37
|
}) {
|
|
34
38
|
const t = useT();
|
|
35
39
|
const cssRatio = (aspectRatio ?? "16:9").replace(":", " / ");
|
|
@@ -55,6 +59,7 @@ export default function GeneratingSlidePreview({
|
|
|
55
59
|
slide={previewSlide}
|
|
56
60
|
aspectRatio={aspectRatio}
|
|
57
61
|
thumbnail={thumbnail}
|
|
62
|
+
designSystem={designSystem}
|
|
58
63
|
className="h-full w-full"
|
|
59
64
|
/>
|
|
60
65
|
) : (
|
|
@@ -80,7 +80,7 @@ export function buildImageGenerationContext({
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
contextParts.push(
|
|
83
|
-
'\nGenerate 3 variations. Show each as an inline rendered image preview using markdown image syntax (), not a plain text link — the chat renders "![]()" as an actual image but "[]()" as a bare link.
|
|
83
|
+
'\nGenerate 3 preview-only variations. Show each as an inline rendered image preview using markdown image syntax (), not a plain text link — the chat renders "![]()" as an actual image but "[]()" as a bare link. After the user chooses one, place that preview URL with update-slide and then get-deck to verify the persisted slide HTML contains it before claiming success. For a direct one-image request instead, call `generate-image-api` with insertIntoSlide: true plus deckId and slideId; claim it was added only if the action returns inserted: true.',
|
|
84
84
|
);
|
|
85
85
|
|
|
86
86
|
return contextParts.join("\n");
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
resolveSlideAnimationElement,
|
|
23
23
|
} from "@/lib/slide-animation-elements";
|
|
24
24
|
|
|
25
|
+
import type { DesignSystemData } from "../../../shared/api";
|
|
25
26
|
import { openPresentChannel, type PresentMessage } from "./present-channel";
|
|
26
27
|
|
|
27
28
|
interface PresentationViewProps {
|
|
@@ -29,6 +30,7 @@ interface PresentationViewProps {
|
|
|
29
30
|
deckId: string;
|
|
30
31
|
startIndex?: number;
|
|
31
32
|
aspectRatio?: AspectRatio;
|
|
33
|
+
designSystem?: DesignSystemData;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
// ─── Element animation helpers ────────────────────────────────────────────────
|
|
@@ -182,6 +184,7 @@ export default function PresentationView({
|
|
|
182
184
|
deckId,
|
|
183
185
|
startIndex = 0,
|
|
184
186
|
aspectRatio,
|
|
187
|
+
designSystem,
|
|
185
188
|
}: PresentationViewProps) {
|
|
186
189
|
const t = useT();
|
|
187
190
|
const safeSlides = useMemo(
|
|
@@ -534,6 +537,7 @@ export default function PresentationView({
|
|
|
534
537
|
slide={safeSlides[prevIndex]}
|
|
535
538
|
thumbnail={false}
|
|
536
539
|
aspectRatio={aspectRatio}
|
|
540
|
+
designSystem={designSystem}
|
|
537
541
|
/>
|
|
538
542
|
</div>
|
|
539
543
|
)}
|
|
@@ -548,6 +552,7 @@ export default function PresentationView({
|
|
|
548
552
|
slide={displaySlide}
|
|
549
553
|
thumbnail={false}
|
|
550
554
|
aspectRatio={aspectRatio}
|
|
555
|
+
designSystem={designSystem}
|
|
551
556
|
/>
|
|
552
557
|
</div>
|
|
553
558
|
|
|
@@ -6,6 +6,7 @@ import SlideRenderer from "@/components/deck/SlideRenderer";
|
|
|
6
6
|
import type { Slide } from "@/context/DeckContext";
|
|
7
7
|
import type { AspectRatio } from "@/lib/aspect-ratios";
|
|
8
8
|
|
|
9
|
+
import type { DesignSystemData } from "../../../shared/api";
|
|
9
10
|
import { openPresentChannel, type PresentMessage } from "./present-channel";
|
|
10
11
|
|
|
11
12
|
interface PresenterViewProps {
|
|
@@ -13,6 +14,7 @@ interface PresenterViewProps {
|
|
|
13
14
|
deckId: string;
|
|
14
15
|
startIndex?: number;
|
|
15
16
|
aspectRatio?: AspectRatio;
|
|
17
|
+
designSystem?: DesignSystemData;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
function formatElapsed(seconds: number) {
|
|
@@ -26,6 +28,7 @@ export default function PresenterView({
|
|
|
26
28
|
deckId,
|
|
27
29
|
startIndex = 0,
|
|
28
30
|
aspectRatio,
|
|
31
|
+
designSystem,
|
|
29
32
|
}: PresenterViewProps) {
|
|
30
33
|
const t = useT();
|
|
31
34
|
const [index, setIndex] = useState(startIndex);
|
|
@@ -136,6 +139,7 @@ export default function PresenterView({
|
|
|
136
139
|
slide={current}
|
|
137
140
|
thumbnail
|
|
138
141
|
aspectRatio={aspectRatio}
|
|
142
|
+
designSystem={designSystem}
|
|
139
143
|
/>
|
|
140
144
|
)}
|
|
141
145
|
</div>
|
|
@@ -149,6 +153,7 @@ export default function PresenterView({
|
|
|
149
153
|
slide={next}
|
|
150
154
|
thumbnail
|
|
151
155
|
aspectRatio={aspectRatio}
|
|
156
|
+
designSystem={designSystem}
|
|
152
157
|
/>
|
|
153
158
|
</div>
|
|
154
159
|
) : (
|
|
@@ -1128,6 +1128,7 @@ export default function DeckEditor() {
|
|
|
1128
1128
|
slidePresence={slidePresence}
|
|
1129
1129
|
recentEdits={deckRecentEdits}
|
|
1130
1130
|
aspectRatio={deck.aspectRatio}
|
|
1131
|
+
designSystem={designSystem}
|
|
1131
1132
|
generatingSlide={
|
|
1132
1133
|
generatingSlideVisible
|
|
1133
1134
|
? {
|
|
@@ -1166,6 +1167,7 @@ export default function DeckEditor() {
|
|
|
1166
1167
|
<GeneratingSlidePreview
|
|
1167
1168
|
content={streamedGeneratingSlideContent}
|
|
1168
1169
|
aspectRatio={deck.aspectRatio}
|
|
1170
|
+
designSystem={designSystem}
|
|
1169
1171
|
thumbnail={false}
|
|
1170
1172
|
/>
|
|
1171
1173
|
</div>
|
|
@@ -1181,6 +1183,7 @@ export default function DeckEditor() {
|
|
|
1181
1183
|
<GeneratingSlidePreview
|
|
1182
1184
|
content={streamedGeneratingSlideContent}
|
|
1183
1185
|
aspectRatio={deck.aspectRatio}
|
|
1186
|
+
designSystem={designSystem}
|
|
1184
1187
|
thumbnail={false}
|
|
1185
1188
|
/>
|
|
1186
1189
|
</div>
|
|
@@ -7,6 +7,7 @@ import PresentationView from "@/components/presentation/PresentationView";
|
|
|
7
7
|
import PresenterView from "@/components/presentation/PresenterView";
|
|
8
8
|
import { useDecks } from "@/context/DeckContext";
|
|
9
9
|
import type { Deck } from "@/context/DeckContext";
|
|
10
|
+
import { useDeckDesignSystem } from "@/hooks/use-deck-design-system";
|
|
10
11
|
|
|
11
12
|
export default function Presentation() {
|
|
12
13
|
const { id } = useParams<{ id: string }>();
|
|
@@ -25,6 +26,7 @@ export default function Presentation() {
|
|
|
25
26
|
const [searchParams] = useSearchParams();
|
|
26
27
|
const contextDeck = getDeck(id || "");
|
|
27
28
|
const deck = contextDeck ?? fallbackDeck;
|
|
29
|
+
const { designSystem } = useDeckDesignSystem(deck?.designSystemId);
|
|
28
30
|
|
|
29
31
|
useEffect(() => {
|
|
30
32
|
if (!id || loading || contextDeck) {
|
|
@@ -104,6 +106,7 @@ export default function Presentation() {
|
|
|
104
106
|
deckId={id}
|
|
105
107
|
startIndex={startSlide}
|
|
106
108
|
aspectRatio={deck.aspectRatio}
|
|
109
|
+
designSystem={designSystem}
|
|
107
110
|
/>
|
|
108
111
|
);
|
|
109
112
|
}
|
|
@@ -7,6 +7,7 @@ import { useParams } from "react-router";
|
|
|
7
7
|
|
|
8
8
|
import PresentationView from "@/components/presentation/PresentationView";
|
|
9
9
|
import type { Slide } from "@/context/DeckContext";
|
|
10
|
+
import { mergeDesignSystemData } from "@/hooks/use-deck-design-system";
|
|
10
11
|
|
|
11
12
|
interface SharedPresentationProps {
|
|
12
13
|
initialDeck?: SharedDeckResponse | null;
|
|
@@ -81,6 +82,9 @@ export default function SharedPresentation({
|
|
|
81
82
|
...s,
|
|
82
83
|
layout: s.layout as Slide["layout"],
|
|
83
84
|
}));
|
|
85
|
+
const designSystem = deck.designSystem
|
|
86
|
+
? mergeDesignSystemData(deck.designSystem)
|
|
87
|
+
: undefined;
|
|
84
88
|
|
|
85
89
|
// Use a fake deckId that routes "exit" back to the share page itself
|
|
86
90
|
return (
|
|
@@ -88,6 +92,7 @@ export default function SharedPresentation({
|
|
|
88
92
|
slides={slides}
|
|
89
93
|
deckId={`__shared__/${token}`}
|
|
90
94
|
aspectRatio={deck.aspectRatio}
|
|
95
|
+
designSystem={designSystem}
|
|
91
96
|
/>
|
|
92
97
|
);
|
|
93
98
|
}
|
|
@@ -10,6 +10,7 @@ import { useSearchParams } from "react-router";
|
|
|
10
10
|
|
|
11
11
|
import SlideRenderer from "@/components/deck/SlideRenderer";
|
|
12
12
|
import type { Slide } from "@/context/DeckContext";
|
|
13
|
+
import { useDeckDesignSystem } from "@/hooks/use-deck-design-system";
|
|
13
14
|
import messages from "@/i18n/en-US";
|
|
14
15
|
import type { AspectRatio } from "@/lib/aspect-ratios";
|
|
15
16
|
|
|
@@ -42,8 +43,10 @@ export default function SlideRoute() {
|
|
|
42
43
|
const [aspectRatio, setAspectRatio] = useState<AspectRatio | undefined>(
|
|
43
44
|
undefined,
|
|
44
45
|
);
|
|
46
|
+
const [designSystemId, setDesignSystemId] = useState<string | null>(null);
|
|
45
47
|
const [error, setError] = useState<string | null>(null);
|
|
46
48
|
const [loading, setLoading] = useState(true);
|
|
49
|
+
const { designSystem } = useDeckDesignSystem(designSystemId);
|
|
47
50
|
|
|
48
51
|
const slideNumber =
|
|
49
52
|
slideNumberParam !== null ? parseInt(slideNumberParam, 10) : null;
|
|
@@ -75,11 +78,11 @@ export default function SlideRoute() {
|
|
|
75
78
|
return;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
callAction<{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
81
|
+
callAction<{
|
|
82
|
+
slides?: Slide[];
|
|
83
|
+
aspectRatio?: AspectRatio;
|
|
84
|
+
designSystemId?: string | null;
|
|
85
|
+
}>("get-deck", { id: deckId }, { method: "GET" })
|
|
83
86
|
.then((deck) => {
|
|
84
87
|
const slides: Slide[] = deck.slides ?? [];
|
|
85
88
|
if (slides.length === 0) {
|
|
@@ -88,6 +91,7 @@ export default function SlideRoute() {
|
|
|
88
91
|
const idx = Math.max(0, Math.min(slideIndex, slides.length - 1));
|
|
89
92
|
setSlide(slides[idx]);
|
|
90
93
|
setAspectRatio(deck.aspectRatio);
|
|
94
|
+
setDesignSystemId(deck.designSystemId ?? null);
|
|
91
95
|
setLoading(false);
|
|
92
96
|
})
|
|
93
97
|
.catch((err: unknown) => {
|
|
@@ -123,6 +127,7 @@ export default function SlideRoute() {
|
|
|
123
127
|
slide={slide}
|
|
124
128
|
thumbnail={false}
|
|
125
129
|
aspectRatio={aspectRatio}
|
|
130
|
+
designSystem={designSystem}
|
|
126
131
|
/>
|
|
127
132
|
|
|
128
133
|
{inEmbed && (
|
|
@@ -54,6 +54,7 @@ export const deckShareLinks = table("deck_share_links", {
|
|
|
54
54
|
title: text("title").notNull(),
|
|
55
55
|
slides: text("slides").notNull(), // JSON array of slide snapshots
|
|
56
56
|
aspectRatio: text("aspect_ratio"),
|
|
57
|
+
designSystemData: text("design_system_data"), // Share-safe token snapshot
|
|
57
58
|
createdAt: text("created_at").notNull().default(now()),
|
|
58
59
|
});
|
|
59
60
|
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
|
|
3
3
|
import { readBody } from "@agent-native/core/server";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
assertAccess,
|
|
6
|
+
ForbiddenError,
|
|
7
|
+
resolveAccess,
|
|
8
|
+
} from "@agent-native/core/sharing";
|
|
5
9
|
import { toSharedDeckSlide } from "@shared/api";
|
|
6
10
|
import type {
|
|
11
|
+
DesignSystemData,
|
|
7
12
|
ShareDeckRequest,
|
|
8
13
|
ShareDeckResponse,
|
|
9
14
|
SharedDeckResponse,
|
|
@@ -19,6 +24,12 @@ import {
|
|
|
19
24
|
|
|
20
25
|
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
21
26
|
|
|
27
|
+
interface DeckShareResource {
|
|
28
|
+
title?: string | null;
|
|
29
|
+
data: string;
|
|
30
|
+
designSystemId?: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
/**
|
|
23
34
|
* POST /api/share
|
|
24
35
|
* Persist a deck snapshot with a random token.
|
|
@@ -53,11 +64,13 @@ async function createShareLink(event: any, deckId: string) {
|
|
|
53
64
|
const db = getDb();
|
|
54
65
|
let storedDeck: any;
|
|
55
66
|
let title = "Untitled";
|
|
67
|
+
let deckResource: DeckShareResource;
|
|
56
68
|
|
|
57
69
|
try {
|
|
58
70
|
const access = await assertAccess("deck", deckId, "admin");
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
deckResource = access.resource as DeckShareResource;
|
|
72
|
+
title = deckResource.title ?? "Untitled";
|
|
73
|
+
storedDeck = JSON.parse(deckResource.data);
|
|
61
74
|
} catch (err) {
|
|
62
75
|
if (err instanceof ForbiddenError) {
|
|
63
76
|
setResponseStatus(event, err.statusCode);
|
|
@@ -73,6 +86,29 @@ async function createShareLink(event: any, deckId: string) {
|
|
|
73
86
|
|
|
74
87
|
const token = crypto.randomBytes(12).toString("base64url");
|
|
75
88
|
const now = new Date().toISOString();
|
|
89
|
+
const designSystemId =
|
|
90
|
+
deckResource.designSystemId ?? storedDeck.designSystemId;
|
|
91
|
+
let designSystemData: string | null = null;
|
|
92
|
+
|
|
93
|
+
if (typeof designSystemId === "string" && designSystemId.trim()) {
|
|
94
|
+
const designSystemAccess = await resolveAccess(
|
|
95
|
+
"design-system",
|
|
96
|
+
designSystemId,
|
|
97
|
+
);
|
|
98
|
+
const rawData = designSystemAccess?.resource?.data;
|
|
99
|
+
if (typeof rawData === "string") {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(rawData);
|
|
102
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
103
|
+
designSystemData = JSON.stringify(parsed as DesignSystemData);
|
|
104
|
+
}
|
|
105
|
+
// coercion-ok: malformed optional style data keeps a valid deck shareable.
|
|
106
|
+
} catch {
|
|
107
|
+
// A malformed style record should not make an otherwise valid deck
|
|
108
|
+
// impossible to share; the presentation will use its default tokens.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
76
112
|
|
|
77
113
|
const slides = storedDeck.slides.map((slide: unknown, index: number) =>
|
|
78
114
|
toSharedDeckSlide(slide, index),
|
|
@@ -83,6 +119,7 @@ async function createShareLink(event: any, deckId: string) {
|
|
|
83
119
|
title: title || storedDeck.title || "Untitled",
|
|
84
120
|
slides: JSON.stringify(slides),
|
|
85
121
|
aspectRatio: storedDeck.aspectRatio ?? null,
|
|
122
|
+
designSystemData,
|
|
86
123
|
createdAt: now,
|
|
87
124
|
});
|
|
88
125
|
|
|
@@ -136,5 +173,16 @@ export const getSharedDeck = defineEventHandler(async (event) => {
|
|
|
136
173
|
slides: JSON.parse(shared.slides),
|
|
137
174
|
aspectRatio: shared.aspectRatio as SharedDeckResponse["aspectRatio"],
|
|
138
175
|
};
|
|
176
|
+
if (shared.designSystemData) {
|
|
177
|
+
try {
|
|
178
|
+
const parsed = JSON.parse(shared.designSystemData);
|
|
179
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
180
|
+
response.designSystem = parsed as DesignSystemData;
|
|
181
|
+
}
|
|
182
|
+
// coercion-ok: malformed optional snapshots remain viewable with default tokens.
|
|
183
|
+
} catch {
|
|
184
|
+
// Keep legacy or malformed snapshots viewable with default tokens.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
139
187
|
return response;
|
|
140
188
|
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
interface InsertSlideImageOptions {
|
|
2
|
+
alt?: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function cleanAlt(value: string | undefined): string {
|
|
6
|
+
return (value || "Uploaded image").replace(/\s+/g, " ").trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function escapeAttribute(value: string): string {
|
|
10
|
+
return value
|
|
11
|
+
.replace(/&/g, "&")
|
|
12
|
+
.replace(/"/g, """)
|
|
13
|
+
.replace(/</g, "<")
|
|
14
|
+
.replace(/>/g, ">");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function decodeHtml(value: string): string {
|
|
18
|
+
return value
|
|
19
|
+
.replace(/"/gi, '"')
|
|
20
|
+
.replace(/'/gi, "'")
|
|
21
|
+
.replace(/</gi, "<")
|
|
22
|
+
.replace(/>/gi, ">")
|
|
23
|
+
.replace(/&/gi, "&");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function textContent(html: string): string {
|
|
27
|
+
return decodeHtml(html.replace(/<[^>]+>/g, " "))
|
|
28
|
+
.replace(/\s+/g, " ")
|
|
29
|
+
.trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function attribute(attributes: string, name: string): string | null {
|
|
33
|
+
const match = attributes.match(
|
|
34
|
+
new RegExp(`\\b${name}\\s*=\\s*(["'])([\\s\\S]*?)\\1`, "i"),
|
|
35
|
+
);
|
|
36
|
+
return match ? match[2] : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hasStyleProperty(style: string, property: string): boolean {
|
|
40
|
+
return new RegExp(`(?:^|;)\\s*${property}\\s*:`, "i").test(style);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function appendImageStyle(baseStyle: string): string {
|
|
44
|
+
const declarations = [baseStyle.trim().replace(/;+\s*$/, "")].filter(Boolean);
|
|
45
|
+
if (!hasStyleProperty(baseStyle, "display"))
|
|
46
|
+
declarations.push("display: block");
|
|
47
|
+
if (!hasStyleProperty(baseStyle, "object-fit")) {
|
|
48
|
+
declarations.push("object-fit: cover");
|
|
49
|
+
}
|
|
50
|
+
if (!hasStyleProperty(baseStyle, "min-width"))
|
|
51
|
+
declarations.push("min-width: 0");
|
|
52
|
+
return declarations.length > 0 ? `${declarations.join("; ")};` : "";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function imageHtml(src: string, alt: string, style: string): string {
|
|
56
|
+
return `<img src="${escapeAttribute(src)}" alt="${escapeAttribute(alt)}" class="fmd-img-uploaded"${style ? ` style="${escapeAttribute(style)}"` : ""}>`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Server-safe counterpart to the editor's image replacement helper. It uses
|
|
61
|
+
* deterministic string transforms because DOMParser is not available in the
|
|
62
|
+
* server runtime that executes actions.
|
|
63
|
+
*/
|
|
64
|
+
export function insertImageIntoSlideHtml(
|
|
65
|
+
content: string,
|
|
66
|
+
src: string,
|
|
67
|
+
options: InsertSlideImageOptions = {},
|
|
68
|
+
): string {
|
|
69
|
+
const openingTags = /<([a-z][\w:-]*)([^>]*)>/gi;
|
|
70
|
+
for (const match of content.matchAll(openingTags)) {
|
|
71
|
+
const className = attribute(match[2], "class") || "";
|
|
72
|
+
if (!/(?:^|\s)fmd-img-placeholder(?:\s|$)/.test(className)) continue;
|
|
73
|
+
|
|
74
|
+
const closingTag = new RegExp(`</${match[1]}\\s*>`, "gi");
|
|
75
|
+
closingTag.lastIndex = (match.index ?? 0) + match[0].length;
|
|
76
|
+
const closing = closingTag.exec(content);
|
|
77
|
+
if (!closing || match.index === undefined) continue;
|
|
78
|
+
|
|
79
|
+
const style = appendImageStyle(
|
|
80
|
+
attribute(match[2], "style") ||
|
|
81
|
+
"width: 100%; height: 100%; border-radius: 8px; object-fit: cover;",
|
|
82
|
+
);
|
|
83
|
+
const replacement = imageHtml(
|
|
84
|
+
src,
|
|
85
|
+
cleanAlt(
|
|
86
|
+
options.alt ||
|
|
87
|
+
textContent(
|
|
88
|
+
content.slice(match.index + match[0].length, closing.index),
|
|
89
|
+
) ||
|
|
90
|
+
"Uploaded image",
|
|
91
|
+
),
|
|
92
|
+
style,
|
|
93
|
+
);
|
|
94
|
+
return `${content.slice(0, match.index)}${replacement}${content.slice(closing.index + closing[0].length)}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fullBleed = imageHtml(
|
|
98
|
+
src,
|
|
99
|
+
cleanAlt(options.alt),
|
|
100
|
+
"position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -1;",
|
|
101
|
+
);
|
|
102
|
+
const slideRoot =
|
|
103
|
+
/<([a-z][\w:-]*)([^>]*\bclass\s*=\s*(["'])[^"']*\bfmd-slide\b[^"']*\3[^>]*)>/i.exec(
|
|
104
|
+
content,
|
|
105
|
+
);
|
|
106
|
+
if (!slideRoot || slideRoot.index === undefined)
|
|
107
|
+
return `${fullBleed}${content}`;
|
|
108
|
+
|
|
109
|
+
let openingTag = slideRoot[0];
|
|
110
|
+
const style = attribute(slideRoot[2], "style") || "";
|
|
111
|
+
if (!hasStyleProperty(style, "position")) {
|
|
112
|
+
const nextStyle =
|
|
113
|
+
`${style.trim().replace(/;+\s*$/, "")}; position: relative;`.replace(
|
|
114
|
+
/^;\s*/,
|
|
115
|
+
"",
|
|
116
|
+
);
|
|
117
|
+
if (/\bstyle\s*=\s*(["'])/i.test(openingTag)) {
|
|
118
|
+
openingTag = openingTag.replace(
|
|
119
|
+
/\bstyle\s*=\s*(["'])[^"']*\1/i,
|
|
120
|
+
`style="${escapeAttribute(nextStyle)}"`,
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
openingTag = openingTag.replace(
|
|
124
|
+
/>$/,
|
|
125
|
+
` style="${escapeAttribute(nextStyle)}">`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return `${content.slice(0, slideRoot.index)}${openingTag}${fullBleed}${content.slice(slideRoot.index + slideRoot[0].length)}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function slideHtmlContainsImageSource(
|
|
133
|
+
html: string,
|
|
134
|
+
src: string,
|
|
135
|
+
): boolean {
|
|
136
|
+
const images = /<img\b([^>]*)>/gi;
|
|
137
|
+
for (const match of html.matchAll(images)) {
|
|
138
|
+
if (decodeHtml(attribute(match[1], "src") || "") === src) return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
@@ -62,6 +62,8 @@ Layout-fit workflow is strict. When the user asks to fix overflow, first call vi
|
|
|
62
62
|
|
|
63
63
|
Fit means the main content fits the native content area. A small outer-wrapper spill is tolerated by the measurement, but cards, text, columns, and other visible content must fit. Never use zoom, transform: scale(), overflow: hidden/scroll, clipping, or a smaller-than-16px body font to hide overflow. Preserve manually positioned freeform objects and their data-slide-object-id values; repair normal-flow structure, copy, gaps, or slide padding instead. A successful action result must include the affected slide IDs; if it does not, report that no verified write occurred.
|
|
64
64
|
|
|
65
|
+
Image workflow is strict. For direct insertion, call generate-image-api with insertIntoSlide: true plus deckId and slideId. Claim that an image was added only when that action returns inserted: true; a preview URL or completed generation alone is not a slide edit. For preview-only variations, call generate-image-api without insertIntoSlide, then use update-slide to place the chosen URL and re-read the target with get-deck to confirm its persisted HTML contains that image source before claiming success.
|
|
66
|
+
|
|
65
67
|
Provider-specific Slides actions are shortcuts, not limits. If a first-class action cannot express the exact Google Drive endpoint, file metadata field, export format, query, request body, pagination mode, payload shape, or API version needed, call provider-api-catalog and provider-api-docs as needed, then call provider-api-request against the real provider API. Use the raw provider API escape hatch instead of weakening the answer or claiming Slides cannot do something the underlying Google Drive API can do.
|
|
66
68
|
|
|
67
69
|
Slides' Google Drive provider API uses the user's connected Google Docs OAuth account. The drive.file scope is intentionally limited to files the user selected or the app created. For large Drive file lists or metadata sweeps, pass stageAs and pagination options to provider-api-request, then use query-staged-dataset to count, filter, group, or project the staged rows.
|
|
@@ -230,6 +230,11 @@ const runSlidesMigrations = runMigrations(
|
|
|
230
230
|
);
|
|
231
231
|
CREATE INDEX IF NOT EXISTS uploaded_assets_owner_created_idx ON uploaded_assets (owner_email, created_at)`,
|
|
232
232
|
},
|
|
233
|
+
{
|
|
234
|
+
version: 21,
|
|
235
|
+
name: "slides-share-design-system-snapshot",
|
|
236
|
+
sql: `ALTER TABLE deck_share_links ADD COLUMN IF NOT EXISTS design_system_data TEXT`,
|
|
237
|
+
},
|
|
233
238
|
],
|
|
234
239
|
{ table: "slides_migrations" },
|
|
235
240
|
);
|
|
@@ -82,6 +82,8 @@ export interface SharedDeckResponse {
|
|
|
82
82
|
title: string;
|
|
83
83
|
slides: SharedDeckSlide[];
|
|
84
84
|
aspectRatio?: import("./aspect-ratios").AspectRatio;
|
|
85
|
+
/** Resolved at share creation so public links keep the deck's styling. */
|
|
86
|
+
designSystem?: DesignSystemData;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
export type SharedSlideTransition =
|