@powerhousedao/connect 6.2.3-dev.2 → 6.2.3-dev.21

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 (103) hide show
  1. package/dist/{AddDriveModal-Q-Ffh5XA.js → AddDriveModal-CW-ZSWkf.js} +6 -21
  2. package/dist/AddDriveModal-CW-ZSWkf.js.map +1 -0
  3. package/dist/{ClearStorageModal-CcGmYZTu.js → ClearStorageModal-LiaQURpM.js} +5 -5
  4. package/dist/{ClearStorageModal-CcGmYZTu.js.map → ClearStorageModal-LiaQURpM.js.map} +1 -1
  5. package/dist/{ConfirmDocumentUpgradeModal-FUOhZsk7.js → ConfirmDocumentUpgradeModal-BBrFlHp_.js} +6 -6
  6. package/dist/{ConfirmDocumentUpgradeModal-FUOhZsk7.js.map → ConfirmDocumentUpgradeModal-BBrFlHp_.js.map} +1 -1
  7. package/dist/{CookiesPolicyModal-BpyPPyxS.js → CookiesPolicyModal-lKdOuTNm.js} +4 -4
  8. package/dist/{CookiesPolicyModal-BpyPPyxS.js.map → CookiesPolicyModal-lKdOuTNm.js.map} +1 -1
  9. package/dist/{CreateDocumentModal-C45W8J0V.js → CreateDocumentModal-HmK-78sB.js} +4 -4
  10. package/dist/{CreateDocumentModal-C45W8J0V.js.map → CreateDocumentModal-HmK-78sB.js.map} +1 -1
  11. package/dist/{DebugSettingsModal-Bmfn-T8m.js → DebugSettingsModal-CzovsTzG.js} +7 -7
  12. package/dist/{DebugSettingsModal-Bmfn-T8m.js.map → DebugSettingsModal-CzovsTzG.js.map} +1 -1
  13. package/dist/{DeleteDriveModal-Dug84t1j.js → DeleteDriveModal-Cyw6hwc2.js} +5 -5
  14. package/dist/{DeleteDriveModal-Dug84t1j.js.map → DeleteDriveModal-Cyw6hwc2.js.map} +1 -1
  15. package/dist/{DeleteItemModal-mMzy3V8o.js → DeleteItemModal-CiFwstZx.js} +5 -5
  16. package/dist/{DeleteItemModal-mMzy3V8o.js.map → DeleteItemModal-CiFwstZx.js.map} +1 -1
  17. package/dist/{DisclaimerModal-DvpD5dPM.js → DisclaimerModal-DIRsjUvu.js} +4 -4
  18. package/dist/{DisclaimerModal-DvpD5dPM.js.map → DisclaimerModal-DIRsjUvu.js.map} +1 -1
  19. package/dist/{DocumentVersionUnsupportedModal-C3Q0V75l.js → DocumentVersionUnsupportedModal-KftC3-jr.js} +4 -4
  20. package/dist/{DocumentVersionUnsupportedModal-C3Q0V75l.js.map → DocumentVersionUnsupportedModal-KftC3-jr.js.map} +1 -1
  21. package/dist/{DownloadDocumentWithErrorsModal-IKl0ljQb.js → DownloadDocumentWithErrorsModal-CxsR74d-.js} +4 -4
  22. package/dist/{DownloadDocumentWithErrorsModal-IKl0ljQb.js.map → DownloadDocumentWithErrorsModal-CxsR74d-.js.map} +1 -1
  23. package/dist/{DriveAuthRequiredModal-Cw9eCAQ_.js → DriveAuthRequiredModal-BoqnnNKT.js} +19 -7
  24. package/dist/DriveAuthRequiredModal-BoqnnNKT.js.map +1 -0
  25. package/dist/{DriveSettingsModal-C6-mRQiv.js → DriveSettingsModal-DRarHRIb.js} +4 -4
  26. package/dist/{DriveSettingsModal-C6-mRQiv.js.map → DriveSettingsModal-DRarHRIb.js.map} +1 -1
  27. package/dist/{InspectorModal-PtIbilu_.js → InspectorModal-iJTZXoFG.js} +8 -8
  28. package/dist/{InspectorModal-PtIbilu_.js.map → InspectorModal-iJTZXoFG.js.map} +1 -1
  29. package/dist/{LoginModal-CybbOm3N.js → LoginModal-DhQTxsoG.js} +5 -5
  30. package/dist/{LoginModal-CybbOm3N.js.map → LoginModal-DhQTxsoG.js.map} +1 -1
  31. package/dist/{MissingPackageModal-nBnExqO0.js → MissingPackageModal-Bar7ErIs.js} +5 -5
  32. package/dist/{MissingPackageModal-nBnExqO0.js.map → MissingPackageModal-Bar7ErIs.js.map} +1 -1
  33. package/dist/{OpenFileDocumentsModal-Du-V-3od.js → OpenFileDocumentsModal-BvDNshmD.js} +6 -6
  34. package/dist/{OpenFileDocumentsModal-Du-V-3od.js.map → OpenFileDocumentsModal-BvDNshmD.js.map} +1 -1
  35. package/dist/{SettingsModal-C75UaQE8.js → SettingsModal-D_6sBNEp.js} +10 -9
  36. package/dist/{SettingsModal-C75UaQE8.js.map → SettingsModal-D_6sBNEp.js.map} +1 -1
  37. package/dist/{UpgradeDriveModal-_8t9slxQ.js → UpgradeDriveModal-C8scFkuq.js} +4 -4
  38. package/dist/{UpgradeDriveModal-_8t9slxQ.js.map → UpgradeDriveModal-C8scFkuq.js.map} +1 -1
  39. package/dist/{app-loader-4yYa_F0I.js → app-loader-B5pvqL4j.js} +1188 -1117
  40. package/dist/app-loader-B5pvqL4j.js.map +1 -0
  41. package/dist/app-loader-Dn3DbqEz.js +4 -0
  42. package/dist/app-skeleton-BQts0IHn.js +4 -0
  43. package/dist/app-skeleton-C6RjKQuy.js +111 -0
  44. package/dist/app-skeleton-C6RjKQuy.js.map +1 -0
  45. package/dist/boot-uobbAyWT.js +210 -0
  46. package/dist/boot-uobbAyWT.js.map +1 -0
  47. package/dist/buffer-Bu1eWySI.js +66 -0
  48. package/dist/buffer-Bu1eWySI.js.map +1 -0
  49. package/dist/{build-info-B24DIeTL.js → build-info-DtHSkL55.js} +5 -5
  50. package/dist/{build-info-B24DIeTL.js.map → build-info-DtHSkL55.js.map} +1 -1
  51. package/dist/components-z0G60181.js +5 -0
  52. package/dist/{connect.config-DaHDEUrw.js → connect.config-BofwIsWJ.js} +4 -3
  53. package/dist/{connect.config-DaHDEUrw.js.map → connect.config-BofwIsWJ.js.map} +1 -1
  54. package/dist/{connection-state-3w4liC1X.js → connection-state-DAqW06pk.js} +5 -5
  55. package/dist/{connection-state-3w4liC1X.js.map → connection-state-DAqW06pk.js.map} +1 -1
  56. package/dist/{cookie-banner-BH6bIq24.js → cookie-banner-BQescTYk.js} +6 -6
  57. package/dist/{cookie-banner-BH6bIq24.js.map → cookie-banner-BQescTYk.js.map} +1 -1
  58. package/dist/{i18n-CWO0vYVd.js → i18n-90Q8hAPL.js} +3 -3
  59. package/dist/{i18n-CWO0vYVd.js.map → i18n-90Q8hAPL.js.map} +1 -1
  60. package/dist/{load-B45H0s1X.js → load-BHPVWGa5.js} +8 -8
  61. package/dist/{load-B45H0s1X.js.map → load-BHPVWGa5.js.map} +1 -1
  62. package/dist/main.js +8 -8
  63. package/dist/main.js.map +1 -1
  64. package/dist/package-manager-CKEdIJGv.js +475 -0
  65. package/dist/package-manager-CKEdIJGv.js.map +1 -0
  66. package/dist/{pglite-major-B39aOEiE.js → pglite-major-D3sdYMs2.js} +3 -3
  67. package/dist/{pglite-major-B39aOEiE.js.map → pglite-major-D3sdYMs2.js.map} +1 -1
  68. package/dist/{pglite-migrate-core-Boxqqx-c.js → pglite-migrate-core-BIWwtl5e.js} +4 -4
  69. package/dist/{pglite-migrate-core-Boxqqx-c.js.map → pglite-migrate-core-BIWwtl5e.js.map} +1 -1
  70. package/dist/{pglite-seed-BqAUvNFw.js → pglite-seed-ChNTfFkc.js} +4 -4
  71. package/dist/{pglite-seed-BqAUvNFw.js.map → pglite-seed-ChNTfFkc.js.map} +1 -1
  72. package/dist/{preferences-store-_5Cw4_YV.js → preferences-store-Coa1SHvi.js} +6 -25
  73. package/dist/preferences-store-Coa1SHvi.js.map +1 -0
  74. package/dist/{pwa-manifest-link-DIgOSo2Q.js → pwa-manifest-link-B38lBmEu.js} +3 -3
  75. package/dist/{pwa-manifest-link-DIgOSo2Q.js.map → pwa-manifest-link-B38lBmEu.js.map} +1 -1
  76. package/dist/{reactor-BMNq8chi.js → reactor-DcHPEDvb.js} +26 -421
  77. package/dist/reactor-DcHPEDvb.js.map +1 -0
  78. package/dist/reactor.worker.js +29 -13
  79. package/dist/reactor.worker.js.map +1 -1
  80. package/dist/{registerServiceWorker-BSzbNfvz.js → registerServiceWorker-BAbPbSW4.js} +5 -5
  81. package/dist/{registerServiceWorker-BSzbNfvz.js.map → registerServiceWorker-BAbPbSW4.js.map} +1 -1
  82. package/dist/start-connect.d.ts.map +1 -1
  83. package/dist/start-connect.js +6 -8
  84. package/dist/start-connect.js.map +1 -1
  85. package/dist/style.css +2060 -0
  86. package/dist/{toast-BXIGd4Eb.js → toast-CO6ChlzE.js} +3 -3
  87. package/dist/{toast-BXIGd4Eb.js.map → toast-CO6ChlzE.js.map} +1 -1
  88. package/dist/{use-renown-login-Cf3va6u_.js → use-renown-login-rS9wfcuG.js} +3 -3
  89. package/dist/{use-renown-login-Cf3va6u_.js.map → use-renown-login-rS9wfcuG.js.map} +1 -1
  90. package/dist/useIsEmbedded-B_AIFqBu.js +26 -0
  91. package/dist/useIsEmbedded-B_AIFqBu.js.map +1 -0
  92. package/dist/{useRegistryPackages-BnUcL_nZ.js → useRegistryPackages-C2WHmqn4.js} +60 -3
  93. package/dist/useRegistryPackages-C2WHmqn4.js.map +1 -0
  94. package/package.json +13 -12
  95. package/style.css +4 -0
  96. package/dist/AddDriveModal-Q-Ffh5XA.js.map +0 -1
  97. package/dist/DriveAuthRequiredModal-Cw9eCAQ_.js.map +0 -1
  98. package/dist/app-loader-4yYa_F0I.js.map +0 -1
  99. package/dist/app-loader-e4nqwWF5.js +0 -4
  100. package/dist/components-Dx56Q8EI.js +0 -4
  101. package/dist/preferences-store-_5Cw4_YV.js.map +0 -1
  102. package/dist/reactor-BMNq8chi.js.map +0 -1
  103. package/dist/useRegistryPackages-BnUcL_nZ.js.map +0 -1
@@ -1,27 +1,31 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b8568829-74c8-5629-8861-97fa4a82010c")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ac4746d5-1e0b-5233-a0f4-95264a1e2ea1")}catch(e){}}();
3
3
  import { n as getRuntimeConfig } from "./runtime-config-DL3QjcfM.js";
4
- import { c as idbError } from "./pglite-major-B39aOEiE.js";
5
- import { n as cloneFileData, r as migrateIdb } from "./pglite-migrate-core-Boxqqx-c.js";
6
- import { a as defaultPHDocumentEditorConfig, i as defaultPHAppConfig, r as connectConfig } from "./connect.config-DaHDEUrw.js";
7
- import { f as PRIMARY_IDB_NAMES, r as subscribeWorkerConnection, s as getCachedMigrationMajor, t as getWorkerConnectionStatus, u as subscribeMigrationMajor } from "./connection-state-3w4liC1X.js";
8
- import { n as serviceWorkerManager, r as subscribeServiceWorkerUpdate, t as getServiceWorkerUpdateAvailable } from "./registerServiceWorker-BSzbNfvz.js";
4
+ import { n as drainOpenPanelBuffer, t as clearOpenPanelBuffer } from "./buffer-Bu1eWySI.js";
5
+ import { c as idbError } from "./pglite-major-D3sdYMs2.js";
6
+ import { n as cloneFileData, r as migrateIdb } from "./pglite-migrate-core-BIWwtl5e.js";
7
+ import { n as setMigrationStatus, t as AppSkeleton } from "./app-skeleton-C6RjKQuy.js";
8
+ import { n as useIsEmbedded } from "./useIsEmbedded-B_AIFqBu.js";
9
+ import { a as defaultPHDocumentEditorConfig, i as defaultPHAppConfig, r as connectConfig } from "./connect.config-BofwIsWJ.js";
10
+ import { n as useAcceptedCookies, t as setBannerShown } from "./preferences-store-Coa1SHvi.js";
9
11
  import { t as setSentryModule } from "./sentry-QXKgQmr2.js";
10
- import { i as useIsEmbedded, r as getIsEmbedded, t as useAcceptedCookies } from "./preferences-store-_5Cw4_YV.js";
11
- import { n as dismissToast, r as toast, t as ToastContainer } from "./toast-BXIGd4Eb.js";
12
- import { t as useOpenRenownLogin } from "./use-renown-login-Cf3va6u_.js";
12
+ import { n as dismissToast, r as toast, t as ToastContainer } from "./toast-CO6ChlzE.js";
13
+ import { f as PRIMARY_IDB_NAMES, r as subscribeWorkerConnection, s as getCachedMigrationMajor, t as getWorkerConnectionStatus, u as subscribeMigrationMajor } from "./connection-state-DAqW06pk.js";
14
+ import { n as serviceWorkerManager, r as subscribeServiceWorkerUpdate, t as getServiceWorkerUpdateAvailable } from "./registerServiceWorker-BAbPbSW4.js";
15
+ import { t as useOpenRenownLogin } from "./use-renown-login-rS9wfcuG.js";
13
16
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
- import { useTranslation } from "react-i18next";
17
+ import { Trans, useTranslation } from "react-i18next";
15
18
  import React, { StrictMode, Suspense, createElement, lazy, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
16
- import { DriveCollectionId, RenownWalletProvider, addFileWithProgress, getRevisionFromDate, initTheme, logout, setPHAppConfig, setPHDocumentEditorConfig, setRevisionHistoryVisible, setSelectedDrive, setSelectedNode, showPHModal, useAppModuleById, useConnectionStates, useDefaultAppModule, useDocumentById, useDocumentModelModuleById, useDocumentOperations, useDocumentVersionStatus, useDrives, useEditorModuleById, useEditorPreloader, useFallbackEditorModule, useIsAddDriveEnabled, useNodeParentFolderById, usePHModal, usePackageDiscoveryService, useReactorClientModule, useRevisionHistoryVisible, useSelectedDocument, useSelectedDocumentId, useSelectedDriveSafe, useSelectedFolder, useSelectedTimelineItem, useSyncList, useTheme, useUser, useVetraPackageManager, useVetraPackages } from "@powerhousedao/reactor-browser";
17
- import { childLogger } from "document-model";
18
- import { ConnectSidebar, ConnectTooltipProvider, DefaultEditorLoader, DriveAuthGate, DropZoneWrapper, HomeScreen, HomeScreenAddDriveItem, HomeScreenItem, LogoAnimation, PackageInstallModal, RevisionHistory, SidebarAddDriveItem, SidebarItem, UploadFileListContainer, formatFileSize, mapProgressStageToStatus, useEns } from "@powerhousedao/design-system/connect";
19
- import { Outlet, RouterProvider, createBrowserRouter, createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from "react-router-dom";
20
- import { useHotkeys } from "react-hotkeys-hook";
19
+ import { ConnectSearchBar, ConnectSidebar, ConnectTooltipProvider, DefaultEditorLoader, DriveAuthGate, DropZoneWrapper, Footer, FooterLink, HomeScreen, HomeScreenAddDriveItem, HomeScreenItem, PackageInstallModal, RevisionHistory, SidebarAddDriveItem, SidebarItem, UploadFileListContainer, formatFileSize, mapProgressStageToStatus, useEns } from "@powerhousedao/design-system/connect";
20
+ import { DriveCollectionId, RenownWalletProvider, addFileWithProgress, getRevisionFromDate, logout, setPHAppConfig, setPHDocumentEditorConfig, setRevisionHistoryVisible, setSelectedDrive, setSelectedNode, showPHModal, useAppModuleById, useConnectionStates, useDefaultAppModule, useDocumentById, useDocumentModelModuleById, useDocumentOperations, useDocumentVersionStatus, useDrives, useEditorModuleById, useEditorPreloader, useFallbackEditorModule, useIsAddDriveEnabled, useNodeParentFolderById, usePHModal, usePackageDiscoveryService, useReactorClientModule, useRevisionHistoryVisible, useSelectedDocument, useSelectedDocumentId, useSelectedDriveId, useSelectedDriveSafe, useSelectedFolder, useSelectedTimelineItem, useSync, useSyncList, useTheme, useUser, useVetraPackageManager, useVetraPackages } from "@powerhousedao/reactor-browser";
21
21
  import { Icon, Modal, PowerhouseButton } from "@powerhousedao/design-system";
22
+ import { childLogger } from "document-model";
22
23
  import { ErrorBoundary } from "react-error-boundary";
23
24
  import { z } from "zod";
24
25
  import { GenericDriveExplorer } from "@powerhousedao/powerhouse-vetra-packages/editors";
26
+ import { Outlet, RouterProvider, createBrowserRouter, createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from "react-router-dom";
27
+ import { redo, undo } from "@powerhousedao/shared/document-model";
28
+ import { useHotkeys } from "react-hotkeys-hook";
25
29
  import { createReactorMcpProvider } from "@powerhousedao/reactor-mcp/tools";
26
30
  import { ReactorChatFab, createSwitchboardSchemaTool } from "@powerhousedao/reactor-browser/ai";
27
31
  import { twMerge } from "tailwind-merge";
@@ -29,487 +33,663 @@ import { mockAdapter } from "@renown/sdk/wallet/mock";
29
33
  import { privyAdapter } from "@renown/sdk/wallet/privy";
30
34
  import { rainbowAdapter } from "@renown/sdk/wallet/rainbow";
31
35
  import { arbitrum, base, mainnet, optimism, polygon, sepolia } from "wagmi/chains";
32
- import { redo, undo } from "@powerhousedao/shared/document-model";
33
- //#region src/utils/browser.ts
34
- function getBasePath() {
35
- if (typeof document === "undefined") return "/";
36
- return document.querySelector("base")?.getAttribute("href") || "/";
37
- }
38
- //#endregion
39
- //#region src/utils/document-editor-debug-tools.ts
40
- var DocumentEditorDebugTools = class {
41
- document;
42
- operations = [];
43
- constructor(document) {
44
- if (document) this.document = document;
45
- }
46
- operationsToTableObject(operations) {
47
- return operations.map((op) => ({
48
- ...op,
49
- input: JSON.stringify(op.action.input)
50
- }));
51
- }
52
- setDocument(document) {
53
- this.document = document;
54
- }
55
- getDocument() {
56
- return this.document;
57
- }
58
- getOperations() {
59
- return this.operations;
60
- }
61
- pushOperation(operation) {
62
- this.operations.push(operation);
63
- }
64
- operationsTable() {
65
- if (!this.document) console.warn("No document");
66
- const ops = Object.values(this.document?.operations || {}).filter((array) => array !== void 0).flatMap((array) => array).sort((a, b) => a.index - b.index);
67
- console.table(this.operationsToTableObject(ops));
68
- }
69
- scopeOperationsTable(scope) {
70
- if (!this.document) console.warn("No document");
71
- const ops = this.document?.operations[scope] || [];
72
- console.table(this.operationsToTableObject(ops));
73
- }
74
- operationsLog() {
75
- console.log(this.operations);
76
- }
77
- operationsLogTable() {
78
- console.table(this.operationsToTableObject(this.operations));
79
- }
80
- clear() {
81
- this.operations = [];
82
- this.document = void 0;
83
- }
84
- };
85
- //#endregion
86
- //#region src/utils/launch-queue.ts
87
- let pendingImportFiles = [];
88
- const pendingListeners = /* @__PURE__ */ new Set();
89
- function notifyPendingListeners() {
90
- for (const listener of pendingListeners) listener();
91
- }
92
- function getPendingImportFiles() {
93
- return pendingImportFiles;
36
+ //#region src/components/error-boundary.tsx
37
+ const logger$2 = childLogger(["Connect"]);
38
+ function hasCustomFallback(props) {
39
+ return "fallbackRender" in props;
94
40
  }
95
- /** Stable identity of a pending file (also the picker's pre-check cache key). */
96
- function pendingFileKey(file) {
97
- return `${file.name}|${file.size}|${file.lastModified}`;
41
+ function SilentFallback() {
42
+ return null;
98
43
  }
99
- function addPendingImportFiles(files) {
100
- const seen = new Set(pendingImportFiles.map(pendingFileKey));
101
- const fresh = files.filter((file) => {
102
- const key = pendingFileKey(file);
103
- if (seen.has(key)) return false;
104
- seen.add(key);
105
- return true;
44
+ /**
45
+ * Simple text-based fallback component.
46
+ * Displays a centered message when an error occurs.
47
+ */
48
+ function TextFallback({ message = "Something went wrong" }) {
49
+ return /* @__PURE__ */ jsx("div", {
50
+ className: "text-center wrap-break-word text-foreground",
51
+ children: message
106
52
  });
107
- if (fresh.length === 0) return;
108
- pendingImportFiles = [...pendingImportFiles, ...fresh];
109
- notifyPendingListeners();
110
53
  }
111
- function clearPendingImportFiles() {
112
- if (pendingImportFiles.length === 0) return;
113
- pendingImportFiles = [];
114
- notifyPendingListeners();
54
+ /**
55
+ * Detailed fallback component showing full error information.
56
+ * Displays error message with a collapsible details section.
57
+ * Useful for development and debugging purposes.
58
+ */
59
+ function DetailedFallback({ error, resetErrorBoundary }) {
60
+ const errorMessage = error instanceof Error ? error.message : String(error);
61
+ const errorDetails = error instanceof Error ? error.stack ?? String(error.cause) : JSON.stringify(error, null, 2);
62
+ return /* @__PURE__ */ jsx("div", {
63
+ className: "z-10 mx-auto flex max-w-[80%] flex-1 items-center justify-center p-6",
64
+ children: /* @__PURE__ */ jsxs("div", {
65
+ className: "w-full max-w-lg rounded-lg border border-border bg-card p-6 text-foreground shadow-sm",
66
+ children: [
67
+ /* @__PURE__ */ jsxs("div", {
68
+ className: "mb-3 flex items-center gap-2",
69
+ children: [/* @__PURE__ */ jsx(Icon, {
70
+ name: "Error",
71
+ className: "size-5 shrink-0 text-destructive"
72
+ }), /* @__PURE__ */ jsx("h1", {
73
+ className: "text-lg font-semibold text-foreground",
74
+ children: "Something went wrong"
75
+ })]
76
+ }),
77
+ /* @__PURE__ */ jsx("p", {
78
+ className: "mb-4 text-sm wrap-break-word text-foreground",
79
+ children: errorMessage
80
+ }),
81
+ errorDetails !== "{}" && /* @__PURE__ */ jsxs("details", {
82
+ className: "group",
83
+ children: [/* @__PURE__ */ jsx("summary", {
84
+ className: "cursor-pointer text-sm font-medium text-foreground underline select-none hover:hover-effect",
85
+ children: "Show details"
86
+ }), /* @__PURE__ */ jsx("pre", {
87
+ className: "mt-2 max-h-48 overflow-auto rounded-sm bg-muted p-3 text-xs text-foreground",
88
+ children: errorDetails
89
+ })]
90
+ }),
91
+ /* @__PURE__ */ jsx(PowerhouseButton, {
92
+ type: "button",
93
+ onClick: resetErrorBoundary,
94
+ className: "mt-4 px-3 py-1.5 text-base font-medium",
95
+ children: "Try again"
96
+ })
97
+ ]
98
+ })
99
+ });
115
100
  }
116
- function subscribePendingImportFiles(cb) {
117
- pendingListeners.add(cb);
118
- return () => {
119
- pendingListeners.delete(cb);
120
- };
101
+ /**
102
+ * Centered error message fallback component.
103
+ * Displays the error message in a centered, full-size container.
104
+ * Suitable for use as a custom fallbackRender when you need simple error display.
105
+ */
106
+ function CenteredErrorMessage({ error }) {
107
+ return /* @__PURE__ */ jsx("div", {
108
+ className: "flex size-full items-center justify-center p-6",
109
+ children: /* @__PURE__ */ jsx("h3", {
110
+ className: "max-w-lg min-w-0 text-center text-lg font-semibold wrap-break-word text-foreground",
111
+ children: error instanceof Error ? error.message : String(error)
112
+ })
113
+ });
121
114
  }
122
- async function handleLaunchParams(params) {
123
- const handles = params.files.filter((handle) => handle.kind === "file");
124
- if (handles.length === 0) return;
125
- const files = [];
126
- for (const handle of handles) try {
127
- files.push(await handle.getFile());
128
- } catch (error) {
129
- console.error("Failed to read launched file:", handle.name, error);
115
+ function getFallbackComponent(variant, fallbackMessage) {
116
+ switch (variant) {
117
+ case "silent": return SilentFallback;
118
+ case "text": return () => /* @__PURE__ */ jsx(TextFallback, { message: fallbackMessage });
119
+ case "detailed": return DetailedFallback;
120
+ default: return CenteredErrorMessage;
130
121
  }
131
- if (files.length === 0) return;
132
- addPendingImportFiles(files);
133
- showPHModal({ type: "openFileDocuments" });
134
122
  }
135
- function maybeReopenImportModal() {
136
- if (getPendingImportFiles().length === 0) return;
137
- if (window.ph?.modal !== void 0) return;
138
- showPHModal({ type: "openFileDocuments" });
139
- }
140
- let initialized = false;
141
123
  /**
142
- * Register the launchQueue consumer that receives files opened with Connect
143
- * through the OS file association. Idempotent (safe under StrictMode double
144
- * effects) and a no-op where the File Handling API doesn't exist — Firefox,
145
- * Safari, and any context without an installed PWA manifest simply never
146
- * deliver launches.
124
+ * Unified error boundary component for catching and handling React errors.
125
+ *
126
+ * Built on top of `react-error-boundary`, this component provides:
127
+ * - Automatic error logging with configurable context
128
+ * - Multiple fallback UI variants (silent, text, detailed)
129
+ * - Support for custom fallback render functions
130
+ * - Reset capability via `resetKeys`
131
+ *
132
+ * @example
133
+ * // Using a predefined variant
134
+ * <ErrorBoundary variant="detailed" loggerContext={["Connect", "Editor"]}>
135
+ * <EditorComponent />
136
+ * </ErrorBoundary>
137
+ *
138
+ * @example
139
+ * // Using a custom message with the text variant
140
+ * <ErrorBoundary variant="text" fallbackMessage="Failed to load drives">
141
+ * <DrivesList />
142
+ * </ErrorBoundary>
143
+ *
144
+ * @example
145
+ * // Using a custom fallback render
146
+ * <ErrorBoundary
147
+ * fallbackRender={CenteredErrorMessage}
148
+ * onError={(error) => trackError(error)}
149
+ * >
150
+ * <MyComponent />
151
+ * </ErrorBoundary>
152
+ *
153
+ * @example
154
+ * // Silent mode for modals (renders null on error)
155
+ * <ErrorBoundary variant="silent" loggerContext={["Connect", "Modals"]}>
156
+ * <ModalContent />
157
+ * </ErrorBoundary>
147
158
  */
148
- function initLaunchQueueFileHandling() {
149
- if (initialized) return;
150
- initialized = true;
151
- window.addEventListener("ph:modalUpdated", maybeReopenImportModal);
152
- window.launchQueue?.setConsumer((params) => {
153
- handleLaunchParams(params);
159
+ function ErrorBoundary$1(props) {
160
+ const { children, onError, resetKeys, loggerContext } = props;
161
+ const handleError = (error, info) => {
162
+ if (error.message.includes("Failed to fetch dynamically imported module") && error.message.includes("node_modules/.vite")) {
163
+ console.error(error);
164
+ console.log("Outdated chunk detected, reloading page...");
165
+ window.location.reload();
166
+ return;
167
+ }
168
+ (loggerContext ? childLogger(loggerContext) : logger$2).error("@error @info", error, info);
169
+ onError?.(error, info);
170
+ };
171
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
172
+ fallbackRender: hasCustomFallback(props) ? props.fallbackRender : getFallbackComponent(props.variant, props.fallbackMessage),
173
+ onError: handleError,
174
+ resetKeys,
175
+ children
154
176
  });
155
177
  }
156
178
  //#endregion
157
- //#region src/utils/open-file-uploads.ts
158
- let uploads = [];
159
- const listeners$1 = /* @__PURE__ */ new Set();
160
- function notifyUploadListeners() {
161
- for (const listener of listeners$1) listener();
162
- }
163
- function getOpenFileUploads() {
164
- return uploads;
165
- }
166
- function subscribeOpenFileUploads(cb) {
167
- listeners$1.add(cb);
168
- return () => {
169
- listeners$1.delete(cb);
170
- };
179
+ //#region src/components/analytics.tsx
180
+ function gtag(...args) {
181
+ window.dataLayer?.push(...args);
171
182
  }
172
- function removeOpenFileUpload(id) {
173
- const next = uploads.filter((upload) => upload.id !== id);
174
- if (next.length === uploads.length) return;
175
- uploads = next;
176
- notifyUploadListeners();
177
- }
178
- function clearOpenFileUploads() {
179
- if (uploads.length === 0) return;
180
- uploads = [];
181
- notifyUploadListeners();
182
- }
183
- function addOpenFileUploads(rows) {
184
- uploads = [...uploads, ...rows];
185
- notifyUploadListeners();
186
- }
187
- function patchOpenFileUpload(id, patch) {
188
- uploads = uploads.map((upload) => upload.id === id ? {
189
- ...upload,
190
- ...patch
191
- } : upload);
192
- notifyUploadListeners();
193
- }
194
- let uploadCounter = 0;
195
- let importChain = Promise.resolve();
183
+ const Analytics = () => {
184
+ const gaTrackingId = connectConfig.gaTrackingId;
185
+ const [{ analytics }] = useAcceptedCookies();
186
+ const useAnalytics = gaTrackingId && analytics;
187
+ useEffect(() => {
188
+ if (useAnalytics) {
189
+ const script = document.createElement("script");
190
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${gaTrackingId}`;
191
+ script.async = true;
192
+ document.head.appendChild(script);
193
+ window.dataLayer = window.dataLayer || [];
194
+ gtag("js", /* @__PURE__ */ new Date());
195
+ gtag("config", gaTrackingId);
196
+ return () => {
197
+ document.head.removeChild(script);
198
+ };
199
+ }
200
+ }, [useAnalytics, gaTrackingId]);
201
+ return null;
202
+ };
203
+ //#endregion
204
+ //#region src/services/openpanel/client.ts
205
+ let client;
196
206
  /**
197
- * Import one file and resolve once the flow truly ends. Completion is
198
- * tracked through the progress stages, NOT the returned promise — the
199
- * package-discovery retry path resolves early and keeps reporting through
200
- * the callback. `resolveConflict: "duplicate"` is always passed: duplicates
201
- * were resolved in the picker (rename), so the conflict stage must never
202
- * fire — this also covers a duplicate appearing between pre-check and import.
207
+ * Returns the cached `OpenPanel` singleton, building it on first call.
208
+ *
209
+ * When `config.clientId` is empty it returns `undefined` without importing
210
+ * `@openpanel/web`, so the bundler can tree-shake the SDK out of production.
203
211
  */
204
- function importOne(row, driveId, documentTypes) {
205
- return new Promise((resolve) => {
206
- let settled = false;
207
- const settle = (fileNode) => {
208
- if (settled) return;
209
- settled = true;
210
- resolve(fileNode);
211
- };
212
- addFileWithProgress(row.file, driveId, row.name, void 0, (progress) => {
213
- patchOpenFileUpload(row.id, {
214
- status: mapProgressStageToStatus(progress.stage),
215
- progress: progress.progress,
216
- ...progress.error ? { errorDetails: progress.error } : {},
217
- ...progress.documentType ? { documentType: progress.documentType } : {},
218
- ...progress.fileNode ? { fileNode: progress.fileNode } : {}
219
- });
220
- switch (progress.stage) {
221
- case "complete":
222
- settle(progress.fileNode);
223
- break;
224
- case "failed":
225
- case "unsupported-document-type":
226
- settle(void 0);
227
- break;
228
- default: break;
229
- }
230
- }, documentTypes, "duplicate").catch(() => {
231
- settle(void 0);
232
- });
212
+ async function getOpenPanelClient(config) {
213
+ if (!config.clientId) return;
214
+ if (client) return client;
215
+ const { OpenPanel: OpenPanelClass } = await import("@openpanel/web");
216
+ client = new OpenPanelClass({
217
+ clientId: config.clientId,
218
+ ...config.apiUrl ? { apiUrl: config.apiUrl } : {},
219
+ trackScreenViews: true,
220
+ trackOutgoingLinks: true
233
221
  });
222
+ client.setGlobalProperties({ app: "connect" });
223
+ drainOpenPanelBuffer(client);
224
+ return client;
234
225
  }
235
226
  /**
236
- * Run a confirmed batch of imports into a drive, feeding the floating upload
237
- * panel. Rows appear immediately (queued), files import sequentially, and a
238
- * single-file batch that succeeds opens its document in the editor.
227
+ * Clears the cached singleton and the pre-init buffer. Call on consent
228
+ * revocation or during tests; buffered events are discarded, not replayed.
239
229
  */
240
- function runOpenFileImports(args) {
241
- const { driveId, imports, documentTypes } = args;
242
- if (imports.length === 0) return importChain;
243
- const rows = imports.map(({ file, name }) => ({
244
- id: `open-file-${++uploadCounter}`,
245
- file,
246
- name
247
- }));
248
- addOpenFileUploads(rows.map((row) => ({
249
- id: row.id,
250
- fileName: row.name,
251
- fileSize: formatFileSize(row.file.size),
252
- status: "pending",
253
- progress: 0
254
- })));
255
- const run = async () => {
256
- const results = [];
257
- for (const row of rows) results.push(await importOne(row, driveId, documentTypes));
258
- if (rows.length === 1 && results[0]) setSelectedNode(results[0]);
259
- };
260
- importChain = importChain.then(run, run);
261
- return importChain;
262
- }
263
- //#endregion
264
- //#region src/components/migration-status.ts
265
- let current = null;
266
- const listeners = /* @__PURE__ */ new Set();
267
- function setMigrationStatus(status) {
268
- current = status;
269
- for (const l of listeners) l();
270
- }
271
- function getMigrationStatus() {
272
- return current;
273
- }
274
- function subscribeMigrationStatus(cb) {
275
- listeners.add(cb);
276
- return () => {
277
- listeners.delete(cb);
278
- };
230
+ function resetOpenPanelClient() {
231
+ client = void 0;
232
+ clearOpenPanelBuffer();
279
233
  }
280
234
  //#endregion
281
- //#region src/utils/pglite-migration.ts
282
- const BACKUP_PREFIX = "ph-pglite-backup::";
283
- const BACKUP_INDEX_KEY = "ph:pglite-backups";
284
- const BACKUP_RETENTION_MS = 720 * 60 * 60 * 1e3;
285
- function deleteIdb(name, timeoutMs = 15e3) {
286
- return new Promise((resolve, reject) => {
287
- const req = indexedDB.deleteDatabase(name);
288
- let settled = false;
289
- const done = (fn) => {
290
- if (settled) return;
291
- settled = true;
292
- fn();
293
- };
294
- const timer = setTimeout(() => done(() => reject(/* @__PURE__ */ new Error(`IDB delete timed out for ${name} after ${timeoutMs}ms`))), timeoutMs);
295
- req.onsuccess = () => done(() => {
296
- clearTimeout(timer);
297
- resolve();
298
- });
299
- req.onerror = () => done(() => {
300
- clearTimeout(timer);
301
- reject(idbError(req, `IDB delete failed for ${name}`));
302
- });
303
- });
304
- }
305
- function readBackupIndex() {
306
- try {
307
- const raw = localStorage.getItem(BACKUP_INDEX_KEY);
308
- if (!raw) return [];
309
- const parsed = JSON.parse(raw);
310
- return Array.isArray(parsed) ? parsed : [];
311
- } catch {
312
- return [];
235
+ //#region src/services/openpanel/events.json
236
+ var events_default = [
237
+ {
238
+ "documentType": "powerhouse/document-drive",
239
+ "actionTypes": [
240
+ "ADD_FOLDER",
241
+ "DELETE_NODE",
242
+ "ADD_FILE",
243
+ "UPDATE_FILE",
244
+ "COPY_NODE",
245
+ "MOVE_NODE",
246
+ "SET_DRIVE_NAME",
247
+ "SET_DRIVE_ICON"
248
+ ]
249
+ },
250
+ {
251
+ "documentType": "powerhouse/document-model",
252
+ "actionTypes": [
253
+ "SET_MODEL_NAME",
254
+ "SET_MODEL_ID",
255
+ "ADD_MODULE",
256
+ "DELETE_MODULE",
257
+ "ADD_OPERATION",
258
+ "DELETE_OPERATION"
259
+ ]
260
+ },
261
+ {
262
+ "documentType": "powerhouse/reactor-drive",
263
+ "actionTypes": [
264
+ "ADD_FOLDER",
265
+ "REMOVE_FOLDER",
266
+ "UPDATE_FOLDER"
267
+ ]
313
268
  }
314
- }
315
- function writeBackupIndex(records) {
316
- try {
317
- localStorage.setItem(BACKUP_INDEX_KEY, JSON.stringify(records));
318
- } catch {}
319
- }
320
- function registerBackup(rec) {
321
- writeBackupIndex([...readBackupIndex(), rec]);
322
- }
323
- function forgetBackup(name) {
324
- writeBackupIndex(readBackupIndex().filter((r) => r.name !== name));
325
- }
326
- async function janitor() {
327
- const cutoff = Date.now() - BACKUP_RETENTION_MS;
328
- const keep = [];
329
- for (const rec of readBackupIndex()) {
330
- if (rec.createdAt >= cutoff) {
331
- keep.push(rec);
332
- continue;
269
+ ];
270
+ //#endregion
271
+ //#region src/services/openpanel/events.ts
272
+ const EventMappingSchema = z.object({
273
+ documentType: z.string().min(1),
274
+ actionTypes: z.array(z.string().min(1)).min(1),
275
+ alias: z.string().optional()
276
+ }).strict();
277
+ const EventMappingsSchema = z.array(EventMappingSchema).readonly();
278
+ /**
279
+ * Parses and validates the event mapping table, returning the validated array
280
+ * and an O(1) lookup map. Accepts a `raw` override for tests. Throws on zod
281
+ * failures and on duplicate `(documentType, actionType)` pairs.
282
+ */
283
+ function loadEvents(raw = events_default) {
284
+ const result = EventMappingsSchema.safeParse(raw);
285
+ if (!result.success) throw new Error(`Invalid OpenPanel events.json: ${result.error.message}`);
286
+ const mappings = result.data;
287
+ const lookupMap = /* @__PURE__ */ new Map();
288
+ for (const mapping of mappings) {
289
+ let inner = lookupMap.get(mapping.documentType);
290
+ if (!inner) {
291
+ inner = /* @__PURE__ */ new Map();
292
+ lookupMap.set(mapping.documentType, inner);
333
293
  }
334
- try {
335
- await deleteIdb(rec.name);
336
- } catch (err) {
337
- console.warn(`Failed to delete expired pglite backup ${rec.name}`, err);
338
- keep.push(rec);
294
+ for (const actionType of mapping.actionTypes) {
295
+ if (inner.has(actionType)) throw new Error(`Duplicate OpenPanel event mapping for ${mapping.documentType}/${actionType}`);
296
+ inner.set(actionType, mapping);
339
297
  }
340
298
  }
341
- writeBackupIndex(keep);
342
- }
343
- function createPersistentBackup() {
344
299
  return {
345
- snapshot: async (idbName) => {
346
- const backupName = `${BACKUP_PREFIX}${idbName}::${(/* @__PURE__ */ new Date()).toISOString()}`;
347
- await cloneFileData(idbName, backupName);
348
- registerBackup({
349
- name: backupName,
350
- idbName,
351
- createdAt: Date.now()
352
- });
353
- return backupName;
354
- },
355
- rollback: async (handle, idbName, cause) => {
356
- const backupName = handle;
357
- try {
358
- await cloneFileData(backupName, idbName);
359
- } catch (rollbackErr) {
360
- console.error(`PGlite migration rollback failed for ${idbName}; backup ${backupName} retained. Primary migration error:`, cause, "Rollback error:", rollbackErr);
361
- return;
362
- }
363
- await deleteIdb(backupName).catch(() => void 0);
364
- forgetBackup(backupName);
365
- },
366
- discard: async (handle) => {
367
- const backupName = handle;
368
- await deleteIdb(backupName).catch(() => void 0);
369
- forgetBackup(backupName);
370
- },
371
- commit: () => Promise.resolve()
300
+ mappings,
301
+ lookupMap
372
302
  };
373
303
  }
374
- async function migrateAllLocked() {
375
- const backup = createPersistentBackup();
376
- try {
377
- for (const idbName of PRIMARY_IDB_NAMES) await migrateIdb(idbName, (phase) => setMigrationStatus({
378
- idbName,
379
- phase
380
- }), backup);
381
- } finally {
382
- setMigrationStatus(null);
383
- }
304
+ const { mappings: defaultMappings, lookupMap: defaultLookupMap } = loadEvents();
305
+ /** Validated mapping array from the bundled events.json. */
306
+ const eventMappings = defaultMappings;
307
+ /** O(1) lookup map: documentType → actionType → mapping. */
308
+ const eventLookupMap = defaultLookupMap;
309
+ /**
310
+ * Normalizes a document type into a safe event-name segment: lowercased, `/`
311
+ * replaced with `.`, and any character outside `[a-z0-9._-]` stripped.
312
+ * e.g. `powerhouse/document-drive` → `powerhouse.document-drive`.
313
+ */
314
+ function normalize(documentType) {
315
+ return documentType.toLowerCase().replace(/\//g, ".").replace(/[^a-z0-9._-]/g, "");
384
316
  }
385
- async function migrateAllIfNeeded() {
386
- janitor().catch((err) => console.warn("PGlite backup janitor failed", err));
387
- if (!(typeof navigator !== "undefined" && "locks" in navigator && typeof navigator.locks.request === "function")) {
388
- await migrateAllLocked();
389
- return;
390
- }
391
- await navigator.locks.request("ph-pglite-migration", { mode: "exclusive" }, async () => {
392
- await migrateAllLocked();
393
- });
317
+ /**
318
+ * Derives the OpenPanel event name: `mapping.alias` when present, otherwise
319
+ * `${normalize(documentType)}.${actionType.toLowerCase()}`.
320
+ */
321
+ function deriveEventName(mapping, op) {
322
+ if (mapping.alias) return mapping.alias;
323
+ return `${normalize(op.context.documentType)}.${op.operation.action.type.toLowerCase()}`;
394
324
  }
395
- //#endregion
396
- //#region src/hooks/useFailedInstallations.ts
397
- const emptyArray$1 = [];
398
- function useFailedInstallations() {
399
- const discoveryService = usePackageDiscoveryService();
400
- return useSyncExternalStore((cb) => discoveryService?.subscribeFailed(cb) ?? (() => {}), () => discoveryService?.getFailedInstallations() ?? emptyArray$1);
325
+ /**
326
+ * Builds the default properties attached to every OpenPanel event. The `app`
327
+ * property is stamped globally by the client, not here.
328
+ */
329
+ function buildDefaultProperties(op) {
330
+ return {
331
+ documentType: op.context.documentType,
332
+ actionType: op.operation.action.type,
333
+ documentId: op.context.documentId,
334
+ scope: op.context.scope,
335
+ branch: op.context.branch
336
+ };
401
337
  }
402
338
  //#endregion
403
- //#region src/hooks/useInitSentry.ts
404
- const logger$2 = childLogger(["sentry"]);
405
- let clientStarted = false;
406
- async function getSentry() {
407
- return await import("@sentry/react");
408
- }
409
- async function initSentry() {
410
- const Sentry = await getSentry();
411
- const integrations = [
412
- Sentry.httpClientIntegration(),
413
- Sentry.extraErrorDataIntegration({ depth: 5 }),
414
- Sentry.replayIntegration(),
415
- Sentry.captureConsoleIntegration({ levels: ["error"] })
416
- ];
417
- if (connectConfig.sentry.tracing) integrations.push(Sentry.reactRouterV6BrowserTracingIntegration({
418
- useEffect: React.useEffect,
419
- useLocation,
420
- useNavigationType,
421
- createRoutesFromChildren,
422
- matchRoutes
423
- }));
424
- Sentry.init({
425
- release: connectConfig.sentry.release,
426
- dsn: connectConfig.sentry.dsn ?? void 0,
427
- environment: connectConfig.sentry.env,
428
- integrations,
429
- ignoreErrors: [
430
- "User is not allowed to create files",
431
- "User is not allowed to move documents",
432
- "The user aborted a request."
433
- ],
434
- sendDefaultPii: true,
435
- tracesSampleRate: 1,
436
- replaysSessionSampleRate: .1,
437
- replaysOnErrorSampleRate: 1,
438
- beforeSend(event, hint) {
439
- const error = hint.originalException;
440
- if (error instanceof TypeError && error.message.includes("Failed to fetch") && !navigator.onLine) return null;
441
- return event;
442
- }
443
- });
444
- setSentryModule(Sentry);
445
- clientStarted = true;
446
- }
447
- async function closeClient() {
448
- if (!clientStarted) return;
449
- const sentry = await getSentry();
450
- setSentryModule(void 0);
451
- return sentry.getClient()?.close();
452
- }
453
- function useInitSentry() {
454
- const [acceptedCookies] = useAcceptedCookies();
455
- const { analytics } = acceptedCookies;
456
- useEffect(() => {
457
- if (!analytics) {
458
- closeClient().catch((error) => logger$2.error("@error", error));
459
- return;
339
+ //#region src/services/openpanel/processor.ts
340
+ /**
341
+ * Read-only reactor `IProcessor` that translates matching document operations
342
+ * into OpenPanel analytics events. Operations whose `(documentType, actionType)`
343
+ * pair is absent from the lookup map are skipped; per-operation errors are
344
+ * routed to `onError` and never thrown back to the manager.
345
+ */
346
+ var OpenPanelProcessor = class {
347
+ constructor(client, lookupMap, onError = (err) => console.warn("[OpenPanelProcessor] Error tracking event:", err)) {
348
+ this.client = client;
349
+ this.lookupMap = lookupMap;
350
+ this.onError = onError;
351
+ }
352
+ async onOperations(operations) {
353
+ for (const op of operations) {
354
+ const inner = this.lookupMap.get(op.context.documentType);
355
+ if (!inner) continue;
356
+ const mapping = inner.get(op.operation.action.type);
357
+ if (!mapping) continue;
358
+ const name = deriveEventName(mapping, op);
359
+ const payload = buildDefaultProperties(op);
360
+ try {
361
+ await this.client.track(name, payload);
362
+ } catch (err) {
363
+ this.onError(err, op);
364
+ }
460
365
  }
461
- if (clientStarted || connectConfig.sentry.dsn === null || connectConfig.sentry.dsn === void 0 || connectConfig.sentry.dsn === "") return;
462
- initSentry().catch((error) => logger$2.error("@error", error));
463
- }, [analytics]);
464
- }
366
+ }
367
+ onDisconnect() {
368
+ this.client.flush?.();
369
+ return Promise.resolve();
370
+ }
371
+ };
465
372
  //#endregion
466
- //#region src/services/openpanel/buffer.ts
467
- const buffer = [];
468
- let errorHandler;
469
- function defaultOnError(err) {
470
- console.warn("[useOpenPanelAnalytics] track failed:", err);
471
- }
373
+ //#region src/services/openpanel/factory.ts
472
374
  /**
473
- * Forward a single event to the active client, swallowing any throw.
474
- *
475
- * The outer try/catch handles synchronous throws from `client.track`; the
476
- * inner `.catch()` handles rejected promises. Both are needed because
477
- * `Promise.resolve(x).catch()` cannot catch a throw from evaluating `x`.
375
+ * Returns a `ProcessorFactory` that, for each drive, creates a single
376
+ * `OpenPanelProcessor` record whose `filter.documentType` is the union of all
377
+ * document types present in the event mapping table.
478
378
  */
479
- function forward(client, name, props) {
480
- try {
481
- Promise.resolve(client.track(name, props)).catch((err) => (errorHandler ?? defaultOnError)(err));
482
- } catch (err) {
483
- (errorHandler ?? defaultOnError)(err);
484
- }
379
+ function createOpenPanelProcessorFactory(config) {
380
+ return function openPanelProcessorFactory(_driveHeader) {
381
+ const documentType = Array.from(config.events.lookupMap.keys());
382
+ return [{
383
+ processor: new OpenPanelProcessor(config.client, config.events.lookupMap, config.onError),
384
+ filter: { documentType },
385
+ startFrom: config.startFrom ?? "current"
386
+ }];
387
+ };
485
388
  }
389
+ //#endregion
390
+ //#region src/components/openpanel-traits.ts
486
391
  /**
487
- * Drain the pre-init buffer in FIFO order through `client.track`, then forward
488
- * subsequent events directly. Called once from `getOpenPanelClient()`.
392
+ * Builds the OpenPanel identity traits from a renown User.
393
+ *
394
+ * Canonical trait schema (shared with Renown and Vetra — each app sends every
395
+ * field it has data for, under these names):
396
+ * - `address`, `did`, `networkId`, `chainId`, `caip2` — wallet/chain facts.
397
+ * - `ensName`, `ensAvatar` — raw ENS facts.
398
+ * - `username`, `userImage`, `profileDocumentId`, `profileCreatedAt` — raw
399
+ * Renown profile facts.
400
+ * - `accountType` — wallet-adapter concept; only Renown has it.
401
+ *
402
+ * Rules:
403
+ * - `credential` (contains a JWT) is **never** forwarded.
404
+ * - Traits carry raw facts only; resolved presentation values (avatar,
405
+ * display name) go in OpenPanel's native identify fields — see
406
+ * {@link buildIdentifyPayload}.
407
+ * - Optional fields (`ens`, `profile`) are only included when their value is
408
+ * non-nullish (guards against `null` from `RenownProfile` fields as well
409
+ * as plain `undefined`).
410
+ *
411
+ * Mirror the Sentry pattern in `store/user.ts`: destructure off `credential`
412
+ * first, then assemble the trait payload from the remainder.
489
413
  */
490
- function drainOpenPanelBuffer(client, onError) {
491
- errorHandler = onError;
492
- const pending = buffer.splice(0, buffer.length);
493
- for (const [name, props] of pending) forward(client, name, props);
414
+ function buildTraits(user) {
415
+ const { credential: _credential, ...rest } = user;
416
+ const traits = {
417
+ address: rest.address,
418
+ did: rest.did,
419
+ networkId: rest.networkId,
420
+ chainId: rest.chainId,
421
+ caip2: `${rest.networkId}:${rest.chainId}`
422
+ };
423
+ if (rest.ens?.name != null) traits.ensName = rest.ens.name;
424
+ if (rest.ens?.avatarUrl != null) traits.ensAvatar = rest.ens.avatarUrl;
425
+ if (rest.profile?.username != null) traits.username = rest.profile.username;
426
+ if (rest.profile?.userImage != null) traits.userImage = rest.profile.userImage;
427
+ if (rest.profile?.documentId != null) traits.profileDocumentId = rest.profile.documentId;
428
+ if (rest.profile?.createdAt != null) traits.profileCreatedAt = rest.profile.createdAt;
429
+ return traits;
494
430
  }
495
431
  /**
496
- * Clear the buffer and reset the active client. Buffered pre-consent events
497
- * are discarded so they are not replayed if the user later re-accepts.
432
+ * Builds the full `identify()` payload:
433
+ * - `profileId` is the wallet address — the cross-app profile key shared with
434
+ * Renown and Vetra.
435
+ * - `avatar` / `firstName` are OpenPanel's native profile fields (they drive
436
+ * the picture and name in the dashboard UI), resolved as
437
+ * `userImage ?? ensAvatar` and `ensName ?? username`.
438
+ * - `properties` are the raw traits from {@link buildTraits}.
498
439
  */
499
- function clearOpenPanelBuffer() {
500
- buffer.length = 0;
501
- errorHandler = void 0;
440
+ function buildIdentifyPayload(user) {
441
+ const avatar = user.profile?.userImage ?? user.ens?.avatarUrl;
442
+ const firstName = user.ens?.name ?? user.profile?.username;
443
+ return {
444
+ profileId: user.address,
445
+ ...avatar != null ? { avatar } : {},
446
+ ...firstName != null ? { firstName } : {},
447
+ properties: buildTraits(user)
448
+ };
502
449
  }
503
450
  //#endregion
504
- //#region src/hooks/usePendingInstallations.ts
505
- const emptyArray = [];
506
- function usePendingInstallations() {
507
- const discoveryService = usePackageDiscoveryService();
508
- const cacheRef = useRef(emptyArray);
509
- return useSyncExternalStore((cb) => discoveryService?.subscribePending(cb) ?? (() => {}), useCallback(() => {
510
- if (!discoveryService) return emptyArray;
511
- const next = discoveryService.getPendingInstallations() ?? emptyArray;
512
- const prev = cacheRef.current;
451
+ //#region src/components/openpanel.tsx
452
+ /**
453
+ * `OpenPanel` is a `null`-rendering component that manages the full lifecycle
454
+ * of the OpenPanel analytics subsystem.
455
+ *
456
+ * Gates:
457
+ * - `useAcceptedCookies().analytics === true` — the existing analytics cookie.
458
+ * - `connectConfig.openPanel.clientId` non-empty — no client ID → no-op.
459
+ *
460
+ * Behaviour when both gates pass:
461
+ * 1. Lazy-imports `@openpanel/web` and builds the singleton via
462
+ * `getOpenPanelClient(connectConfig.openPanel)`.
463
+ * 2. Exposes the client on `window.openPanel` for the card-6
464
+ * `useOpenPanelAnalytics()` hook to consume.
465
+ * 3. If `connectConfig.openPanel.trackOperations`, registers the processor
466
+ * factory against `processorManager` so document operations are forwarded.
467
+ * 4. Calls `client.identify()` when the renown user transitions from
468
+ * `undefined` → defined (login). The profile ID is the wallet address —
469
+ * the cross-app key shared with Renown and Vetra.
470
+ * 5. Calls `client.clear()` when the user transitions from defined →
471
+ * `undefined` (logout).
472
+ *
473
+ * Teardown (unmount **or** consent revocation):
474
+ * - `unregisterFactory("openpanel")` to remove the processor.
475
+ * - `resetOpenPanelClient()` to clear the singleton.
476
+ * - `window.openPanel` is cleared.
477
+ *
478
+ * Mirrors the Sentry pattern in `store/user.ts` for login/logout detection.
479
+ * Analytics failures are caught and forwarded to `console.warn` — they must
480
+ * never throw into the application.
481
+ *
482
+ * Mounted next to `<Analytics />` in `app.tsx`.
483
+ */
484
+ function OpenPanel() {
485
+ const [{ analytics }] = useAcceptedCookies();
486
+ const user = useUser();
487
+ const module = useReactorClientModule();
488
+ const reactorClientModule = module?.kind === "browser" ? module : void 0;
489
+ const [client, setClient] = useState();
490
+ /**
491
+ * Tracks the previous `user` value so we can detect the two transitions:
492
+ * undefined → defined (login)
493
+ * defined → undefined (logout)
494
+ */
495
+ const prevUserRef = useRef(void 0);
496
+ const enabled = analytics && !!connectConfig.openPanel.clientId;
497
+ const processorManager = reactorClientModule?.reactorModule?.processorManager;
498
+ useEffect(() => {
499
+ if (!enabled) return;
500
+ let cancelled = false;
501
+ const isCancelled = () => cancelled;
502
+ const setup = async () => {
503
+ let builtClient;
504
+ try {
505
+ builtClient = await getOpenPanelClient(connectConfig.openPanel);
506
+ } catch (err) {
507
+ console.warn("[OpenPanel] Failed to build client:", err);
508
+ return;
509
+ }
510
+ if (isCancelled() || !builtClient) return;
511
+ setClient(builtClient);
512
+ window.openPanel = builtClient;
513
+ if (connectConfig.openPanel.trackOperations && processorManager) try {
514
+ await processorManager.registerFactory("openpanel", createOpenPanelProcessorFactory({
515
+ client: builtClient,
516
+ events: {
517
+ mappings: eventMappings,
518
+ lookupMap: eventLookupMap
519
+ },
520
+ startFrom: "current"
521
+ }));
522
+ if (isCancelled()) await processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister factory after cancellation:", err));
523
+ } catch (err) {
524
+ console.warn("[OpenPanel] Failed to register processor factory:", err);
525
+ }
526
+ };
527
+ setup();
528
+ return () => {
529
+ cancelled = true;
530
+ prevUserRef.current = void 0;
531
+ if (connectConfig.openPanel.trackOperations && processorManager) processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister processor factory:", err));
532
+ resetOpenPanelClient();
533
+ window.openPanel = void 0;
534
+ setClient(void 0);
535
+ };
536
+ }, [enabled, processorManager]);
537
+ useEffect(() => {
538
+ if (!enabled || !client) return;
539
+ const prev = prevUserRef.current;
540
+ prevUserRef.current = user;
541
+ if (!prev && user) try {
542
+ client.identify(buildIdentifyPayload(user));
543
+ } catch (err) {
544
+ console.warn("[OpenPanel] Failed to identify user:", err);
545
+ }
546
+ else if (prev && !user) try {
547
+ client.clear();
548
+ } catch (err) {
549
+ console.warn("[OpenPanel] Failed to clear user:", err);
550
+ }
551
+ }, [
552
+ client,
553
+ user,
554
+ enabled
555
+ ]);
556
+ return null;
557
+ }
558
+ //#endregion
559
+ //#region src/components/document-editor-container.tsx
560
+ function DocumentEditorContainer() {
561
+ const [selectedDocument] = useSelectedDocument();
562
+ const parentFolder = useNodeParentFolderById(selectedDocument.header.id);
563
+ useMemo(() => {
564
+ return async () => {};
565
+ }, []);
566
+ useCallback(() => {
567
+ setSelectedNode(parentFolder);
568
+ }, [parentFolder]);
569
+ return /* @__PURE__ */ jsx("div", {
570
+ id: "document-editor-container",
571
+ className: "flex-1",
572
+ "data-document-type": selectedDocument.header.documentType,
573
+ children: /* @__PURE__ */ jsx(DocumentEditor, { document: selectedDocument })
574
+ });
575
+ }
576
+ //#endregion
577
+ //#region src/components/editor-loader.tsx
578
+ function EditorLoader(props) {
579
+ const [showLoading, setShowLoading] = useState(false);
580
+ useEffect(() => {
581
+ setTimeout(() => {
582
+ setShowLoading(true);
583
+ }, props.loadingTimeout ?? 200);
584
+ }, [props]);
585
+ if (!showLoading) return null;
586
+ const { customEditorLoader, ...defaultProps } = props;
587
+ if (customEditorLoader) return /* @__PURE__ */ jsx(Fragment, { children: customEditorLoader });
588
+ return /* @__PURE__ */ jsx(DefaultEditorLoader, { ...defaultProps });
589
+ }
590
+ //#endregion
591
+ //#region src/components/app-container.tsx
592
+ function AppContainer() {
593
+ const [selectedDrive] = useSelectedDriveSafe();
594
+ const selectedDocumentId = useSelectedDocumentId();
595
+ const app = useAppModuleById(selectedDrive?.header.meta?.preferredEditor);
596
+ const defaultApp = useDefaultAppModule();
597
+ if (!selectedDrive) return /* @__PURE__ */ jsx(EditorLoader, {});
598
+ const AppComponent = app?.Component ?? defaultApp?.Component ?? GenericDriveExplorer.Component;
599
+ if (!AppComponent) throw new Error("No app component found");
600
+ return /* @__PURE__ */ jsx(ErrorBoundary$1, {
601
+ variant: "detailed",
602
+ resetKeys: [selectedDrive.header.id],
603
+ loggerContext: ["Connect", "App"],
604
+ children: /* @__PURE__ */ jsx(Suspense, {
605
+ fallback: /* @__PURE__ */ jsx(EditorLoader, {}),
606
+ children: /* @__PURE__ */ jsx(DropZoneWrapper, {
607
+ className: "flex h-full flex-col overflow-auto",
608
+ children: /* @__PURE__ */ jsx(AppComponent, { children: selectedDocumentId ? /* @__PURE__ */ jsx(DocumentEditorContainer, {}) : null })
609
+ })
610
+ })
611
+ });
612
+ }
613
+ //#endregion
614
+ //#region src/hooks/useFailedInstallations.ts
615
+ const emptyArray$1 = [];
616
+ function useFailedInstallations() {
617
+ const discoveryService = usePackageDiscoveryService();
618
+ return useSyncExternalStore((cb) => discoveryService?.subscribeFailed(cb) ?? (() => {}), () => discoveryService?.getFailedInstallations() ?? emptyArray$1);
619
+ }
620
+ //#endregion
621
+ //#region src/hooks/useInitSentry.ts
622
+ const logger$1 = childLogger(["sentry"]);
623
+ let clientStarted = false;
624
+ async function getSentry() {
625
+ return await import("@sentry/react");
626
+ }
627
+ async function initSentry() {
628
+ const Sentry = await getSentry();
629
+ const integrations = [
630
+ Sentry.httpClientIntegration(),
631
+ Sentry.extraErrorDataIntegration({ depth: 5 }),
632
+ Sentry.replayIntegration(),
633
+ Sentry.captureConsoleIntegration({ levels: ["error"] })
634
+ ];
635
+ if (connectConfig.sentry.tracing) integrations.push(Sentry.reactRouterV6BrowserTracingIntegration({
636
+ useEffect: React.useEffect,
637
+ useLocation,
638
+ useNavigationType,
639
+ createRoutesFromChildren,
640
+ matchRoutes
641
+ }));
642
+ Sentry.init({
643
+ release: connectConfig.sentry.release,
644
+ dsn: connectConfig.sentry.dsn ?? void 0,
645
+ environment: connectConfig.sentry.env,
646
+ integrations,
647
+ ignoreErrors: [
648
+ "User is not allowed to create files",
649
+ "User is not allowed to move documents",
650
+ "The user aborted a request."
651
+ ],
652
+ sendDefaultPii: true,
653
+ tracesSampleRate: 1,
654
+ replaysSessionSampleRate: .1,
655
+ replaysOnErrorSampleRate: 1,
656
+ beforeSend(event, hint) {
657
+ const error = hint.originalException;
658
+ if (error instanceof TypeError && error.message.includes("Failed to fetch") && !navigator.onLine) return null;
659
+ return event;
660
+ }
661
+ });
662
+ setSentryModule(Sentry);
663
+ clientStarted = true;
664
+ }
665
+ async function closeClient() {
666
+ if (!clientStarted) return;
667
+ const sentry = await getSentry();
668
+ setSentryModule(void 0);
669
+ return sentry.getClient()?.close();
670
+ }
671
+ function useInitSentry() {
672
+ const [acceptedCookies] = useAcceptedCookies();
673
+ const { analytics } = acceptedCookies;
674
+ useEffect(() => {
675
+ if (!analytics) {
676
+ closeClient().catch((error) => logger$1.error("@error", error));
677
+ return;
678
+ }
679
+ if (clientStarted || connectConfig.sentry.dsn === null || connectConfig.sentry.dsn === void 0 || connectConfig.sentry.dsn === "") return;
680
+ initSentry().catch((error) => logger$1.error("@error", error));
681
+ }, [analytics]);
682
+ }
683
+ //#endregion
684
+ //#region src/hooks/usePendingInstallations.ts
685
+ const emptyArray = [];
686
+ function usePendingInstallations() {
687
+ const discoveryService = usePackageDiscoveryService();
688
+ const cacheRef = useRef(emptyArray);
689
+ return useSyncExternalStore((cb) => discoveryService?.subscribePending(cb) ?? (() => {}), useCallback(() => {
690
+ if (!discoveryService) return emptyArray;
691
+ const next = discoveryService.getPendingInstallations() ?? emptyArray;
692
+ const prev = cacheRef.current;
513
693
  if (next === prev || next.length === prev.length && next.every((p, i) => p === prev[i])) return prev;
514
694
  cacheRef.current = next;
515
695
  return next;
@@ -536,666 +716,356 @@ const useUndoRedoShortcuts = (props) => {
536
716
  }, {}, [canRedo, redo]);
537
717
  };
538
718
  //#endregion
539
- //#region src/components/app-skeleton.tsx
540
- const LOADER_DELAY = 250;
541
- const PHASE_LABEL = {
542
- clone: "Backing up local database…",
543
- dump: "Exporting data from previous version…",
544
- restore: "Restoring data into the new database…"
545
- };
546
- const MigrationOverlay = () => {
547
- const status = useSyncExternalStore(subscribeMigrationStatus, getMigrationStatus, () => null);
548
- if (!status) return null;
549
- return /* @__PURE__ */ jsx("div", {
550
- className: "absolute inset-0 z-20 flex items-center justify-center",
551
- children: /* @__PURE__ */ jsxs("div", {
552
- className: "rounded-lg bg-card/90 px-6 py-4 text-sm text-foreground shadow-lg",
553
- children: [/* @__PURE__ */ jsx("div", {
554
- className: "font-medium",
555
- children: "Upgrading local database…"
556
- }), /* @__PURE__ */ jsx("div", {
557
- className: "text-foreground",
558
- children: PHASE_LABEL[status.phase]
559
- })]
560
- })
561
- });
562
- };
563
- const Loader = ({ delay = LOADER_DELAY }) => {
564
- const isSSR = typeof window === "undefined";
565
- const showInitialLoader = typeof document !== "undefined" && document.body.getAttribute("data-show-loader") === "true";
566
- if (!isSSR) initTheme();
567
- const [showLoading, setShowLoading] = useState(!delay || showInitialLoader);
568
- useEffect(() => {
569
- const id = setTimeout(() => {
570
- setShowLoading(true);
571
- }, delay);
572
- return () => clearTimeout(id);
573
- }, []);
574
- return /* @__PURE__ */ jsxs("div", {
575
- className: `skeleton-loader absolute inset-0 z-10 flex items-center justify-center ${showLoading ? "" : "hidden"}`,
576
- children: [/* @__PURE__ */ jsx("div", {
577
- className: "animate-pulse overflow-hidden rounded-full shadow-lg",
578
- children: /* @__PURE__ */ jsx(LogoAnimation, {})
579
- }), isSSR ? /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `setTimeout(() => {
580
- document.querySelector('.skeleton-loader')?.classList.remove('hidden');
581
- document.body.setAttribute('data-show-loader', 'true');
582
- }, ${delay})` } }) : null]
583
- });
719
+ //#region src/context/sentry-provider.tsx
720
+ const SentryProvider = ({ children }) => {
721
+ useInitSentry();
722
+ return children;
584
723
  };
585
- const AppSkeleton = (props) => {
586
- const isSSR = typeof window === "undefined";
587
- const isHomeScreen = !isSSR && window.location.pathname === getBasePath();
588
- return /* @__PURE__ */ jsxs("div", {
589
- className: "flex h-screen overflow-hidden bg-background",
590
- children: [
591
- !(!isSSR && getIsEmbedded()) && /* @__PURE__ */ jsx(ConnectSidebar, {
592
- className: "animate-pulse",
593
- onLogin: void 0,
594
- onDisconnect: void 0,
595
- onClickSettings: void 0,
596
- address: void 0
597
- }),
598
- /* @__PURE__ */ jsx(HomeScreen, {
599
- containerClassName: isSSR || !isHomeScreen ? "home-screen hidden" : void 0,
600
- children: props.children ?? null
601
- }),
602
- isSSR ? /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `
603
- const baseEl = document.querySelector('base');
604
- const href = baseEl?.getAttribute('href');
605
- const basePath = href || '/';
606
- if (window.location.pathname === basePath) {
607
- document.querySelector('.home-screen')?.classList.remove('hidden')
608
- }` } }) : null,
609
- !props.children ? /* @__PURE__ */ jsx(Loader, {}) : null,
610
- /* @__PURE__ */ jsx(MigrationOverlay, {})
611
- ]
612
- });
724
+ //#endregion
725
+ //#region src/utils/document-editor-debug-tools.ts
726
+ var DocumentEditorDebugTools = class {
727
+ document;
728
+ operations = [];
729
+ constructor(document) {
730
+ if (document) this.document = document;
731
+ }
732
+ operationsToTableObject(operations) {
733
+ return operations.map((op) => ({
734
+ ...op,
735
+ input: JSON.stringify(op.action.input)
736
+ }));
737
+ }
738
+ setDocument(document) {
739
+ this.document = document;
740
+ }
741
+ getDocument() {
742
+ return this.document;
743
+ }
744
+ getOperations() {
745
+ return this.operations;
746
+ }
747
+ pushOperation(operation) {
748
+ this.operations.push(operation);
749
+ }
750
+ operationsTable() {
751
+ if (!this.document) console.warn("No document");
752
+ const ops = Object.values(this.document?.operations || {}).filter((array) => array !== void 0).flatMap((array) => array).sort((a, b) => a.index - b.index);
753
+ console.table(this.operationsToTableObject(ops));
754
+ }
755
+ scopeOperationsTable(scope) {
756
+ if (!this.document) console.warn("No document");
757
+ const ops = this.document?.operations[scope] || [];
758
+ console.table(this.operationsToTableObject(ops));
759
+ }
760
+ operationsLog() {
761
+ console.log(this.operations);
762
+ }
763
+ operationsLogTable() {
764
+ console.table(this.operationsToTableObject(this.operations));
765
+ }
766
+ clear() {
767
+ this.operations = [];
768
+ this.document = void 0;
769
+ }
613
770
  };
614
771
  //#endregion
615
- //#region src/components/error-boundary.tsx
616
- const logger$1 = childLogger(["Connect"]);
617
- function hasCustomFallback(props) {
618
- return "fallbackRender" in props;
772
+ //#region src/utils/launch-queue.ts
773
+ let pendingImportFiles = [];
774
+ const pendingListeners = /* @__PURE__ */ new Set();
775
+ function notifyPendingListeners() {
776
+ for (const listener of pendingListeners) listener();
619
777
  }
620
- function SilentFallback() {
621
- return null;
778
+ function getPendingImportFiles() {
779
+ return pendingImportFiles;
622
780
  }
623
- /**
624
- * Simple text-based fallback component.
625
- * Displays a centered message when an error occurs.
626
- */
627
- function TextFallback({ message = "Something went wrong" }) {
628
- return /* @__PURE__ */ jsx("div", {
629
- className: "text-center wrap-break-word text-foreground",
630
- children: message
631
- });
781
+ /** Stable identity of a pending file (also the picker's pre-check cache key). */
782
+ function pendingFileKey(file) {
783
+ return `${file.name}|${file.size}|${file.lastModified}`;
632
784
  }
633
- /**
634
- * Detailed fallback component showing full error information.
635
- * Displays error message with a collapsible details section.
636
- * Useful for development and debugging purposes.
637
- */
638
- function DetailedFallback({ error, resetErrorBoundary }) {
639
- const errorMessage = error instanceof Error ? error.message : String(error);
640
- const errorDetails = error instanceof Error ? error.stack ?? String(error.cause) : JSON.stringify(error, null, 2);
641
- return /* @__PURE__ */ jsx("div", {
642
- className: "z-10 mx-auto flex max-w-[80%] flex-1 items-center justify-center p-6",
643
- children: /* @__PURE__ */ jsxs("div", {
644
- className: "w-full max-w-lg rounded-lg border border-border bg-card p-6 text-foreground shadow-sm",
645
- children: [
646
- /* @__PURE__ */ jsxs("div", {
647
- className: "mb-3 flex items-center gap-2",
648
- children: [/* @__PURE__ */ jsx(Icon, {
649
- name: "Error",
650
- className: "size-5 shrink-0 text-destructive"
651
- }), /* @__PURE__ */ jsx("h1", {
652
- className: "text-lg font-semibold text-foreground",
653
- children: "Something went wrong"
654
- })]
655
- }),
656
- /* @__PURE__ */ jsx("p", {
657
- className: "mb-4 text-sm wrap-break-word text-foreground",
658
- children: errorMessage
659
- }),
660
- errorDetails !== "{}" && /* @__PURE__ */ jsxs("details", {
661
- className: "group",
662
- children: [/* @__PURE__ */ jsx("summary", {
663
- className: "cursor-pointer text-sm font-medium text-foreground underline select-none hover:hover-effect",
664
- children: "Show details"
665
- }), /* @__PURE__ */ jsx("pre", {
666
- className: "mt-2 max-h-48 overflow-auto rounded-sm bg-muted p-3 text-xs text-foreground",
667
- children: errorDetails
668
- })]
669
- }),
670
- /* @__PURE__ */ jsx(PowerhouseButton, {
671
- type: "button",
672
- onClick: resetErrorBoundary,
673
- className: "mt-4 px-3 py-1.5 text-base font-medium",
674
- children: "Try again"
675
- })
676
- ]
677
- })
785
+ function addPendingImportFiles(files) {
786
+ const seen = new Set(pendingImportFiles.map(pendingFileKey));
787
+ const fresh = files.filter((file) => {
788
+ const key = pendingFileKey(file);
789
+ if (seen.has(key)) return false;
790
+ seen.add(key);
791
+ return true;
678
792
  });
793
+ if (fresh.length === 0) return;
794
+ pendingImportFiles = [...pendingImportFiles, ...fresh];
795
+ notifyPendingListeners();
679
796
  }
680
- /**
681
- * Centered error message fallback component.
682
- * Displays the error message in a centered, full-size container.
683
- * Suitable for use as a custom fallbackRender when you need simple error display.
684
- */
685
- function CenteredErrorMessage({ error }) {
686
- return /* @__PURE__ */ jsx("div", {
687
- className: "flex size-full items-center justify-center p-6",
688
- children: /* @__PURE__ */ jsx("h3", {
689
- className: "max-w-lg min-w-0 text-center text-lg font-semibold wrap-break-word text-foreground",
690
- children: error instanceof Error ? error.message : String(error)
691
- })
692
- });
797
+ function clearPendingImportFiles() {
798
+ if (pendingImportFiles.length === 0) return;
799
+ pendingImportFiles = [];
800
+ notifyPendingListeners();
693
801
  }
694
- function getFallbackComponent(variant, fallbackMessage) {
695
- switch (variant) {
696
- case "silent": return SilentFallback;
697
- case "text": return () => /* @__PURE__ */ jsx(TextFallback, { message: fallbackMessage });
698
- case "detailed": return DetailedFallback;
699
- default: return CenteredErrorMessage;
802
+ function subscribePendingImportFiles(cb) {
803
+ pendingListeners.add(cb);
804
+ return () => {
805
+ pendingListeners.delete(cb);
806
+ };
807
+ }
808
+ async function handleLaunchParams(params) {
809
+ const handles = params.files.filter((handle) => handle.kind === "file");
810
+ if (handles.length === 0) return;
811
+ const files = [];
812
+ for (const handle of handles) try {
813
+ files.push(await handle.getFile());
814
+ } catch (error) {
815
+ console.error("Failed to read launched file:", handle.name, error);
700
816
  }
817
+ if (files.length === 0) return;
818
+ addPendingImportFiles(files);
819
+ showPHModal({ type: "openFileDocuments" });
820
+ }
821
+ function maybeReopenImportModal() {
822
+ if (getPendingImportFiles().length === 0) return;
823
+ if (window.ph?.modal !== void 0) return;
824
+ showPHModal({ type: "openFileDocuments" });
701
825
  }
826
+ let initialized = false;
702
827
  /**
703
- * Unified error boundary component for catching and handling React errors.
704
- *
705
- * Built on top of `react-error-boundary`, this component provides:
706
- * - Automatic error logging with configurable context
707
- * - Multiple fallback UI variants (silent, text, detailed)
708
- * - Support for custom fallback render functions
709
- * - Reset capability via `resetKeys`
710
- *
711
- * @example
712
- * // Using a predefined variant
713
- * <ErrorBoundary variant="detailed" loggerContext={["Connect", "Editor"]}>
714
- * <EditorComponent />
715
- * </ErrorBoundary>
716
- *
717
- * @example
718
- * // Using a custom message with the text variant
719
- * <ErrorBoundary variant="text" fallbackMessage="Failed to load drives">
720
- * <DrivesList />
721
- * </ErrorBoundary>
722
- *
723
- * @example
724
- * // Using a custom fallback render
725
- * <ErrorBoundary
726
- * fallbackRender={CenteredErrorMessage}
727
- * onError={(error) => trackError(error)}
728
- * >
729
- * <MyComponent />
730
- * </ErrorBoundary>
731
- *
732
- * @example
733
- * // Silent mode for modals (renders null on error)
734
- * <ErrorBoundary variant="silent" loggerContext={["Connect", "Modals"]}>
735
- * <ModalContent />
736
- * </ErrorBoundary>
828
+ * Register the launchQueue consumer that receives files opened with Connect
829
+ * through the OS file association. Idempotent (safe under StrictMode double
830
+ * effects) and a no-op where the File Handling API doesn't exist — Firefox,
831
+ * Safari, and any context without an installed PWA manifest simply never
832
+ * deliver launches.
737
833
  */
738
- function ErrorBoundary$1(props) {
739
- const { children, onError, resetKeys, loggerContext } = props;
740
- const handleError = (error, info) => {
741
- if (error.message.includes("Failed to fetch dynamically imported module") && error.message.includes("node_modules/.vite")) {
742
- console.error(error);
743
- console.log("Outdated chunk detected, reloading page...");
744
- window.location.reload();
745
- return;
746
- }
747
- (loggerContext ? childLogger(loggerContext) : logger$1).error("@error @info", error, info);
748
- onError?.(error, info);
749
- };
750
- return /* @__PURE__ */ jsx(ErrorBoundary, {
751
- fallbackRender: hasCustomFallback(props) ? props.fallbackRender : getFallbackComponent(props.variant, props.fallbackMessage),
752
- onError: handleError,
753
- resetKeys,
754
- children
834
+ function initLaunchQueueFileHandling() {
835
+ if (initialized) return;
836
+ initialized = true;
837
+ window.addEventListener("ph:modalUpdated", maybeReopenImportModal);
838
+ window.launchQueue?.setConsumer((params) => {
839
+ handleLaunchParams(params);
755
840
  });
756
841
  }
757
842
  //#endregion
758
- //#region src/components/analytics.tsx
759
- function gtag(...args) {
760
- window.dataLayer?.push(...args);
843
+ //#region src/utils/open-file-uploads.ts
844
+ let uploads = [];
845
+ const listeners = /* @__PURE__ */ new Set();
846
+ function notifyUploadListeners() {
847
+ for (const listener of listeners) listener();
761
848
  }
762
- const Analytics = () => {
763
- const gaTrackingId = connectConfig.gaTrackingId;
764
- const [{ analytics }] = useAcceptedCookies();
765
- const useAnalytics = gaTrackingId && analytics;
766
- useEffect(() => {
767
- if (useAnalytics) {
768
- const script = document.createElement("script");
769
- script.src = `https://www.googletagmanager.com/gtag/js?id=${gaTrackingId}`;
770
- script.async = true;
771
- document.head.appendChild(script);
772
- window.dataLayer = window.dataLayer || [];
773
- gtag("js", /* @__PURE__ */ new Date());
774
- gtag("config", gaTrackingId);
775
- return () => {
776
- document.head.removeChild(script);
777
- };
778
- }
779
- }, [useAnalytics, gaTrackingId]);
780
- return null;
781
- };
782
- //#endregion
783
- //#region src/services/openpanel/client.ts
784
- let client;
785
- /**
786
- * Returns the cached `OpenPanel` singleton, building it on first call.
787
- *
788
- * When `config.clientId` is empty it returns `undefined` without importing
789
- * `@openpanel/web`, so the bundler can tree-shake the SDK out of production.
790
- */
791
- async function getOpenPanelClient(config) {
792
- if (!config.clientId) return;
793
- if (client) return client;
794
- const { OpenPanel: OpenPanelClass } = await import("@openpanel/web");
795
- client = new OpenPanelClass({
796
- clientId: config.clientId,
797
- ...config.apiUrl ? { apiUrl: config.apiUrl } : {},
798
- trackScreenViews: true,
799
- trackOutgoingLinks: true
800
- });
801
- client.setGlobalProperties({ app: "connect" });
802
- drainOpenPanelBuffer(client);
803
- return client;
804
- }
805
- /**
806
- * Clears the cached singleton and the pre-init buffer. Call on consent
807
- * revocation or during tests; buffered events are discarded, not replayed.
808
- */
809
- function resetOpenPanelClient() {
810
- client = void 0;
811
- clearOpenPanelBuffer();
849
+ function getOpenFileUploads() {
850
+ return uploads;
812
851
  }
813
- //#endregion
814
- //#region src/services/openpanel/events.json
815
- var events_default = [
816
- {
817
- "documentType": "powerhouse/document-drive",
818
- "actionTypes": [
819
- "ADD_FOLDER",
820
- "DELETE_NODE",
821
- "ADD_FILE",
822
- "UPDATE_FILE",
823
- "COPY_NODE",
824
- "MOVE_NODE",
825
- "SET_DRIVE_NAME",
826
- "SET_DRIVE_ICON"
827
- ]
828
- },
829
- {
830
- "documentType": "powerhouse/document-model",
831
- "actionTypes": [
832
- "SET_MODEL_NAME",
833
- "SET_MODEL_ID",
834
- "ADD_MODULE",
835
- "DELETE_MODULE",
836
- "ADD_OPERATION",
837
- "DELETE_OPERATION"
838
- ]
839
- },
840
- {
841
- "documentType": "powerhouse/reactor-drive",
842
- "actionTypes": [
843
- "ADD_FOLDER",
844
- "REMOVE_FOLDER",
845
- "UPDATE_FOLDER"
846
- ]
847
- }
848
- ];
849
- //#endregion
850
- //#region src/services/openpanel/events.ts
851
- const EventMappingSchema = z.object({
852
- documentType: z.string().min(1),
853
- actionTypes: z.array(z.string().min(1)).min(1),
854
- alias: z.string().optional()
855
- }).strict();
856
- const EventMappingsSchema = z.array(EventMappingSchema).readonly();
857
- /**
858
- * Parses and validates the event mapping table, returning the validated array
859
- * and an O(1) lookup map. Accepts a `raw` override for tests. Throws on zod
860
- * failures and on duplicate `(documentType, actionType)` pairs.
861
- */
862
- function loadEvents(raw = events_default) {
863
- const result = EventMappingsSchema.safeParse(raw);
864
- if (!result.success) throw new Error(`Invalid OpenPanel events.json: ${result.error.message}`);
865
- const mappings = result.data;
866
- const lookupMap = /* @__PURE__ */ new Map();
867
- for (const mapping of mappings) {
868
- let inner = lookupMap.get(mapping.documentType);
869
- if (!inner) {
870
- inner = /* @__PURE__ */ new Map();
871
- lookupMap.set(mapping.documentType, inner);
872
- }
873
- for (const actionType of mapping.actionTypes) {
874
- if (inner.has(actionType)) throw new Error(`Duplicate OpenPanel event mapping for ${mapping.documentType}/${actionType}`);
875
- inner.set(actionType, mapping);
876
- }
877
- }
878
- return {
879
- mappings,
880
- lookupMap
852
+ function subscribeOpenFileUploads(cb) {
853
+ listeners.add(cb);
854
+ return () => {
855
+ listeners.delete(cb);
881
856
  };
882
857
  }
883
- const { mappings: defaultMappings, lookupMap: defaultLookupMap } = loadEvents();
884
- /** Validated mapping array from the bundled events.json. */
885
- const eventMappings = defaultMappings;
886
- /** O(1) lookup map: documentType → actionType → mapping. */
887
- const eventLookupMap = defaultLookupMap;
888
- /**
889
- * Normalizes a document type into a safe event-name segment: lowercased, `/`
890
- * replaced with `.`, and any character outside `[a-z0-9._-]` stripped.
891
- * e.g. `powerhouse/document-drive` → `powerhouse.document-drive`.
892
- */
893
- function normalize(documentType) {
894
- return documentType.toLowerCase().replace(/\//g, ".").replace(/[^a-z0-9._-]/g, "");
895
- }
896
- /**
897
- * Derives the OpenPanel event name: `mapping.alias` when present, otherwise
898
- * `${normalize(documentType)}.${actionType.toLowerCase()}`.
899
- */
900
- function deriveEventName(mapping, op) {
901
- if (mapping.alias) return mapping.alias;
902
- return `${normalize(op.context.documentType)}.${op.operation.action.type.toLowerCase()}`;
858
+ function removeOpenFileUpload(id) {
859
+ const next = uploads.filter((upload) => upload.id !== id);
860
+ if (next.length === uploads.length) return;
861
+ uploads = next;
862
+ notifyUploadListeners();
903
863
  }
904
- /**
905
- * Builds the default properties attached to every OpenPanel event. The `app`
906
- * property is stamped globally by the client, not here.
907
- */
908
- function buildDefaultProperties(op) {
909
- return {
910
- documentType: op.context.documentType,
911
- actionType: op.operation.action.type,
912
- documentId: op.context.documentId,
913
- scope: op.context.scope,
914
- branch: op.context.branch
915
- };
864
+ function clearOpenFileUploads() {
865
+ if (uploads.length === 0) return;
866
+ uploads = [];
867
+ notifyUploadListeners();
916
868
  }
917
- //#endregion
918
- //#region src/services/openpanel/processor.ts
919
- /**
920
- * Read-only reactor `IProcessor` that translates matching document operations
921
- * into OpenPanel analytics events. Operations whose `(documentType, actionType)`
922
- * pair is absent from the lookup map are skipped; per-operation errors are
923
- * routed to `onError` and never thrown back to the manager.
924
- */
925
- var OpenPanelProcessor = class {
926
- constructor(client, lookupMap, onError = (err) => console.warn("[OpenPanelProcessor] Error tracking event:", err)) {
927
- this.client = client;
928
- this.lookupMap = lookupMap;
929
- this.onError = onError;
930
- }
931
- async onOperations(operations) {
932
- for (const op of operations) {
933
- const inner = this.lookupMap.get(op.context.documentType);
934
- if (!inner) continue;
935
- const mapping = inner.get(op.operation.action.type);
936
- if (!mapping) continue;
937
- const name = deriveEventName(mapping, op);
938
- const payload = buildDefaultProperties(op);
939
- try {
940
- await this.client.track(name, payload);
941
- } catch (err) {
942
- this.onError(err, op);
943
- }
944
- }
945
- }
946
- onDisconnect() {
947
- this.client.flush?.();
948
- return Promise.resolve();
949
- }
950
- };
951
- //#endregion
952
- //#region src/services/openpanel/factory.ts
953
- /**
954
- * Returns a `ProcessorFactory` that, for each drive, creates a single
955
- * `OpenPanelProcessor` record whose `filter.documentType` is the union of all
956
- * document types present in the event mapping table.
957
- */
958
- function createOpenPanelProcessorFactory(config) {
959
- return function openPanelProcessorFactory(_driveHeader) {
960
- const documentType = Array.from(config.events.lookupMap.keys());
961
- return [{
962
- processor: new OpenPanelProcessor(config.client, config.events.lookupMap, config.onError),
963
- filter: { documentType },
964
- startFrom: config.startFrom ?? "current"
965
- }];
966
- };
869
+ function addOpenFileUploads(rows) {
870
+ uploads = [...uploads, ...rows];
871
+ notifyUploadListeners();
967
872
  }
968
- //#endregion
969
- //#region src/components/openpanel-traits.ts
970
- /**
971
- * Builds the OpenPanel identity traits from a renown User.
972
- *
973
- * Canonical trait schema (shared with Renown and Vetra — each app sends every
974
- * field it has data for, under these names):
975
- * - `address`, `did`, `networkId`, `chainId`, `caip2` — wallet/chain facts.
976
- * - `ensName`, `ensAvatar` — raw ENS facts.
977
- * - `username`, `userImage`, `profileDocumentId`, `profileCreatedAt` — raw
978
- * Renown profile facts.
979
- * - `accountType` — wallet-adapter concept; only Renown has it.
980
- *
981
- * Rules:
982
- * - `credential` (contains a JWT) is **never** forwarded.
983
- * - Traits carry raw facts only; resolved presentation values (avatar,
984
- * display name) go in OpenPanel's native identify fields — see
985
- * {@link buildIdentifyPayload}.
986
- * - Optional fields (`ens`, `profile`) are only included when their value is
987
- * non-nullish (guards against `null` from `RenownProfile` fields as well
988
- * as plain `undefined`).
989
- *
990
- * Mirror the Sentry pattern in `store/user.ts`: destructure off `credential`
991
- * first, then assemble the trait payload from the remainder.
992
- */
993
- function buildTraits(user) {
994
- const { credential: _credential, ...rest } = user;
995
- const traits = {
996
- address: rest.address,
997
- did: rest.did,
998
- networkId: rest.networkId,
999
- chainId: rest.chainId,
1000
- caip2: `${rest.networkId}:${rest.chainId}`
1001
- };
1002
- if (rest.ens?.name != null) traits.ensName = rest.ens.name;
1003
- if (rest.ens?.avatarUrl != null) traits.ensAvatar = rest.ens.avatarUrl;
1004
- if (rest.profile?.username != null) traits.username = rest.profile.username;
1005
- if (rest.profile?.userImage != null) traits.userImage = rest.profile.userImage;
1006
- if (rest.profile?.documentId != null) traits.profileDocumentId = rest.profile.documentId;
1007
- if (rest.profile?.createdAt != null) traits.profileCreatedAt = rest.profile.createdAt;
1008
- return traits;
873
+ function patchOpenFileUpload(id, patch) {
874
+ uploads = uploads.map((upload) => upload.id === id ? {
875
+ ...upload,
876
+ ...patch
877
+ } : upload);
878
+ notifyUploadListeners();
1009
879
  }
880
+ let uploadCounter = 0;
881
+ let importChain = Promise.resolve();
1010
882
  /**
1011
- * Builds the full `identify()` payload:
1012
- * - `profileId` is the wallet address — the cross-app profile key shared with
1013
- * Renown and Vetra.
1014
- * - `avatar` / `firstName` are OpenPanel's native profile fields (they drive
1015
- * the picture and name in the dashboard UI), resolved as
1016
- * `userImage ?? ensAvatar` and `ensName ?? username`.
1017
- * - `properties` are the raw traits from {@link buildTraits}.
883
+ * Import one file and resolve once the flow truly ends. Completion is
884
+ * tracked through the progress stages, NOT the returned promise — the
885
+ * package-discovery retry path resolves early and keeps reporting through
886
+ * the callback. `resolveConflict: "duplicate"` is always passed: duplicates
887
+ * were resolved in the picker (rename), so the conflict stage must never
888
+ * fire — this also covers a duplicate appearing between pre-check and import.
1018
889
  */
1019
- function buildIdentifyPayload(user) {
1020
- const avatar = user.profile?.userImage ?? user.ens?.avatarUrl;
1021
- const firstName = user.ens?.name ?? user.profile?.username;
1022
- return {
1023
- profileId: user.address,
1024
- ...avatar != null ? { avatar } : {},
1025
- ...firstName != null ? { firstName } : {},
1026
- properties: buildTraits(user)
1027
- };
890
+ function importOne(row, driveId, documentTypes) {
891
+ return new Promise((resolve) => {
892
+ let settled = false;
893
+ const settle = (fileNode) => {
894
+ if (settled) return;
895
+ settled = true;
896
+ resolve(fileNode);
897
+ };
898
+ addFileWithProgress(row.file, driveId, row.name, void 0, (progress) => {
899
+ patchOpenFileUpload(row.id, {
900
+ status: mapProgressStageToStatus(progress.stage),
901
+ progress: progress.progress,
902
+ ...progress.error ? { errorDetails: progress.error } : {},
903
+ ...progress.documentType ? { documentType: progress.documentType } : {},
904
+ ...progress.fileNode ? { fileNode: progress.fileNode } : {}
905
+ });
906
+ switch (progress.stage) {
907
+ case "complete":
908
+ settle(progress.fileNode);
909
+ break;
910
+ case "failed":
911
+ case "unsupported-document-type":
912
+ settle(void 0);
913
+ break;
914
+ default: break;
915
+ }
916
+ }, documentTypes, "duplicate").catch(() => {
917
+ settle(void 0);
918
+ });
919
+ });
1028
920
  }
1029
- //#endregion
1030
- //#region src/components/openpanel.tsx
1031
921
  /**
1032
- * `OpenPanel` is a `null`-rendering component that manages the full lifecycle
1033
- * of the OpenPanel analytics subsystem.
1034
- *
1035
- * Gates:
1036
- * - `useAcceptedCookies().analytics === true` — the existing analytics cookie.
1037
- * - `connectConfig.openPanel.clientId` non-empty — no client ID → no-op.
1038
- *
1039
- * Behaviour when both gates pass:
1040
- * 1. Lazy-imports `@openpanel/web` and builds the singleton via
1041
- * `getOpenPanelClient(connectConfig.openPanel)`.
1042
- * 2. Exposes the client on `window.openPanel` for the card-6
1043
- * `useOpenPanelAnalytics()` hook to consume.
1044
- * 3. If `connectConfig.openPanel.trackOperations`, registers the processor
1045
- * factory against `processorManager` so document operations are forwarded.
1046
- * 4. Calls `client.identify()` when the renown user transitions from
1047
- * `undefined` → defined (login). The profile ID is the wallet address —
1048
- * the cross-app key shared with Renown and Vetra.
1049
- * 5. Calls `client.clear()` when the user transitions from defined →
1050
- * `undefined` (logout).
1051
- *
1052
- * Teardown (unmount **or** consent revocation):
1053
- * - `unregisterFactory("openpanel")` to remove the processor.
1054
- * - `resetOpenPanelClient()` to clear the singleton.
1055
- * - `window.openPanel` is cleared.
1056
- *
1057
- * Mirrors the Sentry pattern in `store/user.ts` for login/logout detection.
1058
- * Analytics failures are caught and forwarded to `console.warn` — they must
1059
- * never throw into the application.
1060
- *
1061
- * Mounted next to `<Analytics />` in `app.tsx`.
922
+ * Run a confirmed batch of imports into a drive, feeding the floating upload
923
+ * panel. Rows appear immediately (queued), files import sequentially, and a
924
+ * single-file batch that succeeds opens its document in the editor.
1062
925
  */
1063
- function OpenPanel() {
1064
- const [{ analytics }] = useAcceptedCookies();
1065
- const user = useUser();
1066
- const module = useReactorClientModule();
1067
- const reactorClientModule = module?.kind === "browser" ? module : void 0;
1068
- const [client, setClient] = useState();
1069
- /**
1070
- * Tracks the previous `user` value so we can detect the two transitions:
1071
- * undefined → defined (login)
1072
- * defined → undefined (logout)
1073
- */
1074
- const prevUserRef = useRef(void 0);
1075
- const enabled = analytics && !!connectConfig.openPanel.clientId;
1076
- const processorManager = reactorClientModule?.reactorModule?.processorManager;
1077
- useEffect(() => {
1078
- if (!enabled) return;
1079
- let cancelled = false;
1080
- const isCancelled = () => cancelled;
1081
- const setup = async () => {
1082
- let builtClient;
1083
- try {
1084
- builtClient = await getOpenPanelClient(connectConfig.openPanel);
1085
- } catch (err) {
1086
- console.warn("[OpenPanel] Failed to build client:", err);
1087
- return;
1088
- }
1089
- if (isCancelled() || !builtClient) return;
1090
- setClient(builtClient);
1091
- window.openPanel = builtClient;
1092
- if (connectConfig.openPanel.trackOperations && processorManager) try {
1093
- await processorManager.registerFactory("openpanel", createOpenPanelProcessorFactory({
1094
- client: builtClient,
1095
- events: {
1096
- mappings: eventMappings,
1097
- lookupMap: eventLookupMap
1098
- },
1099
- startFrom: "current"
1100
- }));
1101
- if (isCancelled()) await processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister factory after cancellation:", err));
1102
- } catch (err) {
1103
- console.warn("[OpenPanel] Failed to register processor factory:", err);
1104
- }
1105
- };
1106
- setup();
1107
- return () => {
1108
- cancelled = true;
1109
- prevUserRef.current = void 0;
1110
- if (connectConfig.openPanel.trackOperations && processorManager) processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister processor factory:", err));
1111
- resetOpenPanelClient();
1112
- window.openPanel = void 0;
1113
- setClient(void 0);
926
+ function runOpenFileImports(args) {
927
+ const { driveId, imports, documentTypes } = args;
928
+ if (imports.length === 0) return importChain;
929
+ const rows = imports.map(({ file, name }) => ({
930
+ id: `open-file-${++uploadCounter}`,
931
+ file,
932
+ name
933
+ }));
934
+ addOpenFileUploads(rows.map((row) => ({
935
+ id: row.id,
936
+ fileName: row.name,
937
+ fileSize: formatFileSize(row.file.size),
938
+ status: "pending",
939
+ progress: 0
940
+ })));
941
+ const run = async () => {
942
+ const results = [];
943
+ for (const row of rows) results.push(await importOne(row, driveId, documentTypes));
944
+ if (rows.length === 1 && results[0]) setSelectedNode(results[0]);
945
+ };
946
+ importChain = importChain.then(run, run);
947
+ return importChain;
948
+ }
949
+ //#endregion
950
+ //#region src/utils/openUrl.ts
951
+ const openBrowserUrl = (url) => window.open(url, "_blank");
952
+ const openUrl = openBrowserUrl;
953
+ //#endregion
954
+ //#region src/utils/pglite-migration.ts
955
+ const BACKUP_PREFIX = "ph-pglite-backup::";
956
+ const BACKUP_INDEX_KEY = "ph:pglite-backups";
957
+ const BACKUP_RETENTION_MS = 720 * 60 * 60 * 1e3;
958
+ function deleteIdb(name, timeoutMs = 15e3) {
959
+ return new Promise((resolve, reject) => {
960
+ const req = indexedDB.deleteDatabase(name);
961
+ let settled = false;
962
+ const done = (fn) => {
963
+ if (settled) return;
964
+ settled = true;
965
+ fn();
1114
966
  };
1115
- }, [enabled, processorManager]);
1116
- useEffect(() => {
1117
- if (!enabled || !client) return;
1118
- const prev = prevUserRef.current;
1119
- prevUserRef.current = user;
1120
- if (!prev && user) try {
1121
- client.identify(buildIdentifyPayload(user));
1122
- } catch (err) {
1123
- console.warn("[OpenPanel] Failed to identify user:", err);
967
+ const timer = setTimeout(() => done(() => reject(/* @__PURE__ */ new Error(`IDB delete timed out for ${name} after ${timeoutMs}ms`))), timeoutMs);
968
+ req.onsuccess = () => done(() => {
969
+ clearTimeout(timer);
970
+ resolve();
971
+ });
972
+ req.onerror = () => done(() => {
973
+ clearTimeout(timer);
974
+ reject(idbError(req, `IDB delete failed for ${name}`));
975
+ });
976
+ });
977
+ }
978
+ function readBackupIndex() {
979
+ try {
980
+ const raw = localStorage.getItem(BACKUP_INDEX_KEY);
981
+ if (!raw) return [];
982
+ const parsed = JSON.parse(raw);
983
+ return Array.isArray(parsed) ? parsed : [];
984
+ } catch {
985
+ return [];
986
+ }
987
+ }
988
+ function writeBackupIndex(records) {
989
+ try {
990
+ localStorage.setItem(BACKUP_INDEX_KEY, JSON.stringify(records));
991
+ } catch {}
992
+ }
993
+ function registerBackup(rec) {
994
+ writeBackupIndex([...readBackupIndex(), rec]);
995
+ }
996
+ function forgetBackup(name) {
997
+ writeBackupIndex(readBackupIndex().filter((r) => r.name !== name));
998
+ }
999
+ async function janitor() {
1000
+ const cutoff = Date.now() - BACKUP_RETENTION_MS;
1001
+ const keep = [];
1002
+ for (const rec of readBackupIndex()) {
1003
+ if (rec.createdAt >= cutoff) {
1004
+ keep.push(rec);
1005
+ continue;
1124
1006
  }
1125
- else if (prev && !user) try {
1126
- client.clear();
1007
+ try {
1008
+ await deleteIdb(rec.name);
1127
1009
  } catch (err) {
1128
- console.warn("[OpenPanel] Failed to clear user:", err);
1010
+ console.warn(`Failed to delete expired pglite backup ${rec.name}`, err);
1011
+ keep.push(rec);
1129
1012
  }
1130
- }, [
1131
- client,
1132
- user,
1133
- enabled
1134
- ]);
1135
- return null;
1013
+ }
1014
+ writeBackupIndex(keep);
1136
1015
  }
1137
- //#endregion
1138
- //#region src/components/document-editor-container.tsx
1139
- function DocumentEditorContainer() {
1140
- const [selectedDocument] = useSelectedDocument();
1141
- const parentFolder = useNodeParentFolderById(selectedDocument.header.id);
1142
- useMemo(() => {
1143
- return async () => {};
1144
- }, []);
1145
- useCallback(() => {
1146
- setSelectedNode(parentFolder);
1147
- }, [parentFolder]);
1148
- return /* @__PURE__ */ jsx("div", {
1149
- id: "document-editor-container",
1150
- className: "flex-1",
1151
- "data-document-type": selectedDocument.header.documentType,
1152
- children: /* @__PURE__ */ jsx(DocumentEditor, { document: selectedDocument })
1153
- });
1016
+ function createPersistentBackup() {
1017
+ return {
1018
+ snapshot: async (idbName) => {
1019
+ const backupName = `${BACKUP_PREFIX}${idbName}::${(/* @__PURE__ */ new Date()).toISOString()}`;
1020
+ await cloneFileData(idbName, backupName);
1021
+ registerBackup({
1022
+ name: backupName,
1023
+ idbName,
1024
+ createdAt: Date.now()
1025
+ });
1026
+ return backupName;
1027
+ },
1028
+ rollback: async (handle, idbName, cause) => {
1029
+ const backupName = handle;
1030
+ try {
1031
+ await cloneFileData(backupName, idbName);
1032
+ } catch (rollbackErr) {
1033
+ console.error(`PGlite migration rollback failed for ${idbName}; backup ${backupName} retained. Primary migration error:`, cause, "Rollback error:", rollbackErr);
1034
+ return;
1035
+ }
1036
+ await deleteIdb(backupName).catch(() => void 0);
1037
+ forgetBackup(backupName);
1038
+ },
1039
+ discard: async (handle) => {
1040
+ const backupName = handle;
1041
+ await deleteIdb(backupName).catch(() => void 0);
1042
+ forgetBackup(backupName);
1043
+ },
1044
+ commit: () => Promise.resolve()
1045
+ };
1154
1046
  }
1155
- //#endregion
1156
- //#region src/components/editor-loader.tsx
1157
- function EditorLoader(props) {
1158
- const [showLoading, setShowLoading] = useState(false);
1159
- useEffect(() => {
1160
- setTimeout(() => {
1161
- setShowLoading(true);
1162
- }, props.loadingTimeout ?? 200);
1163
- }, [props]);
1164
- if (!showLoading) return null;
1165
- const { customEditorLoader, ...defaultProps } = props;
1166
- if (customEditorLoader) return /* @__PURE__ */ jsx(Fragment, { children: customEditorLoader });
1167
- return /* @__PURE__ */ jsx(DefaultEditorLoader, { ...defaultProps });
1047
+ async function migrateAllLocked() {
1048
+ const backup = createPersistentBackup();
1049
+ try {
1050
+ for (const idbName of PRIMARY_IDB_NAMES) await migrateIdb(idbName, (phase) => setMigrationStatus({
1051
+ idbName,
1052
+ phase
1053
+ }), backup);
1054
+ } finally {
1055
+ setMigrationStatus(null);
1056
+ }
1168
1057
  }
1169
- //#endregion
1170
- //#region src/components/app-container.tsx
1171
- function AppContainer() {
1172
- const [selectedDrive] = useSelectedDriveSafe();
1173
- const selectedDocumentId = useSelectedDocumentId();
1174
- const app = useAppModuleById(selectedDrive?.header.meta?.preferredEditor);
1175
- const defaultApp = useDefaultAppModule();
1176
- if (!selectedDrive) return /* @__PURE__ */ jsx(EditorLoader, {});
1177
- const AppComponent = app?.Component ?? defaultApp?.Component ?? GenericDriveExplorer.Component;
1178
- if (!AppComponent) throw new Error("No app component found");
1179
- return /* @__PURE__ */ jsx(ErrorBoundary$1, {
1180
- variant: "detailed",
1181
- resetKeys: [selectedDrive.header.id],
1182
- loggerContext: ["Connect", "App"],
1183
- children: /* @__PURE__ */ jsx(Suspense, {
1184
- fallback: /* @__PURE__ */ jsx(EditorLoader, {}),
1185
- children: /* @__PURE__ */ jsx(DropZoneWrapper, {
1186
- className: "flex h-full flex-col overflow-auto",
1187
- children: /* @__PURE__ */ jsx(AppComponent, { children: selectedDocumentId ? /* @__PURE__ */ jsx(DocumentEditorContainer, {}) : null })
1188
- })
1189
- })
1058
+ async function migrateAllIfNeeded() {
1059
+ janitor().catch((err) => console.warn("PGlite backup janitor failed", err));
1060
+ if (!(typeof navigator !== "undefined" && "locks" in navigator && typeof navigator.locks.request === "function")) {
1061
+ await migrateAllLocked();
1062
+ return;
1063
+ }
1064
+ await navigator.locks.request("ph-pglite-migration", { mode: "exclusive" }, async () => {
1065
+ await migrateAllLocked();
1190
1066
  });
1191
1067
  }
1192
1068
  //#endregion
1193
- //#region src/context/sentry-provider.tsx
1194
- const SentryProvider = ({ children }) => {
1195
- useInitSentry();
1196
- return children;
1197
- };
1198
- //#endregion
1199
1069
  //#region src/components/reactor-chat.tsx
1200
1070
  /**
1201
1071
  * Resolves the reactor tool descriptors for the chat agent from the
@@ -1533,6 +1403,20 @@ const App = () => {
1533
1403
  ] }) });
1534
1404
  };
1535
1405
  //#endregion
1406
+ //#region src/components/dots-loader.tsx
1407
+ const DotsLoader = () => {
1408
+ const [dots, setDots] = useState(0);
1409
+ useEffect(() => {
1410
+ const id = setInterval(() => {
1411
+ setDots((dots) => dots === 3 ? 0 : dots + 1);
1412
+ }, 200);
1413
+ return () => {
1414
+ clearInterval(id);
1415
+ };
1416
+ }, []);
1417
+ return /* @__PURE__ */ jsx("span", { children: new Array(dots).fill(".").join("") });
1418
+ };
1419
+ //#endregion
1536
1420
  //#region src/components/drive-icon.tsx
1537
1421
  function DriveIcon({ drive }) {
1538
1422
  const remotes = useSyncList();
@@ -1597,6 +1481,9 @@ function EditorError({ title, children }) {
1597
1481
  })
1598
1482
  });
1599
1483
  }
1484
+ function defaultScope(scopes) {
1485
+ return scopes.includes("global") ? "global" : scopes[0];
1486
+ }
1600
1487
  function OpenPackageManagerButton() {
1601
1488
  return /* @__PURE__ */ jsx(PowerhouseButton, {
1602
1489
  className: "px-3 py-1.5 text-base font-medium",
@@ -1616,10 +1503,30 @@ const DocumentEditor = (props) => {
1616
1503
  const documentName = document?.header.name ?? void 0;
1617
1504
  const documentType = document?.header.documentType ?? void 0;
1618
1505
  const preferredEditor = document?.header.meta?.preferredEditor ?? void 0;
1619
- const { globalOperations, localOperations, isLoading: isLoadingOperations, refetch: refetchOperations } = useDocumentOperations(documentId);
1506
+ const scopes = useMemo(() => {
1507
+ const keys = Object.keys(document?.header.revision ?? {});
1508
+ return keys.length > 0 ? keys : ["global"];
1509
+ }, [document?.header.revision]);
1510
+ const [operationScope, setOperationScope] = useState(() => defaultScope(scopes));
1511
+ const selectedScope = scopes.includes(operationScope) ? operationScope : defaultScope(scopes);
1512
+ const { operations: historyOperations, isLoading: isLoadingHistory, hasNextPage: historyHasNextPage, fetchNextPage: fetchNextHistoryPage, error: historyError } = useDocumentOperations(documentId, selectedScope, {
1513
+ enabled: revisionHistoryVisible,
1514
+ limit: 500
1515
+ });
1516
+ const { operations: globalOperations, hasNextPage: globalHasNextPage, isLoading: isLoadingGlobal, fetchNextPage: fetchNextGlobalPage, error: globalError } = useDocumentOperations(documentId, "global", {
1517
+ enabled: !!selectedTimelineItem,
1518
+ limit: 500
1519
+ });
1620
1520
  useEffect(() => {
1621
- if (revisionHistoryVisible) refetchOperations();
1622
- }, [revisionHistoryVisible, refetchOperations]);
1521
+ if (!globalHasNextPage || isLoadingGlobal || globalError) return;
1522
+ const handle = window.setTimeout(fetchNextGlobalPage, 0);
1523
+ return () => window.clearTimeout(handle);
1524
+ }, [
1525
+ globalHasNextPage,
1526
+ isLoadingGlobal,
1527
+ fetchNextGlobalPage,
1528
+ globalError
1529
+ ]);
1623
1530
  const globalRevisionNumber = document?.header.revision.global ?? 0;
1624
1531
  const localRevisionNumber = document?.header.revision.local ?? 0;
1625
1532
  const documentModelModule = useDocumentModelModuleById(documentType);
@@ -1725,11 +1632,17 @@ const DocumentEditor = (props) => {
1725
1632
  id: "document-editor-context",
1726
1633
  "data-editor": editorModule.config.id,
1727
1634
  "data-document-type": documentType,
1728
- children: revisionHistoryVisible ? isLoadingOperations ? /* @__PURE__ */ jsx(EditorLoader, { message: "Loading operations" }) : /* @__PURE__ */ jsx(RevisionHistory, {
1635
+ children: revisionHistoryVisible ? /* @__PURE__ */ jsx(RevisionHistory, {
1729
1636
  documentTitle: documentName ?? "",
1730
1637
  documentId: documentId ?? "",
1731
- globalOperations,
1732
- localOperations,
1638
+ operations: historyOperations,
1639
+ isLoading: isLoadingHistory,
1640
+ hasNextPage: historyHasNextPage,
1641
+ onLoadNextPage: fetchNextHistoryPage,
1642
+ error: historyError,
1643
+ scopes,
1644
+ scope: selectedScope,
1645
+ onScopeChange: setOperationScope,
1733
1646
  onClose: () => setRevisionHistoryVisible(false),
1734
1647
  documentState: document.state,
1735
1648
  onCopyState: () => {
@@ -1749,7 +1662,7 @@ const DocumentEditor = (props) => {
1749
1662
  children: !editorError?.error && /* @__PURE__ */ jsx(EditorComponent, {
1750
1663
  context: {
1751
1664
  readMode: !!selectedTimelineItem,
1752
- selectedTimelineRevision: getRevisionFromDate(selectedTimelineItem?.startDate, selectedTimelineItem?.endDate, globalOperations)
1665
+ selectedTimelineRevision: (isLoadingGlobal || globalHasNextPage) && !globalError ? void 0 : getRevisionFromDate(selectedTimelineItem?.startDate, selectedTimelineItem?.endDate, globalOperations)
1753
1666
  },
1754
1667
  documentId: document.header.id
1755
1668
  }, `${editorBundleKey}:${documentId}`)
@@ -1758,6 +1671,32 @@ const DocumentEditor = (props) => {
1758
1671
  });
1759
1672
  };
1760
1673
  //#endregion
1674
+ //#region src/components/footer.tsx
1675
+ const Footer$1 = () => {
1676
+ return /* @__PURE__ */ jsxs(Footer, { children: [
1677
+ /* @__PURE__ */ jsx(FooterLink, {
1678
+ onClick: () => setBannerShown(true),
1679
+ children: /* @__PURE__ */ jsx(Trans, { i18nKey: "footer.cookiePolicy" })
1680
+ }),
1681
+ /* @__PURE__ */ jsx(FooterLink, {
1682
+ onClick: () => showPHModal({ type: "disclaimer" }),
1683
+ children: /* @__PURE__ */ jsx(Trans, { i18nKey: "footer.disclaimer" })
1684
+ }),
1685
+ /* @__PURE__ */ jsx(FooterLink, {
1686
+ id: "ph-logo-link",
1687
+ onClick: () => openUrl("https://www.powerhouse.inc/"),
1688
+ children: /* @__PURE__ */ jsx(Trans, {
1689
+ i18nKey: "footer.builtWith",
1690
+ components: { icon: /* @__PURE__ */ jsx(Icon, {
1691
+ name: "PowerhouseLogoSmall",
1692
+ size: 16,
1693
+ className: "mx-1"
1694
+ }) }
1695
+ })
1696
+ })
1697
+ ] });
1698
+ };
1699
+ //#endregion
1761
1700
  //#region src/components/root.tsx
1762
1701
  function Root() {
1763
1702
  return /* @__PURE__ */ jsx("div", {
@@ -1778,25 +1717,98 @@ function Root() {
1778
1717
  }
1779
1718
  //#endregion
1780
1719
  //#region src/components/use-drive-auth-gate.ts
1781
- function computeAuthGate(isAuthenticated, connectionStates) {
1782
- let sawAuthError = false;
1783
- for (const snap of connectionStates.values()) if (snap.state === "error" && snap.requiresAuth) {
1784
- sawAuthError = true;
1785
- break;
1720
+ /**
1721
+ * Decides the gate for the drive the user selected, and only that drive. A
1722
+ * refusal on any other drive must not hide the rest of Connect.
1723
+ *
1724
+ * Connection states are keyed by remote name, which is a random uuid, so the
1725
+ * caller supplies the remote -> drive mapping.
1726
+ */
1727
+ function computeAuthGate(isAuthenticated, connectionStates, selection) {
1728
+ const { selectedDriveId, remotes } = selection;
1729
+ if (!selectedDriveId) return null;
1730
+ let refused = false;
1731
+ for (const remote of remotes) {
1732
+ if (remote.driveId !== selectedDriveId) continue;
1733
+ const snapshot = connectionStates.get(remote.name);
1734
+ if (snapshot?.state === "error" && snapshot.requiresAuth) {
1735
+ refused = true;
1736
+ break;
1737
+ }
1786
1738
  }
1787
- if (!sawAuthError) return null;
1739
+ if (!refused) return null;
1788
1740
  return isAuthenticated ? "unauthorized" : "login";
1789
1741
  }
1790
- /** Live wrapper over {@link computeAuthGate}. */
1742
+ const hiddenDriveIds = /* @__PURE__ */ new Set();
1743
+ let hiddenDriveIdsSnapshot = [];
1744
+ const hiddenDriveListeners = /* @__PURE__ */ new Set();
1745
+ function publishHiddenDrives() {
1746
+ hiddenDriveIdsSnapshot = [...hiddenDriveIds];
1747
+ for (const listener of [...hiddenDriveListeners]) listener();
1748
+ }
1749
+ function subscribeHiddenDrives(listener) {
1750
+ hiddenDriveListeners.add(listener);
1751
+ return () => {
1752
+ hiddenDriveListeners.delete(listener);
1753
+ };
1754
+ }
1755
+ function getHiddenDriveIds() {
1756
+ return hiddenDriveIdsSnapshot;
1757
+ }
1758
+ /** Hides a drive from Connect's drive lists until the page reloads. */
1759
+ function hideDriveForSession(driveId) {
1760
+ if (hiddenDriveIds.has(driveId)) return;
1761
+ hiddenDriveIds.add(driveId);
1762
+ publishHiddenDrives();
1763
+ }
1764
+ /** Restores every drive hidden this session. */
1765
+ function clearHiddenDrives() {
1766
+ if (hiddenDriveIds.size === 0) return;
1767
+ hiddenDriveIds.clear();
1768
+ publishHiddenDrives();
1769
+ }
1770
+ function useHiddenDriveIds() {
1771
+ return useSyncExternalStore(subscribeHiddenDrives, getHiddenDriveIds, getHiddenDriveIds);
1772
+ }
1773
+ /** `useDrives()` minus the drives dismissed from the auth gate this session. */
1774
+ function useVisibleDrives() {
1775
+ const drives = useDrives();
1776
+ const hidden = useHiddenDriveIds();
1777
+ return useMemo(() => {
1778
+ if (!drives || hidden.length === 0) return drives;
1779
+ return drives.filter((drive) => !hidden.includes(drive.header.id));
1780
+ }, [drives, hidden]);
1781
+ }
1782
+ /** Live wrapper over {@link computeAuthGate}, plus the dismiss action. */
1791
1783
  function useDriveAuthGate() {
1792
1784
  const user = useUser();
1793
1785
  const connectionStates = useConnectionStates();
1794
- return { gate: computeAuthGate(Boolean(user), connectionStates) };
1786
+ const syncManager = useSync();
1787
+ const selectedDriveId = useSelectedDriveId();
1788
+ const remotes = useMemo(() => (syncManager?.list() ?? []).map((remote) => ({
1789
+ name: remote.meta.name,
1790
+ driveId: remote.meta.collectionId.driveId
1791
+ })), [syncManager, connectionStates]);
1792
+ const address = user?.address;
1793
+ useEffect(() => {
1794
+ clearHiddenDrives();
1795
+ }, [address]);
1796
+ const dismiss = useCallback(() => {
1797
+ if (selectedDriveId) hideDriveForSession(selectedDriveId);
1798
+ setSelectedDrive(void 0);
1799
+ }, [selectedDriveId]);
1800
+ return {
1801
+ gate: computeAuthGate(Boolean(user), connectionStates, {
1802
+ selectedDriveId,
1803
+ remotes
1804
+ }),
1805
+ dismiss
1806
+ };
1795
1807
  }
1796
1808
  //#endregion
1797
1809
  //#region src/pages/content.tsx
1798
1810
  function Content() {
1799
- const { gate } = useDriveAuthGate();
1811
+ const { gate, dismiss } = useDriveAuthGate();
1800
1812
  const openLogin = useOpenRenownLogin();
1801
1813
  const [selectedDrive] = useSelectedDriveSafe();
1802
1814
  const selectedFolder = useSelectedFolder();
@@ -1812,7 +1824,8 @@ function Content() {
1812
1824
  children: /* @__PURE__ */ jsx(DriveAuthGate, {
1813
1825
  mode: gate,
1814
1826
  onLogin: openLogin,
1815
- onLogout: () => void logout()
1827
+ onLogout: () => void logout(),
1828
+ onClose: dismiss
1816
1829
  })
1817
1830
  }) : !selectedDocumentId && !selectedDrive && !selectedFolder ? /* @__PURE__ */ jsx(HomeScreenContainer, {}) : /* @__PURE__ */ jsx(AppContainer, {}) });
1818
1831
  }
@@ -1833,7 +1846,7 @@ function DriveItem({ drive }) {
1833
1846
  }, drive.header.id);
1834
1847
  }
1835
1848
  function HomeScreenContainer() {
1836
- const drives = useDrives();
1849
+ const drives = useVisibleDrives();
1837
1850
  const isAddDriveEnabled = useIsAddDriveEnabled();
1838
1851
  const homeBackground = getRuntimeConfig().connect?.branding?.homeBackground;
1839
1852
  return /* @__PURE__ */ jsxs(HomeScreen, {
@@ -1876,11 +1889,69 @@ const Router = () => {
1876
1889
  return /* @__PURE__ */ jsx(RouterProvider, { router });
1877
1890
  };
1878
1891
  //#endregion
1892
+ //#region src/components/search-bar.tsx
1893
+ const defaultFilterItems = [
1894
+ {
1895
+ id: "project",
1896
+ label: ".project",
1897
+ icon: /* @__PURE__ */ jsx(Icon, {
1898
+ name: "Project",
1899
+ color: "#FF6A55",
1900
+ size: 16
1901
+ })
1902
+ },
1903
+ {
1904
+ id: "budget",
1905
+ label: ".budget",
1906
+ icon: /* @__PURE__ */ jsx(Icon, {
1907
+ name: "BarChart",
1908
+ color: "#8E55EA",
1909
+ size: 16
1910
+ })
1911
+ },
1912
+ {
1913
+ id: "profile",
1914
+ label: ".profile",
1915
+ icon: /* @__PURE__ */ jsx(Icon, {
1916
+ name: "Person",
1917
+ color: "#3E90F0",
1918
+ size: 16
1919
+ })
1920
+ },
1921
+ {
1922
+ id: "legal",
1923
+ label: ".legal",
1924
+ icon: /* @__PURE__ */ jsx(Icon, {
1925
+ name: "Briefcase",
1926
+ color: "#4BAB71",
1927
+ size: 16
1928
+ })
1929
+ },
1930
+ {
1931
+ id: "atlas",
1932
+ label: ".Atlas",
1933
+ icon: /* @__PURE__ */ jsx(Icon, {
1934
+ name: "Globe",
1935
+ color: "#FF8A00",
1936
+ size: 16
1937
+ })
1938
+ }
1939
+ ];
1940
+ const SearchBar = () => {
1941
+ const { t } = useTranslation();
1942
+ return /* @__PURE__ */ jsx(ConnectSearchBar, {
1943
+ className: "m-4 shrink-0 bg-muted",
1944
+ placeholder: t("searchbar.placeholder"),
1945
+ filterLabel: t("searchbar.filterLabel"),
1946
+ filterItems: defaultFilterItems
1947
+ });
1948
+ };
1949
+ //#endregion
1879
1950
  //#region src/components/sidebar.tsx
1880
1951
  function Sidebar() {
1881
1952
  const user = useUser();
1882
1953
  const openLogin = useOpenRenownLogin();
1883
- const drives = useDrives();
1954
+ const drives = useVisibleDrives();
1884
1955
  const isAddDriveEnabled = useIsAddDriveEnabled();
1885
1956
  const [selectedDrive] = useSelectedDriveSafe();
1886
1957
  const connectDebug = localStorage.getItem("CONNECT_DEBUG") === "true";
@@ -1912,12 +1983,12 @@ function Sidebar() {
1912
1983
  variant: "text",
1913
1984
  fallbackMessage: "There was an error loading drives",
1914
1985
  loggerContext: ["Connect", "Sidebar"],
1915
- children: [drives?.map((drive, index) => /* @__PURE__ */ jsx(SidebarItem, {
1986
+ children: [drives?.map((drive) => /* @__PURE__ */ jsx(SidebarItem, {
1916
1987
  title: drive.header.name,
1917
1988
  onClick: () => setSelectedDrive(drive),
1918
1989
  active: selectedDrive?.header.id === drive.header.id,
1919
1990
  icon: /* @__PURE__ */ jsx(DriveIcon, { drive })
1920
- }, index)), isAddDriveEnabled && /* @__PURE__ */ jsx(SidebarAddDriveItem, { onClick: onAddDriveClick })]
1991
+ }, drive.header.id)), isAddDriveEnabled && /* @__PURE__ */ jsx(SidebarAddDriveItem, { onClick: onAddDriveClick })]
1921
1992
  })
1922
1993
  }) });
1923
1994
  }
@@ -2056,20 +2127,20 @@ const MigrationBanner = () => {
2056
2127
  };
2057
2128
  //#endregion
2058
2129
  //#region src/components/modal/modals-container.tsx
2059
- const AddDriveModal$1 = lazy(() => import("./AddDriveModal-Q-Ffh5XA.js").then((m) => ({ default: m.AddDriveModal })));
2060
- const ClearStorageModal = lazy(() => import("./ClearStorageModal-CcGmYZTu.js").then((m) => ({ default: m.ClearStorageModal })));
2061
- const CookiesPolicyModal = lazy(() => import("./CookiesPolicyModal-BpyPPyxS.js").then((m) => ({ default: m.CookiesPolicyModal })));
2062
- const CreateDocumentModal$1 = lazy(() => import("./CreateDocumentModal-C45W8J0V.js").then((m) => ({ default: m.CreateDocumentModal })));
2063
- const DebugSettingsModal = lazy(() => import("./DebugSettingsModal-Bmfn-T8m.js").then((m) => ({ default: m.DebugSettingsModal })));
2064
- const DeleteDriveModal = lazy(() => import("./DeleteDriveModal-Dug84t1j.js").then((m) => ({ default: m.DeleteDriveModal })));
2065
- const DeleteItemModal = lazy(() => import("./DeleteItemModal-mMzy3V8o.js").then((m) => ({ default: m.DeleteItemModal })));
2066
- const DisclaimerModal = lazy(() => import("./DisclaimerModal-DvpD5dPM.js").then((m) => ({ default: m.DisclaimerModal })));
2067
- const DriveSettingsModal$1 = lazy(() => import("./DriveSettingsModal-C6-mRQiv.js").then((m) => ({ default: m.DriveSettingsModal })));
2068
- const DownloadDocumentWithErrorsModal = lazy(() => import("./DownloadDocumentWithErrorsModal-IKl0ljQb.js").then((m) => ({ default: m.DownloadDocumentWithErrorsModal })));
2069
- const DocumentVersionUnsupportedModal = lazy(() => import("./DocumentVersionUnsupportedModal-C3Q0V75l.js").then((m) => ({ default: m.DocumentVersionUnsupportedModal })));
2070
- const ConfirmDocumentUpgradeModal = lazy(() => import("./ConfirmDocumentUpgradeModal-FUOhZsk7.js").then((m) => ({ default: m.ConfirmDocumentUpgradeModal })));
2071
- const SettingsModal$1 = lazy(() => import("./SettingsModal-C75UaQE8.js").then((m) => ({ default: m.SettingsModal })));
2072
- const UpgradeDriveModal = lazy(() => import("./UpgradeDriveModal-_8t9slxQ.js").then((m) => ({ default: m.UpgradeDriveModal })));
2130
+ const AddDriveModal$1 = lazy(() => import("./AddDriveModal-CW-ZSWkf.js").then((m) => ({ default: m.AddDriveModal })));
2131
+ const ClearStorageModal = lazy(() => import("./ClearStorageModal-LiaQURpM.js").then((m) => ({ default: m.ClearStorageModal })));
2132
+ const CookiesPolicyModal = lazy(() => import("./CookiesPolicyModal-lKdOuTNm.js").then((m) => ({ default: m.CookiesPolicyModal })));
2133
+ const CreateDocumentModal$1 = lazy(() => import("./CreateDocumentModal-HmK-78sB.js").then((m) => ({ default: m.CreateDocumentModal })));
2134
+ const DebugSettingsModal = lazy(() => import("./DebugSettingsModal-CzovsTzG.js").then((m) => ({ default: m.DebugSettingsModal })));
2135
+ const DeleteDriveModal = lazy(() => import("./DeleteDriveModal-Cyw6hwc2.js").then((m) => ({ default: m.DeleteDriveModal })));
2136
+ const DeleteItemModal = lazy(() => import("./DeleteItemModal-CiFwstZx.js").then((m) => ({ default: m.DeleteItemModal })));
2137
+ const DisclaimerModal = lazy(() => import("./DisclaimerModal-DIRsjUvu.js").then((m) => ({ default: m.DisclaimerModal })));
2138
+ const DriveSettingsModal$1 = lazy(() => import("./DriveSettingsModal-DRarHRIb.js").then((m) => ({ default: m.DriveSettingsModal })));
2139
+ const DownloadDocumentWithErrorsModal = lazy(() => import("./DownloadDocumentWithErrorsModal-CxsR74d-.js").then((m) => ({ default: m.DownloadDocumentWithErrorsModal })));
2140
+ const DocumentVersionUnsupportedModal = lazy(() => import("./DocumentVersionUnsupportedModal-KftC3-jr.js").then((m) => ({ default: m.DocumentVersionUnsupportedModal })));
2141
+ const ConfirmDocumentUpgradeModal = lazy(() => import("./ConfirmDocumentUpgradeModal-BBrFlHp_.js").then((m) => ({ default: m.ConfirmDocumentUpgradeModal })));
2142
+ const SettingsModal$1 = lazy(() => import("./SettingsModal-D_6sBNEp.js").then((m) => ({ default: m.SettingsModal })));
2143
+ const UpgradeDriveModal = lazy(() => import("./UpgradeDriveModal-C8scFkuq.js").then((m) => ({ default: m.UpgradeDriveModal })));
2073
2144
  const modalComponents = {
2074
2145
  addDrive: AddDriveModal$1,
2075
2146
  clearStorage: ClearStorageModal,
@@ -2083,13 +2154,13 @@ const modalComponents = {
2083
2154
  downloadDocumentWithErrors: DownloadDocumentWithErrorsModal,
2084
2155
  documentVersionUnsupported: DocumentVersionUnsupportedModal,
2085
2156
  confirmDocumentUpgrade: ConfirmDocumentUpgradeModal,
2086
- inspector: lazy(() => import("./InspectorModal-PtIbilu_.js").then((m) => ({ default: m.InspectorModal }))),
2157
+ inspector: lazy(() => import("./InspectorModal-iJTZXoFG.js").then((m) => ({ default: m.InspectorModal }))),
2087
2158
  settings: SettingsModal$1,
2088
2159
  upgradeDrive: UpgradeDriveModal,
2089
- missingPackage: lazy(() => import("./MissingPackageModal-nBnExqO0.js").then((m) => ({ default: m.ConnectMissingPackageModal }))),
2090
- driveAuthRequired: lazy(() => import("./DriveAuthRequiredModal-Cw9eCAQ_.js").then((m) => ({ default: m.DriveAuthRequiredModal }))),
2091
- openFileDocuments: lazy(() => import("./OpenFileDocumentsModal-Du-V-3od.js").then((m) => ({ default: m.OpenFileDocumentsModal }))),
2092
- login: lazy(() => import("./LoginModal-CybbOm3N.js").then((m) => ({ default: m.LoginModal })))
2160
+ missingPackage: lazy(() => import("./MissingPackageModal-Bar7ErIs.js").then((m) => ({ default: m.ConnectMissingPackageModal }))),
2161
+ driveAuthRequired: lazy(() => import("./DriveAuthRequiredModal-BoqnnNKT.js").then((m) => ({ default: m.DriveAuthRequiredModal }))),
2162
+ openFileDocuments: lazy(() => import("./OpenFileDocumentsModal-BvDNshmD.js").then((m) => ({ default: m.OpenFileDocumentsModal }))),
2163
+ login: lazy(() => import("./LoginModal-DhQTxsoG.js").then((m) => ({ default: m.LoginModal })))
2093
2164
  };
2094
2165
  const ModalsContainer = lazy(() => {
2095
2166
  return Promise.resolve({ default: () => {
@@ -2128,9 +2199,9 @@ const ServiceWorkerUpdatePrompt = () => {
2128
2199
  };
2129
2200
  //#endregion
2130
2201
  //#region src/components/app-loader.tsx
2131
- const CookieBanner$1 = lazy(() => import("./cookie-banner-BH6bIq24.js").then((m) => ({ default: m.CookieBanner })));
2202
+ const CookieBanner$1 = lazy(() => import("./cookie-banner-BQescTYk.js").then((m) => ({ default: m.CookieBanner })));
2132
2203
  const AppLoader = (props) => {
2133
- const Load = lazy(() => import("./load-B45H0s1X.js").then((m) => m.loadComponent(props.localPackage)));
2204
+ const Load = lazy(() => import("./load-BHPVWGa5.js").then((m) => m.loadComponent(props.localPackage)));
2134
2205
  return /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsxs(ErrorBoundary$1, {
2135
2206
  fallbackRender: (props) => /* @__PURE__ */ jsx(AppSkeleton, { children: /* @__PURE__ */ jsx(DetailedFallback, { ...props }) }),
2136
2207
  resetKeys: [props.localPackage],
@@ -2164,7 +2235,7 @@ const AppLoader = (props) => {
2164
2235
  }) });
2165
2236
  };
2166
2237
  //#endregion
2167
- export { pendingFileKey as _, DocumentEditor as a, AppContainer as c, OpenPanel as d, Analytics as f, getPendingImportFiles as g, clearPendingImportFiles as h, Root as i, EditorLoader as l, runOpenFileImports as m, Sidebar as n, DriveIcon as o, AppSkeleton as p, Router as r, App as s, AppLoader as t, DocumentEditorContainer as u, subscribePendingImportFiles as v };
2238
+ export { EditorLoader as _, Root as a, Analytics as b, DriveIcon as c, runOpenFileImports as d, clearPendingImportFiles as f, AppContainer as g, subscribePendingImportFiles as h, Router as i, DotsLoader as l, pendingFileKey as m, Sidebar as n, Footer$1 as o, getPendingImportFiles as p, SearchBar as r, DocumentEditor as s, AppLoader as t, App as u, DocumentEditorContainer as v, OpenPanel as y };
2168
2239
 
2169
- //# sourceMappingURL=app-loader-4yYa_F0I.js.map
2170
- //# debugId=b8568829-74c8-5629-8861-97fa4a82010c
2240
+ //# sourceMappingURL=app-loader-B5pvqL4j.js.map
2241
+ //# debugId=ac4746d5-1e0b-5233-a0f4-95264a1e2ea1