@agent-native/core 0.114.4 → 0.114.6

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 (95) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/resources/ResourceTree.tsx +11 -5
  5. package/corpus/core/src/client/resources/ResourcesPanel.tsx +141 -18
  6. package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +11 -1
  7. package/corpus/templates/clips/AGENTS.md +14 -4
  8. package/corpus/templates/clips/actions/update-meeting.ts +13 -3
  9. package/corpus/templates/clips/actions/view-screen.ts +1 -0
  10. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +96 -4
  11. package/corpus/templates/clips/app/i18n/en-US.ts +11 -0
  12. package/corpus/templates/clips/app/lib/public-meeting.ts +85 -0
  13. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +11 -1
  14. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -7
  15. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +241 -135
  16. package/corpus/templates/clips/changelog/2026-07-20-meeting-share-links-can-include-the-full-transcript-with-an-.md +6 -0
  17. package/corpus/templates/clips/changelog/2026-07-20-opening-zoom-meeting-launches-native-app.md +6 -0
  18. package/corpus/templates/clips/changelog/2026-07-20-recording-retries-the-default-mac-microphone-when-a-saved-in.md +6 -0
  19. package/corpus/templates/clips/changelog/2026-07-20-shared-clips-now-tell-agents-to-wait-while-uploads-and-trans.md +6 -0
  20. package/corpus/templates/clips/desktop/src/app.tsx +7 -11
  21. package/corpus/templates/clips/desktop/src/lib/meeting-join-url.ts +1 -33
  22. package/corpus/templates/clips/desktop/src/lib/open-meeting-join-url.ts +22 -0
  23. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +65 -2
  24. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -3
  25. package/corpus/templates/clips/server/db/schema.ts +3 -0
  26. package/corpus/templates/clips/server/lib/public-agent-context.ts +31 -21
  27. package/corpus/templates/clips/server/plugins/auth.ts +1 -0
  28. package/corpus/templates/clips/server/plugins/db.ts +6 -0
  29. package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +13 -3
  30. package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +152 -0
  31. package/corpus/templates/clips/shared/agent-context.ts +60 -0
  32. package/corpus/templates/clips/shared/meeting-join-url.ts +31 -0
  33. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +9 -1
  34. package/corpus/templates/content/AGENTS.md +4 -1
  35. package/corpus/templates/content/actions/_content-spaces.ts +6 -1
  36. package/corpus/templates/content/actions/_database-row-batch.ts +15 -2
  37. package/corpus/templates/content/actions/_database-utils.ts +83 -15
  38. package/corpus/templates/content/actions/add-database-item.ts +3 -0
  39. package/corpus/templates/content/actions/connect-local-folder-source.ts +2 -1
  40. package/corpus/templates/content/actions/delete-content-database.ts +12 -9
  41. package/corpus/templates/content/actions/delete-database-items.ts +3 -2
  42. package/corpus/templates/content/actions/delete-document.ts +233 -6
  43. package/corpus/templates/content/actions/duplicate-database-item.ts +1 -0
  44. package/corpus/templates/content/actions/get-document.ts +4 -1
  45. package/corpus/templates/content/actions/list-content-databases.ts +1 -0
  46. package/corpus/templates/content/actions/list-documents.ts +1 -0
  47. package/corpus/templates/content/actions/list-trashed-content-databases.ts +8 -2
  48. package/corpus/templates/content/actions/list-trashed-documents.ts +50 -0
  49. package/corpus/templates/content/actions/permanently-delete-document.ts +28 -0
  50. package/corpus/templates/content/actions/pull-document.ts +15 -2
  51. package/corpus/templates/content/actions/restore-content-database.ts +48 -11
  52. package/corpus/templates/content/actions/restore-document.ts +28 -0
  53. package/corpus/templates/content/actions/search-documents.ts +2 -1
  54. package/corpus/templates/content/actions/view-screen.ts +2 -1
  55. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +149 -55
  56. package/corpus/templates/content/app/components/editor/database/GalleryView.tsx +7 -2
  57. package/corpus/templates/content/app/components/editor/database/ListView.tsx +7 -2
  58. package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +3 -0
  59. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +26 -5
  60. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +243 -163
  61. package/corpus/templates/content/app/components/sidebar/WorkspaceSourceMenu.tsx +154 -0
  62. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +11 -0
  63. package/corpus/templates/content/app/hooks/use-content-database.ts +6 -0
  64. package/corpus/templates/content/app/hooks/use-documents.ts +57 -0
  65. package/corpus/templates/content/app/i18n-data.ts +83 -2
  66. package/corpus/templates/content/app/routes/_app.local-files.tsx +8 -1
  67. package/corpus/templates/content/changelog/2026-07-19-pages-now-move-to-a-reversible-trash-and-the-organization-pi.md +6 -0
  68. package/corpus/templates/content/changelog/2026-07-20-trash-actions-now-preserve-independent-archived-pages-enforc.md +6 -0
  69. package/corpus/templates/content/changelog/2026-07-20-workspace-toggles-now-stay-independently-open-or-closed-and-.md +6 -0
  70. package/corpus/templates/content/changelog/2026-07-20-workspaces-can-be-added-from-a-blank-workspace-or-a-connecte.md +6 -0
  71. package/corpus/templates/content/parity/matrix.md +1 -1
  72. package/corpus/templates/content/parity/matrix.ts +3 -0
  73. package/corpus/templates/content/server/db/schema.ts +2 -0
  74. package/corpus/templates/content/server/lib/public-documents.ts +8 -2
  75. package/corpus/templates/content/server/plugins/db.ts +52 -0
  76. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +2 -2
  77. package/corpus/templates/content/shared/api.ts +10 -0
  78. package/corpus/toolkit/CHANGELOG.md +6 -0
  79. package/corpus/toolkit/package.json +1 -1
  80. package/corpus/toolkit/src/ui/progress.tsx +3 -2
  81. package/dist/client/resources/ResourceTree.d.ts +3 -1
  82. package/dist/client/resources/ResourceTree.d.ts.map +1 -1
  83. package/dist/client/resources/ResourceTree.js +4 -4
  84. package/dist/client/resources/ResourceTree.js.map +1 -1
  85. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  86. package/dist/client/resources/ResourcesPanel.js +55 -6
  87. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  88. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  89. package/dist/notifications/routes.d.ts +3 -3
  90. package/dist/observability/routes.d.ts +5 -5
  91. package/dist/secrets/routes.d.ts +9 -9
  92. package/dist/server/transcribe-voice.d.ts +1 -1
  93. package/package.json +3 -3
  94. package/src/client/resources/ResourceTree.tsx +11 -5
  95. package/src/client/resources/ResourcesPanel.tsx +141 -18
@@ -11,6 +11,7 @@ import {
11
11
  useActionQuery,
12
12
  } from "@agent-native/core/client/hooks";
13
13
  import { useT } from "@agent-native/core/client/i18n";
14
+ import { OrgSwitcher } from "@agent-native/core/client/org";
14
15
  import { FeedbackButton } from "@agent-native/core/client/ui";
15
16
  import {
16
17
  closestCenter,
@@ -42,6 +43,7 @@ import {
42
43
  IconRestore,
43
44
  IconSearch,
44
45
  IconSettings,
46
+ IconStar,
45
47
  IconTrashX,
46
48
  IconLayoutSidebarLeftCollapse,
47
49
  IconLayoutSidebarLeftExpand,
@@ -76,14 +78,6 @@ import {
76
78
  AlertDialogTitle,
77
79
  AlertDialogTrigger,
78
80
  } from "@/components/ui/alert-dialog";
79
- import {
80
- Dialog,
81
- DialogContent,
82
- DialogDescription,
83
- DialogFooter,
84
- DialogHeader,
85
- DialogTitle,
86
- } from "@/components/ui/dialog";
87
81
  import {
88
82
  DropdownMenu,
89
83
  DropdownMenuContent,
@@ -91,7 +85,6 @@ import {
91
85
  DropdownMenuSeparator,
92
86
  DropdownMenuTrigger,
93
87
  } from "@/components/ui/dropdown-menu";
94
- import { Input } from "@/components/ui/input";
95
88
  import { ScrollArea } from "@/components/ui/scroll-area";
96
89
  import {
97
90
  Tooltip,
@@ -111,7 +104,6 @@ import {
111
104
  useTrashedContentDatabases,
112
105
  } from "@/hooks/use-content-database";
113
106
  import {
114
- useCreateContentSpace,
115
107
  useContentSpaces,
116
108
  useEnsureContentSpaces,
117
109
  type ContentSpaceSummary,
@@ -120,7 +112,10 @@ import {
120
112
  useDocuments,
121
113
  useCreateDocument,
122
114
  useDeleteDocument,
115
+ usePermanentlyDeleteDocument,
123
116
  useMoveDocument,
117
+ useRestoreDocument,
118
+ useTrashedDocuments,
124
119
  useUpdateDocument,
125
120
  buildDocumentTree,
126
121
  filterDocumentTreeDocuments,
@@ -141,12 +136,17 @@ import { NotionButton } from "./NotionButton";
141
136
  import {
142
137
  contentSpaceAvailability,
143
138
  contentSpaceForStoredSelection,
139
+ createContentSidebarStateWriteQueue,
144
140
  createContentSpaceSelectionQueue,
145
141
  ensureWorkspaceExpanded,
146
142
  SELECTED_CONTENT_SPACE_STORAGE_KEY,
147
143
  selectContentSpace,
148
144
  toggleExpandedWorkspaceIds,
149
145
  } from "./select-content-space";
146
+ import {
147
+ WorkspaceSourceMenu,
148
+ type CreatedWorkspace,
149
+ } from "./WorkspaceSourceMenu";
150
150
 
151
151
  function nanoid(size = 12): string {
152
152
  const chars =
@@ -212,14 +212,21 @@ type CollapsedSectionsState = Record<SidebarSectionId, boolean>;
212
212
 
213
213
  const SIDEBAR_SECTION_COLLAPSE_STORAGE_KEY =
214
214
  "content-sidebar-collapsed-sections";
215
+ const TRASH_COLLAPSED_DEFAULT_MIGRATION_KEY =
216
+ "content-sidebar-trash-collapsed-default-v2";
215
217
  const CONTENT_SIDEBAR_STATE_VERSION = 1 as const;
218
+ interface ContentSidebarStateSnapshot {
219
+ version: typeof CONTENT_SIDEBAR_STATE_VERSION;
220
+ expandedWorkspaceIds: string[];
221
+ expandedDocumentIds: string[];
222
+ }
216
223
  const DEFAULT_COLLAPSED_SECTIONS: CollapsedSectionsState = {
217
224
  favorites: false,
218
225
  "local-files": false,
219
226
  "shared-copies": false,
220
227
  private: false,
221
228
  organization: false,
222
- trash: false,
229
+ trash: true,
223
230
  };
224
231
 
225
232
  function normalizeCollapsedSections(
@@ -231,7 +238,7 @@ function normalizeCollapsedSections(
231
238
  "shared-copies": value?.["shared-copies"] ?? false,
232
239
  private: value?.private ?? false,
233
240
  organization: value?.organization ?? false,
234
- trash: value?.trash ?? false,
241
+ trash: value?.trash ?? true,
235
242
  };
236
243
  }
237
244
 
@@ -351,13 +358,15 @@ export function DocumentSidebar({
351
358
  const createDatabase = useCreateContentDatabase(null);
352
359
  const deleteContentDatabase = useDeleteContentDatabase();
353
360
  const deleteDocument = useDeleteDocument();
361
+ const permanentlyDeleteDocument = usePermanentlyDeleteDocument();
354
362
  const moveDocument = useMoveDocument();
363
+ const restoreDocument = useRestoreDocument();
364
+ const { data: trashedDocuments } = useTrashedDocuments();
355
365
  const restoreContentDatabase = useRestoreContentDatabase();
356
366
  const { data: trashedDatabases } = useTrashedContentDatabases();
357
367
  const { isCodeMode } = useCodeMode();
358
368
  const updateDocument = useUpdateDocument();
359
369
  const contentSpacesQuery = useContentSpaces();
360
- const createContentSpace = useCreateContentSpace();
361
370
  const ensureContentSpaces = useEnsureContentSpaces();
362
371
  const workspaceSelectionQueueRef = useRef(createContentSpaceSelectionQueue());
363
372
  const contentSpaces = contentSpacesQuery.data?.spaces ?? [];
@@ -406,6 +415,17 @@ export function DocumentSidebar({
406
415
  );
407
416
  },
408
417
  });
418
+ const updateSidebarStateRef = useRef(updateSidebarState);
419
+ updateSidebarStateRef.current = updateSidebarState;
420
+ const sidebarStateWriteQueueRef = useRef<
421
+ ((snapshot: ContentSidebarStateSnapshot) => Promise<unknown>) | null
422
+ >(null);
423
+ if (!sidebarStateWriteQueueRef.current) {
424
+ sidebarStateWriteQueueRef.current = createContentSidebarStateWriteQueue(
425
+ (snapshot: ContentSidebarStateSnapshot) =>
426
+ updateSidebarStateRef.current.mutateAsync(snapshot),
427
+ );
428
+ }
409
429
  const [expandedWorkspaceIds, setExpandedWorkspaceIds] = useState<string[]>(
410
430
  [],
411
431
  );
@@ -417,13 +437,6 @@ export function DocumentSidebar({
417
437
  const sidebarStateHydratedRef = useRef(false);
418
438
  const expandedWorkspaceIdsRef = useRef<string[]>([]);
419
439
  const expandedDocumentIdsRef = useRef<string[]>([]);
420
- const sidebarStateWriteTimerRef = useRef<ReturnType<
421
- typeof setTimeout
422
- > | null>(null);
423
- const [createWorkspaceDialogOpen, setCreateWorkspaceDialogOpen] =
424
- useState(false);
425
- const [newWorkspaceName, setNewWorkspaceName] = useState("");
426
- const createWorkspaceRequestIdRef = useRef<string | null>(null);
427
440
  const contentSpaceState = contentSpaceAvailability({
428
441
  hasSelectedSpace: Boolean(selectedSpace),
429
442
  contentSpacesLoading: contentSpacesQuery.isLoading,
@@ -474,19 +487,19 @@ export function DocumentSidebar({
474
487
  const queueSidebarStateWrite = useCallback(
475
488
  (workspaceIds: string[], documentIds: string[]) => {
476
489
  if (!sidebarStateHydratedRef.current) return;
477
- if (sidebarStateWriteTimerRef.current) {
478
- clearTimeout(sidebarStateWriteTimerRef.current);
479
- }
480
- sidebarStateWriteTimerRef.current = setTimeout(() => {
481
- sidebarStateWriteTimerRef.current = null;
482
- updateSidebarState.mutate({
490
+ void sidebarStateWriteQueueRef
491
+ .current?.({
483
492
  version: CONTENT_SIDEBAR_STATE_VERSION,
484
493
  expandedWorkspaceIds: workspaceIds,
485
494
  expandedDocumentIds: documentIds,
495
+ })
496
+ .catch((error) => {
497
+ toast.error(t("sidebar.failedSaveSidebarState"), {
498
+ description: error instanceof Error ? error.message : String(error),
499
+ });
486
500
  });
487
- }, 150);
488
501
  },
489
- [updateSidebarState],
502
+ [t],
490
503
  );
491
504
 
492
505
  const updateExpandedWorkspaceIds = useCallback(
@@ -517,21 +530,6 @@ export function DocumentSidebar({
517
530
  [queueSidebarStateWrite],
518
531
  );
519
532
 
520
- useEffect(
521
- () => () => {
522
- if (sidebarStateWriteTimerRef.current) {
523
- clearTimeout(sidebarStateWriteTimerRef.current);
524
- }
525
- },
526
- [],
527
- );
528
-
529
- useEffect(() => {
530
- if (!selectedSpace || !sidebarStateHydratedRef.current) return;
531
- updateExpandedWorkspaceIds((current) =>
532
- ensureWorkspaceExpanded(current, selectedSpace.id),
533
- );
534
- }, [selectedSpace, updateExpandedWorkspaceIds]);
535
533
  const handleSelectContentSpace = useCallback(
536
534
  async (
537
535
  space: (typeof contentSpaces)[number],
@@ -572,14 +570,9 @@ export function DocumentSidebar({
572
570
  },
573
571
  [navigate, setStoredSpaceId, updateExpandedWorkspaceIds],
574
572
  );
575
- const handleCreateWorkspace = useCallback(async () => {
576
- const name = newWorkspaceName.trim();
577
- if (!name) return;
578
- const requestId = createWorkspaceRequestIdRef.current ?? nanoid();
579
- createWorkspaceRequestIdRef.current = requestId;
580
- try {
581
- const created = await createContentSpace.mutateAsync({ name, requestId });
582
- const space: ContentSpaceSummary = {
573
+ const handleWorkspaceCreated = useCallback(
574
+ (created: CreatedWorkspace) =>
575
+ handleSelectContentSpace({
583
576
  id: created.spaceId,
584
577
  name: created.name,
585
578
  kind: created.kind,
@@ -589,18 +582,9 @@ export function DocumentSidebar({
589
582
  role: "owner",
590
583
  catalogItemId: created.catalogItemId,
591
584
  catalogDocumentId: created.catalogDocumentId,
592
- };
593
- const selected = await handleSelectContentSpace(space);
594
- if (!selected) return;
595
- setCreateWorkspaceDialogOpen(false);
596
- setNewWorkspaceName("");
597
- createWorkspaceRequestIdRef.current = null;
598
- } catch (error) {
599
- toast.error(t("sidebar.failedCreateWorkspace"), {
600
- description: error instanceof Error ? error.message : String(error),
601
- });
602
- }
603
- }, [createContentSpace, handleSelectContentSpace, newWorkspaceName, t]);
585
+ }),
586
+ [handleSelectContentSpace],
587
+ );
604
588
  useEffect(() => {
605
589
  if (!selectedSpace) return;
606
590
  void setClientAppState(
@@ -634,6 +618,21 @@ export function DocumentSidebar({
634
618
  () => normalizeCollapsedSections(storedCollapsedSections),
635
619
  [storedCollapsedSections],
636
620
  );
621
+ useEffect(() => {
622
+ try {
623
+ if (
624
+ window.localStorage.getItem(TRASH_COLLAPSED_DEFAULT_MIGRATION_KEY) ===
625
+ "1"
626
+ ) {
627
+ return;
628
+ }
629
+ setStoredCollapsedSections((current) => ({
630
+ ...normalizeCollapsedSections(current),
631
+ trash: true,
632
+ }));
633
+ window.localStorage.setItem(TRASH_COLLAPSED_DEFAULT_MIGRATION_KEY, "1");
634
+ } catch {}
635
+ }, [setStoredCollapsedSections]);
637
636
  const [removeLocalFilesDialogOpen, setRemoveLocalFilesDialogOpen] =
638
637
  useState(false);
639
638
  const agentActive = location.pathname.startsWith("/agent");
@@ -700,6 +699,7 @@ export function DocumentSidebar({
700
699
  ? documents.find((doc) => doc.id === activeDocumentId)
701
700
  : null;
702
701
  const trashItems = trashedDatabases?.databases ?? [];
702
+ const trashedPageItems = trashedDocuments?.documents ?? [];
703
703
  const parentByDocumentId = useMemo(
704
704
  () => new Map(documents.map((doc) => [doc.id, doc.parentId])),
705
705
  [documents],
@@ -1124,7 +1124,7 @@ export function DocumentSidebar({
1124
1124
  const handlePermanentDeleteDatabase = useCallback(
1125
1125
  async (documentId: string) => {
1126
1126
  try {
1127
- await deleteDocument.mutateAsync({ id: documentId });
1127
+ await permanentlyDeleteDocument.mutateAsync({ id: documentId });
1128
1128
  queryClient.invalidateQueries({
1129
1129
  queryKey: ["action", "list-documents"],
1130
1130
  });
@@ -1139,7 +1139,37 @@ export function DocumentSidebar({
1139
1139
  });
1140
1140
  }
1141
1141
  },
1142
- [deleteDocument, queryClient, t],
1142
+ [permanentlyDeleteDocument, queryClient, t],
1143
+ );
1144
+
1145
+ const handleRestoreDocument = useCallback(
1146
+ async (documentId: string) => {
1147
+ try {
1148
+ await restoreDocument.mutateAsync({ id: documentId });
1149
+ toast.success(t("sidebar.pageRestored"));
1150
+ } catch (err) {
1151
+ toast.error(t("sidebar.failedRestorePage"), {
1152
+ description:
1153
+ err instanceof Error ? err.message : t("empty.genericError"),
1154
+ });
1155
+ }
1156
+ },
1157
+ [restoreDocument, t],
1158
+ );
1159
+
1160
+ const handlePermanentDeleteDocument = useCallback(
1161
+ async (documentId: string) => {
1162
+ try {
1163
+ await permanentlyDeleteDocument.mutateAsync({ id: documentId });
1164
+ toast.success(t("sidebar.pagePermanentlyDeleted"));
1165
+ } catch (err) {
1166
+ toast.error(t("sidebar.failedPermanentDeletePage"), {
1167
+ description:
1168
+ err instanceof Error ? err.message : t("empty.genericError"),
1169
+ });
1170
+ }
1171
+ },
1172
+ [permanentlyDeleteDocument, t],
1143
1173
  );
1144
1174
 
1145
1175
  const handleRemoveLocalFiles = useCallback(async () => {
@@ -1423,21 +1453,21 @@ export function DocumentSidebar({
1423
1453
  type="button"
1424
1454
  aria-expanded={expanded}
1425
1455
  aria-label={`${expanded ? t("sidebar.collapse") : t("sidebar.expand")} ${space.name}`}
1426
- className="relative flex size-7 shrink-0 items-center justify-center rounded-md hover:bg-background/60"
1456
+ className="group/workspace-toggle relative flex size-7 shrink-0 items-center justify-center rounded-md hover:bg-background/60"
1427
1457
  onClick={() =>
1428
1458
  updateExpandedWorkspaceIds((current) =>
1429
1459
  toggleExpandedWorkspaceIds(current, space.id),
1430
1460
  )
1431
1461
  }
1432
1462
  >
1433
- <span className="group-hover/workspace-header:opacity-0 group-focus-within/workspace-header:opacity-0">
1463
+ <span className="group-hover/workspace-header:opacity-0 group-focus-visible/workspace-toggle:opacity-0">
1434
1464
  {expanded ? (
1435
1465
  <IconFolderOpen size={14} />
1436
1466
  ) : (
1437
1467
  <IconFolder size={14} />
1438
1468
  )}
1439
1469
  </span>
1440
- <span className="absolute inset-0 flex items-center justify-center opacity-0 group-hover/workspace-header:opacity-100 group-focus-within/workspace-header:opacity-100">
1470
+ <span className="absolute inset-0 flex items-center justify-center opacity-0 group-hover/workspace-header:opacity-100 group-focus-visible/workspace-toggle:opacity-100">
1441
1471
  {expanded ? (
1442
1472
  <IconChevronDown size={14} />
1443
1473
  ) : (
@@ -1555,37 +1585,20 @@ export function DocumentSidebar({
1555
1585
  />
1556
1586
  )}
1557
1587
  <div className="px-1">
1558
- <DropdownMenu>
1559
- <DropdownMenuTrigger asChild>
1560
- <button
1561
- type="button"
1562
- className="flex h-7 w-full min-w-0 items-center rounded-md text-muted-foreground hover:bg-accent/40 hover:text-foreground"
1563
- aria-label={t("sidebar.addWorkspace")}
1564
- >
1565
- <span className="flex size-7 shrink-0 items-center justify-center">
1566
- <IconPlus size={14} />
1567
- </span>
1568
- <span className="truncate text-start text-[10px] font-semibold uppercase tracking-wider">
1569
- {t("sidebar.addWorkspace")}
1570
- </span>
1571
- </button>
1572
- </DropdownMenuTrigger>
1573
- <DropdownMenuContent align="start" className="w-52">
1574
- <DropdownMenuItem
1575
- onSelect={() => setCreateWorkspaceDialogOpen(true)}
1576
- >
1577
- <IconPlus className="me-2 size-4" />
1578
- {t("sidebar.newWorkspace")}
1579
- </DropdownMenuItem>
1580
- <DropdownMenuSeparator />
1581
- <DropdownMenuItem asChild>
1582
- <Link to="/local-files">
1583
- <IconFolder className="me-2 size-4" />
1584
- {t("sidebar.localFolder")}
1585
- </Link>
1586
- </DropdownMenuItem>
1587
- </DropdownMenuContent>
1588
- </DropdownMenu>
1588
+ <WorkspaceSourceMenu onCreated={handleWorkspaceCreated}>
1589
+ <button
1590
+ type="button"
1591
+ className="flex h-7 w-full min-w-0 items-center rounded-md text-muted-foreground hover:bg-accent/40 hover:text-foreground"
1592
+ aria-label={t("sidebar.addWorkspace")}
1593
+ >
1594
+ <span className="flex size-7 shrink-0 items-center justify-center">
1595
+ <IconPlus size={14} />
1596
+ </span>
1597
+ <span className="truncate text-start text-[10px] font-semibold uppercase tracking-wider">
1598
+ {t("sidebar.addWorkspace")}
1599
+ </span>
1600
+ </button>
1601
+ </WorkspaceSourceMenu>
1589
1602
  </div>
1590
1603
  </div>
1591
1604
  ) : contentSpaceState === "loading" ? (
@@ -1605,14 +1618,123 @@ export function DocumentSidebar({
1605
1618
  );
1606
1619
 
1607
1620
  const renderTrashSection = () => {
1608
- if (trashItems.length === 0) return null;
1609
1621
  const collapsed = collapsedSections.trash;
1610
1622
 
1611
1623
  return (
1612
1624
  <div className="mt-3 border-t border-border/60 pt-2">
1613
- {renderSectionHeader("trash", t("sidebar.trash"))}
1625
+ <div className="px-2">
1626
+ <button
1627
+ type="button"
1628
+ aria-expanded={!collapsed}
1629
+ aria-label={`${collapsed ? t("sidebar.expand") : t("sidebar.collapse")} ${t("sidebar.trash")}`}
1630
+ className="group/trash flex h-7 w-full min-w-0 items-center rounded-md px-1 text-start text-[10px] font-semibold uppercase tracking-wider text-muted-foreground hover:bg-accent/40 hover:text-foreground"
1631
+ onClick={() => toggleSection("trash")}
1632
+ >
1633
+ <span className="flex size-7 shrink-0 items-center justify-center">
1634
+ <span className="relative size-3.5">
1635
+ <IconTrash
1636
+ aria-hidden="true"
1637
+ className="absolute inset-0 size-3.5 transition-opacity group-hover/trash:opacity-0 group-focus-visible/trash:opacity-0"
1638
+ />
1639
+ <IconChevronRight
1640
+ aria-hidden="true"
1641
+ className={cn(
1642
+ "absolute inset-0 size-3.5 opacity-0 transition-[opacity,transform] group-hover/trash:opacity-100 group-focus-visible/trash:opacity-100 rtl:-scale-x-100",
1643
+ !collapsed && "rotate-90",
1644
+ )}
1645
+ />
1646
+ </span>
1647
+ </span>
1648
+ <span className="min-w-0 flex-1 truncate">
1649
+ {t("sidebar.trash")}
1650
+ </span>
1651
+ </button>
1652
+ </div>
1614
1653
  {!collapsed && (
1615
1654
  <div className="px-1 py-1">
1655
+ {trashedPageItems.map((document) => {
1656
+ const title = document.title || t("sidebar.untitled");
1657
+ return (
1658
+ <div
1659
+ key={document.documentId}
1660
+ className="group flex min-w-0 items-center gap-1 rounded-md px-2 py-1.5 text-sm text-muted-foreground hover:bg-accent/50 hover:text-foreground"
1661
+ >
1662
+ <span className="min-w-0 flex-1 truncate">{title}</span>
1663
+ <Tooltip>
1664
+ <TooltipTrigger asChild>
1665
+ <button
1666
+ type="button"
1667
+ aria-label={t("sidebar.restorePageNamed", { title })}
1668
+ className="flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground disabled:opacity-50"
1669
+ disabled={restoreDocument.isPending}
1670
+ onClick={() =>
1671
+ void handleRestoreDocument(document.documentId)
1672
+ }
1673
+ >
1674
+ <IconRestore size={14} />
1675
+ </button>
1676
+ </TooltipTrigger>
1677
+ <TooltipContent>{t("sidebar.restorePage")}</TooltipContent>
1678
+ </Tooltip>
1679
+ <AlertDialog>
1680
+ <Tooltip>
1681
+ <TooltipTrigger asChild>
1682
+ <AlertDialogTrigger asChild>
1683
+ <button
1684
+ type="button"
1685
+ aria-label={t(
1686
+ "sidebar.deletePageNamedPermanently",
1687
+ {
1688
+ title,
1689
+ },
1690
+ )}
1691
+ className="flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
1692
+ disabled={permanentlyDeleteDocument.isPending}
1693
+ >
1694
+ <IconTrashX size={14} />
1695
+ </button>
1696
+ </AlertDialogTrigger>
1697
+ </TooltipTrigger>
1698
+ <TooltipContent>
1699
+ {t("sidebar.deletePermanently")}
1700
+ </TooltipContent>
1701
+ </Tooltip>
1702
+ <AlertDialogContent>
1703
+ <AlertDialogHeader>
1704
+ <AlertDialogTitle>
1705
+ {t("sidebar.deletePagePermanentlyQuestion")}
1706
+ </AlertDialogTitle>
1707
+ <AlertDialogDescription>
1708
+ {t("sidebar.deletePagePermanentlyDescription", {
1709
+ title,
1710
+ })}
1711
+ </AlertDialogDescription>
1712
+ </AlertDialogHeader>
1713
+ <AlertDialogFooter>
1714
+ <AlertDialogCancel>
1715
+ {t("comments.cancel")}
1716
+ </AlertDialogCancel>
1717
+ <AlertDialogAction
1718
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
1719
+ onClick={() =>
1720
+ void handlePermanentDeleteDocument(
1721
+ document.documentId,
1722
+ )
1723
+ }
1724
+ >
1725
+ {t("sidebar.deletePermanently")}
1726
+ </AlertDialogAction>
1727
+ </AlertDialogFooter>
1728
+ </AlertDialogContent>
1729
+ </AlertDialog>
1730
+ </div>
1731
+ );
1732
+ })}
1733
+ {trashedPageItems.length === 0 && trashItems.length === 0 ? (
1734
+ <div className="px-2 py-1.5 text-sm text-muted-foreground">
1735
+ {t("sidebar.trashEmpty")}
1736
+ </div>
1737
+ ) : null}
1616
1738
  {trashItems.map((database) => {
1617
1739
  const title = database.title || t("editor.untitledDatabase");
1618
1740
  return (
@@ -1653,7 +1775,7 @@ export function DocumentSidebar({
1653
1775
  { title },
1654
1776
  )}
1655
1777
  className="flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
1656
- disabled={deleteDocument.isPending}
1778
+ disabled={permanentlyDeleteDocument.isPending}
1657
1779
  >
1658
1780
  <IconTrashX size={14} />
1659
1781
  </button>
@@ -1873,19 +1995,27 @@ export function DocumentSidebar({
1873
1995
  {/* Favorites */}
1874
1996
  {showFavorites && (
1875
1997
  <div className="mb-2 min-w-0 px-2">
1876
- <div className="flex h-7 w-full min-w-0 items-center rounded-md px-1 text-muted-foreground hover:bg-accent/40 hover:text-foreground">
1998
+ <div className="group/favorites flex h-7 w-full min-w-0 items-center rounded-md px-1 text-muted-foreground hover:bg-accent/40 hover:text-foreground">
1877
1999
  <button
1878
2000
  type="button"
1879
2001
  aria-expanded={!collapsedSections.favorites}
1880
2002
  aria-label={`${collapsedSections.favorites ? t("sidebar.expand") : t("sidebar.collapse")} ${t("sidebar.favorites")}`}
1881
- className="flex size-7 shrink-0 items-center justify-center rounded-md hover:bg-background/60"
2003
+ className="group/favorites-toggle flex size-7 shrink-0 items-center justify-center rounded-md hover:bg-background/60"
1882
2004
  onClick={() => toggleSection("favorites")}
1883
2005
  >
1884
- {collapsedSections.favorites ? (
1885
- <IconChevronRight size={14} />
1886
- ) : (
1887
- <IconChevronDown size={14} />
1888
- )}
2006
+ <span className="relative size-3.5">
2007
+ <IconStar
2008
+ aria-hidden="true"
2009
+ className="absolute inset-0 size-3.5 transition-opacity group-hover/favorites:opacity-0 group-focus-visible/favorites-toggle:opacity-0"
2010
+ />
2011
+ <IconChevronRight
2012
+ aria-hidden="true"
2013
+ className={cn(
2014
+ "absolute inset-0 size-3.5 opacity-0 transition-[opacity,transform] group-hover/favorites:opacity-100 group-focus-visible/favorites-toggle:opacity-100 rtl:-scale-x-100",
2015
+ !collapsedSections.favorites && "rotate-90",
2016
+ )}
2017
+ />
2018
+ </span>
1889
2019
  </button>
1890
2020
  <Link
1891
2021
  to={
@@ -1956,6 +2086,10 @@ export function DocumentSidebar({
1956
2086
  <ExtensionsSidebarSection />
1957
2087
  </div>
1958
2088
 
2089
+ <div className="shrink-0 px-3 py-2">
2090
+ <OrgSwitcher reserveSpace />
2091
+ </div>
2092
+
1959
2093
  {/* Footer */}
1960
2094
  <div className="shrink-0 space-y-2 px-3 py-2">
1961
2095
  {isCodeMode ? <DevDatabaseLink /> : null}
@@ -1978,60 +2112,6 @@ export function DocumentSidebar({
1978
2112
  onMouseDown={handleMouseDown}
1979
2113
  />
1980
2114
  )}
1981
- <Dialog
1982
- open={createWorkspaceDialogOpen}
1983
- onOpenChange={(open) => {
1984
- setCreateWorkspaceDialogOpen(open);
1985
- if (!open && !createContentSpace.isPending) {
1986
- setNewWorkspaceName("");
1987
- createWorkspaceRequestIdRef.current = null;
1988
- }
1989
- }}
1990
- >
1991
- <DialogContent className="sm:max-w-sm">
1992
- <form
1993
- className="grid gap-4"
1994
- onSubmit={(event) => {
1995
- event.preventDefault();
1996
- void handleCreateWorkspace();
1997
- }}
1998
- >
1999
- <DialogHeader>
2000
- <DialogTitle>{t("sidebar.newWorkspace")}</DialogTitle>
2001
- <DialogDescription>
2002
- {t("sidebar.newWorkspaceDescription")}
2003
- </DialogDescription>
2004
- </DialogHeader>
2005
- <Input
2006
- autoFocus
2007
- aria-label={t("sidebar.workspaceName")}
2008
- placeholder={t("sidebar.workspaceName")}
2009
- value={newWorkspaceName}
2010
- maxLength={200}
2011
- onChange={(event) => setNewWorkspaceName(event.target.value)}
2012
- />
2013
- <DialogFooter>
2014
- <button
2015
- type="button"
2016
- className="inline-flex h-9 items-center justify-center rounded-md px-4 text-sm font-medium hover:bg-accent"
2017
- disabled={createContentSpace.isPending}
2018
- onClick={() => setCreateWorkspaceDialogOpen(false)}
2019
- >
2020
- {t("comments.cancel")}
2021
- </button>
2022
- <button
2023
- type="submit"
2024
- className="inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
2025
- disabled={
2026
- createContentSpace.isPending || !newWorkspaceName.trim()
2027
- }
2028
- >
2029
- {t("sidebar.createWorkspace")}
2030
- </button>
2031
- </DialogFooter>
2032
- </form>
2033
- </DialogContent>
2034
- </Dialog>
2035
2115
  <AlertDialog
2036
2116
  open={removeLocalFilesDialogOpen}
2037
2117
  onOpenChange={setRemoveLocalFilesDialogOpen}