@agent-native/core 0.137.0 → 0.137.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 (47) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/AgentCompletionSound.tsx +105 -0
  3. package/corpus/templates/analytics/app/components/layout/Layout.tsx +44 -35
  4. package/corpus/templates/analytics/app/i18n-data.ts +38 -0
  5. package/corpus/templates/analytics/app/pages/Settings.tsx +53 -1
  6. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +6 -0
  7. package/corpus/templates/analytics/changelog/2026-08-04-choose-whether-analytics-plays-a-bell-when-the-agent-finishe.md +6 -0
  8. package/corpus/templates/analytics/shared/analytics-user-prefs.ts +2 -0
  9. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +258 -0
  10. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +130 -0
  11. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +25 -337
  12. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +73 -53
  13. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +321 -0
  14. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +32 -11
  15. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +804 -0
  16. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +101 -45
  17. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +7 -4
  18. package/corpus/templates/slides/app/components/editor/bullet-editing.ts +11 -3
  19. package/corpus/templates/slides/app/components/editor/commit-active-edit.ts +21 -0
  20. package/corpus/templates/slides/app/components/editor/list-editing.ts +219 -0
  21. package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +0 -3
  22. package/corpus/templates/slides/app/components/editor/slide-style.ts +199 -0
  23. package/corpus/templates/slides/app/global.css +14 -3
  24. package/corpus/templates/slides/app/i18n/en-US.ts +8 -1
  25. package/corpus/templates/slides/app/lib/recent-references.ts +82 -0
  26. package/corpus/templates/slides/app/pages/DeckEditor.tsx +51 -22
  27. package/corpus/templates/slides/app/pages/Index.tsx +236 -181
  28. package/corpus/templates/slides/changelog/2026-08-01-add-slide-moved-to-the-toolbar-and-the-slide-rail-is-now-mor.md +9 -0
  29. package/corpus/templates/slides/changelog/2026-08-01-add-slide-undo-redo-and-the-text-tool-now-lead-the-slide-too.md +9 -0
  30. package/corpus/templates/slides/changelog/2026-08-01-slide-styling-now-appears-in-a-contextual-toolbar-above-the-.md +6 -0
  31. package/corpus/templates/slides/changelog/2026-08-01-the-slide-style-side-panel-is-retired-all-styling-now-lives-.md +6 -0
  32. package/corpus/templates/slides/changelog/2026-08-03-the-slide-toolbar-is-denser-swatch-only-colors-dropdowns-for.md +10 -0
  33. package/corpus/templates/slides/changelog/2026-08-04-bullet-and-numbered-list-buttons-in-the-slide-toolbar-conver.md +6 -0
  34. package/corpus/templates/slides/changelog/2026-08-04-italic-and-underline-are-now-one-click-away-in-the-slide-too.md +6 -0
  35. package/dist/client/DefaultSpinner.d.ts +0 -12
  36. package/dist/client/DefaultSpinner.js +22 -2
  37. package/dist/client/onboarding/use-onboarding.js +3 -0
  38. package/dist/collab/routes.d.ts +1 -1
  39. package/dist/collab/struct-routes.d.ts +1 -1
  40. package/dist/deploy/build.js +2 -1
  41. package/dist/notifications/routes.d.ts +2 -2
  42. package/dist/progress/routes.d.ts +1 -1
  43. package/dist/server/realtime-token.d.ts +1 -1
  44. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
  45. package/package.json +2 -2
  46. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +25 -9
  47. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +0 -763
@@ -0,0 +1,199 @@
1
+ import type { DesignSystemData } from "@shared/api";
2
+
3
+ import type { InlineTextStyleKey } from "./rich-text-selection";
4
+
5
+ export interface SlideStyleSnapshot {
6
+ /** Omitted snapshots are existing object snapshots for backward compatibility. */
7
+ mode?: "object";
8
+ selector: string;
9
+ label: string;
10
+ tagName: string;
11
+ textPreview: string;
12
+ isText: boolean;
13
+ isImage: boolean;
14
+ isAbsolute: boolean;
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ rotation: number;
20
+ slideWidth: number;
21
+ slideHeight: number;
22
+ color: string;
23
+ backgroundColor: string;
24
+ fontSize: number;
25
+ fontWeight: string;
26
+ fontStyle: string;
27
+ textDecoration: string;
28
+ lineHeight: number;
29
+ textAlign: string;
30
+ opacity: number;
31
+ borderRadius: number;
32
+ borderWidth: number;
33
+ borderColor: string;
34
+ paddingX: number;
35
+ paddingY: number;
36
+ zIndex: number;
37
+ listKind: "bullet" | "ordered" | null;
38
+ textStyleScope?: "block" | "selection";
39
+ mixedTextStyles?: InlineTextStyleKey[];
40
+ }
41
+
42
+ export type SlideStylePatch = Partial<{
43
+ color: string;
44
+ backgroundColor: string;
45
+ fontSize: string;
46
+ fontWeight: string;
47
+ fontStyle: string;
48
+ textDecoration: string;
49
+ lineHeight: string;
50
+ textAlign: string;
51
+ opacity: string;
52
+ borderRadius: string;
53
+ borderWidth: string;
54
+ borderColor: string;
55
+ paddingLeft: string;
56
+ paddingRight: string;
57
+ paddingTop: string;
58
+ paddingBottom: string;
59
+ left: string;
60
+ top: string;
61
+ width: string;
62
+ height: string;
63
+ transform: string;
64
+ zIndex: string;
65
+ }>;
66
+
67
+ export function tokenPalette(
68
+ designSystem: DesignSystemData | undefined,
69
+ t: (key: string) => string,
70
+ ) {
71
+ const colors = designSystem?.colors;
72
+ const base = colors
73
+ ? [
74
+ {
75
+ label: t("styleInspector.primary"),
76
+ value: colors.primary,
77
+ color: colors.primary,
78
+ },
79
+ {
80
+ label: t("styleInspector.secondary"),
81
+ value: colors.secondary,
82
+ color: colors.secondary,
83
+ },
84
+ {
85
+ label: t("styleInspector.accent"),
86
+ value: colors.accent,
87
+ color: colors.accent,
88
+ },
89
+ {
90
+ label: t("styleInspector.surface"),
91
+ value: colors.surface,
92
+ color: colors.surface,
93
+ },
94
+ {
95
+ label: t("styleInspector.background"),
96
+ value: colors.background,
97
+ color: colors.background,
98
+ },
99
+ {
100
+ label: t("styleInspector.text"),
101
+ value: colors.text,
102
+ color: colors.text,
103
+ },
104
+ {
105
+ label: t("styleInspector.muted"),
106
+ value: colors.textMuted,
107
+ color: colors.textMuted,
108
+ },
109
+ ]
110
+ : [];
111
+
112
+ // Fixed swatches offered when a deck has no design system. These are
113
+ // document colors the user paints onto slide content, so they must stay
114
+ // literal — theming them would repaint finished decks on a theme switch.
115
+ const presets: Array<[key: string, hex: string]> = [
116
+ ["white", "#ffffff"], // guard:allow-raw-color
117
+ ["black", "#000000"], // guard:allow-raw-color
118
+ ["slate", "#1f2937"], // guard:allow-raw-color
119
+ ["blue", "#609ff8"], // guard:allow-raw-color
120
+ ["cyan", "#22d3ee"], // guard:allow-raw-color
121
+ ["emerald", "#34d399"], // guard:allow-raw-color
122
+ ["amber", "#fbbf24"], // guard:allow-raw-color
123
+ ["rose", "#fb7185"], // guard:allow-raw-color
124
+ ];
125
+
126
+ return [
127
+ ...base,
128
+ ...presets.map(([key, hex]) => ({
129
+ label: t(`styleInspector.${key}`),
130
+ value: hex,
131
+ color: hex,
132
+ })),
133
+ ];
134
+ }
135
+
136
+ // `slide.background` holds either a raw CSS value or a Tailwind arbitrary
137
+ // class (`bg-[...]`), which SlideRenderer applies as a class rather than
138
+ // an inline style. The picker only speaks CSS colors, so unwrap the arbitrary
139
+ // form and report anything else (named utilities, gradients) as unreadable
140
+ // rather than guessing a hex the slide is not actually using.
141
+ export function backgroundCssValue(
142
+ background: string | undefined,
143
+ ): string | null {
144
+ // guard:allow-raw-color — mirrors SlideRenderer's own default slide fill
145
+ if (!background) return "#000000";
146
+ const arbitrary = background.match(/^bg-\[(.+)\]$/);
147
+ if (arbitrary) return arbitrary[1].replace(/_/g, " ");
148
+ return background.startsWith("bg-") ? null : background;
149
+ }
150
+
151
+ export function formatValue(value: number) {
152
+ return Number.isInteger(value)
153
+ ? String(value)
154
+ : String(Number(value.toFixed(2)));
155
+ }
156
+
157
+ export function rotationTransform(rotation: number) {
158
+ return `rotate(${formatValue(rotation)}deg)`;
159
+ }
160
+
161
+ export function resolveHorizontalAlignment(snapshot: SlideStyleSnapshot) {
162
+ if (snapshot.x <= 0) return "left";
163
+ const centered = (snapshot.slideWidth - snapshot.width) / 2;
164
+ return Math.abs(snapshot.x - centered) < 1 ? "center" : "right";
165
+ }
166
+
167
+ export function resolveVerticalAlignment(snapshot: SlideStyleSnapshot) {
168
+ if (snapshot.y <= 0) return "top";
169
+ const centered = (snapshot.slideHeight - snapshot.height) / 2;
170
+ return Math.abs(snapshot.y - centered) < 1 ? "middle" : "bottom";
171
+ }
172
+
173
+ export function horizontalAlignPatch(
174
+ snapshot: SlideStyleSnapshot,
175
+ alignment: string,
176
+ ): SlideStylePatch {
177
+ const available = Math.max(0, snapshot.slideWidth - snapshot.width);
178
+ const x =
179
+ alignment === "left"
180
+ ? 0
181
+ : alignment === "center"
182
+ ? available / 2
183
+ : available;
184
+ return { left: `${formatValue(x)}px` };
185
+ }
186
+
187
+ export function verticalAlignPatch(
188
+ snapshot: SlideStyleSnapshot,
189
+ alignment: string,
190
+ ): SlideStylePatch {
191
+ const available = Math.max(0, snapshot.slideHeight - snapshot.height);
192
+ const y =
193
+ alignment === "top"
194
+ ? 0
195
+ : alignment === "middle"
196
+ ? available / 2
197
+ : available;
198
+ return { top: `${formatValue(y)}px` };
199
+ }
@@ -109,9 +109,10 @@ button[title="Open agent sidebar"] {
109
109
  --accent-cyan: 193 82% 31%;
110
110
  }
111
111
 
112
- /* Slides keeps the shared visual inspector behavior, but the dock needs the
112
+ /* Slides keeps the shared visual inspector behavior, but the dock and context toolbar need the
113
113
  flatter, filled-control treatment used by the slide editor. */
114
- .slide-style-inspector {
114
+ .slide-style-inspector,
115
+ .slide-context-toolbar {
115
116
  --slides-inspector-control-background: hsl(var(--muted) / 0.82);
116
117
  --slides-inspector-divider: hsl(var(--border) / 0.7);
117
118
  --design-editor-control-bg: var(--slides-inspector-control-background);
@@ -161,11 +162,21 @@ button[title="Open agent sidebar"] {
161
162
  box-shadow: none;
162
163
  }
163
164
 
165
+ /* The toolbar sits on a muted surface, so its inputs need the opposite
166
+ treatment to stay readable: a light box rather than a darker fill. */
167
+ .slide-context-toolbar input {
168
+ border: 1px solid var(--slides-inspector-divider);
169
+ border-radius: 0.25rem;
170
+ background: hsl(var(--background));
171
+ box-shadow: none;
172
+ }
173
+
164
174
  .slide-style-inspector input:focus {
165
175
  border: 0;
166
176
  }
167
177
 
168
- .slide-style-inspector .slides-inspector-segment {
178
+ .slide-style-inspector .slides-inspector-segment,
179
+ .slide-context-toolbar .slides-inspector-segment {
169
180
  min-height: 1.5rem;
170
181
  border: 0;
171
182
  border-radius: 0.125rem;
@@ -404,6 +404,14 @@ const messages = {
404
404
  cornerRadius: "Corner radius",
405
405
  strokeWeight: "Weight",
406
406
  typography: "Typography",
407
+ weight: "Weight",
408
+ italic: "Italic",
409
+ underline: "Underline",
410
+ bulletList: "Bullet list",
411
+ numberedList: "Numbered list",
412
+ align: "Align",
413
+ decreaseSize: "Decrease font size",
414
+ increaseSize: "Increase font size",
407
415
  mixed: "Mixed",
408
416
  textColor: "Text color",
409
417
  primary: "Primary",
@@ -501,7 +509,6 @@ const messages = {
501
509
  noAi: "no AI",
502
510
  duplicateCurrentSlide: "Duplicate current slide",
503
511
  promptPlaceholder: "Describe the slides you want...",
504
- slides: "Slides",
505
512
  },
506
513
  presentation: {
507
514
  loadFailed: "Could not load this presentation.",
@@ -0,0 +1,82 @@
1
+ export const RECENT_REFERENCES_STORAGE_KEY = "slides:recent-references";
2
+
3
+ export type RecentReferenceKind = "deck" | "design-system";
4
+
5
+ export interface RecentReference {
6
+ id: string;
7
+ kind: RecentReferenceKind;
8
+ lastUsedAt: number;
9
+ }
10
+
11
+ export interface RecentReferencesResult {
12
+ items: RecentReference[];
13
+ readable: boolean;
14
+ }
15
+
16
+ const MAX_RECENT_REFERENCES = 8;
17
+
18
+ function isRecentReference(value: unknown): value is RecentReference {
19
+ if (!value || typeof value !== "object") return false;
20
+ const candidate = value as Partial<RecentReference>;
21
+ return (
22
+ typeof candidate.id === "string" &&
23
+ candidate.id.length > 0 &&
24
+ (candidate.kind === "deck" || candidate.kind === "design-system") &&
25
+ typeof candidate.lastUsedAt === "number" &&
26
+ Number.isFinite(candidate.lastUsedAt)
27
+ );
28
+ }
29
+
30
+ export function readRecentReferences(
31
+ storage: Pick<Storage, "getItem"> | null | undefined = typeof window ===
32
+ "undefined"
33
+ ? null
34
+ : window.localStorage,
35
+ ): RecentReferencesResult {
36
+ if (!storage) return { items: [], readable: true };
37
+
38
+ let raw: string | null;
39
+ try {
40
+ raw = storage.getItem(RECENT_REFERENCES_STORAGE_KEY);
41
+ } catch {
42
+ return { items: [], readable: false };
43
+ }
44
+
45
+ if (raw === null) return { items: [], readable: true };
46
+
47
+ try {
48
+ const parsed: unknown = JSON.parse(raw);
49
+ if (!Array.isArray(parsed)) return { items: [], readable: false };
50
+ return {
51
+ items: parsed.filter(isRecentReference).slice(0, MAX_RECENT_REFERENCES),
52
+ readable: true,
53
+ };
54
+ } catch {
55
+ return { items: [], readable: false };
56
+ }
57
+ }
58
+
59
+ export function rememberRecentReference(
60
+ reference: Omit<RecentReference, "lastUsedAt">,
61
+ storage:
62
+ | Pick<Storage, "getItem" | "setItem">
63
+ | null
64
+ | undefined = typeof window === "undefined" ? null : window.localStorage,
65
+ ): RecentReferencesResult {
66
+ const current = readRecentReferences(storage);
67
+ if (!storage || !current.readable) return current;
68
+
69
+ const next: RecentReference[] = [
70
+ { ...reference, lastUsedAt: Date.now() },
71
+ ...current.items.filter(
72
+ (item) => !(item.id === reference.id && item.kind === reference.kind),
73
+ ),
74
+ ].slice(0, MAX_RECENT_REFERENCES);
75
+
76
+ try {
77
+ storage.setItem(RECENT_REFERENCES_STORAGE_KEY, JSON.stringify(next));
78
+ return { items: next, readable: true };
79
+ } catch {
80
+ return { items: current.items, readable: false };
81
+ }
82
+ }
@@ -31,6 +31,7 @@ import { SlideCommentsPanel } from "@/components/comments/SlideCommentsPanel";
31
31
  import { AnimationsPanel } from "@/components/editor/AnimationsPanel";
32
32
  import AssetLibraryPanel from "@/components/editor/AssetLibraryPanel";
33
33
  import { DeckEditorSkeleton } from "@/components/editor/DeckEditorSkeleton";
34
+ import { EditorActionCluster } from "@/components/editor/EditorActionCluster";
34
35
  import EditorSidebar from "@/components/editor/EditorSidebar";
35
36
  import EditorToolbar from "@/components/editor/EditorToolbar";
36
37
  import GeneratingOverlay from "@/components/editor/GeneratingOverlay";
@@ -78,7 +79,7 @@ import { TAB_ID } from "@/lib/tab-id";
78
79
  import { shouldActivateTextTool } from "@/lib/text-tool-shortcut";
79
80
  import { shortcutLabel } from "@/lib/utils";
80
81
 
81
- type EditorSidePanel = "style" | "comments" | null;
82
+ type EditorSidePanel = "comments" | null;
82
83
 
83
84
  function MissingDeckAccessPane({
84
85
  hasTeamJoinOption,
@@ -184,6 +185,8 @@ export default function DeckEditor() {
184
185
  const [sidebarOpen, setSidebarOpen] = useState(
185
186
  () => typeof window !== "undefined" && window.innerWidth >= 768,
186
187
  );
188
+ const [contextToolbarSlot, setContextToolbarSlot] =
189
+ useState<HTMLDivElement | null>(null);
187
190
  const [retryingMissingDeck, setRetryingMissingDeck] = useState(false);
188
191
  const [checkedDeckAccessKey, setCheckedDeckAccessKey] = useState<
189
192
  string | null
@@ -285,7 +288,6 @@ export default function DeckEditor() {
285
288
  });
286
289
  const { designSystem } = useDeckDesignSystem(deck?.designSystemId);
287
290
  const commentsOpen = sidePanel === "comments";
288
- const styleOpen = sidePanel === "style";
289
291
 
290
292
  const {
291
293
  questions: questionFlowQuestions,
@@ -296,6 +298,9 @@ export default function DeckEditor() {
296
298
  handleSubmit: handleQuestionSubmit,
297
299
  handleSkip: handleQuestionSkip,
298
300
  } = useGuidedQuestionFlow({
301
+ stateKey: "guided-questions",
302
+ browserTabId: TAB_ID,
303
+ queryKey: ["guided-questions"],
299
304
  submitMessage: "Here are my answers — go ahead and create the slides.",
300
305
  skipMessage:
301
306
  "Skip the questions — just go ahead and create the slides with your best judgment.",
@@ -327,6 +332,12 @@ export default function DeckEditor() {
327
332
  if (!generatingSlideVisible) setGeneratingSlideSelected(false);
328
333
  }, [generatingSlideVisible]);
329
334
 
335
+ // The add-slide request is finished once the agent stops generating, so the
336
+ // rail's placeholder must not outlive it.
337
+ useEffect(() => {
338
+ if (!generating) setAddSlideGenerating(false);
339
+ }, [generating]);
340
+
330
341
  const previousSlideCountRef = useRef(slideCount);
331
342
  useEffect(() => {
332
343
  if (previousSlideCountRef.current === slideCount) return;
@@ -938,6 +949,13 @@ export default function DeckEditor() {
938
949
  ? `Current slide: ${currentSlide.id} (index ${currentIndex >= 0 ? currentIndex : 0}). Deck: ${id}.`
939
950
  : `Deck: ${id}.`;
940
951
 
952
+ const handleAddEmptySlide = () => {
953
+ const activeIdx = deck.slides.findIndex((s) => s.id === activeSlideId);
954
+ setActiveSlideId(
955
+ addSlide(id, "blank", activeIdx >= 0 ? activeIdx : undefined),
956
+ );
957
+ };
958
+
941
959
  return (
942
960
  <div
943
961
  className="flex h-full min-h-0 flex-1 flex-col overflow-hidden bg-background"
@@ -975,10 +993,6 @@ export default function DeckEditor() {
975
993
  onToggleComments={() =>
976
994
  setSidePanel((panel) => (panel === "comments" ? null : "comments"))
977
995
  }
978
- styleOpen={styleOpen}
979
- onToggleStyle={() =>
980
- setSidePanel((panel) => (panel === "style" ? null : "style"))
981
- }
982
996
  unresolvedCommentCount={unresolvedCommentCount}
983
997
  currentUserEmail={session?.email}
984
998
  animationsOpen={animationsOpen}
@@ -1049,8 +1063,19 @@ export default function DeckEditor() {
1049
1063
  updateDeck(id, { aspectRatio: previous });
1050
1064
  });
1051
1065
  }}
1066
+ currentSlideId={currentSlide?.id}
1067
+ addSlideGenerating={addSlideGenerating}
1068
+ onAddSlideGeneratingChange={setAddSlideGenerating}
1069
+ onAddEmptySlide={handleAddEmptySlide}
1070
+ onDuplicateCurrentSlide={
1071
+ currentSlide ? () => duplicateSlide(id, currentSlide.id) : undefined
1072
+ }
1052
1073
  />
1053
1074
 
1075
+ {/* Full-width host for the slide's contextual style toolbar: it spans the
1076
+ * slide rail as well as the canvas, matching the deck toolbar above it. */}
1077
+ <div ref={setContextToolbarSlot} className="shrink-0" />
1078
+
1054
1079
  <div className="relative flex min-h-0 flex-1 overflow-hidden">
1055
1080
  {sidebarOpen && (
1056
1081
  <>
@@ -1068,24 +1093,12 @@ export default function DeckEditor() {
1068
1093
  slides={deck.slides}
1069
1094
  activeSlideId={currentSlide?.id || ""}
1070
1095
  deckId={id}
1071
- deckTitle={deck.title}
1072
1096
  onSelectSlide={(slideId) => {
1073
1097
  setGeneratingSlideSelected(false);
1074
1098
  setActiveSlideId(slideId);
1075
1099
  if (window.innerWidth < 768) setSidebarOpen(false);
1076
1100
  }}
1077
1101
  onDuplicateSlide={(slideId) => duplicateSlide(id, slideId)}
1078
- onAddEmptySlide={() => {
1079
- const activeIdx = deck.slides.findIndex(
1080
- (s) => s.id === activeSlideId,
1081
- );
1082
- const newId = addSlide(
1083
- id,
1084
- "blank",
1085
- activeIdx >= 0 ? activeIdx : undefined,
1086
- );
1087
- setActiveSlideId(newId);
1088
- }}
1089
1102
  onDeleteSlide={(slideId) => {
1090
1103
  const idx = deck.slides.findIndex((s) => s.id === slideId);
1091
1104
  const nextSlide =
@@ -1110,8 +1123,6 @@ export default function DeckEditor() {
1110
1123
  setGeneratingSlideSelected(true);
1111
1124
  if (window.innerWidth < 768) setSidebarOpen(false);
1112
1125
  }}
1113
- addSlideGenerating={addSlideGenerating}
1114
- onAddSlideGeneratingChange={setAddSlideGenerating}
1115
1126
  />
1116
1127
  </DndContext>
1117
1128
  </div>
@@ -1162,6 +1173,26 @@ export default function DeckEditor() {
1162
1173
  slide={currentSlide}
1163
1174
  deckId={id}
1164
1175
  readOnly={!canEdit}
1176
+ contextToolbarSlot={contextToolbarSlot}
1177
+ contextToolbarLeading={
1178
+ canEdit ? (
1179
+ <EditorActionCluster
1180
+ deckId={id}
1181
+ deckTitle={deck.title}
1182
+ currentSlideId={currentSlide.id}
1183
+ slideCount={deck.slides.length}
1184
+ currentSlideIndex={currentIndex >= 0 ? currentIndex : 0}
1185
+ addSlideGenerating={addSlideGenerating}
1186
+ onAddSlideGeneratingChange={setAddSlideGenerating}
1187
+ onAddEmptySlide={handleAddEmptySlide}
1188
+ onDuplicateCurrentSlide={() =>
1189
+ duplicateSlide(id, currentSlide.id)
1190
+ }
1191
+ textBoxMode={textBoxMode}
1192
+ onToggleTextBoxMode={toggleTextBoxMode}
1193
+ />
1194
+ ) : undefined
1195
+ }
1165
1196
  onUpdateSlide={(updates, slideIdOverride, options) =>
1166
1197
  updateSlide(
1167
1198
  id,
@@ -1194,8 +1225,6 @@ export default function DeckEditor() {
1194
1225
  slideIndex={currentIndex >= 0 ? currentIndex : 0}
1195
1226
  slideCount={deck.slides.length}
1196
1227
  designSystem={designSystem}
1197
- stylePanelOpen={styleOpen}
1198
- onCloseStylePanel={() => setSidePanel(null)}
1199
1228
  aspectRatio={deck.aspectRatio}
1200
1229
  collabUser={
1201
1230
  currentUser