@powerhousedao/connect 6.2.3-dev.9 → 6.2.3

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 (119) hide show
  1. package/dist/{AddDriveModal-xAw48kdl.js → AddDriveModal-DhNfP94R.js} +5 -5
  2. package/dist/{AddDriveModal-xAw48kdl.js.map → AddDriveModal-DhNfP94R.js.map} +1 -1
  3. package/dist/{ClearStorageModal-C1BXHTq3.js → ClearStorageModal-DyU-FIgY.js} +5 -5
  4. package/dist/{ClearStorageModal-C1BXHTq3.js.map → ClearStorageModal-DyU-FIgY.js.map} +1 -1
  5. package/dist/{ConfirmDocumentUpgradeModal-FUOhZsk7.js → ConfirmDocumentUpgradeModal-DcOZy20m.js} +8 -7
  6. package/dist/{ConfirmDocumentUpgradeModal-FUOhZsk7.js.map → ConfirmDocumentUpgradeModal-DcOZy20m.js.map} +1 -1
  7. package/dist/{CookiesPolicyModal-BpyPPyxS.js → CookiesPolicyModal-BLw4pnhn.js} +4 -4
  8. package/dist/{CookiesPolicyModal-BpyPPyxS.js.map → CookiesPolicyModal-BLw4pnhn.js.map} +1 -1
  9. package/dist/{CreateDocumentModal-C45W8J0V.js → CreateDocumentModal-Bmv0V-kY.js} +6 -5
  10. package/dist/{CreateDocumentModal-C45W8J0V.js.map → CreateDocumentModal-Bmv0V-kY.js.map} +1 -1
  11. package/dist/{DebugSettingsModal-ClqHzcxM.js → DebugSettingsModal-CwUN9gIr.js} +7 -7
  12. package/dist/{DebugSettingsModal-ClqHzcxM.js.map → DebugSettingsModal-CwUN9gIr.js.map} +1 -1
  13. package/dist/{DeleteDriveModal-Dug84t1j.js → DeleteDriveModal-CjGnG0ht.js} +5 -5
  14. package/dist/{DeleteDriveModal-Dug84t1j.js.map → DeleteDriveModal-CjGnG0ht.js.map} +1 -1
  15. package/dist/{DeleteItemModal-mMzy3V8o.js → DeleteItemModal-BbY_4_xT.js} +5 -5
  16. package/dist/{DeleteItemModal-mMzy3V8o.js.map → DeleteItemModal-BbY_4_xT.js.map} +1 -1
  17. package/dist/{DisclaimerModal-DvpD5dPM.js → DisclaimerModal-DuglmEwL.js} +4 -4
  18. package/dist/{DisclaimerModal-DvpD5dPM.js.map → DisclaimerModal-DuglmEwL.js.map} +1 -1
  19. package/dist/{DocumentVersionUnsupportedModal-C3Q0V75l.js → DocumentVersionUnsupportedModal-C4fy7fZ6.js} +4 -4
  20. package/dist/{DocumentVersionUnsupportedModal-C3Q0V75l.js.map → DocumentVersionUnsupportedModal-C4fy7fZ6.js.map} +1 -1
  21. package/dist/{DownloadDocumentWithErrorsModal-IKl0ljQb.js → DownloadDocumentWithErrorsModal-BsPQ5qgy.js} +6 -5
  22. package/dist/{DownloadDocumentWithErrorsModal-IKl0ljQb.js.map → DownloadDocumentWithErrorsModal-BsPQ5qgy.js.map} +1 -1
  23. package/dist/{DriveAuthRequiredModal-DVK7ZQzO.js → DriveAuthRequiredModal-CtsB0kwH.js} +5 -5
  24. package/dist/{DriveAuthRequiredModal-DVK7ZQzO.js.map → DriveAuthRequiredModal-CtsB0kwH.js.map} +1 -1
  25. package/dist/{DriveSettingsModal-C6-mRQiv.js → DriveSettingsModal-d1L0eYof.js} +4 -4
  26. package/dist/{DriveSettingsModal-C6-mRQiv.js.map → DriveSettingsModal-d1L0eYof.js.map} +1 -1
  27. package/dist/{InspectorModal-7FQBTEzt.js → InspectorModal-Cd8kbCV8.js} +61 -21
  28. package/dist/InspectorModal-Cd8kbCV8.js.map +1 -0
  29. package/dist/{LoginModal-CybbOm3N.js → LoginModal-CI0ogC2-.js} +5 -5
  30. package/dist/{LoginModal-CybbOm3N.js.map → LoginModal-CI0ogC2-.js.map} +1 -1
  31. package/dist/{MissingPackageModal-nBnExqO0.js → MissingPackageModal-HFUPQMML.js} +5 -5
  32. package/dist/{MissingPackageModal-nBnExqO0.js.map → MissingPackageModal-HFUPQMML.js.map} +1 -1
  33. package/dist/{OpenFileDocumentsModal-DGaYJpXW.js → OpenFileDocumentsModal-BQsaBP4x.js} +8 -7
  34. package/dist/{OpenFileDocumentsModal-DGaYJpXW.js.map → OpenFileDocumentsModal-BQsaBP4x.js.map} +1 -1
  35. package/dist/{SettingsModal-CPv3_jmp.js → SettingsModal-CgjW-M2a.js} +17 -15
  36. package/dist/{SettingsModal-CPv3_jmp.js.map → SettingsModal-CgjW-M2a.js.map} +1 -1
  37. package/dist/{UpgradeDriveModal-_8t9slxQ.js → UpgradeDriveModal-CXX6D5Au.js} +4 -4
  38. package/dist/{UpgradeDriveModal-_8t9slxQ.js.map → UpgradeDriveModal-CXX6D5Au.js.map} +1 -1
  39. package/dist/app-loader-D2bV-77j.js +4 -0
  40. package/dist/{app-loader-DyGcBhGV.js → app-loader-l5wtQh5z.js} +1160 -1121
  41. package/dist/app-loader-l5wtQh5z.js.map +1 -0
  42. package/dist/app-skeleton--Me5zlSz.js +112 -0
  43. package/dist/app-skeleton--Me5zlSz.js.map +1 -0
  44. package/dist/app-skeleton-CBN4tenZ.js +4 -0
  45. package/dist/boot-DPNEOI2A.js +210 -0
  46. package/dist/boot-DPNEOI2A.js.map +1 -0
  47. package/dist/buffer-DNTcuap_.js +66 -0
  48. package/dist/buffer-DNTcuap_.js.map +1 -0
  49. package/dist/{build-info-D9_h0032.js → build-info-BV8UtyA4.js} +5 -5
  50. package/dist/{build-info-D9_h0032.js.map → build-info-BV8UtyA4.js.map} +1 -1
  51. package/dist/components-BH57lxCU.js +5 -0
  52. package/dist/{connect.config-DaHDEUrw.js → connect.config-CaXMoB42.js} +24 -12
  53. package/dist/connect.config-CaXMoB42.js.map +1 -0
  54. package/dist/{connection-state-3w4liC1X.js → connection-state-Eqc2izmt.js} +6 -6
  55. package/dist/{connection-state-3w4liC1X.js.map → connection-state-Eqc2izmt.js.map} +1 -1
  56. package/dist/{cookie-banner-BH6bIq24.js → cookie-banner-BxXvPdIY.js} +6 -6
  57. package/dist/{cookie-banner-BH6bIq24.js.map → cookie-banner-BxXvPdIY.js.map} +1 -1
  58. package/dist/{i18n-CWO0vYVd.js → i18n-CTgT1HG-.js} +3 -3
  59. package/dist/{i18n-CWO0vYVd.js.map → i18n-CTgT1HG-.js.map} +1 -1
  60. package/dist/{load-Cu7S9GPc.js → load-ftpCX1HI.js} +13 -11
  61. package/dist/{load-Cu7S9GPc.js.map → load-ftpCX1HI.js.map} +1 -1
  62. package/dist/main.d.ts +2 -1
  63. package/dist/main.js +8 -8
  64. package/dist/main.js.map +1 -1
  65. package/dist/package-manager-BbzIV9aN.js +485 -0
  66. package/dist/package-manager-BbzIV9aN.js.map +1 -0
  67. package/dist/{pglite-major-B39aOEiE.js → pglite-major-p4_YefAE.js} +23 -5
  68. package/dist/pglite-major-p4_YefAE.js.map +1 -0
  69. package/dist/{pglite-migrate-core-Boxqqx-c.js → pglite-migrate-core-BcXcImng.js} +8 -6
  70. package/dist/{pglite-migrate-core-Boxqqx-c.js.map → pglite-migrate-core-BcXcImng.js.map} +1 -1
  71. package/dist/{pglite-seed-BqAUvNFw.js → pglite-seed-Cl2d-iyl.js} +4 -4
  72. package/dist/{pglite-seed-BqAUvNFw.js.map → pglite-seed-Cl2d-iyl.js.map} +1 -1
  73. package/dist/pglite.worker.d.ts +2 -1
  74. package/dist/pglite.worker.js +4 -3
  75. package/dist/pglite.worker.js.map +1 -1
  76. package/dist/pglite.worker.legacy.d.ts +2 -1
  77. package/dist/pglite.worker.legacy.js +4 -3
  78. package/dist/pglite.worker.legacy.js.map +1 -1
  79. package/dist/{preferences-store-_5Cw4_YV.js → preferences-store-DZUIAkHE.js} +6 -25
  80. package/dist/preferences-store-DZUIAkHE.js.map +1 -0
  81. package/dist/{pwa-manifest-link-DIgOSo2Q.js → pwa-manifest-link-DQRJpF0r.js} +3 -3
  82. package/dist/{pwa-manifest-link-DIgOSo2Q.js.map → pwa-manifest-link-DQRJpF0r.js.map} +1 -1
  83. package/dist/{reactor-yC8BveHb.js → reactor-CItc8KaF.js} +68 -437
  84. package/dist/reactor-CItc8KaF.js.map +1 -0
  85. package/dist/reactor.worker.d.ts +2 -1
  86. package/dist/reactor.worker.js +76 -21
  87. package/dist/reactor.worker.js.map +1 -1
  88. package/dist/{registerServiceWorker-BSzbNfvz.js → registerServiceWorker-smkROQhm.js} +5 -5
  89. package/dist/{registerServiceWorker-BSzbNfvz.js.map → registerServiceWorker-smkROQhm.js.map} +1 -1
  90. package/dist/renown-trust-DRgHpxlZ.js +34 -0
  91. package/dist/renown-trust-DRgHpxlZ.js.map +1 -0
  92. package/dist/{runtime-config-DL3QjcfM.js → runtime-config-DtJIUgYw.js} +3 -3
  93. package/dist/{runtime-config-DL3QjcfM.js.map → runtime-config-DtJIUgYw.js.map} +1 -1
  94. package/dist/{sentry-QXKgQmr2.js → sentry-jfDyrxdX.js} +3 -3
  95. package/dist/{sentry-QXKgQmr2.js.map → sentry-jfDyrxdX.js.map} +1 -1
  96. package/dist/start-connect.d.ts +4 -5
  97. package/dist/start-connect.d.ts.map +1 -1
  98. package/dist/start-connect.js +6 -8
  99. package/dist/start-connect.js.map +1 -1
  100. package/dist/style.css +2907 -1
  101. package/dist/{toast-BXIGd4Eb.js → toast-C6WW-_O-.js} +3 -3
  102. package/dist/{toast-BXIGd4Eb.js.map → toast-C6WW-_O-.js.map} +1 -1
  103. package/dist/{use-renown-login-Cf3va6u_.js → use-renown-login-BzOSQETV.js} +3 -3
  104. package/dist/{use-renown-login-Cf3va6u_.js.map → use-renown-login-BzOSQETV.js.map} +1 -1
  105. package/dist/useIsEmbedded-BWJ0qb7w.js +26 -0
  106. package/dist/useIsEmbedded-BWJ0qb7w.js.map +1 -0
  107. package/dist/{useRegistryPackages-BnUcL_nZ.js → useRegistryPackages-DuoU75ap.js} +73 -10
  108. package/dist/useRegistryPackages-DuoU75ap.js.map +1 -0
  109. package/package.json +19 -19
  110. package/style.css +7 -5
  111. package/dist/InspectorModal-7FQBTEzt.js.map +0 -1
  112. package/dist/app-loader-C4pFYc82.js +0 -4
  113. package/dist/app-loader-DyGcBhGV.js.map +0 -1
  114. package/dist/components-MEmS6hgr.js +0 -4
  115. package/dist/connect.config-DaHDEUrw.js.map +0 -1
  116. package/dist/pglite-major-B39aOEiE.js.map +0 -1
  117. package/dist/preferences-store-_5Cw4_YV.js.map +0 -1
  118. package/dist/reactor-yC8BveHb.js.map +0 -1
  119. package/dist/useRegistryPackages-BnUcL_nZ.js.map +0 -1
@@ -1,27 +1,32 @@
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]="299d9fea-95ac-5d8c-875e-9d856ad66b70")}catch(e){}}();
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";
9
- 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";
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]="021db84f-f706-5644-9182-2425eb27187c")}catch(e){}}();
3
+ import { n as getRuntimeConfig } from "./runtime-config-DtJIUgYw.js";
4
+ import { n as drainOpenPanelBuffer, t as clearOpenPanelBuffer } from "./buffer-DNTcuap_.js";
5
+ import { c as idbError, d as isStoredDocumentsRefused } from "./pglite-major-p4_YefAE.js";
6
+ import { n as cloneFileData, r as migrateIdb } from "./pglite-migrate-core-BcXcImng.js";
7
+ import "./i18n-CTgT1HG-.js";
8
+ import { n as setMigrationStatus, t as AppSkeleton } from "./app-skeleton--Me5zlSz.js";
9
+ import { n as useIsEmbedded } from "./useIsEmbedded-BWJ0qb7w.js";
10
+ import { a as defaultPHDocumentEditorConfig, i as defaultPHAppConfig, r as connectConfig } from "./connect.config-CaXMoB42.js";
11
+ import { n as useAcceptedCookies, t as setBannerShown } from "./preferences-store-DZUIAkHE.js";
12
+ import { t as setSentryModule } from "./sentry-jfDyrxdX.js";
13
+ import { n as dismissToast, r as toast, t as ToastContainer } from "./toast-C6WW-_O-.js";
14
+ import { f as PRIMARY_IDB_NAMES, r as subscribeWorkerConnection, s as getCachedMigrationMajor, t as getWorkerConnectionStatus, u as subscribeMigrationMajor } from "./connection-state-Eqc2izmt.js";
15
+ import { n as serviceWorkerManager, r as subscribeServiceWorkerUpdate, t as getServiceWorkerUpdateAvailable } from "./registerServiceWorker-smkROQhm.js";
16
+ import { t as useOpenRenownLogin } from "./use-renown-login-BzOSQETV.js";
13
17
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
- import { useTranslation } from "react-i18next";
18
+ import { Trans, useTranslation } from "react-i18next";
15
19
  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, useSelectedDriveId, useSelectedDriveSafe, useSelectedFolder, useSelectedTimelineItem, useSync, 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";
20
+ 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";
21
+ 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
22
  import { Icon, Modal, PowerhouseButton } from "@powerhousedao/design-system";
23
+ import { childLogger } from "document-model";
22
24
  import { ErrorBoundary } from "react-error-boundary";
23
25
  import { z } from "zod";
24
26
  import { GenericDriveExplorer } from "@powerhousedao/powerhouse-vetra-packages/editors";
27
+ import { Outlet, RouterProvider, createBrowserRouter, createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from "react-router-dom";
28
+ import { redo, undo } from "@powerhousedao/shared/document-model";
29
+ import { useHotkeys } from "react-hotkeys-hook";
25
30
  import { createReactorMcpProvider } from "@powerhousedao/reactor-mcp/tools";
26
31
  import { ReactorChatFab, createSwitchboardSchemaTool } from "@powerhousedao/reactor-browser/ai";
27
32
  import { twMerge } from "tailwind-merge";
@@ -29,1173 +34,1042 @@ import { mockAdapter } from "@renown/sdk/wallet/mock";
29
34
  import { privyAdapter } from "@renown/sdk/wallet/privy";
30
35
  import { rainbowAdapter } from "@renown/sdk/wallet/rainbow";
31
36
  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;
37
+ //#region src/components/error-boundary.tsx
38
+ const logger$2 = childLogger(["Connect"]);
39
+ function hasCustomFallback(props) {
40
+ return "fallbackRender" in props;
94
41
  }
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}`;
42
+ function SilentFallback() {
43
+ return null;
98
44
  }
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;
45
+ /**
46
+ * Simple text-based fallback component.
47
+ * Displays a centered message when an error occurs.
48
+ */
49
+ function TextFallback({ message = "Something went wrong" }) {
50
+ return /* @__PURE__ */ jsx("div", {
51
+ className: "text-center wrap-break-word text-foreground",
52
+ children: message
106
53
  });
107
- if (fresh.length === 0) return;
108
- pendingImportFiles = [...pendingImportFiles, ...fresh];
109
- notifyPendingListeners();
110
54
  }
111
- function clearPendingImportFiles() {
112
- if (pendingImportFiles.length === 0) return;
113
- pendingImportFiles = [];
114
- notifyPendingListeners();
55
+ /**
56
+ * Detailed fallback component showing full error information.
57
+ * Displays error message with a collapsible details section.
58
+ * Useful for development and debugging purposes.
59
+ */
60
+ function DetailedFallback({ error, resetErrorBoundary }) {
61
+ const errorMessage = error instanceof Error ? error.message : String(error);
62
+ const errorDetails = error instanceof Error ? error.stack ?? String(error.cause) : JSON.stringify(error, null, 2);
63
+ return /* @__PURE__ */ jsx("div", {
64
+ className: "z-10 mx-auto flex max-w-[80%] flex-1 items-center justify-center p-6",
65
+ children: /* @__PURE__ */ jsxs("div", {
66
+ className: "w-full max-w-lg rounded-lg border border-border bg-card p-6 text-foreground shadow-sm",
67
+ children: [
68
+ /* @__PURE__ */ jsxs("div", {
69
+ className: "mb-3 flex items-center gap-2",
70
+ children: [/* @__PURE__ */ jsx(Icon, {
71
+ name: "Error",
72
+ className: "size-5 shrink-0 text-destructive"
73
+ }), /* @__PURE__ */ jsx("h1", {
74
+ className: "text-lg font-semibold text-foreground",
75
+ children: "Something went wrong"
76
+ })]
77
+ }),
78
+ /* @__PURE__ */ jsx("p", {
79
+ className: "mb-4 text-sm wrap-break-word text-foreground",
80
+ children: errorMessage
81
+ }),
82
+ errorDetails !== "{}" && /* @__PURE__ */ jsxs("details", {
83
+ className: "group",
84
+ children: [/* @__PURE__ */ jsx("summary", {
85
+ className: "cursor-pointer text-sm font-medium text-foreground underline select-none hover:hover-effect",
86
+ children: "Show details"
87
+ }), /* @__PURE__ */ jsx("pre", {
88
+ className: "mt-2 max-h-48 overflow-auto rounded-sm bg-muted p-3 text-xs text-foreground",
89
+ children: errorDetails
90
+ })]
91
+ }),
92
+ /* @__PURE__ */ jsx(PowerhouseButton, {
93
+ type: "button",
94
+ onClick: resetErrorBoundary,
95
+ className: "mt-4 px-3 py-1.5 text-base font-medium",
96
+ children: "Try again"
97
+ })
98
+ ]
99
+ })
100
+ });
115
101
  }
116
- function subscribePendingImportFiles(cb) {
117
- pendingListeners.add(cb);
118
- return () => {
119
- pendingListeners.delete(cb);
120
- };
102
+ /**
103
+ * Centered error message fallback component.
104
+ * Displays the error message in a centered, full-size container.
105
+ * Suitable for use as a custom fallbackRender when you need simple error display.
106
+ */
107
+ function CenteredErrorMessage({ error }) {
108
+ const errorMessage = error instanceof Error ? error.message : String(error);
109
+ return /* @__PURE__ */ jsx("div", {
110
+ className: "flex size-full items-center justify-center p-6",
111
+ children: /* @__PURE__ */ jsx("h3", {
112
+ className: "max-w-lg min-w-0 text-center text-lg font-semibold wrap-break-word text-foreground",
113
+ children: errorMessage
114
+ })
115
+ });
121
116
  }
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);
117
+ function getFallbackComponent(variant, fallbackMessage) {
118
+ switch (variant) {
119
+ case "silent": return SilentFallback;
120
+ case "text": return () => /* @__PURE__ */ jsx(TextFallback, { message: fallbackMessage });
121
+ case "detailed": return DetailedFallback;
122
+ default: return CenteredErrorMessage;
130
123
  }
131
- if (files.length === 0) return;
132
- addPendingImportFiles(files);
133
- showPHModal({ type: "openFileDocuments" });
134
124
  }
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
125
  /**
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.
126
+ * Unified error boundary component for catching and handling React errors.
127
+ *
128
+ * Built on top of `react-error-boundary`, this component provides:
129
+ * - Automatic error logging with configurable context
130
+ * - Multiple fallback UI variants (silent, text, detailed)
131
+ * - Support for custom fallback render functions
132
+ * - Reset capability via `resetKeys`
133
+ *
134
+ * @example
135
+ * // Using a predefined variant
136
+ * <ErrorBoundary variant="detailed" loggerContext={["Connect", "Editor"]}>
137
+ * <EditorComponent />
138
+ * </ErrorBoundary>
139
+ *
140
+ * @example
141
+ * // Using a custom message with the text variant
142
+ * <ErrorBoundary variant="text" fallbackMessage="Failed to load drives">
143
+ * <DrivesList />
144
+ * </ErrorBoundary>
145
+ *
146
+ * @example
147
+ * // Using a custom fallback render
148
+ * <ErrorBoundary
149
+ * fallbackRender={CenteredErrorMessage}
150
+ * onError={(error) => trackError(error)}
151
+ * >
152
+ * <MyComponent />
153
+ * </ErrorBoundary>
154
+ *
155
+ * @example
156
+ * // Silent mode for modals (renders null on error)
157
+ * <ErrorBoundary variant="silent" loggerContext={["Connect", "Modals"]}>
158
+ * <ModalContent />
159
+ * </ErrorBoundary>
147
160
  */
148
- function initLaunchQueueFileHandling() {
149
- if (initialized) return;
150
- initialized = true;
151
- window.addEventListener("ph:modalUpdated", maybeReopenImportModal);
152
- window.launchQueue?.setConsumer((params) => {
153
- handleLaunchParams(params);
161
+ function ErrorBoundary$1(props) {
162
+ const { children, onError, resetKeys, loggerContext } = props;
163
+ const handleError = (error, info) => {
164
+ if (error.message.includes("Failed to fetch dynamically imported module") && error.message.includes("node_modules/.vite")) {
165
+ console.error(error);
166
+ console.log("Outdated chunk detected, reloading page...");
167
+ window.location.reload();
168
+ return;
169
+ }
170
+ (loggerContext ? childLogger(loggerContext) : logger$2).error("@error @info", error, info);
171
+ onError?.(error, info);
172
+ };
173
+ const fallbackRender = hasCustomFallback(props) ? props.fallbackRender : getFallbackComponent(props.variant, props.fallbackMessage);
174
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
175
+ fallbackRender,
176
+ onError: handleError,
177
+ resetKeys,
178
+ children
154
179
  });
155
180
  }
156
181
  //#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
- };
182
+ //#region src/components/analytics.tsx
183
+ function gtag(...args) {
184
+ window.dataLayer?.push(...args);
171
185
  }
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();
186
+ const Analytics = () => {
187
+ const gaTrackingId = connectConfig.gaTrackingId;
188
+ const [{ analytics }] = useAcceptedCookies();
189
+ const useAnalytics = gaTrackingId && analytics;
190
+ useEffect(() => {
191
+ if (useAnalytics) {
192
+ const script = document.createElement("script");
193
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${gaTrackingId}`;
194
+ script.async = true;
195
+ document.head.appendChild(script);
196
+ window.dataLayer = window.dataLayer || [];
197
+ gtag("js", /* @__PURE__ */ new Date());
198
+ gtag("config", gaTrackingId);
199
+ return () => {
200
+ document.head.removeChild(script);
201
+ };
202
+ }
203
+ }, [useAnalytics, gaTrackingId]);
204
+ return null;
205
+ };
206
+ //#endregion
207
+ //#region src/services/openpanel/client.ts
208
+ let client;
196
209
  /**
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.
210
+ * Returns the cached `OpenPanel` singleton, building it on first call.
211
+ *
212
+ * When `config.clientId` is empty it returns `undefined` without importing
213
+ * `@openpanel/web`, so the bundler can tree-shake the SDK out of production.
203
214
  */
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
- });
215
+ async function getOpenPanelClient(config) {
216
+ if (!config.clientId) return;
217
+ if (client) return client;
218
+ const { OpenPanel: OpenPanelClass } = await import("@openpanel/web");
219
+ client = new OpenPanelClass({
220
+ clientId: config.clientId,
221
+ ...config.apiUrl ? { apiUrl: config.apiUrl } : {},
222
+ trackScreenViews: true,
223
+ trackOutgoingLinks: true
233
224
  });
225
+ client.setGlobalProperties({ app: "connect" });
226
+ drainOpenPanelBuffer(client);
227
+ return client;
234
228
  }
235
229
  /**
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.
230
+ * Clears the cached singleton and the pre-init buffer. Call on consent
231
+ * revocation or during tests; buffered events are discarded, not replayed.
239
232
  */
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
- };
233
+ function resetOpenPanelClient() {
234
+ client = void 0;
235
+ clearOpenPanelBuffer();
279
236
  }
280
237
  //#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 [];
238
+ //#region src/services/openpanel/events.json
239
+ var events_default = [
240
+ {
241
+ "documentType": "powerhouse/document-drive",
242
+ "actionTypes": [
243
+ "ADD_FOLDER",
244
+ "DELETE_NODE",
245
+ "ADD_FILE",
246
+ "UPDATE_FILE",
247
+ "COPY_NODE",
248
+ "MOVE_NODE",
249
+ "SET_DRIVE_NAME",
250
+ "SET_DRIVE_ICON"
251
+ ]
252
+ },
253
+ {
254
+ "documentType": "powerhouse/document-model",
255
+ "actionTypes": [
256
+ "SET_MODEL_NAME",
257
+ "SET_MODEL_ID",
258
+ "ADD_MODULE",
259
+ "DELETE_MODULE",
260
+ "ADD_OPERATION",
261
+ "DELETE_OPERATION"
262
+ ]
263
+ },
264
+ {
265
+ "documentType": "powerhouse/reactor-drive",
266
+ "actionTypes": [
267
+ "ADD_FOLDER",
268
+ "REMOVE_FOLDER",
269
+ "UPDATE_FOLDER"
270
+ ]
313
271
  }
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;
272
+ ];
273
+ //#endregion
274
+ //#region src/services/openpanel/events.ts
275
+ const EventMappingSchema = z.object({
276
+ documentType: z.string().min(1),
277
+ actionTypes: z.array(z.string().min(1)).min(1),
278
+ alias: z.string().optional()
279
+ }).strict();
280
+ const EventMappingsSchema = z.array(EventMappingSchema).readonly();
281
+ /**
282
+ * Parses and validates the event mapping table, returning the validated array
283
+ * and an O(1) lookup map. Accepts a `raw` override for tests. Throws on zod
284
+ * failures and on duplicate `(documentType, actionType)` pairs.
285
+ */
286
+ function loadEvents(raw = events_default) {
287
+ const result = EventMappingsSchema.safeParse(raw);
288
+ if (!result.success) throw new Error(`Invalid OpenPanel events.json: ${result.error.message}`);
289
+ const mappings = result.data;
290
+ const lookupMap = /* @__PURE__ */ new Map();
291
+ for (const mapping of mappings) {
292
+ let inner = lookupMap.get(mapping.documentType);
293
+ if (!inner) {
294
+ inner = /* @__PURE__ */ new Map();
295
+ lookupMap.set(mapping.documentType, inner);
333
296
  }
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);
297
+ for (const actionType of mapping.actionTypes) {
298
+ if (inner.has(actionType)) throw new Error(`Duplicate OpenPanel event mapping for ${mapping.documentType}/${actionType}`);
299
+ inner.set(actionType, mapping);
339
300
  }
340
301
  }
341
- writeBackupIndex(keep);
342
- }
343
- function createPersistentBackup() {
344
302
  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()
303
+ mappings,
304
+ lookupMap
372
305
  };
373
306
  }
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
- }
384
- }
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
- });
394
- }
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);
401
- }
402
- //#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;
460
- }
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
- }
465
- //#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
- }
307
+ const { mappings: defaultMappings, lookupMap: defaultLookupMap } = loadEvents();
308
+ /** Validated mapping array from the bundled events.json. */
309
+ const eventMappings = defaultMappings;
310
+ /** O(1) lookup map: documentType → actionType → mapping. */
311
+ const eventLookupMap = defaultLookupMap;
472
312
  /**
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`.
313
+ * Normalizes a document type into a safe event-name segment: lowercased, `/`
314
+ * replaced with `.`, and any character outside `[a-z0-9._-]` stripped.
315
+ * e.g. `powerhouse/document-drive` → `powerhouse.document-drive`.
478
316
  */
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
- }
317
+ function normalize(documentType) {
318
+ return documentType.toLowerCase().replace(/\//g, ".").replace(/[^a-z0-9._-]/g, "");
485
319
  }
486
320
  /**
487
- * Drain the pre-init buffer in FIFO order through `client.track`, then forward
488
- * subsequent events directly. Called once from `getOpenPanelClient()`.
321
+ * Derives the OpenPanel event name: `mapping.alias` when present, otherwise
322
+ * `${normalize(documentType)}.${actionType.toLowerCase()}`.
489
323
  */
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);
324
+ function deriveEventName(mapping, op) {
325
+ if (mapping.alias) return mapping.alias;
326
+ return `${normalize(op.context.documentType)}.${op.operation.action.type.toLowerCase()}`;
494
327
  }
495
328
  /**
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.
329
+ * Builds the default properties attached to every OpenPanel event. The `app`
330
+ * property is stamped globally by the client, not here.
498
331
  */
499
- function clearOpenPanelBuffer() {
500
- buffer.length = 0;
501
- errorHandler = void 0;
502
- }
503
- //#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;
513
- if (next === prev || next.length === prev.length && next.every((p, i) => p === prev[i])) return prev;
514
- cacheRef.current = next;
515
- return next;
516
- }, [discoveryService]));
332
+ function buildDefaultProperties(op) {
333
+ return {
334
+ documentType: op.context.documentType,
335
+ actionType: op.operation.action.type,
336
+ documentId: op.context.documentId,
337
+ scope: op.context.scope,
338
+ branch: op.context.branch
339
+ };
517
340
  }
518
341
  //#endregion
519
- //#region src/hooks/useUndoRedoShortcuts.ts
520
- const useUndoRedoShortcuts = (props) => {
521
- const { undo, redo, canRedo, canUndo } = props;
522
- const isMac = window.navigator.userAgent.includes("Mac");
523
- let undoShortcut = "ctrl+z";
524
- let redoShortcut = "ctrl+y";
525
- if (isMac) {
526
- undoShortcut = "mod+z";
527
- redoShortcut = "mod+shift+z";
342
+ //#region src/services/openpanel/processor.ts
343
+ /**
344
+ * Read-only reactor `IProcessor` that translates matching document operations
345
+ * into OpenPanel analytics events. Operations whose `(documentType, actionType)`
346
+ * pair is absent from the lookup map are skipped; per-operation errors are
347
+ * routed to `onError` and never thrown back to the manager.
348
+ */
349
+ var OpenPanelProcessor = class {
350
+ client;
351
+ lookupMap;
352
+ onError;
353
+ constructor(client, lookupMap, onError = (err) => console.warn("[OpenPanelProcessor] Error tracking event:", err)) {
354
+ this.client = client;
355
+ this.lookupMap = lookupMap;
356
+ this.onError = onError;
357
+ }
358
+ async onOperations(operations) {
359
+ for (const op of operations) {
360
+ const inner = this.lookupMap.get(op.context.documentType);
361
+ if (!inner) continue;
362
+ const mapping = inner.get(op.operation.action.type);
363
+ if (!mapping) continue;
364
+ const name = deriveEventName(mapping, op);
365
+ const payload = buildDefaultProperties(op);
366
+ try {
367
+ await this.client.track(name, payload);
368
+ } catch (err) {
369
+ this.onError(err, op);
370
+ }
371
+ }
372
+ }
373
+ onDisconnect() {
374
+ this.client.flush?.();
375
+ return Promise.resolve();
528
376
  }
529
- useHotkeys(undoShortcut, (event) => {
530
- event.preventDefault();
531
- if (canUndo) undo();
532
- }, {}, [canUndo, undo]);
533
- useHotkeys(redoShortcut, (event) => {
534
- event.preventDefault();
535
- if (canRedo) redo();
536
- }, {}, [canRedo, redo]);
537
- };
538
- //#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
- });
584
- };
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
- });
613
377
  };
614
378
  //#endregion
615
- //#region src/components/error-boundary.tsx
616
- const logger$1 = childLogger(["Connect"]);
617
- function hasCustomFallback(props) {
618
- return "fallbackRender" in props;
619
- }
620
- function SilentFallback() {
621
- return null;
622
- }
379
+ //#region src/services/openpanel/factory.ts
623
380
  /**
624
- * Simple text-based fallback component.
625
- * Displays a centered message when an error occurs.
381
+ * Returns a `ProcessorFactory` that, for each drive, creates a single
382
+ * `OpenPanelProcessor` record whose `filter.documentType` is the union of all
383
+ * document types present in the event mapping table.
626
384
  */
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
- });
385
+ function createOpenPanelProcessorFactory(config) {
386
+ return function openPanelProcessorFactory(_driveHeader) {
387
+ const documentType = Array.from(config.events.lookupMap.keys());
388
+ return [{
389
+ processor: new OpenPanelProcessor(config.client, config.events.lookupMap, config.onError),
390
+ filter: { documentType },
391
+ startFrom: config.startFrom ?? "current"
392
+ }];
393
+ };
632
394
  }
395
+ //#endregion
396
+ //#region src/components/openpanel-traits.ts
633
397
  /**
634
- * Detailed fallback component showing full error information.
635
- * Displays error message with a collapsible details section.
636
- * Useful for development and debugging purposes.
398
+ * Builds the OpenPanel identity traits from a renown User.
399
+ *
400
+ * Canonical trait schema (shared with Renown and Vetra — each app sends every
401
+ * field it has data for, under these names):
402
+ * - `address`, `did`, `networkId`, `chainId`, `caip2` — wallet/chain facts.
403
+ * - `ensName`, `ensAvatar` — raw ENS facts.
404
+ * - `username`, `userImage`, `profileDocumentId`, `profileCreatedAt` — raw
405
+ * Renown profile facts.
406
+ * - `accountType` — wallet-adapter concept; only Renown has it.
407
+ *
408
+ * Rules:
409
+ * - `credential` (contains a JWT) is **never** forwarded.
410
+ * - Traits carry raw facts only; resolved presentation values (avatar,
411
+ * display name) go in OpenPanel's native identify fields — see
412
+ * {@link buildIdentifyPayload}.
413
+ * - Optional fields (`ens`, `profile`) are only included when their value is
414
+ * non-nullish (guards against `null` from `RenownProfile` fields as well
415
+ * as plain `undefined`).
416
+ *
417
+ * Mirror the Sentry pattern in `store/user.ts`: destructure off `credential`
418
+ * first, then assemble the trait payload from the remainder.
637
419
  */
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
- })
678
- });
420
+ function buildTraits(user) {
421
+ const { credential: _credential, ...rest } = user;
422
+ const traits = {
423
+ address: rest.address,
424
+ did: rest.did,
425
+ networkId: rest.networkId,
426
+ chainId: rest.chainId,
427
+ caip2: `${rest.networkId}:${rest.chainId}`
428
+ };
429
+ if (rest.ens?.name != null) traits.ensName = rest.ens.name;
430
+ if (rest.ens?.avatarUrl != null) traits.ensAvatar = rest.ens.avatarUrl;
431
+ if (rest.profile?.username != null) traits.username = rest.profile.username;
432
+ if (rest.profile?.userImage != null) traits.userImage = rest.profile.userImage;
433
+ if (rest.profile?.documentId != null) traits.profileDocumentId = rest.profile.documentId;
434
+ if (rest.profile?.createdAt != null) traits.profileCreatedAt = rest.profile.createdAt;
435
+ return traits;
679
436
  }
680
437
  /**
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.
438
+ * Builds the full `identify()` payload:
439
+ * - `profileId` is the wallet address — the cross-app profile key shared with
440
+ * Renown and Vetra.
441
+ * - `avatar` / `firstName` are OpenPanel's native profile fields (they drive
442
+ * the picture and name in the dashboard UI), resolved as
443
+ * `userImage ?? ensAvatar` and `ensName ?? username`.
444
+ * - `properties` are the raw traits from {@link buildTraits}.
684
445
  */
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
- });
693
- }
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;
700
- }
446
+ function buildIdentifyPayload(user) {
447
+ const avatar = user.profile?.userImage ?? user.ens?.avatarUrl;
448
+ const firstName = user.ens?.name ?? user.profile?.username;
449
+ return {
450
+ profileId: user.address,
451
+ ...avatar != null ? { avatar } : {},
452
+ ...firstName != null ? { firstName } : {},
453
+ properties: buildTraits(user)
454
+ };
701
455
  }
456
+ //#endregion
457
+ //#region src/components/openpanel.tsx
702
458
  /**
703
- * Unified error boundary component for catching and handling React errors.
459
+ * `OpenPanel` is a `null`-rendering component that manages the full lifecycle
460
+ * of the OpenPanel analytics subsystem.
704
461
  *
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`
462
+ * Gates:
463
+ * - `useAcceptedCookies().analytics === true` — the existing analytics cookie.
464
+ * - `connectConfig.openPanel.clientId` non-empty — no client ID → no-op.
710
465
  *
711
- * @example
712
- * // Using a predefined variant
713
- * <ErrorBoundary variant="detailed" loggerContext={["Connect", "Editor"]}>
714
- * <EditorComponent />
715
- * </ErrorBoundary>
466
+ * Behaviour when both gates pass:
467
+ * 1. Lazy-imports `@openpanel/web` and builds the singleton via
468
+ * `getOpenPanelClient(connectConfig.openPanel)`.
469
+ * 2. Exposes the client on `window.openPanel` for the card-6
470
+ * `useOpenPanelAnalytics()` hook to consume.
471
+ * 3. If `connectConfig.openPanel.trackOperations`, registers the processor
472
+ * factory against `processorManager` so document operations are forwarded.
473
+ * 4. Calls `client.identify()` when the renown user transitions from
474
+ * `undefined` → defined (login). The profile ID is the wallet address —
475
+ * the cross-app key shared with Renown and Vetra.
476
+ * 5. Calls `client.clear()` when the user transitions from defined →
477
+ * `undefined` (logout).
716
478
  *
717
- * @example
718
- * // Using a custom message with the text variant
719
- * <ErrorBoundary variant="text" fallbackMessage="Failed to load drives">
720
- * <DrivesList />
721
- * </ErrorBoundary>
479
+ * Teardown (unmount **or** consent revocation):
480
+ * - `unregisterFactory("openpanel")` to remove the processor.
481
+ * - `resetOpenPanelClient()` to clear the singleton.
482
+ * - `window.openPanel` is cleared.
722
483
  *
723
- * @example
724
- * // Using a custom fallback render
725
- * <ErrorBoundary
726
- * fallbackRender={CenteredErrorMessage}
727
- * onError={(error) => trackError(error)}
728
- * >
729
- * <MyComponent />
730
- * </ErrorBoundary>
484
+ * Mirrors the Sentry pattern in `store/user.ts` for login/logout detection.
485
+ * Analytics failures are caught and forwarded to `console.warn` — they must
486
+ * never throw into the application.
731
487
  *
732
- * @example
733
- * // Silent mode for modals (renders null on error)
734
- * <ErrorBoundary variant="silent" loggerContext={["Connect", "Modals"]}>
735
- * <ModalContent />
736
- * </ErrorBoundary>
488
+ * Mounted next to `<Analytics />` in `app.tsx`.
737
489
  */
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;
490
+ function OpenPanel() {
491
+ const [{ analytics }] = useAcceptedCookies();
492
+ const user = useUser();
493
+ const module = useReactorClientModule();
494
+ const reactorClientModule = module?.kind === "browser" ? module : void 0;
495
+ const [client, setClient] = useState();
496
+ /**
497
+ * Tracks the previous `user` value so we can detect the two transitions:
498
+ * undefined → defined (login)
499
+ * defined → undefined (logout)
500
+ */
501
+ const prevUserRef = useRef(void 0);
502
+ const enabled = analytics && !!connectConfig.openPanel.clientId;
503
+ const processorManager = reactorClientModule?.reactorModule?.processorManager;
504
+ useEffect(() => {
505
+ if (!enabled) return;
506
+ let cancelled = false;
507
+ const isCancelled = () => cancelled;
508
+ const setup = async () => {
509
+ let builtClient;
510
+ try {
511
+ builtClient = await getOpenPanelClient(connectConfig.openPanel);
512
+ } catch (err) {
513
+ console.warn("[OpenPanel] Failed to build client:", err);
514
+ return;
515
+ }
516
+ if (isCancelled() || !builtClient) return;
517
+ setClient(builtClient);
518
+ window.openPanel = builtClient;
519
+ if (connectConfig.openPanel.trackOperations && processorManager) try {
520
+ await processorManager.registerFactory("openpanel", createOpenPanelProcessorFactory({
521
+ client: builtClient,
522
+ events: {
523
+ mappings: eventMappings,
524
+ lookupMap: eventLookupMap
525
+ },
526
+ startFrom: "current"
527
+ }));
528
+ if (isCancelled()) await processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister factory after cancellation:", err));
529
+ } catch (err) {
530
+ console.warn("[OpenPanel] Failed to register processor factory:", err);
531
+ }
532
+ };
533
+ setup();
534
+ return () => {
535
+ cancelled = true;
536
+ prevUserRef.current = void 0;
537
+ if (connectConfig.openPanel.trackOperations && processorManager) processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister processor factory:", err));
538
+ resetOpenPanelClient();
539
+ window.openPanel = void 0;
540
+ setClient(void 0);
541
+ };
542
+ }, [enabled, processorManager]);
543
+ useEffect(() => {
544
+ if (!enabled || !client) return;
545
+ const prev = prevUserRef.current;
546
+ prevUserRef.current = user;
547
+ if (!prev && user) try {
548
+ client.identify(buildIdentifyPayload(user));
549
+ } catch (err) {
550
+ console.warn("[OpenPanel] Failed to identify user:", err);
746
551
  }
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
755
- });
552
+ else if (prev && !user) try {
553
+ client.clear();
554
+ } catch (err) {
555
+ console.warn("[OpenPanel] Failed to clear user:", err);
556
+ }
557
+ }, [
558
+ client,
559
+ user,
560
+ enabled
561
+ ]);
562
+ return null;
756
563
  }
757
564
  //#endregion
758
- //#region src/components/analytics.tsx
759
- function gtag(...args) {
760
- window.dataLayer?.push(...args);
565
+ //#region src/components/document-editor-container.tsx
566
+ function DocumentEditorContainer() {
567
+ const [selectedDocument] = useSelectedDocument();
568
+ const parentFolder = useNodeParentFolderById(selectedDocument.header.id);
569
+ useMemo(() => {
570
+ return async () => {};
571
+ }, []);
572
+ useCallback(() => {
573
+ setSelectedNode(parentFolder);
574
+ }, [parentFolder]);
575
+ return /* @__PURE__ */ jsx("div", {
576
+ id: "document-editor-container",
577
+ className: "flex-1",
578
+ "data-document-type": selectedDocument.header.documentType,
579
+ children: /* @__PURE__ */ jsx(DocumentEditor, { document: selectedDocument })
580
+ });
761
581
  }
762
- const Analytics = () => {
763
- const gaTrackingId = connectConfig.gaTrackingId;
764
- const [{ analytics }] = useAcceptedCookies();
765
- const useAnalytics = gaTrackingId && analytics;
582
+ //#endregion
583
+ //#region src/components/editor-loader.tsx
584
+ function EditorLoader(props) {
585
+ const [showLoading, setShowLoading] = useState(false);
766
586
  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
- };
587
+ setTimeout(() => {
588
+ setShowLoading(true);
589
+ }, props.loadingTimeout ?? 200);
590
+ }, [props]);
591
+ if (!showLoading) return null;
592
+ const { customEditorLoader, ...defaultProps } = props;
593
+ if (customEditorLoader) return /* @__PURE__ */ jsx(Fragment, { children: customEditorLoader });
594
+ return /* @__PURE__ */ jsx(DefaultEditorLoader, { ...defaultProps });
595
+ }
782
596
  //#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
597
+ //#region src/components/app-container.tsx
598
+ function AppContainer() {
599
+ const [selectedDrive] = useSelectedDriveSafe();
600
+ const selectedDocumentId = useSelectedDocumentId();
601
+ const app = useAppModuleById(selectedDrive?.header.meta?.preferredEditor);
602
+ const defaultApp = useDefaultAppModule();
603
+ if (!selectedDrive) return /* @__PURE__ */ jsx(EditorLoader, {});
604
+ const AppComponent = app?.Component ?? defaultApp?.Component ?? GenericDriveExplorer.Component;
605
+ if (!AppComponent) throw new Error("No app component found");
606
+ return /* @__PURE__ */ jsx(ErrorBoundary$1, {
607
+ variant: "detailed",
608
+ resetKeys: [selectedDrive.header.id],
609
+ loggerContext: ["Connect", "App"],
610
+ children: /* @__PURE__ */ jsx(Suspense, {
611
+ fallback: /* @__PURE__ */ jsx(EditorLoader, {}),
612
+ children: /* @__PURE__ */ jsx(DropZoneWrapper, {
613
+ className: "flex h-full flex-col overflow-auto",
614
+ children: /* @__PURE__ */ jsx(AppComponent, { children: selectedDocumentId ? /* @__PURE__ */ jsx(DocumentEditorContainer, {}) : null })
615
+ })
616
+ })
800
617
  });
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();
812
618
  }
813
619
  //#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
- ];
620
+ //#region src/hooks/useFailedInstallations.ts
621
+ const emptyArray$1 = [];
622
+ function useFailedInstallations() {
623
+ const discoveryService = usePackageDiscoveryService();
624
+ return useSyncExternalStore((cb) => discoveryService?.subscribeFailed(cb) ?? (() => {}), () => discoveryService?.getFailedInstallations() ?? emptyArray$1);
625
+ }
849
626
  //#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);
627
+ //#region src/hooks/useInitSentry.ts
628
+ const logger$1 = childLogger(["sentry"]);
629
+ let clientStarted = false;
630
+ async function getSentry() {
631
+ return await import("@sentry/react");
632
+ }
633
+ async function initSentry() {
634
+ const Sentry = await getSentry();
635
+ const integrations = [
636
+ Sentry.httpClientIntegration(),
637
+ Sentry.extraErrorDataIntegration({ depth: 5 }),
638
+ Sentry.replayIntegration(),
639
+ Sentry.captureConsoleIntegration({ levels: ["error"] })
640
+ ];
641
+ if (connectConfig.sentry.tracing) integrations.push(Sentry.reactRouterV6BrowserTracingIntegration({
642
+ useEffect: React.useEffect,
643
+ useLocation,
644
+ useNavigationType,
645
+ createRoutesFromChildren,
646
+ matchRoutes
647
+ }));
648
+ Sentry.init({
649
+ release: connectConfig.sentry.release,
650
+ dsn: connectConfig.sentry.dsn ?? void 0,
651
+ environment: connectConfig.sentry.env,
652
+ integrations,
653
+ ignoreErrors: [
654
+ "User is not allowed to create files",
655
+ "User is not allowed to move documents",
656
+ "The user aborted a request."
657
+ ],
658
+ sendDefaultPii: true,
659
+ tracesSampleRate: 1,
660
+ replaysSessionSampleRate: .1,
661
+ replaysOnErrorSampleRate: 1,
662
+ beforeSend(event, hint) {
663
+ const error = hint.originalException;
664
+ if (error instanceof TypeError && error.message.includes("Failed to fetch") && !navigator.onLine) return null;
665
+ return event;
876
666
  }
877
- }
878
- return {
879
- mappings,
880
- lookupMap
881
- };
667
+ });
668
+ setSentryModule(Sentry);
669
+ clientStarted = true;
882
670
  }
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, "");
671
+ async function closeClient() {
672
+ if (!clientStarted) return;
673
+ const sentry = await getSentry();
674
+ setSentryModule(void 0);
675
+ return sentry.getClient()?.close();
895
676
  }
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()}`;
677
+ function useInitSentry() {
678
+ const [acceptedCookies] = useAcceptedCookies();
679
+ const { analytics } = acceptedCookies;
680
+ useEffect(() => {
681
+ if (!analytics) {
682
+ closeClient().catch((error) => logger$1.error("@error", error));
683
+ return;
684
+ }
685
+ if (clientStarted || connectConfig.sentry.dsn === null || connectConfig.sentry.dsn === void 0 || connectConfig.sentry.dsn === "") return;
686
+ initSentry().catch((error) => logger$1.error("@error", error));
687
+ }, [analytics]);
903
688
  }
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
- };
689
+ //#endregion
690
+ //#region src/hooks/usePendingInstallations.ts
691
+ const emptyArray = [];
692
+ function usePendingInstallations() {
693
+ const discoveryService = usePackageDiscoveryService();
694
+ const cacheRef = useRef(emptyArray);
695
+ const getSnapshot = useCallback(() => {
696
+ if (!discoveryService) return emptyArray;
697
+ const next = discoveryService.getPendingInstallations() ?? emptyArray;
698
+ const prev = cacheRef.current;
699
+ if (next === prev || next.length === prev.length && next.every((p, i) => p === prev[i])) return prev;
700
+ cacheRef.current = next;
701
+ return next;
702
+ }, [discoveryService]);
703
+ return useSyncExternalStore((cb) => discoveryService?.subscribePending(cb) ?? (() => {}), getSnapshot);
916
704
  }
917
705
  //#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;
706
+ //#region src/hooks/useUndoRedoShortcuts.ts
707
+ const useUndoRedoShortcuts = (props) => {
708
+ const { undo, redo, canRedo, canUndo } = props;
709
+ const isMac = window.navigator.userAgent.includes("Mac");
710
+ let undoShortcut = "ctrl+z";
711
+ let redoShortcut = "ctrl+y";
712
+ if (isMac) {
713
+ undoShortcut = "mod+z";
714
+ redoShortcut = "mod+shift+z";
930
715
  }
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
- }
716
+ useHotkeys(undoShortcut, (event) => {
717
+ event.preventDefault();
718
+ if (canUndo) undo();
719
+ }, {}, [canUndo, undo]);
720
+ useHotkeys(redoShortcut, (event) => {
721
+ event.preventDefault();
722
+ if (canRedo) redo();
723
+ }, {}, [canRedo, redo]);
724
+ };
725
+ //#endregion
726
+ //#region src/context/sentry-provider.tsx
727
+ const SentryProvider = ({ children }) => {
728
+ useInitSentry();
729
+ return children;
730
+ };
731
+ //#endregion
732
+ //#region src/utils/document-editor-debug-tools.ts
733
+ var DocumentEditorDebugTools = class {
734
+ document;
735
+ operations = [];
736
+ constructor(document) {
737
+ if (document) this.document = document;
945
738
  }
946
- onDisconnect() {
947
- this.client.flush?.();
948
- return Promise.resolve();
739
+ operationsToTableObject(operations) {
740
+ return operations.map((op) => ({
741
+ ...op,
742
+ input: JSON.stringify(op.action.input)
743
+ }));
744
+ }
745
+ setDocument(document) {
746
+ this.document = document;
747
+ }
748
+ getDocument() {
749
+ return this.document;
750
+ }
751
+ getOperations() {
752
+ return this.operations;
753
+ }
754
+ pushOperation(operation) {
755
+ this.operations.push(operation);
756
+ }
757
+ operationsTable() {
758
+ if (!this.document) console.warn("No document");
759
+ const ops = Object.values(this.document?.operations || {}).filter((array) => array !== void 0).flatMap((array) => array).sort((a, b) => a.index - b.index);
760
+ console.table(this.operationsToTableObject(ops));
761
+ }
762
+ scopeOperationsTable(scope) {
763
+ if (!this.document) console.warn("No document");
764
+ const ops = this.document?.operations[scope] || [];
765
+ console.table(this.operationsToTableObject(ops));
766
+ }
767
+ operationsLog() {
768
+ console.log(this.operations);
769
+ }
770
+ operationsLogTable() {
771
+ console.table(this.operationsToTableObject(this.operations));
772
+ }
773
+ clear() {
774
+ this.operations = [];
775
+ this.document = void 0;
949
776
  }
950
777
  };
951
778
  //#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
- }];
779
+ //#region src/utils/launch-queue.ts
780
+ let pendingImportFiles = [];
781
+ const pendingListeners = /* @__PURE__ */ new Set();
782
+ function notifyPendingListeners() {
783
+ for (const listener of pendingListeners) listener();
784
+ }
785
+ function getPendingImportFiles() {
786
+ return pendingImportFiles;
787
+ }
788
+ /** Stable identity of a pending file (also the picker's pre-check cache key). */
789
+ function pendingFileKey(file) {
790
+ return `${file.name}|${file.size}|${file.lastModified}`;
791
+ }
792
+ function addPendingImportFiles(files) {
793
+ const seen = new Set(pendingImportFiles.map(pendingFileKey));
794
+ const fresh = files.filter((file) => {
795
+ const key = pendingFileKey(file);
796
+ if (seen.has(key)) return false;
797
+ seen.add(key);
798
+ return true;
799
+ });
800
+ if (fresh.length === 0) return;
801
+ pendingImportFiles = [...pendingImportFiles, ...fresh];
802
+ notifyPendingListeners();
803
+ }
804
+ function clearPendingImportFiles() {
805
+ if (pendingImportFiles.length === 0) return;
806
+ pendingImportFiles = [];
807
+ notifyPendingListeners();
808
+ }
809
+ function subscribePendingImportFiles(cb) {
810
+ pendingListeners.add(cb);
811
+ return () => {
812
+ pendingListeners.delete(cb);
966
813
  };
967
814
  }
968
- //#endregion
969
- //#region src/components/openpanel-traits.ts
815
+ async function handleLaunchParams(params) {
816
+ const handles = params.files.filter((handle) => handle.kind === "file");
817
+ if (handles.length === 0) return;
818
+ const files = [];
819
+ for (const handle of handles) try {
820
+ files.push(await handle.getFile());
821
+ } catch (error) {
822
+ console.error("Failed to read launched file:", handle.name, error);
823
+ }
824
+ if (files.length === 0) return;
825
+ addPendingImportFiles(files);
826
+ showPHModal({ type: "openFileDocuments" });
827
+ }
828
+ function maybeReopenImportModal() {
829
+ if (getPendingImportFiles().length === 0) return;
830
+ if (window.ph?.modal !== void 0) return;
831
+ showPHModal({ type: "openFileDocuments" });
832
+ }
833
+ let initialized = false;
970
834
  /**
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.
835
+ * Register the launchQueue consumer that receives files opened with Connect
836
+ * through the OS file association. Idempotent (safe under StrictMode double
837
+ * effects) and a no-op where the File Handling API doesn't exist — Firefox,
838
+ * Safari, and any context without an installed PWA manifest simply never
839
+ * deliver launches.
992
840
  */
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}`
841
+ function initLaunchQueueFileHandling() {
842
+ if (initialized) return;
843
+ initialized = true;
844
+ window.addEventListener("ph:modalUpdated", maybeReopenImportModal);
845
+ window.launchQueue?.setConsumer((params) => {
846
+ handleLaunchParams(params);
847
+ });
848
+ }
849
+ //#endregion
850
+ //#region src/utils/open-file-uploads.ts
851
+ let uploads = [];
852
+ const listeners = /* @__PURE__ */ new Set();
853
+ function notifyUploadListeners() {
854
+ for (const listener of listeners) listener();
855
+ }
856
+ function getOpenFileUploads() {
857
+ return uploads;
858
+ }
859
+ function subscribeOpenFileUploads(cb) {
860
+ listeners.add(cb);
861
+ return () => {
862
+ listeners.delete(cb);
1001
863
  };
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;
1009
864
  }
865
+ function removeOpenFileUpload(id) {
866
+ const next = uploads.filter((upload) => upload.id !== id);
867
+ if (next.length === uploads.length) return;
868
+ uploads = next;
869
+ notifyUploadListeners();
870
+ }
871
+ function clearOpenFileUploads() {
872
+ if (uploads.length === 0) return;
873
+ uploads = [];
874
+ notifyUploadListeners();
875
+ }
876
+ function addOpenFileUploads(rows) {
877
+ uploads = [...uploads, ...rows];
878
+ notifyUploadListeners();
879
+ }
880
+ function patchOpenFileUpload(id, patch) {
881
+ uploads = uploads.map((upload) => upload.id === id ? {
882
+ ...upload,
883
+ ...patch
884
+ } : upload);
885
+ notifyUploadListeners();
886
+ }
887
+ let uploadCounter = 0;
888
+ let importChain = Promise.resolve();
1010
889
  /**
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}.
890
+ * Import one file and resolve once the flow truly ends. Completion is
891
+ * tracked through the progress stages, NOT the returned promise — the
892
+ * package-discovery retry path resolves early and keeps reporting through
893
+ * the callback. `resolveConflict: "duplicate"` is always passed: duplicates
894
+ * were resolved in the picker (rename), so the conflict stage must never
895
+ * fire — this also covers a duplicate appearing between pre-check and import.
1018
896
  */
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
- };
897
+ function importOne(row, driveId, documentTypes) {
898
+ return new Promise((resolve) => {
899
+ let settled = false;
900
+ const settle = (fileNode) => {
901
+ if (settled) return;
902
+ settled = true;
903
+ resolve(fileNode);
904
+ };
905
+ addFileWithProgress(row.file, driveId, row.name, void 0, (progress) => {
906
+ patchOpenFileUpload(row.id, {
907
+ status: mapProgressStageToStatus(progress.stage),
908
+ progress: progress.progress,
909
+ ...progress.error ? { errorDetails: progress.error } : {},
910
+ ...progress.documentType ? { documentType: progress.documentType } : {},
911
+ ...progress.fileNode ? { fileNode: progress.fileNode } : {}
912
+ });
913
+ switch (progress.stage) {
914
+ case "complete":
915
+ settle(progress.fileNode);
916
+ break;
917
+ case "failed":
918
+ case "unsupported-document-type": settle(void 0);
919
+ }
920
+ }, documentTypes, "duplicate").catch(() => {
921
+ settle(void 0);
922
+ });
923
+ });
1028
924
  }
1029
- //#endregion
1030
- //#region src/components/openpanel.tsx
1031
925
  /**
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`.
926
+ * Run a confirmed batch of imports into a drive, feeding the floating upload
927
+ * panel. Rows appear immediately (queued), files import sequentially, and a
928
+ * single-file batch that succeeds opens its document in the editor.
1062
929
  */
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);
930
+ function runOpenFileImports(args) {
931
+ const { driveId, imports, documentTypes } = args;
932
+ if (imports.length === 0) return importChain;
933
+ const rows = imports.map(({ file, name }) => ({
934
+ id: `open-file-${++uploadCounter}`,
935
+ file,
936
+ name
937
+ }));
938
+ addOpenFileUploads(rows.map((row) => ({
939
+ id: row.id,
940
+ fileName: row.name,
941
+ fileSize: formatFileSize(row.file.size),
942
+ status: "pending",
943
+ progress: 0
944
+ })));
945
+ const run = async () => {
946
+ const results = [];
947
+ for (const row of rows) results.push(await importOne(row, driveId, documentTypes));
948
+ if (rows.length === 1 && results[0]) setSelectedNode(results[0]);
949
+ };
950
+ importChain = importChain.then(run, run);
951
+ return importChain;
952
+ }
953
+ //#endregion
954
+ //#region src/utils/openUrl.ts
955
+ const openBrowserUrl = (url) => window.open(url, "_blank");
956
+ const openUrl = openBrowserUrl;
957
+ //#endregion
958
+ //#region src/utils/pglite-migration.ts
959
+ const BACKUP_PREFIX = "ph-pglite-backup::";
960
+ const BACKUP_INDEX_KEY = "ph:pglite-backups";
961
+ const BACKUP_RETENTION_MS = 2592e6;
962
+ function deleteIdb(name, timeoutMs = 15e3) {
963
+ return new Promise((resolve, reject) => {
964
+ const req = indexedDB.deleteDatabase(name);
965
+ let settled = false;
966
+ const done = (fn) => {
967
+ if (settled) return;
968
+ settled = true;
969
+ fn();
1114
970
  };
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);
971
+ const timer = setTimeout(() => done(() => reject(/* @__PURE__ */ new Error(`IDB delete timed out for ${name} after ${timeoutMs}ms`))), timeoutMs);
972
+ req.onsuccess = () => done(() => {
973
+ clearTimeout(timer);
974
+ resolve();
975
+ });
976
+ req.onerror = () => done(() => {
977
+ clearTimeout(timer);
978
+ reject(idbError(req, `IDB delete failed for ${name}`));
979
+ });
980
+ });
981
+ }
982
+ function readBackupIndex() {
983
+ try {
984
+ const raw = localStorage.getItem(BACKUP_INDEX_KEY);
985
+ if (!raw) return [];
986
+ const parsed = JSON.parse(raw);
987
+ return Array.isArray(parsed) ? parsed : [];
988
+ } catch {
989
+ return [];
990
+ }
991
+ }
992
+ function writeBackupIndex(records) {
993
+ try {
994
+ localStorage.setItem(BACKUP_INDEX_KEY, JSON.stringify(records));
995
+ } catch {}
996
+ }
997
+ function registerBackup(rec) {
998
+ writeBackupIndex([...readBackupIndex(), rec]);
999
+ }
1000
+ function forgetBackup(name) {
1001
+ writeBackupIndex(readBackupIndex().filter((r) => r.name !== name));
1002
+ }
1003
+ async function janitor() {
1004
+ const cutoff = Date.now() - BACKUP_RETENTION_MS;
1005
+ const keep = [];
1006
+ for (const rec of readBackupIndex()) {
1007
+ if (rec.createdAt >= cutoff) {
1008
+ keep.push(rec);
1009
+ continue;
1124
1010
  }
1125
- else if (prev && !user) try {
1126
- client.clear();
1011
+ try {
1012
+ await deleteIdb(rec.name);
1127
1013
  } catch (err) {
1128
- console.warn("[OpenPanel] Failed to clear user:", err);
1014
+ console.warn(`Failed to delete expired pglite backup ${rec.name}`, err);
1015
+ keep.push(rec);
1129
1016
  }
1130
- }, [
1131
- client,
1132
- user,
1133
- enabled
1134
- ]);
1135
- return null;
1017
+ }
1018
+ writeBackupIndex(keep);
1136
1019
  }
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
- });
1020
+ function createPersistentBackup() {
1021
+ return {
1022
+ snapshot: async (idbName) => {
1023
+ const backupName = `${BACKUP_PREFIX}${idbName}::${(/* @__PURE__ */ new Date()).toISOString()}`;
1024
+ await cloneFileData(idbName, backupName);
1025
+ registerBackup({
1026
+ name: backupName,
1027
+ idbName,
1028
+ createdAt: Date.now()
1029
+ });
1030
+ return backupName;
1031
+ },
1032
+ rollback: async (handle, idbName, cause) => {
1033
+ const backupName = handle;
1034
+ try {
1035
+ await cloneFileData(backupName, idbName);
1036
+ } catch (rollbackErr) {
1037
+ console.error(`PGlite migration rollback failed for ${idbName}; backup ${backupName} retained. Primary migration error:`, cause, "Rollback error:", rollbackErr);
1038
+ return;
1039
+ }
1040
+ await deleteIdb(backupName).catch(() => void 0);
1041
+ forgetBackup(backupName);
1042
+ },
1043
+ discard: async (handle) => {
1044
+ const backupName = handle;
1045
+ await deleteIdb(backupName).catch(() => void 0);
1046
+ forgetBackup(backupName);
1047
+ },
1048
+ commit: () => Promise.resolve()
1049
+ };
1154
1050
  }
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 });
1051
+ async function migrateAllLocked() {
1052
+ const backup = createPersistentBackup();
1053
+ try {
1054
+ for (const idbName of PRIMARY_IDB_NAMES) await migrateIdb(idbName, (phase) => setMigrationStatus({
1055
+ idbName,
1056
+ phase
1057
+ }), backup);
1058
+ } finally {
1059
+ setMigrationStatus(null);
1060
+ }
1168
1061
  }
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
- })
1062
+ async function migrateAllIfNeeded() {
1063
+ janitor().catch((err) => console.warn("PGlite backup janitor failed", err));
1064
+ if (!(typeof navigator !== "undefined" && "locks" in navigator && typeof navigator.locks.request === "function")) {
1065
+ await migrateAllLocked();
1066
+ return;
1067
+ }
1068
+ await navigator.locks.request("ph-pglite-migration", { mode: "exclusive" }, async () => {
1069
+ await migrateAllLocked();
1190
1070
  });
1191
1071
  }
1192
1072
  //#endregion
1193
- //#region src/context/sentry-provider.tsx
1194
- const SentryProvider = ({ children }) => {
1195
- useInitSentry();
1196
- return children;
1197
- };
1198
- //#endregion
1199
1073
  //#region src/components/reactor-chat.tsx
1200
1074
  /**
1201
1075
  * Resolves the reactor tool descriptors for the chat agent from the
@@ -1401,11 +1275,12 @@ function PackageInstallPrompt() {
1401
1275
  const pendingInstallations = usePendingInstallations();
1402
1276
  const discoveryService = usePackageDiscoveryService();
1403
1277
  if (!discoveryService || pendingInstallations.length === 0) return null;
1278
+ const installations = pendingInstallations.flatMap((p) => p.packageNames.map((packageName) => ({
1279
+ documentType: p.documentType,
1280
+ packageName
1281
+ })));
1404
1282
  return /* @__PURE__ */ jsx(PackageInstallModal, {
1405
- pendingInstallations: pendingInstallations.flatMap((p) => p.packageNames.map((packageName) => ({
1406
- documentType: p.documentType,
1407
- packageName
1408
- }))),
1283
+ pendingInstallations: installations,
1409
1284
  onInstall: (packageName) => discoveryService.approveInstallation(packageName),
1410
1285
  onDismiss: (packageName) => discoveryService.dismissInstallation(packageName)
1411
1286
  });
@@ -1533,6 +1408,20 @@ const App = () => {
1533
1408
  ] }) });
1534
1409
  };
1535
1410
  //#endregion
1411
+ //#region src/components/dots-loader.tsx
1412
+ const DotsLoader = () => {
1413
+ const [dots, setDots] = useState(0);
1414
+ useEffect(() => {
1415
+ const id = setInterval(() => {
1416
+ setDots((dots) => dots === 3 ? 0 : dots + 1);
1417
+ }, 200);
1418
+ return () => {
1419
+ clearInterval(id);
1420
+ };
1421
+ }, []);
1422
+ return /* @__PURE__ */ jsx("span", { children: new Array(dots).fill(".").join("") });
1423
+ };
1424
+ //#endregion
1536
1425
  //#region src/components/drive-icon.tsx
1537
1426
  function DriveIcon({ drive }) {
1538
1427
  const remotes = useSyncList();
@@ -1597,6 +1486,9 @@ function EditorError({ title, children }) {
1597
1486
  })
1598
1487
  });
1599
1488
  }
1489
+ function defaultScope(scopes) {
1490
+ return scopes.includes("global") ? "global" : scopes[0];
1491
+ }
1600
1492
  function OpenPackageManagerButton() {
1601
1493
  return /* @__PURE__ */ jsx(PowerhouseButton, {
1602
1494
  className: "px-3 py-1.5 text-base font-medium",
@@ -1616,10 +1508,30 @@ const DocumentEditor = (props) => {
1616
1508
  const documentName = document?.header.name ?? void 0;
1617
1509
  const documentType = document?.header.documentType ?? void 0;
1618
1510
  const preferredEditor = document?.header.meta?.preferredEditor ?? void 0;
1619
- const { globalOperations, localOperations, isLoading: isLoadingOperations, refetch: refetchOperations } = useDocumentOperations(documentId);
1511
+ const scopes = useMemo(() => {
1512
+ const keys = Object.keys(document?.header.revision ?? {});
1513
+ return keys.length > 0 ? keys : ["global"];
1514
+ }, [document?.header.revision]);
1515
+ const [operationScope, setOperationScope] = useState(() => defaultScope(scopes));
1516
+ const selectedScope = scopes.includes(operationScope) ? operationScope : defaultScope(scopes);
1517
+ const { operations: historyOperations, isLoading: isLoadingHistory, hasNextPage: historyHasNextPage, fetchNextPage: fetchNextHistoryPage, error: historyError } = useDocumentOperations(documentId, selectedScope, {
1518
+ enabled: revisionHistoryVisible,
1519
+ limit: 500
1520
+ });
1521
+ const { operations: globalOperations, hasNextPage: globalHasNextPage, isLoading: isLoadingGlobal, fetchNextPage: fetchNextGlobalPage, error: globalError } = useDocumentOperations(documentId, "global", {
1522
+ enabled: !!selectedTimelineItem,
1523
+ limit: 500
1524
+ });
1620
1525
  useEffect(() => {
1621
- if (revisionHistoryVisible) refetchOperations();
1622
- }, [revisionHistoryVisible, refetchOperations]);
1526
+ if (!globalHasNextPage || isLoadingGlobal || globalError) return;
1527
+ const handle = window.setTimeout(fetchNextGlobalPage, 0);
1528
+ return () => window.clearTimeout(handle);
1529
+ }, [
1530
+ globalHasNextPage,
1531
+ isLoadingGlobal,
1532
+ fetchNextGlobalPage,
1533
+ globalError
1534
+ ]);
1623
1535
  const globalRevisionNumber = document?.header.revision.global ?? 0;
1624
1536
  const localRevisionNumber = document?.header.revision.local ?? 0;
1625
1537
  const documentModelModule = useDocumentModelModuleById(documentType);
@@ -1725,11 +1637,17 @@ const DocumentEditor = (props) => {
1725
1637
  id: "document-editor-context",
1726
1638
  "data-editor": editorModule.config.id,
1727
1639
  "data-document-type": documentType,
1728
- children: revisionHistoryVisible ? isLoadingOperations ? /* @__PURE__ */ jsx(EditorLoader, { message: "Loading operations" }) : /* @__PURE__ */ jsx(RevisionHistory, {
1640
+ children: revisionHistoryVisible ? /* @__PURE__ */ jsx(RevisionHistory, {
1729
1641
  documentTitle: documentName ?? "",
1730
1642
  documentId: documentId ?? "",
1731
- globalOperations,
1732
- localOperations,
1643
+ operations: historyOperations,
1644
+ isLoading: isLoadingHistory,
1645
+ hasNextPage: historyHasNextPage,
1646
+ onLoadNextPage: fetchNextHistoryPage,
1647
+ error: historyError,
1648
+ scopes,
1649
+ scope: selectedScope,
1650
+ onScopeChange: setOperationScope,
1733
1651
  onClose: () => setRevisionHistoryVisible(false),
1734
1652
  documentState: document.state,
1735
1653
  onCopyState: () => {
@@ -1749,7 +1667,7 @@ const DocumentEditor = (props) => {
1749
1667
  children: !editorError?.error && /* @__PURE__ */ jsx(EditorComponent, {
1750
1668
  context: {
1751
1669
  readMode: !!selectedTimelineItem,
1752
- selectedTimelineRevision: getRevisionFromDate(selectedTimelineItem?.startDate, selectedTimelineItem?.endDate, globalOperations)
1670
+ selectedTimelineRevision: (isLoadingGlobal || globalHasNextPage) && !globalError ? void 0 : getRevisionFromDate(selectedTimelineItem?.startDate, selectedTimelineItem?.endDate, globalOperations)
1753
1671
  },
1754
1672
  documentId: document.header.id
1755
1673
  }, `${editorBundleKey}:${documentId}`)
@@ -1758,8 +1676,35 @@ const DocumentEditor = (props) => {
1758
1676
  });
1759
1677
  };
1760
1678
  //#endregion
1679
+ //#region src/components/footer.tsx
1680
+ const Footer$1 = () => {
1681
+ return /* @__PURE__ */ jsxs(Footer, { children: [
1682
+ /* @__PURE__ */ jsx(FooterLink, {
1683
+ onClick: () => setBannerShown(true),
1684
+ children: /* @__PURE__ */ jsx(Trans, { i18nKey: "footer.cookiePolicy" })
1685
+ }),
1686
+ /* @__PURE__ */ jsx(FooterLink, {
1687
+ onClick: () => showPHModal({ type: "disclaimer" }),
1688
+ children: /* @__PURE__ */ jsx(Trans, { i18nKey: "footer.disclaimer" })
1689
+ }),
1690
+ /* @__PURE__ */ jsx(FooterLink, {
1691
+ id: "ph-logo-link",
1692
+ onClick: () => openUrl("https://www.powerhouse.inc/"),
1693
+ children: /* @__PURE__ */ jsx(Trans, {
1694
+ i18nKey: "footer.builtWith",
1695
+ components: { icon: /* @__PURE__ */ jsx(Icon, {
1696
+ name: "PowerhouseLogoSmall",
1697
+ size: 16,
1698
+ className: "mx-1"
1699
+ }) }
1700
+ })
1701
+ })
1702
+ ] });
1703
+ };
1704
+ //#endregion
1761
1705
  //#region src/components/root.tsx
1762
1706
  function Root() {
1707
+ const isEmbedded = useIsEmbedded();
1763
1708
  return /* @__PURE__ */ jsx("div", {
1764
1709
  className: "h-screen bg-background",
1765
1710
  children: /* @__PURE__ */ jsx("div", {
@@ -1768,7 +1713,7 @@ function Root() {
1768
1713
  tabIndex: 0,
1769
1714
  children: /* @__PURE__ */ jsxs(Suspense, {
1770
1715
  name: "Root",
1771
- children: [!useIsEmbedded() && /* @__PURE__ */ jsx(Sidebar, {}), /* @__PURE__ */ jsx("div", {
1716
+ children: [!isEmbedded && /* @__PURE__ */ jsx(Sidebar, {}), /* @__PURE__ */ jsx("div", {
1772
1717
  className: "relative flex-1 overflow-auto",
1773
1718
  children: /* @__PURE__ */ jsx(Outlet, {})
1774
1719
  })]
@@ -1950,6 +1895,64 @@ const Router = () => {
1950
1895
  return /* @__PURE__ */ jsx(RouterProvider, { router });
1951
1896
  };
1952
1897
  //#endregion
1898
+ //#region src/components/search-bar.tsx
1899
+ const defaultFilterItems = [
1900
+ {
1901
+ id: "project",
1902
+ label: ".project",
1903
+ icon: /* @__PURE__ */ jsx(Icon, {
1904
+ name: "Project",
1905
+ color: "#FF6A55",
1906
+ size: 16
1907
+ })
1908
+ },
1909
+ {
1910
+ id: "budget",
1911
+ label: ".budget",
1912
+ icon: /* @__PURE__ */ jsx(Icon, {
1913
+ name: "BarChart",
1914
+ color: "#8E55EA",
1915
+ size: 16
1916
+ })
1917
+ },
1918
+ {
1919
+ id: "profile",
1920
+ label: ".profile",
1921
+ icon: /* @__PURE__ */ jsx(Icon, {
1922
+ name: "Person",
1923
+ color: "#3E90F0",
1924
+ size: 16
1925
+ })
1926
+ },
1927
+ {
1928
+ id: "legal",
1929
+ label: ".legal",
1930
+ icon: /* @__PURE__ */ jsx(Icon, {
1931
+ name: "Briefcase",
1932
+ color: "#4BAB71",
1933
+ size: 16
1934
+ })
1935
+ },
1936
+ {
1937
+ id: "atlas",
1938
+ label: ".Atlas",
1939
+ icon: /* @__PURE__ */ jsx(Icon, {
1940
+ name: "Globe",
1941
+ color: "#FF8A00",
1942
+ size: 16
1943
+ })
1944
+ }
1945
+ ];
1946
+ const SearchBar = () => {
1947
+ const { t } = useTranslation();
1948
+ return /* @__PURE__ */ jsx(ConnectSearchBar, {
1949
+ className: "m-4 shrink-0 bg-muted",
1950
+ placeholder: t("searchbar.placeholder"),
1951
+ filterLabel: t("searchbar.filterLabel"),
1952
+ filterItems: defaultFilterItems
1953
+ });
1954
+ };
1955
+ //#endregion
1953
1956
  //#region src/components/sidebar.tsx
1954
1957
  function Sidebar() {
1955
1958
  const user = useUser();
@@ -2130,20 +2133,20 @@ const MigrationBanner = () => {
2130
2133
  };
2131
2134
  //#endregion
2132
2135
  //#region src/components/modal/modals-container.tsx
2133
- const AddDriveModal$1 = lazy(() => import("./AddDriveModal-xAw48kdl.js").then((m) => ({ default: m.AddDriveModal })));
2134
- const ClearStorageModal = lazy(() => import("./ClearStorageModal-C1BXHTq3.js").then((m) => ({ default: m.ClearStorageModal })));
2135
- const CookiesPolicyModal = lazy(() => import("./CookiesPolicyModal-BpyPPyxS.js").then((m) => ({ default: m.CookiesPolicyModal })));
2136
- const CreateDocumentModal$1 = lazy(() => import("./CreateDocumentModal-C45W8J0V.js").then((m) => ({ default: m.CreateDocumentModal })));
2137
- const DebugSettingsModal = lazy(() => import("./DebugSettingsModal-ClqHzcxM.js").then((m) => ({ default: m.DebugSettingsModal })));
2138
- const DeleteDriveModal = lazy(() => import("./DeleteDriveModal-Dug84t1j.js").then((m) => ({ default: m.DeleteDriveModal })));
2139
- const DeleteItemModal = lazy(() => import("./DeleteItemModal-mMzy3V8o.js").then((m) => ({ default: m.DeleteItemModal })));
2140
- const DisclaimerModal = lazy(() => import("./DisclaimerModal-DvpD5dPM.js").then((m) => ({ default: m.DisclaimerModal })));
2141
- const DriveSettingsModal$1 = lazy(() => import("./DriveSettingsModal-C6-mRQiv.js").then((m) => ({ default: m.DriveSettingsModal })));
2142
- const DownloadDocumentWithErrorsModal = lazy(() => import("./DownloadDocumentWithErrorsModal-IKl0ljQb.js").then((m) => ({ default: m.DownloadDocumentWithErrorsModal })));
2143
- const DocumentVersionUnsupportedModal = lazy(() => import("./DocumentVersionUnsupportedModal-C3Q0V75l.js").then((m) => ({ default: m.DocumentVersionUnsupportedModal })));
2144
- const ConfirmDocumentUpgradeModal = lazy(() => import("./ConfirmDocumentUpgradeModal-FUOhZsk7.js").then((m) => ({ default: m.ConfirmDocumentUpgradeModal })));
2145
- const SettingsModal$1 = lazy(() => import("./SettingsModal-CPv3_jmp.js").then((m) => ({ default: m.SettingsModal })));
2146
- const UpgradeDriveModal = lazy(() => import("./UpgradeDriveModal-_8t9slxQ.js").then((m) => ({ default: m.UpgradeDriveModal })));
2136
+ const AddDriveModal$1 = lazy(() => import("./AddDriveModal-DhNfP94R.js").then((m) => ({ default: m.AddDriveModal })));
2137
+ const ClearStorageModal = lazy(() => import("./ClearStorageModal-DyU-FIgY.js").then((m) => ({ default: m.ClearStorageModal })));
2138
+ const CookiesPolicyModal = lazy(() => import("./CookiesPolicyModal-BLw4pnhn.js").then((m) => ({ default: m.CookiesPolicyModal })));
2139
+ const CreateDocumentModal$1 = lazy(() => import("./CreateDocumentModal-Bmv0V-kY.js").then((m) => ({ default: m.CreateDocumentModal })));
2140
+ const DebugSettingsModal = lazy(() => import("./DebugSettingsModal-CwUN9gIr.js").then((m) => ({ default: m.DebugSettingsModal })));
2141
+ const DeleteDriveModal = lazy(() => import("./DeleteDriveModal-CjGnG0ht.js").then((m) => ({ default: m.DeleteDriveModal })));
2142
+ const DeleteItemModal = lazy(() => import("./DeleteItemModal-BbY_4_xT.js").then((m) => ({ default: m.DeleteItemModal })));
2143
+ const DisclaimerModal = lazy(() => import("./DisclaimerModal-DuglmEwL.js").then((m) => ({ default: m.DisclaimerModal })));
2144
+ const DriveSettingsModal$1 = lazy(() => import("./DriveSettingsModal-d1L0eYof.js").then((m) => ({ default: m.DriveSettingsModal })));
2145
+ const DownloadDocumentWithErrorsModal = lazy(() => import("./DownloadDocumentWithErrorsModal-BsPQ5qgy.js").then((m) => ({ default: m.DownloadDocumentWithErrorsModal })));
2146
+ const DocumentVersionUnsupportedModal = lazy(() => import("./DocumentVersionUnsupportedModal-C4fy7fZ6.js").then((m) => ({ default: m.DocumentVersionUnsupportedModal })));
2147
+ const ConfirmDocumentUpgradeModal = lazy(() => import("./ConfirmDocumentUpgradeModal-DcOZy20m.js").then((m) => ({ default: m.ConfirmDocumentUpgradeModal })));
2148
+ const SettingsModal$1 = lazy(() => import("./SettingsModal-CgjW-M2a.js").then((m) => ({ default: m.SettingsModal })));
2149
+ const UpgradeDriveModal = lazy(() => import("./UpgradeDriveModal-CXX6D5Au.js").then((m) => ({ default: m.UpgradeDriveModal })));
2147
2150
  const modalComponents = {
2148
2151
  addDrive: AddDriveModal$1,
2149
2152
  clearStorage: ClearStorageModal,
@@ -2157,13 +2160,13 @@ const modalComponents = {
2157
2160
  downloadDocumentWithErrors: DownloadDocumentWithErrorsModal,
2158
2161
  documentVersionUnsupported: DocumentVersionUnsupportedModal,
2159
2162
  confirmDocumentUpgrade: ConfirmDocumentUpgradeModal,
2160
- inspector: lazy(() => import("./InspectorModal-7FQBTEzt.js").then((m) => ({ default: m.InspectorModal }))),
2163
+ inspector: lazy(() => import("./InspectorModal-Cd8kbCV8.js").then((m) => ({ default: m.InspectorModal }))),
2161
2164
  settings: SettingsModal$1,
2162
2165
  upgradeDrive: UpgradeDriveModal,
2163
- missingPackage: lazy(() => import("./MissingPackageModal-nBnExqO0.js").then((m) => ({ default: m.ConnectMissingPackageModal }))),
2164
- driveAuthRequired: lazy(() => import("./DriveAuthRequiredModal-DVK7ZQzO.js").then((m) => ({ default: m.DriveAuthRequiredModal }))),
2165
- openFileDocuments: lazy(() => import("./OpenFileDocumentsModal-DGaYJpXW.js").then((m) => ({ default: m.OpenFileDocumentsModal }))),
2166
- login: lazy(() => import("./LoginModal-CybbOm3N.js").then((m) => ({ default: m.LoginModal })))
2166
+ missingPackage: lazy(() => import("./MissingPackageModal-HFUPQMML.js").then((m) => ({ default: m.ConnectMissingPackageModal }))),
2167
+ driveAuthRequired: lazy(() => import("./DriveAuthRequiredModal-CtsB0kwH.js").then((m) => ({ default: m.DriveAuthRequiredModal }))),
2168
+ openFileDocuments: lazy(() => import("./OpenFileDocumentsModal-BQsaBP4x.js").then((m) => ({ default: m.OpenFileDocumentsModal }))),
2169
+ login: lazy(() => import("./LoginModal-CI0ogC2-.js").then((m) => ({ default: m.LoginModal })))
2167
2170
  };
2168
2171
  const ModalsContainer = lazy(() => {
2169
2172
  return Promise.resolve({ default: () => {
@@ -2201,12 +2204,48 @@ const ServiceWorkerUpdatePrompt = () => {
2201
2204
  });
2202
2205
  };
2203
2206
  //#endregion
2207
+ //#region src/components/stored-documents-refused.tsx
2208
+ /** Boot screen for a reactor that refused this browser's stored documents. */
2209
+ function StoredDocumentsRefusedFallback({ error }) {
2210
+ return /* @__PURE__ */ jsx("div", {
2211
+ className: "z-10 mx-auto flex max-w-[80%] flex-1 items-center justify-center p-6",
2212
+ children: /* @__PURE__ */ jsxs("div", {
2213
+ role: "alert",
2214
+ className: "w-full max-w-lg rounded-lg border border-border bg-card p-6 text-foreground shadow-sm",
2215
+ children: [
2216
+ /* @__PURE__ */ jsxs("div", {
2217
+ className: "mb-3 flex items-center gap-2",
2218
+ children: [/* @__PURE__ */ jsx(Icon, {
2219
+ name: "Error",
2220
+ className: "size-5 shrink-0 text-destructive"
2221
+ }), /* @__PURE__ */ jsx("h1", {
2222
+ className: "text-lg font-semibold text-foreground",
2223
+ children: "Connect cannot open this browser's documents"
2224
+ })]
2225
+ }),
2226
+ /* @__PURE__ */ jsx("p", {
2227
+ className: "mb-4 text-sm wrap-break-word text-foreground",
2228
+ children: error.message
2229
+ }),
2230
+ /* @__PURE__ */ jsx(PowerhouseButton, {
2231
+ type: "button",
2232
+ onClick: () => {
2233
+ window.location.reload();
2234
+ },
2235
+ className: "px-3 py-1.5 text-base font-medium",
2236
+ children: "Reload"
2237
+ })
2238
+ ]
2239
+ })
2240
+ });
2241
+ }
2242
+ //#endregion
2204
2243
  //#region src/components/app-loader.tsx
2205
- const CookieBanner$1 = lazy(() => import("./cookie-banner-BH6bIq24.js").then((m) => ({ default: m.CookieBanner })));
2244
+ const CookieBanner$1 = lazy(() => import("./cookie-banner-BxXvPdIY.js").then((m) => ({ default: m.CookieBanner })));
2206
2245
  const AppLoader = (props) => {
2207
- const Load = lazy(() => import("./load-Cu7S9GPc.js").then((m) => m.loadComponent(props.localPackage)));
2246
+ const Load = lazy(() => import("./load-ftpCX1HI.js").then((m) => m.loadComponent(props.localPackage)));
2208
2247
  return /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsxs(ErrorBoundary$1, {
2209
- fallbackRender: (props) => /* @__PURE__ */ jsx(AppSkeleton, { children: /* @__PURE__ */ jsx(DetailedFallback, { ...props }) }),
2248
+ fallbackRender: (props) => /* @__PURE__ */ jsx(AppSkeleton, { children: isStoredDocumentsRefused(props.error) ? /* @__PURE__ */ jsx(StoredDocumentsRefusedFallback, { error: props.error }) : /* @__PURE__ */ jsx(DetailedFallback, { ...props }) }),
2210
2249
  resetKeys: [props.localPackage],
2211
2250
  loggerContext: ["Connect"],
2212
2251
  children: [
@@ -2238,7 +2277,7 @@ const AppLoader = (props) => {
2238
2277
  }) });
2239
2278
  };
2240
2279
  //#endregion
2241
- 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 };
2280
+ 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 };
2242
2281
 
2243
- //# sourceMappingURL=app-loader-DyGcBhGV.js.map
2244
- //# debugId=299d9fea-95ac-5d8c-875e-9d856ad66b70
2282
+ //# sourceMappingURL=app-loader-l5wtQh5z.js.map
2283
+ //# debugId=021db84f-f706-5644-9182-2425eb27187c