@agent-native/core 0.137.1 → 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 (34) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +258 -0
  3. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +130 -0
  4. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +25 -337
  5. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +73 -53
  6. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +804 -0
  7. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +101 -45
  8. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +7 -4
  9. package/corpus/templates/slides/app/components/editor/bullet-editing.ts +11 -3
  10. package/corpus/templates/slides/app/components/editor/commit-active-edit.ts +21 -0
  11. package/corpus/templates/slides/app/components/editor/list-editing.ts +219 -0
  12. package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +0 -3
  13. package/corpus/templates/slides/app/components/editor/slide-style.ts +199 -0
  14. package/corpus/templates/slides/app/global.css +14 -3
  15. package/corpus/templates/slides/app/i18n/en-US.ts +8 -1
  16. package/corpus/templates/slides/app/pages/DeckEditor.tsx +48 -22
  17. package/corpus/templates/slides/changelog/2026-08-01-add-slide-moved-to-the-toolbar-and-the-slide-rail-is-now-mor.md +9 -0
  18. package/corpus/templates/slides/changelog/2026-08-01-add-slide-undo-redo-and-the-text-tool-now-lead-the-slide-too.md +9 -0
  19. package/corpus/templates/slides/changelog/2026-08-01-slide-styling-now-appears-in-a-contextual-toolbar-above-the-.md +6 -0
  20. package/corpus/templates/slides/changelog/2026-08-01-the-slide-style-side-panel-is-retired-all-styling-now-lives-.md +6 -0
  21. package/corpus/templates/slides/changelog/2026-08-03-the-slide-toolbar-is-denser-swatch-only-colors-dropdowns-for.md +10 -0
  22. package/corpus/templates/slides/changelog/2026-08-04-bullet-and-numbered-list-buttons-in-the-slide-toolbar-conver.md +6 -0
  23. package/corpus/templates/slides/changelog/2026-08-04-italic-and-underline-are-now-one-click-away-in-the-slide-too.md +6 -0
  24. package/dist/collab/awareness.d.ts +2 -2
  25. package/dist/collab/struct-routes.d.ts +1 -1
  26. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  27. package/dist/observability/routes.d.ts +3 -3
  28. package/dist/progress/routes.d.ts +1 -1
  29. package/dist/resources/handlers.d.ts +1 -1
  30. package/dist/secrets/routes.d.ts +3 -3
  31. package/dist/server/realtime-token.d.ts +1 -1
  32. package/dist/server/transcribe-voice.d.ts +1 -1
  33. package/package.json +2 -2
  34. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +0 -763
@@ -6,7 +6,6 @@ import {
6
6
  type AttributedRecentEdit,
7
7
  type CollabUser,
8
8
  } from "@agent-native/core/client/collab";
9
- import { PromptComposer } from "@agent-native/core/client/composer";
10
9
  import { useAvatarUrl } from "@agent-native/core/client/hooks";
11
10
  import { useT } from "@agent-native/core/client/i18n";
12
11
  import { RecentEditHighlights } from "@agent-native/toolkit/collab-ui";
@@ -19,34 +18,23 @@ import { CSS } from "@dnd-kit/utilities";
19
18
  import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
20
19
  import { hashSlideContent, type DeckFitState } from "@shared/slide-fit";
21
20
  import {
22
- IconPlus,
23
21
  IconGripVertical,
24
22
  IconCopy,
25
23
  IconTrash,
26
24
  IconLoader2,
27
- IconSquarePlus,
28
25
  } from "@tabler/icons-react";
29
- import { useState, useRef, useEffect } from "react";
26
+ import { useRef, useEffect } from "react";
30
27
  import { useCallback } from "react";
31
- import { createPortal } from "react-dom";
32
- import { toast } from "sonner";
33
28
 
34
29
  import SlideRenderer from "@/components/deck/SlideRenderer";
35
30
  import type { SlideOverflowInfo } from "@/components/deck/SlideRenderer";
36
31
  import GeneratingSlidePreview from "@/components/editor/GeneratingSlidePreview";
37
- import { GoogleDocImportHint } from "@/components/editor/GoogleDocImportHint";
38
- import {
39
- isInsidePortaledLayer,
40
- type UploadedFile,
41
- } from "@/components/editor/PromptDialog";
42
32
  import {
43
33
  Tooltip,
44
34
  TooltipContent,
45
35
  TooltipTrigger,
46
36
  } from "@/components/ui/tooltip";
47
37
  import type { Slide } from "@/context/DeckContext";
48
- import { useAgentGenerating } from "@/hooks/use-agent-generating";
49
- import { addSlideAgentMessage } from "@/lib/agent-visible-message";
50
38
  import type { AspectRatio } from "@/lib/aspect-ratios";
51
39
  import { TAB_ID } from "@/lib/tab-id";
52
40
 
@@ -54,11 +42,9 @@ interface EditorSidebarProps {
54
42
  slides: Slide[];
55
43
  activeSlideId: string;
56
44
  deckId: string;
57
- deckTitle: string;
58
45
  onSelectSlide: (id: string) => void;
59
46
  onDuplicateSlide: (id: string) => void;
60
47
  onDeleteSlide: (id: string) => void;
61
- onAddEmptySlide: () => void;
62
48
  /** Viewer-role decks get thumbnails only: no add, duplicate, or delete. */
63
49
  readOnly?: boolean;
64
50
  /** Presence map: slideId → list of users currently viewing that slide */
@@ -71,8 +57,6 @@ interface EditorSidebarProps {
71
57
  generatingSlide?: { index: number; content?: string | null };
72
58
  generatingSlideSelected?: boolean;
73
59
  onSelectGeneratingSlide?: () => void;
74
- addSlideGenerating?: boolean;
75
- onAddSlideGeneratingChange?: (generating: boolean) => void;
76
60
  }
77
61
 
78
62
  const DECK_FIT_STATE_KEYS = [
@@ -92,46 +76,6 @@ function slideIdFromEdit(edit: AttributedRecentEdit): string | null {
92
76
  return null;
93
77
  }
94
78
 
95
- const MAX_SOURCE_CONTEXT_CHARS = 60_000;
96
-
97
- function truncateSourceForContext(prompt: string): {
98
- text: string;
99
- truncated: boolean;
100
- } {
101
- if (prompt.length <= MAX_SOURCE_CONTEXT_CHARS) {
102
- return { text: prompt, truncated: false };
103
- }
104
- return {
105
- text: prompt.slice(0, MAX_SOURCE_CONTEXT_CHARS),
106
- truncated: true,
107
- };
108
- }
109
-
110
- function describeUploadedFilesForAgent(
111
- files: UploadedFile[],
112
- deckId: string,
113
- ): string {
114
- if (files.length === 0) return "";
115
- const fileList = files
116
- .map(
117
- (f) =>
118
- `- ${f.originalName} (${f.type}, ${(f.size / 1024).toFixed(1)}KB) at path: ${f.path}${f.url ? `; embeddable URL: ${f.url}` : ""}`,
119
- )
120
- .join("\n");
121
- return [
122
- "",
123
- `The user uploaded ${files.length} file(s). These paths are real uploaded files; process them with import actions before using their contents:`,
124
- fileList,
125
- "",
126
- "File handling rules:",
127
- `- PPTX files: call \`import-pptx --filePath "<path>" --deckId ${deckId}\` when the user wants the deck/slides imported, or to extract slide source from a presentation.`,
128
- `- PDF and DOCX files: call \`import-file --filePath "<path>" --format auto --deckId ${deckId}\` and use the returned extracted text as source material. The returned text is capped for reliability; re-run with maxChars only if more context is needed.`,
129
- "- Text-like files: use the uploaded-text-file blocks already included in the prompt; do not call import-file for them.",
130
- '- Image files with an embeddable URL can be inserted directly into slide HTML as `<img src="...">` or used as visual references.',
131
- "- Image files without a URL are visual/reference assets only; do not claim to have processed a PPTX/PDF/DOCX unless the relevant import action succeeds.",
132
- ].join("\n");
133
- }
134
-
135
79
  /** Small presence avatar circle with hover card showing name + email */
136
80
  function PresenceAvatarTip({
137
81
  user,
@@ -192,6 +136,14 @@ function PresenceAvatarTip({
192
136
  );
193
137
  }
194
138
 
139
+ // Thumbnail overlays sit on top of rendered slide artwork, which is arbitrary
140
+ // user content rather than a themed app surface, so they use a fixed scrim.
141
+ const THUMB_OVERLAY_CLASS =
142
+ // guard:allow-raw-color — matches the duplicate/delete overlays below.
143
+ "absolute top-2 left-7 rounded bg-black/60 p-1 backdrop-blur-sm border border-white/10 cursor-grab active:cursor-grabbing sm:opacity-0 sm:group-hover:opacity-100";
144
+ // guard:allow-raw-color — same fixed scrim as the class above.
145
+ const THUMB_OVERLAY_ICON_CLASS = "w-3 h-3 text-white/60";
146
+
195
147
  function SortableSlideThumb({
196
148
  slide,
197
149
  index,
@@ -245,28 +197,19 @@ function SortableSlideThumb({
245
197
  aria-label={t("editorSidebar.selectSlide", { number: index + 1 })}
246
198
  aria-current={isActive ? "true" : undefined}
247
199
  data-slide-thumbnail-id={slide.id}
248
- className={`w-full text-left flex items-start gap-2 p-2 rounded-lg transition-[background-color,box-shadow] duration-150 ${
200
+ className={`w-full text-left flex items-start gap-1.5 p-1.5 rounded-lg transition-[background-color,box-shadow] duration-150 ${
249
201
  isActive ? "bg-accent ring-1 ring-[#609FF8]/50" : "hover:bg-accent"
250
202
  } focus:outline-none`}
251
203
  >
252
- {/* Drag handle */}
253
- <div
254
- {...attributes}
255
- {...listeners}
256
- className="flex-shrink-0 mt-2 cursor-grab active:cursor-grabbing sm:opacity-0 sm:group-hover:opacity-100"
257
- >
258
- <IconGripVertical className="w-3.5 h-3.5 text-muted-foreground/70" />
259
- </div>
260
-
261
204
  {/* Index and slide presence share the fixed rail so presence does not resize the row. */}
262
- <div className="relative flex-shrink-0 w-5 self-stretch mt-2">
263
- <span className="block text-center text-[10px] font-medium text-muted-foreground/70">
205
+ <div className="relative flex-shrink-0 w-4 self-stretch">
206
+ <span className="block text-center text-[10px] font-medium leading-5 text-muted-foreground/70">
264
207
  {index + 1}
265
208
  </span>
266
209
  {presenceUsers.length > 0 && (
267
- <div className="absolute left-1/2 top-6 z-10 flex -translate-x-1/2 flex-col items-center gap-1">
210
+ <div className="absolute left-1/2 top-5 z-10 flex -translate-x-1/2 flex-col items-center gap-1">
268
211
  {presenceUsers.slice(0, 4).map((u, i) => (
269
- <PresenceAvatarTip key={i} user={u} size={16} />
212
+ <PresenceAvatarTip key={i} user={u} size={14} />
270
213
  ))}
271
214
  {presenceUsers.length > 4 && (
272
215
  <span className="flex h-4 min-w-4 items-center justify-center rounded-full bg-muted px-1 text-[8px] font-medium leading-none text-muted-foreground ring-1 ring-black/40">
@@ -297,6 +240,14 @@ function SortableSlideThumb({
297
240
  </div>
298
241
  </button>
299
242
 
243
+ {/* Drag handle — overlaid on the thumbnail instead of holding its own
244
+ * column, so the rail stays narrow. Mirrors the action buttons opposite. */}
245
+ {!readOnly && (
246
+ <div {...attributes} {...listeners} className={THUMB_OVERLAY_CLASS}>
247
+ <IconGripVertical className={THUMB_OVERLAY_ICON_CLASS} />
248
+ </div>
249
+ )}
250
+
300
251
  {/* Actions - always visible on touch devices */}
301
252
  {!readOnly && (
302
253
  <div className="absolute top-2 right-2 flex gap-0.5 sm:opacity-0 sm:group-hover:opacity-100">
@@ -360,9 +311,8 @@ function GeneratingSlideSkeleton({
360
311
  aria-current={selected ? "true" : undefined}
361
312
  onClick={onSelect}
362
313
  >
363
- <div className="w-full flex items-start gap-2 p-2 rounded-lg bg-accent/30">
364
- <div className="flex-shrink-0 mt-2 w-3.5 h-3.5" />
365
- <span className="flex-shrink-0 w-5 mt-2 text-[10px] font-medium text-muted-foreground/70">
314
+ <div className="w-full flex items-start gap-1.5 p-1.5 rounded-lg bg-accent/30">
315
+ <span className="flex-shrink-0 w-4 text-center text-[10px] font-medium leading-5 text-muted-foreground/70">
366
316
  {index + 1}
367
317
  </span>
368
318
  <div className="flex-1 min-w-0">
@@ -377,218 +327,13 @@ function GeneratingSlideSkeleton({
377
327
  );
378
328
  }
379
329
 
380
- function AddSlidePopover({
381
- open,
382
- onOpenChange,
383
- anchorRef,
384
- deckId,
385
- deckTitle,
386
- activeSlideId,
387
- slideCount,
388
- activeSlideIndex,
389
- agentSubmit,
390
- onDuplicateCurrent,
391
- onAddEmpty,
392
- }: {
393
- open: boolean;
394
- onOpenChange: (open: boolean) => void;
395
- anchorRef: React.RefObject<HTMLElement | null>;
396
- deckId: string;
397
- deckTitle: string;
398
- activeSlideId: string;
399
- slideCount: number;
400
- activeSlideIndex: number;
401
- agentSubmit: (message: string, context: string) => void;
402
- onDuplicateCurrent?: () => void;
403
- onAddEmpty?: () => void;
404
- }) {
405
- const t = useT();
406
- const panelRef = useRef<HTMLDivElement>(null);
407
- const [promptText, setPromptText] = useState("");
408
- const [googleDocContext, setGoogleDocContext] = useState("");
409
-
410
- useEffect(() => {
411
- if (!open) return;
412
- const handleClick = (e: MouseEvent) => {
413
- if (isInsidePortaledLayer(e.target)) return;
414
- if (
415
- panelRef.current &&
416
- !panelRef.current.contains(e.target as Node) &&
417
- anchorRef.current &&
418
- !anchorRef.current.contains(e.target as Node)
419
- ) {
420
- onOpenChange(false);
421
- }
422
- };
423
- const handleKey = (e: KeyboardEvent) => {
424
- if (e.key === "Escape") onOpenChange(false);
425
- };
426
- document.addEventListener("mousedown", handleClick);
427
- document.addEventListener("keydown", handleKey);
428
- return () => {
429
- document.removeEventListener("mousedown", handleClick);
430
- document.removeEventListener("keydown", handleKey);
431
- };
432
- }, [open, onOpenChange, anchorRef]);
433
-
434
- const handleSubmit = useCallback(
435
- async (text: string, files: File[]) => {
436
- let uploaded: UploadedFile[] = [];
437
- if (files.length > 0) {
438
- try {
439
- const formData = new FormData();
440
- files.forEach((f) => formData.append("files", f));
441
- const res = await fetch(`${appBasePath()}/api/uploads`, {
442
- method: "POST",
443
- body: formData,
444
- });
445
- if (!res.ok) {
446
- const data = await res.json().catch(() => null);
447
- throw new Error(data?.error || t("editorSidebar.uploadFailed"));
448
- }
449
- uploaded = (await res.json()) as UploadedFile[];
450
- } catch (error) {
451
- toast.error(t("editorSidebar.uploadFailed"), {
452
- description:
453
- error instanceof Error
454
- ? error.message
455
- : t("editorSidebar.uploadAttachedFileFailed"),
456
- });
457
- return;
458
- }
459
- }
460
-
461
- const trimmedText = text.trim();
462
- const googleDocSourceForContext =
463
- truncateSourceForContext(googleDocContext);
464
- const fileContext = describeUploadedFilesForAgent(uploaded, deckId);
465
- const context = [
466
- `Add a new slide to deck "${deckTitle}" (id: ${deckId}).`,
467
- `Insert after slide ${activeSlideIndex + 1} of ${slideCount} (active slide id: ${activeSlideId}).`,
468
- "The visible user message above contains the user's request and/or pasted source material for the new slide(s). Treat pasted memo content as source material even if the user did not explicitly say they are pasting it.",
469
- googleDocSourceForContext.text,
470
- googleDocSourceForContext.truncated
471
- ? `The pasted source was longer than ${MAX_SOURCE_CONTEXT_CHARS} characters, so only the first ${MAX_SOURCE_CONTEXT_CHARS} characters were included to keep the agent request reliable.`
472
- : "",
473
- fileContext,
474
- "",
475
- "Create the slide content and insert it at the correct position using `add-slide` with --deckId=" +
476
- deckId +
477
- ".",
478
- "Every slide is rendered into a fixed native canvas (default 16:9 is 960x540 CSS pixels). Keep each slide within the density limits in AGENTS.md; split dense source material across more slides instead of packing it tightly.",
479
- "If the user asked for multiple slides, call `add-slide` once per slide. Use positions starting at " +
480
- (activeSlideIndex + 1) +
481
- " so the new slides land after the active slide in order.",
482
- "For larger requests, keep adding slides sequentially: wait for each add-slide result, then call add-slide for the next slide. Start slide 1 immediately; do not wait to design the entire sequence before adding it.",
483
- ].join("\n");
484
-
485
- agentSubmit(addSlideAgentMessage(trimmedText), context);
486
- onOpenChange(false);
487
- },
488
- [
489
- activeSlideId,
490
- activeSlideIndex,
491
- agentSubmit,
492
- deckId,
493
- deckTitle,
494
- googleDocContext,
495
- onOpenChange,
496
- slideCount,
497
- ],
498
- );
499
-
500
- useEffect(() => {
501
- if (!open) {
502
- setPromptText("");
503
- setGoogleDocContext("");
504
- }
505
- }, [open]);
506
-
507
- if (!open || !anchorRef.current) return null;
508
-
509
- const rect = anchorRef.current.getBoundingClientRect();
510
- const panelWidth = Math.min(420, window.innerWidth - 24);
511
- const left = Math.max(
512
- 12,
513
- Math.min(rect.left, window.innerWidth - panelWidth - 12),
514
- );
515
-
516
- return createPortal(
517
- <div
518
- ref={panelRef}
519
- className="fixed w-[min(420px,calc(100vw-24px))] rounded-xl border border-border bg-popover shadow-2xl shadow-black/60 z-[200] p-3"
520
- style={{
521
- top: rect.bottom + 8,
522
- left,
523
- }}
524
- >
525
- <p className="px-1 pb-2 text-sm font-medium text-foreground/90">
526
- {t("editorSidebar.addSlides")}
527
- </p>
528
- {(onAddEmpty || (onDuplicateCurrent && slideCount > 0)) && (
529
- <>
530
- {onAddEmpty && (
531
- <button
532
- type="button"
533
- onClick={() => {
534
- onAddEmpty();
535
- onOpenChange(false);
536
- }}
537
- className="w-full mb-1 px-2.5 py-2 text-left text-sm rounded-md hover:bg-accent transition-colors flex items-center gap-2 text-foreground/90 cursor-pointer"
538
- >
539
- <IconSquarePlus className="w-4 h-4 text-muted-foreground" />
540
- <span>{t("editorSidebar.addEmptySlide")}</span>
541
- <span className="ml-auto text-[11px] text-muted-foreground">
542
- {t("editorSidebar.noAi")}
543
- </span>
544
- </button>
545
- )}
546
- {onDuplicateCurrent && slideCount > 0 && (
547
- <button
548
- type="button"
549
- onClick={() => {
550
- onDuplicateCurrent();
551
- onOpenChange(false);
552
- }}
553
- className="w-full mb-2 px-2.5 py-2 text-left text-sm rounded-md hover:bg-accent transition-colors flex items-center gap-2 text-foreground/90 cursor-pointer"
554
- >
555
- <IconCopy className="w-4 h-4 text-muted-foreground" />
556
- <span>{t("editorSidebar.duplicateCurrentSlide")}</span>
557
- <span className="ml-auto text-[11px] text-muted-foreground">
558
- {t("editorSidebar.noAi")}
559
- </span>
560
- </button>
561
- )}
562
- <div className="-mx-3 mb-2 h-px bg-border" />
563
- </>
564
- )}
565
- <PromptComposer
566
- autoFocus
567
- placeholder={t("editorSidebar.promptPlaceholder")}
568
- draftScope={`slides:add-slide:${deckId}`}
569
- onSubmit={handleSubmit}
570
- onTextChange={setPromptText}
571
- />
572
- <div className="-mx-1 mt-2">
573
- <GoogleDocImportHint
574
- promptText={promptText}
575
- onSourceContextChange={setGoogleDocContext}
576
- />
577
- </div>
578
- </div>,
579
- document.body,
580
- );
581
- }
582
-
583
330
  export default function EditorSidebar({
584
331
  slides,
585
332
  activeSlideId,
586
333
  deckId,
587
- deckTitle,
588
334
  onSelectSlide,
589
335
  onDuplicateSlide,
590
336
  onDeleteSlide,
591
- onAddEmptySlide,
592
337
  readOnly = false,
593
338
  slidePresence,
594
339
  recentEdits,
@@ -596,13 +341,7 @@ export default function EditorSidebar({
596
341
  generatingSlide,
597
342
  generatingSlideSelected = false,
598
343
  onSelectGeneratingSlide,
599
- addSlideGenerating = false,
600
- onAddSlideGeneratingChange,
601
344
  }: EditorSidebarProps) {
602
- const t = useT();
603
- const activeIndex = slides.findIndex((s) => s.id === activeSlideId);
604
- const [addOpen, setAddOpen] = useState(false);
605
- const headerAddRef = useRef<HTMLButtonElement>(null);
606
345
  const slideButtonRefs = useRef(new Map<string, HTMLButtonElement>());
607
346
  const thumbScrollRef = useRef<HTMLDivElement>(null);
608
347
  const measurementsRef = useRef(
@@ -698,8 +437,6 @@ export default function EditorSidebar({
698
437
  },
699
438
  [],
700
439
  );
701
- const { generating, submit: agentSubmit } = useAgentGenerating();
702
-
703
440
  const registerSlideButton = useCallback(
704
441
  (slideId: string, node: HTMLButtonElement | null) => {
705
442
  if (node) {
@@ -711,11 +448,6 @@ export default function EditorSidebar({
711
448
  [],
712
449
  );
713
450
 
714
- // Reset addSlideGenerating when global generating stops
715
- useEffect(() => {
716
- if (!generating) onAddSlideGeneratingChange?.(false);
717
- }, [generating, onAddSlideGeneratingChange]);
718
-
719
451
  // Arrow key navigation for slides
720
452
  useEffect(() => {
721
453
  const handleKeyDown = (e: KeyboardEvent) => {
@@ -754,30 +486,7 @@ export default function EditorSidebar({
754
486
  }, [slides, activeSlideId, onSelectSlide]);
755
487
 
756
488
  return (
757
- <div className="flex h-full min-h-0 w-56 flex-shrink-0 flex-col border-r border-border/70 bg-background/95 sm:w-64">
758
- <div className="flex items-center justify-between border-b border-border/70 p-3">
759
- <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
760
- {t("editorSidebar.slides")}
761
- </span>
762
- {readOnly ? null : addSlideGenerating ? (
763
- <IconLoader2 className="w-4 h-4 text-muted-foreground animate-spin" />
764
- ) : (
765
- <Tooltip>
766
- <TooltipTrigger asChild>
767
- <button
768
- ref={headerAddRef}
769
- onClick={() => setAddOpen(!addOpen)}
770
- className="p-2 rounded-md hover:bg-accent transition-colors"
771
- aria-label={t("editorSidebar.addSlides")}
772
- >
773
- <IconPlus className="w-4 h-4 text-muted-foreground" />
774
- </button>
775
- </TooltipTrigger>
776
- <TooltipContent>{t("editorSidebar.addSlides")}</TooltipContent>
777
- </Tooltip>
778
- )}
779
- </div>
780
-
489
+ <div className="flex h-full min-h-0 w-48 flex-shrink-0 flex-col border-r border-border/70 bg-background/95 sm:w-52">
781
490
  <div
782
491
  ref={thumbScrollRef}
783
492
  className="relative min-h-0 flex-1 space-y-1 overflow-y-auto overscroll-contain p-2"
@@ -824,27 +533,6 @@ export default function EditorSidebar({
824
533
  />
825
534
  )}
826
535
  </div>
827
-
828
- {!readOnly && (
829
- <AddSlidePopover
830
- open={addOpen}
831
- onOpenChange={setAddOpen}
832
- anchorRef={headerAddRef}
833
- deckId={deckId}
834
- deckTitle={deckTitle}
835
- activeSlideId={activeSlideId}
836
- slideCount={slides.length}
837
- activeSlideIndex={activeIndex >= 0 ? activeIndex : 0}
838
- agentSubmit={(msg, ctx) => {
839
- onAddSlideGeneratingChange?.(true);
840
- agentSubmit(msg, ctx);
841
- }}
842
- onDuplicateCurrent={
843
- activeSlideId ? () => onDuplicateSlide(activeSlideId) : undefined
844
- }
845
- onAddEmpty={onAddEmptySlide}
846
- />
847
- )}
848
536
  </div>
849
537
  );
850
538
  }