@powerhousedao/connect-core 6.2.3-dev.78 → 6.2.3-dev.80

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 (110) hide show
  1. package/dist/backend-auth-gate-DNKg_v4Y.js +65 -0
  2. package/dist/backend-auth-gate-DNKg_v4Y.js.map +1 -0
  3. package/dist/backend-auth-gate-DiFpY5rt.d.ts +19 -0
  4. package/dist/backend-auth-gate-DiFpY5rt.d.ts.map +1 -0
  5. package/dist/{index-q4bHCPoJ.d.ts → index-BMlma368.d.ts} +30 -7
  6. package/dist/index-BMlma368.d.ts.map +1 -0
  7. package/dist/{index-DQ0nbYne.d.ts → index-Beqvfchq.d.ts} +16 -3
  8. package/dist/index-Beqvfchq.d.ts.map +1 -0
  9. package/dist/{index-Cm8rYUhs.d.ts → index-Bfl1VYdj.d.ts} +21 -2
  10. package/dist/index-Bfl1VYdj.d.ts.map +1 -0
  11. package/dist/index.d.ts +5 -4
  12. package/dist/index.js +10 -9
  13. package/dist/{multi-reactor-CCMzwoMi.js → multi-reactor-D_2B4jmu.js} +2 -2
  14. package/dist/multi-reactor-D_2B4jmu.js.map +1 -0
  15. package/dist/{plugin-host-HhfxwUZN.js → plugin-host-CMr7lN40.js} +2 -2
  16. package/dist/{plugin-host-HhfxwUZN.js.map → plugin-host-CMr7lN40.js.map} +1 -1
  17. package/dist/{plugin-ui-W0ma6oKJ.js → plugin-ui-DHlnXJmN.js} +3 -3
  18. package/dist/{plugin-ui-W0ma6oKJ.js.map → plugin-ui-DHlnXJmN.js.map} +1 -1
  19. package/dist/plugins.js +2 -2
  20. package/dist/route-tree-D0aszMrk.js.map +1 -1
  21. package/dist/route-tree.d.ts +16 -16
  22. package/dist/route-tree.d.ts.map +1 -1
  23. package/dist/{router-DHL9GT3P.js → router-C4hDJ4_J.js} +10 -4
  24. package/dist/router-C4hDJ4_J.js.map +1 -0
  25. package/dist/router.d.ts +2 -2
  26. package/dist/router.js +4 -4
  27. package/dist/routes/__root.d.ts +3 -3
  28. package/dist/routes/__root.d.ts.map +1 -1
  29. package/dist/routes/__root.js +3 -3
  30. package/dist/routes/__root.js.map +1 -1
  31. package/dist/routes/_app/$.d.ts +3 -3
  32. package/dist/routes/_app/$.d.ts.map +1 -1
  33. package/dist/routes/_app/$.js +3 -3
  34. package/dist/routes/_app/$.js.map +1 -1
  35. package/dist/routes/_app/d/$driveSlug/{-$nodeSlug}.d.ts +5 -5
  36. package/dist/routes/_app/d/$driveSlug/{-$nodeSlug}.d.ts.map +1 -1
  37. package/dist/routes/_app/d/$driveSlug/{-$nodeSlug}.js +3 -3
  38. package/dist/routes/_app/d/$driveSlug/{-$nodeSlug}.js.map +1 -1
  39. package/dist/routes/_app/d/$driveSlug.d.ts +4 -4
  40. package/dist/routes/_app/d/$driveSlug.d.ts.map +1 -1
  41. package/dist/routes/_app/d/$driveSlug.js +3 -3
  42. package/dist/routes/_app/d/$driveSlug.js.map +1 -1
  43. package/dist/routes/_app/index.d.ts +41 -2
  44. package/dist/routes/_app/index.d.ts.map +1 -1
  45. package/dist/routes/_app/index.js +2 -2
  46. package/dist/routes/_app/index.js.map +1 -1
  47. package/dist/routes/_app/settings/appearance.d.ts +3 -3
  48. package/dist/routes/_app/settings/appearance.d.ts.map +1 -1
  49. package/dist/routes/_app/settings/appearance.js +3 -3
  50. package/dist/routes/_app/settings/appearance.js.map +1 -1
  51. package/dist/routes/_app/settings/packages.d.ts +3 -3
  52. package/dist/routes/_app/settings/packages.d.ts.map +1 -1
  53. package/dist/routes/_app/settings/packages.js +3 -3
  54. package/dist/routes/_app/settings/packages.js.map +1 -1
  55. package/dist/routes/_app/settings.d.ts +3 -3
  56. package/dist/routes/_app/settings.d.ts.map +1 -1
  57. package/dist/routes/_app/settings.js +3 -3
  58. package/dist/routes/_app/settings.js.map +1 -1
  59. package/dist/routes/_app.d.ts +41 -2
  60. package/dist/routes/_app.d.ts.map +1 -1
  61. package/dist/routes/_app.js +2 -2
  62. package/dist/routes/_app.js.map +1 -1
  63. package/dist/{runtime-D5hpzfgT.js → runtime-D20_LlB_.js} +64 -14
  64. package/dist/runtime-D20_LlB_.js.map +1 -0
  65. package/dist/runtime-config-D3YPhYyj.d.ts.map +1 -1
  66. package/dist/runtime-config.js +11 -5
  67. package/dist/runtime-config.js.map +1 -1
  68. package/dist/{runtime-flag-U8y14Nth.js → runtime-flag-DvxW_SD4.js} +2 -2
  69. package/dist/{runtime-flag-U8y14Nth.js.map → runtime-flag-DvxW_SD4.js.map} +1 -1
  70. package/dist/runtime.d.ts +3 -2
  71. package/dist/runtime.js +5 -4
  72. package/dist/{sidebar-CRv_DvBV.js → sidebar-C2y4mZnX.js} +13 -2
  73. package/dist/{sidebar-CRv_DvBV.js.map → sidebar-C2y4mZnX.js.map} +1 -1
  74. package/dist/{views-anOyTI7u.js → views-BtUVboYl.js} +79 -6
  75. package/dist/views-BtUVboYl.js.map +1 -0
  76. package/dist/views.d.ts +3 -2
  77. package/dist/views.js +3 -2
  78. package/package.json +8 -8
  79. package/src/router/index.ts +14 -2
  80. package/src/router/modals.tsx +3 -3
  81. package/src/router/route-options.ts +16 -0
  82. package/src/router/sidebar.ts +22 -0
  83. package/src/routes/__root.tsx +3 -2
  84. package/src/routes/_app/$.tsx +3 -2
  85. package/src/routes/_app/d/$driveSlug/{-$nodeSlug}.tsx +3 -2
  86. package/src/routes/_app/d/$driveSlug.tsx +3 -2
  87. package/src/routes/_app/index.tsx +2 -2
  88. package/src/routes/_app/settings/appearance.tsx +3 -3
  89. package/src/routes/_app/settings/packages.tsx +3 -3
  90. package/src/routes/_app/settings.tsx +3 -2
  91. package/src/routes/_app.tsx +2 -2
  92. package/src/runtime/app-reactor-client.ts +63 -12
  93. package/src/runtime/backend-auth-gate.ts +57 -0
  94. package/src/runtime/drive-relist.ts +18 -0
  95. package/src/runtime/graphql-routable-backend.ts +2 -2
  96. package/src/runtime/index.ts +8 -1
  97. package/src/runtime/reactor-client-mode.ts +26 -9
  98. package/src/runtime/runtime-config.ts +12 -1
  99. package/src/views/backend-auth-gate.tsx +76 -0
  100. package/src/views/directory-listing.tsx +2 -2
  101. package/src/views/editor-host.tsx +3 -3
  102. package/src/views/index.ts +5 -0
  103. package/src/views/layout.tsx +6 -2
  104. package/dist/index-Cm8rYUhs.d.ts.map +0 -1
  105. package/dist/index-DQ0nbYne.d.ts.map +0 -1
  106. package/dist/index-q4bHCPoJ.d.ts.map +0 -1
  107. package/dist/multi-reactor-CCMzwoMi.js.map +0 -1
  108. package/dist/router-DHL9GT3P.js.map +0 -1
  109. package/dist/runtime-D5hpzfgT.js.map +0 -1
  110. package/dist/views-anOyTI7u.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-D20_LlB_.js","names":[],"sources":["../src/runtime/multi-reactor-flag.ts","../src/runtime/reactor-worker-flag.ts","../src/runtime/reactor-client-mode.ts","../src/runtime/app-reactor-client.ts","../src/runtime/ph-store.ts","../src/runtime/local-package.ts"],"sourcesContent":["import { getRuntimeConfig } from \"./runtime-config.js\";\n\n// Config only: the flag is in the worker fingerprint, so a per-tab value would split tabs.\nexport function isMultiReactorEnabled(): boolean {\n return getRuntimeConfig().connect.instance?.multiReactor ?? false;\n}\n","import { getRuntimeConfig } from \"./runtime-config.js\";\nimport {\n isRuntimeFlagEnabled,\n resolveRuntimeFlag,\n type RuntimeFlagInput,\n} from \"./runtime-flag.js\";\n\nexport const REACTOR_WORKER_QUERY_KEY = \"reactorWorker\";\nexport const REACTOR_WORKER_STORAGE_KEY = \"ph:reactorWorker\";\n\nexport type ReactorWorkerFlagInput = RuntimeFlagInput;\n\nexport function resolveReactorWorkerEnabled(\n input: ReactorWorkerFlagInput,\n): boolean {\n return resolveRuntimeFlag(input);\n}\n\nexport function isReactorWorkerEnabled(): boolean {\n return isRuntimeFlagEnabled({\n queryKey: REACTOR_WORKER_QUERY_KEY,\n storageKey: REACTOR_WORKER_STORAGE_KEY,\n readConfigFlag: () =>\n getRuntimeConfig().connect.instance?.reactorWorker ?? false,\n });\n}\n","import type { PHReactorClientMode } from \"@powerhousedao/shared/clis\";\nimport { getRuntimeConfig } from \"./runtime-config.js\";\nimport { isMultiReactorEnabled } from \"./multi-reactor-flag.js\";\nimport { isReactorWorkerEnabled } from \"./reactor-worker-flag.js\";\n\n/**\n * Which of the four reactor clients Connect boots. Standardized by the\n * multi-reactor work; this resolves the single selector Connect acts on:\n *\n * in-process | worker | remote | router\n *\n * Precedence: an explicit `?reactorClient=` query override (kept in\n * localStorage so it survives a reload), then a stored override, then the\n * served `connect.instance.reactorClient` (projected from the host's reactor\n * backends), then the LEGACY booleans — so a config that only sets\n * reactorWorker/multiReactor behaves exactly as before.\n *\n * `remote` is the only mode that builds NO local reactor (no PGlite): it is a\n * pure client to a remote Switchboard. `router` keeps the local reactor and\n * adds remote backends; `worker`/`in-process` are the local reactor alone.\n */\nexport type ReactorClientMode = PHReactorClientMode;\n\nconst MODES: ReadonlySet<string> = new Set([\n \"in-process\",\n \"worker\",\n \"remote\",\n \"router\",\n]);\n\nexport const REACTOR_CLIENT_QUERY_KEY = \"reactorClient\";\nexport const REACTOR_CLIENT_STORAGE_KEY = \"ph:reactorClient\";\n\nconst asMode = (value: string | null | undefined) =>\n value && MODES.has(value) ? (value as ReactorClientMode) : undefined;\n\nfunction fromTab(): ReactorClientMode | undefined {\n if (typeof window === \"undefined\") return undefined;\n let query: ReactorClientMode | undefined;\n try {\n query = asMode(\n new URLSearchParams(window.location.search).get(REACTOR_CLIENT_QUERY_KEY),\n );\n } catch {\n return undefined;\n }\n try {\n if (query) {\n window.localStorage.setItem(REACTOR_CLIENT_STORAGE_KEY, query);\n return query;\n }\n return asMode(window.localStorage.getItem(REACTOR_CLIENT_STORAGE_KEY));\n } catch {\n // Blocked storage: the query still applies for this page load.\n return query;\n }\n}\n\nexport function resolveReactorClientMode(): ReactorClientMode {\n const override = fromTab();\n if (override) return override;\n const configured = getRuntimeConfig().connect.instance?.reactorClient;\n if (configured && MODES.has(configured)) return configured;\n // Legacy fallback — unchanged behaviour for configs without reactorClient.\n if (isMultiReactorEnabled()) return \"router\";\n if (isReactorWorkerEnabled()) return \"worker\";\n return \"in-process\";\n}\n","import type { IReactorClient } from \"@powerhousedao/reactor\";\nimport {\n getDrives,\n getFullReactorClient,\n getPHStore,\n recordBackendAuthError,\n setBackendAuth,\n setDrives,\n setFullReactorClient,\n} from \"@powerhousedao/reactor-browser\";\nimport type { DocumentDriveDocument } from \"@powerhousedao/shared/document-drive\";\nimport type { ISigner } from \"@powerhousedao/shared/document-model\";\nimport { logger } from \"document-model\";\nimport type * as MultiReactor from \"./multi-reactor.js\";\nimport type {\n LoadedDocumentModels,\n LocalReactorModule,\n} from \"./multi-reactor.js\";\nimport type { ReactorClientMode } from \"./reactor-client-mode.js\";\nimport { resolveReactorClientMode } from \"./reactor-client-mode.js\";\nimport { isReactorWorkerEnabled } from \"./reactor-worker-flag.js\";\nimport { setDriveRelister } from \"./drive-relist.js\";\n\ntype MultiReactorModule = typeof MultiReactor;\n\nexport type AppReactorClientParams = {\n mode: ReactorClientMode;\n /** Built, and ready (the worker has started up). Undefined in `remote`\n * mode, where no local reactor is built. */\n module: LocalReactorModule | undefined;\n remoteDriveUrl: string | undefined;\n /** Signs routed writes; unsigned (anonymous) without one. */\n signer?: ISigner;\n documentModelModules: LoadedDocumentModels;\n loadRouter?: () => Promise<MultiReactorModule>;\n};\n\nconst loadMultiReactor = (): Promise<MultiReactorModule> =>\n import(\"./multi-reactor.js\");\n\n/** What a mode builds in the tab; `remote` builds no local reactor (no PGlite). */\nexport type ReactorClientPlan = {\n mode: ReactorClientMode;\n /** `router`: the local reactor plus remote backends. */\n multiReactor: boolean;\n needsLocalReactor: boolean;\n /** The local reactor runs in the shared worker. */\n localAsWorker: boolean;\n};\n\nexport function planReactorClient(\n mode: ReactorClientMode = resolveReactorClientMode(),\n): ReactorClientPlan {\n const needsLocalReactor = mode !== \"remote\";\n return {\n mode,\n multiReactor: mode === \"router\",\n needsLocalReactor,\n localAsWorker:\n needsLocalReactor &&\n (mode === \"worker\" || (mode === \"router\" && isReactorWorkerEnabled())),\n };\n}\n\n/** Builds the tab's reactor, e.g. Connect's PGlite or worker reactor. */\nexport type LocalReactorFactory = (\n plan: ReactorClientPlan,\n) => Promise<LocalReactorModule>;\n\nexport type CreateAppReactorClientOptions = Omit<\n AppReactorClientParams,\n \"mode\" | \"module\"\n> & {\n plan?: ReactorClientPlan;\n /** Called only when the plan needs a local reactor. */\n localReactor?: LocalReactorFactory;\n};\n\n/** Builds the local reactor the plan needs, then selects the app's client. */\nexport async function createAppReactorClient(\n options: CreateAppReactorClientOptions,\n) {\n const { localReactor, ...params } = options;\n const plan = options.plan ?? planReactorClient();\n let module: LocalReactorModule | undefined;\n if (plan.needsLocalReactor) {\n if (!localReactor) {\n throw new Error(\n `reactorClient mode \"${plan.mode}\" needs a local reactor; pass localReactor`,\n );\n }\n module = await localReactor(plan);\n }\n const client = await selectAppReactorClient({\n ...params,\n mode: plan.mode,\n module,\n });\n return { plan, module, client };\n}\n\n/**\n * Pick the client for the resolved mode:\n * - `in-process` / `worker`: the local client, router module never loaded.\n * - `router`: the local reactor plus remote backends.\n * - `remote`: a remote-only router — a full IReactorClient that proxies to\n * the Switchboard over GraphQL with NO local reactor (no PGlite).\n */\nexport async function selectAppReactorClient(\n params: AppReactorClientParams,\n): Promise<IReactorClient> {\n if (params.mode === \"remote\") {\n setFullReactorClient(undefined);\n const remote = await buildRemoteOnly(params);\n setFullReactorClient(remote);\n return remote;\n }\n if (params.mode !== \"router\") {\n if (getPHStore()?.fullReactorClient) {\n setFullReactorClient(undefined);\n }\n return requireModule(params).client;\n }\n setFullReactorClient(undefined);\n const routed = await buildRouter(params);\n setFullReactorClient(routed);\n return routed ?? requireModule(params).client;\n}\n\nfunction requireModule(params: AppReactorClientParams): LocalReactorModule {\n if (!params.module) {\n throw new Error(\n `reactorClient mode \"${params.mode}\" needs a local reactor, but none was built`,\n );\n }\n return params.module;\n}\n\n/**\n * A Switchboard that cannot answer must not fail boot: list the tab's drives.\n *\n * A Switchboard that REFUSES the caller (401/403) must not fail boot either:\n * the refusal is recorded (`backendAuth`) so the root view shows the sign-in\n * gate, and the drive list is empty until the user is someone the backend\n * accepts. A remote-only client has no local drives to fall back on.\n */\nexport async function getAppDrives(\n client: IReactorClient,\n local: IReactorClient,\n): Promise<DocumentDriveDocument[]> {\n if (client === local) {\n try {\n const drives = await getDrives(local);\n setBackendAuth(undefined);\n return drives;\n } catch (error) {\n if (recordBackendAuthError(error, \"switchboard\")) {\n logger.warn(\n \"The Switchboard refused this caller; showing the sign-in gate: @error\",\n error,\n );\n return [];\n }\n throw error;\n }\n }\n try {\n const drives = await getDrives(client);\n setBackendAuth(undefined);\n return drives;\n } catch (error) {\n if (recordBackendAuthError(error, \"switchboard\")) {\n logger.warn(\n \"A remote backend refused this caller; listing the local reactor's drives: @error\",\n error,\n );\n } else {\n logger.warn(\n \"Could not list drives through the router; listing the local reactor's: @error\",\n error,\n );\n }\n return getDrives(local);\n }\n}\n\n/**\n * The boot refresh of the drives store. It lists through `getAppDrives`\n * whichever client is in use, so a Switchboard that refuses the caller is\n * recorded for the sign-in gate here too: the remote-only client is its own\n * `local`, and a bare `refreshReactorDataClient` would let the 401 escape\n * and fail the boot before the gate can render.\n */\nexport async function refreshAppDrives(\n client: IReactorClient,\n local: IReactorClient,\n): Promise<void> {\n setDrives(await getAppDrives(client, local));\n}\n\n/**\n * Lists the drives again as the caller the tab now is, through the clients\n * boot left in the store: after a sign-in landing or a sign-out that followed\n * a backend's refusal. Nothing to do before boot has set a client.\n */\nexport async function relistAppDrives(): Promise<void> {\n const client = getFullReactorClient();\n if (!client) return;\n const local = getPHStore()?.reactorClientModule?.client ?? client;\n await refreshAppDrives(client, local);\n}\n\n// The backend auth gate relists through drive-relist (see there)\nsetDriveRelister(relistAppDrives);\n\nasync function buildRouter(\n params: AppReactorClientParams,\n): Promise<IReactorClient | undefined> {\n if (!params.remoteDriveUrl) {\n logger.warn(\n \"multiReactor is on but no remote drive URL is configured; using the local reactor only\",\n );\n return undefined;\n }\n let multiReactor: MultiReactorModule;\n try {\n multiReactor = await (params.loadRouter ?? loadMultiReactor)();\n } catch (error) {\n logger.error(\"Could not load the multi-reactor router: @error\", error);\n return undefined;\n }\n const remoteGraphqlUrl = multiReactor.deriveSwitchboardGraphqlUrl(\n params.remoteDriveUrl,\n );\n if (!remoteGraphqlUrl) {\n logger.warn(\n \"multiReactor is on but the remote drive URL @url does not parse; using the local reactor only\",\n params.remoteDriveUrl,\n );\n return undefined;\n }\n logger.info(\"Multi-reactor routing to @url\", remoteGraphqlUrl);\n return multiReactor.buildMultiReactorClient({\n module: requireModule(params),\n remoteGraphqlUrl,\n signer: params.signer,\n documentModelModules: params.documentModelModules,\n });\n}\n\n/**\n * The `remote` mode: a remote-only router — a full IReactorClient that proxies\n * to the Switchboard over GraphQL with NO local backend, so no local reactor\n * and no PGlite are ever built. A missing/unparseable remote URL is a hard\n * config error here (unlike `router`, there is no local reactor to fall back\n * to).\n */\nasync function buildRemoteOnly(\n params: AppReactorClientParams,\n): Promise<IReactorClient> {\n if (!params.remoteDriveUrl) {\n throw new Error(\n 'reactorClient \"remote\" needs a remote drive URL — declare a remote backend / default drive the client can proxy to',\n );\n }\n const multiReactor = await (params.loadRouter ?? loadMultiReactor)();\n const remoteGraphqlUrl = multiReactor.deriveSwitchboardGraphqlUrl(\n params.remoteDriveUrl,\n );\n if (!remoteGraphqlUrl) {\n throw new Error(\n `reactorClient \"remote\": the remote drive URL \"${params.remoteDriveUrl}\" does not resolve to a Switchboard GraphQL endpoint`,\n );\n }\n logger.info(\"Remote reactor client → @url\", remoteGraphqlUrl);\n return multiReactor.buildMultiReactorClient({\n module: undefined,\n remoteGraphqlUrl,\n signer: params.signer,\n documentModelModules: params.documentModelModules,\n });\n}\n","import type { PHStore } from \"@powerhousedao/reactor-browser\";\nimport {\n bindPHStoreToWindow,\n createPHStore,\n getActivePHStore,\n} from \"@powerhousedao/reactor-browser\";\n\n/** The app's PH store: the active one, else a new one bound to `window.ph`.\n * Values set on `window.ph` before carry over. */\nexport function getConnectPHStore(): PHStore {\n const active = getActivePHStore();\n if (active) return active;\n const store = createPHStore();\n bindPHStoreToWindow(store);\n return store;\n}\n","import type { DocumentModelLib } from \"document-model\";\n\n/** A module namespace, as `import * as pkg` or Vite HMR hands it over. */\nexport type ModuleNamespace = Record<string, any> & {\n [Symbol.toStringTag]: \"Module\";\n};\n\nexport type LocalPackage = DocumentModelLib<any> | ModuleNamespace;\n\n/** The package itself, or a loader (`() => import(\"./index.js\")`) that keeps it off the boot path. */\nexport type LocalPackageSource = LocalPackage | (() => Promise<LocalPackage>);\n\n/** Starts loading the project's package; a loader runs once, here. */\nexport function loadLocalPackage(\n source: LocalPackageSource | undefined,\n): DocumentModelLib | Promise<DocumentModelLib> | undefined {\n const pkg = (typeof source === \"function\" ? source() : source) as\n | DocumentModelLib\n | Promise<DocumentModelLib>\n | undefined;\n // Consumers await it; this only stops an early rejection being reported as unhandled.\n if (pkg instanceof Promise) pkg.catch(() => undefined);\n return pkg;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-config-D3YPhYyj.d.ts","names":[],"sources":["../src/runtime/runtime-config.ts"],"mappings":";;;KAgBY;;;EAGV,gBAAgB,QAAQ,eAAe;;iBAGnB,kBACpB,UAAS,2BACR,QAAQ;;;;;iBAiDK,oBAAoB;;iBAKpB,mBAAmB,QAAQ"}
1
+ {"version":3,"file":"runtime-config-D3YPhYyj.d.ts","names":[],"sources":["../src/runtime/runtime-config.ts"],"mappings":";;;KAiBY;;;EAGV,gBAAgB,QAAQ,eAAe;;iBAGnB,kBACpB,UAAS,2BACR,QAAQ;;;;;iBA2DK,oBAAoB;;iBAKpB,mBAAmB,QAAQ"}
@@ -1,26 +1,32 @@
1
1
  import { t as getConnectRegistry } from "./registry-DCZHBP7M.js";
2
2
  import { ConfigLoader } from "@powerhousedao/shared/connect/config-loader";
3
3
  import { JsonConfigAdapter } from "@powerhousedao/shared/connect/json-adapter";
4
+ import { resolveOriginPlaceholders } from "@powerhousedao/shared/connect/origin-placeholder";
4
5
  //#region src/runtime/runtime-config.ts
5
6
  const loader = () => getConnectRegistry().runtimeConfig ??= new ConfigLoader(new JsonConfigAdapter({ path: `${import.meta.env?.BASE_URL ?? "/"}powerhouse.config.json` }));
6
7
  async function loadRuntimeConfig(options = {}) {
7
- const pluginHost = options.plugins ? import("./plugin-host-HhfxwUZN.js").then((n) => n.r).then(async (host) => {
8
+ const pluginHost = options.plugins ? import("./plugin-host-CMr7lN40.js").then((n) => n.r).then(async (host) => {
8
9
  host.registerConnectPlugins(options.plugins);
9
10
  await host.knownPlugins();
10
11
  return host;
11
12
  }) : void 0;
12
- const pluginUi = options.plugins ? import("./plugin-ui-W0ma6oKJ.js").then((n) => n.a) : void 0;
13
+ const pluginUi = options.plugins ? import("./plugin-ui-DHlnXJmN.js").then((n) => n.a) : void 0;
13
14
  pluginHost?.catch(() => void 0);
14
15
  pluginUi?.catch(() => void 0);
15
- const config = await loader().read();
16
+ let config = await loader().read();
17
+ const origin = globalThis.location?.origin;
18
+ if (origin) {
19
+ config = resolveOriginPlaceholders(config, origin);
20
+ loader().prime(config);
21
+ }
16
22
  if (config.connect?.composition) {
17
- const { isRuntimeFlagEnabled } = await import("./runtime-flag-U8y14Nth.js").then((n) => n.r);
23
+ const { isRuntimeFlagEnabled } = await import("./runtime-flag-DvxW_SD4.js").then((n) => n.r);
18
24
  if (isRuntimeFlagEnabled({
19
25
  queryKey: "pluginRunner",
20
26
  storageKey: "ph:flag:pluginRunner",
21
27
  readConfigFlag: () => true
22
28
  })) {
23
- const { applyComposition } = await pluginHost ?? await import("./plugin-host-HhfxwUZN.js").then((n) => n.r);
29
+ const { applyComposition } = await pluginHost ?? await import("./plugin-host-CMr7lN40.js").then((n) => n.r);
24
30
  config.connect = await applyComposition(config.connect);
25
31
  loader().prime(config);
26
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-config.js","names":[],"sources":["../src/runtime/runtime-config.ts"],"sourcesContent":["// Narrow subpaths: the shared/connect barrel puts zod and semver on the boot path.\nimport type { RuntimePowerhouseConfig } from \"@powerhousedao/shared/connect\";\nimport { ConfigLoader } from \"@powerhousedao/shared/connect/config-loader\";\nimport { JsonConfigAdapter } from \"@powerhousedao/shared/connect/json-adapter\";\nimport type { ConnectPluginDefinition } from \"../plugins/types.js\";\nimport { getConnectRegistry } from \"../registry.js\";\n\n// Shared and made on first use: the app's boot copy makes it, with the app's base.\nconst loader = () =>\n (getConnectRegistry().runtimeConfig ??= new ConfigLoader(\n new JsonConfigAdapter({\n // Optional: Node runs this file untransformed when it prerenders the shell.\n path: `${import.meta.env?.BASE_URL ?? \"/\"}powerhouse.config.json`,\n }),\n ));\n\nexport type LoadRuntimeConfigOptions = {\n /** The host's plugin modules: the composition's transform the config, and\n * the active ones' UI loads (every one's without a composition). */\n plugins?: () => Promise<Record<string, ConnectPluginDefinition>>;\n};\n\nexport async function loadRuntimeConfig(\n options: LoadRuntimeConfigOptions = {},\n): Promise<RuntimePowerhouseConfig> {\n // Beside the config's fetch: the plugin modules and the UI loader don't\n // depend on it; only which plugins' UI loads does.\n const pluginHost = options.plugins\n ? import(\"../plugins/plugin-host.js\").then(async (host) => {\n host.registerConnectPlugins(options.plugins!);\n await host.knownPlugins();\n return host;\n })\n : undefined;\n const pluginUi = options.plugins\n ? import(\"../plugins/plugin-ui.js\")\n : undefined;\n // Handled here so a failed config read doesn't leave them unhandled.\n void pluginHost?.catch(() => undefined);\n void pluginUi?.catch(() => undefined);\n const config = (await loader().read()) as RuntimePowerhouseConfig;\n // R3: a served composition (projected from host.js runner.plugins by the\n // uniform-runners connect runner) runs the plugin pipeline over the config\n // BEFORE the cache warms — every downstream consumer sees the transformed\n // config through the classic paths. Runtime-flagged (query/stored override\n // beats the composition's own presence acting as the config flag); no\n // composition = classic boot, byte-identical.\n if (config.connect?.composition) {\n const { isRuntimeFlagEnabled } = await import(\"./runtime-flag.js\");\n const enabled = isRuntimeFlagEnabled({\n queryKey: \"pluginRunner\",\n storageKey: \"ph:flag:pluginRunner\",\n readConfigFlag: () => true,\n });\n if (enabled) {\n const { applyComposition } =\n (await pluginHost) ?? (await import(\"../plugins/plugin-host.js\"));\n config.connect = await applyComposition(config.connect);\n loader().prime(config);\n }\n }\n if (pluginHost && pluginUi) {\n await pluginHost;\n const { loadConnectPluginUi } = await pluginUi;\n await loadConnectPluginUi(config.connect.composition);\n }\n return config;\n}\n\n/**\n * Returns the cached config synchronously.\n * Must be called after `loadRuntimeConfig()` has resolved.\n */\nexport function getRuntimeConfig(): RuntimePowerhouseConfig {\n return loader().getCached() as RuntimePowerhouseConfig;\n}\n\n/** Warms the cache with `config` without a fetch, e.g. in a test or an embedding app. */\nexport function primeRuntimeConfig(config: RuntimePowerhouseConfig) {\n loader().prime(config);\n}\n"],"mappings":";;;;AAQA,MAAM,eACH,mBAAmB,CAAC,CAAC,kBAAkB,IAAI,aAC1C,IAAI,kBAAkB,EAEpB,MAAM,GAAG,YAAY,KAAK,YAAY,IAAI,wBAC5C,CAAC,CACH;AAQF,eAAsB,kBACpB,UAAoC,CAAC,GACH;CAGlC,MAAM,aAAa,QAAQ,UACvB,OAAO,4BAA4B,CAAA,MAAA,MAAA,EAAA,CAAA,CAAA,CAAC,KAAK,OAAO,SAAS;EACvD,KAAK,uBAAuB,QAAQ,OAAQ;EAC5C,MAAM,KAAK,aAAa;EACxB,OAAO;CACT,CAAC,IACD,KAAA;CACJ,MAAM,WAAW,QAAQ,UACrB,OAAO,0BAA0B,CAAA,MAAA,MAAA,EAAA,CAAA,IACjC,KAAA;CAEJ,YAAiB,YAAY,KAAA,CAAS;CACtC,UAAe,YAAY,KAAA,CAAS;CACpC,MAAM,SAAU,MAAM,OAAO,CAAC,CAAC,KAAK;CAOpC,IAAI,OAAO,SAAS,aAAa;EAC/B,MAAM,EAAE,yBAAyB,MAAM,OAAO,6BAAoB,CAAA,MAAA,MAAA,EAAA,CAAA;EAMlE,IALgB,qBAAqB;GACnC,UAAU;GACV,YAAY;GACZ,sBAAsB;EACxB,CACU,GAAG;GACX,MAAM,EAAE,qBACL,MAAM,cAAgB,MAAM,OAAO,4BAA4B,CAAA,MAAA,MAAA,EAAA,CAAA;GAClE,OAAO,UAAU,MAAM,iBAAiB,OAAO,OAAO;GACtD,OAAO,CAAC,CAAC,MAAM,MAAM;EACvB;CACF;CACA,IAAI,cAAc,UAAU;EAC1B,MAAM;EACN,MAAM,EAAE,wBAAwB,MAAM;EACtC,MAAM,oBAAoB,OAAO,QAAQ,WAAW;CACtD;CACA,OAAO;AACT;;;;;AAMA,SAAgB,mBAA4C;CAC1D,OAAO,OAAO,CAAC,CAAC,UAAU;AAC5B;;AAGA,SAAgB,mBAAmB,QAAiC;CAClE,OAAO,CAAC,CAAC,MAAM,MAAM;AACvB"}
1
+ {"version":3,"file":"runtime-config.js","names":[],"sources":["../src/runtime/runtime-config.ts"],"sourcesContent":["// Narrow subpaths: the shared/connect barrel puts zod and semver on the boot path.\nimport type { RuntimePowerhouseConfig } from \"@powerhousedao/shared/connect\";\nimport { ConfigLoader } from \"@powerhousedao/shared/connect/config-loader\";\nimport { JsonConfigAdapter } from \"@powerhousedao/shared/connect/json-adapter\";\nimport { resolveOriginPlaceholders } from \"@powerhousedao/shared/connect/origin-placeholder\";\nimport type { ConnectPluginDefinition } from \"../plugins/types.js\";\nimport { getConnectRegistry } from \"../registry.js\";\n\n// Shared and made on first use: the app's boot copy makes it, with the app's base.\nconst loader = () =>\n (getConnectRegistry().runtimeConfig ??= new ConfigLoader(\n new JsonConfigAdapter({\n // Optional: Node runs this file untransformed when it prerenders the shell.\n path: `${import.meta.env?.BASE_URL ?? \"/\"}powerhouse.config.json`,\n }),\n ));\n\nexport type LoadRuntimeConfigOptions = {\n /** The host's plugin modules: the composition's transform the config, and\n * the active ones' UI loads (every one's without a composition). */\n plugins?: () => Promise<Record<string, ConnectPluginDefinition>>;\n};\n\nexport async function loadRuntimeConfig(\n options: LoadRuntimeConfigOptions = {},\n): Promise<RuntimePowerhouseConfig> {\n // Beside the config's fetch: the plugin modules and the UI loader don't\n // depend on it; only which plugins' UI loads does.\n const pluginHost = options.plugins\n ? import(\"../plugins/plugin-host.js\").then(async (host) => {\n host.registerConnectPlugins(options.plugins!);\n await host.knownPlugins();\n return host;\n })\n : undefined;\n const pluginUi = options.plugins\n ? import(\"../plugins/plugin-ui.js\")\n : undefined;\n // Handled here so a failed config read doesn't leave them unhandled.\n void pluginHost?.catch(() => undefined);\n void pluginUi?.catch(() => undefined);\n let config = (await loader().read()) as RuntimePowerhouseConfig;\n // A served config names a switchboard this page's server proxies as\n // `${origin}` (the connect runner's `runner.proxy`): resolved here, once,\n // against the page's origin, so every consumer sees absolute URLs and the\n // same config holds from localhost and from a published site alike. Not\n // in Node (the shell prerender): there is no page origin to resolve to.\n const origin = globalThis.location?.origin;\n if (origin) {\n config = resolveOriginPlaceholders(config, origin);\n loader().prime(config);\n }\n // R3: a served composition (projected from host.js runner.plugins by the\n // uniform-runners connect runner) runs the plugin pipeline over the config\n // BEFORE the cache warms — every downstream consumer sees the transformed\n // config through the classic paths. Runtime-flagged (query/stored override\n // beats the composition's own presence acting as the config flag); no\n // composition = classic boot, byte-identical.\n if (config.connect?.composition) {\n const { isRuntimeFlagEnabled } = await import(\"./runtime-flag.js\");\n const enabled = isRuntimeFlagEnabled({\n queryKey: \"pluginRunner\",\n storageKey: \"ph:flag:pluginRunner\",\n readConfigFlag: () => true,\n });\n if (enabled) {\n const { applyComposition } =\n (await pluginHost) ?? (await import(\"../plugins/plugin-host.js\"));\n config.connect = await applyComposition(config.connect);\n loader().prime(config);\n }\n }\n if (pluginHost && pluginUi) {\n await pluginHost;\n const { loadConnectPluginUi } = await pluginUi;\n await loadConnectPluginUi(config.connect.composition);\n }\n return config;\n}\n\n/**\n * Returns the cached config synchronously.\n * Must be called after `loadRuntimeConfig()` has resolved.\n */\nexport function getRuntimeConfig(): RuntimePowerhouseConfig {\n return loader().getCached() as RuntimePowerhouseConfig;\n}\n\n/** Warms the cache with `config` without a fetch, e.g. in a test or an embedding app. */\nexport function primeRuntimeConfig(config: RuntimePowerhouseConfig) {\n loader().prime(config);\n}\n"],"mappings":";;;;;AASA,MAAM,eACH,mBAAmB,CAAC,CAAC,kBAAkB,IAAI,aAC1C,IAAI,kBAAkB,EAEpB,MAAM,GAAG,YAAY,KAAK,YAAY,IAAI,wBAC5C,CAAC,CACH;AAQF,eAAsB,kBACpB,UAAoC,CAAC,GACH;CAGlC,MAAM,aAAa,QAAQ,UACvB,OAAO,4BAA4B,CAAA,MAAA,MAAA,EAAA,CAAA,CAAA,CAAC,KAAK,OAAO,SAAS;EACvD,KAAK,uBAAuB,QAAQ,OAAQ;EAC5C,MAAM,KAAK,aAAa;EACxB,OAAO;CACT,CAAC,IACD,KAAA;CACJ,MAAM,WAAW,QAAQ,UACrB,OAAO,0BAA0B,CAAA,MAAA,MAAA,EAAA,CAAA,IACjC,KAAA;CAEJ,YAAiB,YAAY,KAAA,CAAS;CACtC,UAAe,YAAY,KAAA,CAAS;CACpC,IAAI,SAAU,MAAM,OAAO,CAAC,CAAC,KAAK;CAMlC,MAAM,SAAS,WAAW,UAAU;CACpC,IAAI,QAAQ;EACV,SAAS,0BAA0B,QAAQ,MAAM;EACjD,OAAO,CAAC,CAAC,MAAM,MAAM;CACvB;CAOA,IAAI,OAAO,SAAS,aAAa;EAC/B,MAAM,EAAE,yBAAyB,MAAM,OAAO,6BAAoB,CAAA,MAAA,MAAA,EAAA,CAAA;EAMlE,IALgB,qBAAqB;GACnC,UAAU;GACV,YAAY;GACZ,sBAAsB;EACxB,CACU,GAAG;GACX,MAAM,EAAE,qBACL,MAAM,cAAgB,MAAM,OAAO,4BAA4B,CAAA,MAAA,MAAA,EAAA,CAAA;GAClE,OAAO,UAAU,MAAM,iBAAiB,OAAO,OAAO;GACtD,OAAO,CAAC,CAAC,MAAM,MAAM;EACvB;CACF;CACA,IAAI,cAAc,UAAU;EAC1B,MAAM;EACN,MAAM,EAAE,wBAAwB,MAAM;EACtC,MAAM,oBAAoB,OAAO,QAAQ,WAAW;CACtD;CACA,OAAO;AACT;;;;;AAMA,SAAgB,mBAA4C;CAC1D,OAAO,OAAO,CAAC,CAAC,UAAU;AAC5B;;AAGA,SAAgB,mBAAmB,QAAiC;CAClE,OAAO,CAAC,CAAC,MAAM,MAAM;AACvB"}
@@ -1,4 +1,4 @@
1
- import { O as __exportAll } from "./views-anOyTI7u.js";
1
+ import { k as __exportAll } from "./views-BtUVboYl.js";
2
2
  //#region src/runtime/runtime-flag.ts
3
3
  var runtime_flag_exports = /* @__PURE__ */ __exportAll({
4
4
  isRuntimeFlagEnabled: () => isRuntimeFlagEnabled,
@@ -44,4 +44,4 @@ function parseFlag(value) {
44
44
  //#endregion
45
45
  export { resolveRuntimeFlag as n, runtime_flag_exports as r, isRuntimeFlagEnabled as t };
46
46
 
47
- //# sourceMappingURL=runtime-flag-U8y14Nth.js.map
47
+ //# sourceMappingURL=runtime-flag-DvxW_SD4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-flag-U8y14Nth.js","names":[],"sources":["../src/runtime/runtime-flag.ts"],"sourcesContent":["export type RuntimeFlagInput = {\n configFlag: boolean;\n queryParam?: string | null;\n storedValue?: string | null;\n};\n\n// A query-param override wins, then a stored dev override, then the config flag.\nexport function resolveRuntimeFlag(input: RuntimeFlagInput): boolean {\n const fromQuery = parseFlag(input.queryParam);\n if (fromQuery !== undefined) {\n return fromQuery;\n }\n const fromStore = parseFlag(input.storedValue);\n if (fromStore !== undefined) {\n return fromStore;\n }\n return input.configFlag;\n}\n\nexport type RuntimeFlagKeys = {\n queryKey: string;\n storageKey: string;\n readConfigFlag: () => boolean;\n};\n\n// Effective flag from config + dev override; a query-param override is persisted\n// to localStorage so it survives a refresh. Blocked storage reads as no override.\nexport function isRuntimeFlagEnabled(keys: RuntimeFlagKeys): boolean {\n const configFlag = keys.readConfigFlag();\n if (typeof window === \"undefined\") {\n return configFlag;\n }\n const queryParam = new URLSearchParams(window.location.search).get(\n keys.queryKey,\n );\n const storedValue = readStoredValue(keys.storageKey);\n const enabled = resolveRuntimeFlag({ configFlag, queryParam, storedValue });\n // Persist only an explicit override, not an unrecognized value.\n if (parseFlag(queryParam) !== undefined) {\n persistStoredValue(keys.storageKey, enabled);\n }\n return enabled;\n}\n\nfunction readStoredValue(storageKey: string): string | null {\n try {\n return window.localStorage.getItem(storageKey);\n } catch {\n return null;\n }\n}\n\nfunction persistStoredValue(storageKey: string, enabled: boolean): void {\n try {\n window.localStorage.setItem(storageKey, enabled ? \"true\" : \"false\");\n } catch {\n // The override still applies for this page load.\n }\n}\n\nfunction parseFlag(value: string | null | undefined): boolean | undefined {\n if (value === null || value === undefined) {\n return undefined;\n }\n const normalized = value.trim().toLowerCase();\n if (normalized === \"true\" || normalized === \"1\") {\n return true;\n }\n if (normalized === \"false\" || normalized === \"0\") {\n return false;\n }\n // Unrecognized value (e.g. a typo): no override, fall through to store/config.\n return undefined;\n}\n"],"mappings":";;;;;;AAOA,SAAgB,mBAAmB,OAAkC;CACnE,MAAM,YAAY,UAAU,MAAM,UAAU;CAC5C,IAAI,cAAc,KAAA,GAChB,OAAO;CAET,MAAM,YAAY,UAAU,MAAM,WAAW;CAC7C,IAAI,cAAc,KAAA,GAChB,OAAO;CAET,OAAO,MAAM;AACf;AAUA,SAAgB,qBAAqB,MAAgC;CACnE,MAAM,aAAa,KAAK,eAAe;CACvC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,MAAM,aAAa,IAAI,gBAAgB,OAAO,SAAS,MAAM,CAAC,CAAC,IAC7D,KAAK,QACP;CAEA,MAAM,UAAU,mBAAmB;EAAE;EAAY;EAAY,aADzC,gBAAgB,KAAK,UAC8B;CAAE,CAAC;CAE1E,IAAI,UAAU,UAAU,MAAM,KAAA,GAC5B,mBAAmB,KAAK,YAAY,OAAO;CAE7C,OAAO;AACT;AAEA,SAAS,gBAAgB,YAAmC;CAC1D,IAAI;EACF,OAAO,OAAO,aAAa,QAAQ,UAAU;CAC/C,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,mBAAmB,YAAoB,SAAwB;CACtE,IAAI;EACF,OAAO,aAAa,QAAQ,YAAY,UAAU,SAAS,OAAO;CACpE,QAAQ,CAER;AACF;AAEA,SAAS,UAAU,OAAuD;CACxE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B;CAEF,MAAM,aAAa,MAAM,KAAK,CAAC,CAAC,YAAY;CAC5C,IAAI,eAAe,UAAU,eAAe,KAC1C,OAAO;CAET,IAAI,eAAe,WAAW,eAAe,KAC3C,OAAO;AAIX"}
1
+ {"version":3,"file":"runtime-flag-DvxW_SD4.js","names":[],"sources":["../src/runtime/runtime-flag.ts"],"sourcesContent":["export type RuntimeFlagInput = {\n configFlag: boolean;\n queryParam?: string | null;\n storedValue?: string | null;\n};\n\n// A query-param override wins, then a stored dev override, then the config flag.\nexport function resolveRuntimeFlag(input: RuntimeFlagInput): boolean {\n const fromQuery = parseFlag(input.queryParam);\n if (fromQuery !== undefined) {\n return fromQuery;\n }\n const fromStore = parseFlag(input.storedValue);\n if (fromStore !== undefined) {\n return fromStore;\n }\n return input.configFlag;\n}\n\nexport type RuntimeFlagKeys = {\n queryKey: string;\n storageKey: string;\n readConfigFlag: () => boolean;\n};\n\n// Effective flag from config + dev override; a query-param override is persisted\n// to localStorage so it survives a refresh. Blocked storage reads as no override.\nexport function isRuntimeFlagEnabled(keys: RuntimeFlagKeys): boolean {\n const configFlag = keys.readConfigFlag();\n if (typeof window === \"undefined\") {\n return configFlag;\n }\n const queryParam = new URLSearchParams(window.location.search).get(\n keys.queryKey,\n );\n const storedValue = readStoredValue(keys.storageKey);\n const enabled = resolveRuntimeFlag({ configFlag, queryParam, storedValue });\n // Persist only an explicit override, not an unrecognized value.\n if (parseFlag(queryParam) !== undefined) {\n persistStoredValue(keys.storageKey, enabled);\n }\n return enabled;\n}\n\nfunction readStoredValue(storageKey: string): string | null {\n try {\n return window.localStorage.getItem(storageKey);\n } catch {\n return null;\n }\n}\n\nfunction persistStoredValue(storageKey: string, enabled: boolean): void {\n try {\n window.localStorage.setItem(storageKey, enabled ? \"true\" : \"false\");\n } catch {\n // The override still applies for this page load.\n }\n}\n\nfunction parseFlag(value: string | null | undefined): boolean | undefined {\n if (value === null || value === undefined) {\n return undefined;\n }\n const normalized = value.trim().toLowerCase();\n if (normalized === \"true\" || normalized === \"1\") {\n return true;\n }\n if (normalized === \"false\" || normalized === \"0\") {\n return false;\n }\n // Unrecognized value (e.g. a typo): no override, fall through to store/config.\n return undefined;\n}\n"],"mappings":";;;;;;AAOA,SAAgB,mBAAmB,OAAkC;CACnE,MAAM,YAAY,UAAU,MAAM,UAAU;CAC5C,IAAI,cAAc,KAAA,GAChB,OAAO;CAET,MAAM,YAAY,UAAU,MAAM,WAAW;CAC7C,IAAI,cAAc,KAAA,GAChB,OAAO;CAET,OAAO,MAAM;AACf;AAUA,SAAgB,qBAAqB,MAAgC;CACnE,MAAM,aAAa,KAAK,eAAe;CACvC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,MAAM,aAAa,IAAI,gBAAgB,OAAO,SAAS,MAAM,CAAC,CAAC,IAC7D,KAAK,QACP;CAEA,MAAM,UAAU,mBAAmB;EAAE;EAAY;EAAY,aADzC,gBAAgB,KAAK,UAC8B;CAAE,CAAC;CAE1E,IAAI,UAAU,UAAU,MAAM,KAAA,GAC5B,mBAAmB,KAAK,YAAY,OAAO;CAE7C,OAAO;AACT;AAEA,SAAS,gBAAgB,YAAmC;CAC1D,IAAI;EACF,OAAO,OAAO,aAAa,QAAQ,UAAU;CAC/C,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,mBAAmB,YAAoB,SAAwB;CACtE,IAAI;EACF,OAAO,aAAa,QAAQ,YAAY,UAAU,SAAS,OAAO;CACpE,QAAQ,CAER;AACF;AAEA,SAAS,UAAU,OAAuD;CACxE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B;CAEF,MAAM,aAAa,MAAM,KAAK,CAAC,CAAC,YAAY;CAC5C,IAAI,eAAe,UAAU,eAAe,KAC1C,OAAO;CAET,IAAI,eAAe,WAAW,eAAe,KAC3C,OAAO;AAIX"}
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { C as resolveReactorWorkerEnabled, D as isRuntimeFlagEnabled, E as RuntimeFlagKeys, O as resolveRuntimeFlag, S as isReactorWorkerEnabled, T as RuntimeFlagInput, _ as ReactorClientMode, a as getConnectPHStore, b as REACTOR_WORKER_STORAGE_KEY, c as LocalReactorFactory, d as getAppDrives, f as planReactorClient, g as LocalReactorModule, h as LoadedDocumentModels, i as loadLocalPackage, l as ReactorClientPlan, m as selectAppReactorClient, n as LocalPackageSource, o as AppReactorClientParams, p as refreshAppDrives, r as ModuleNamespace, s as CreateAppReactorClientOptions, t as LocalPackage, u as createAppReactorClient, v as resolveReactorClientMode, w as isMultiReactorEnabled, x as ReactorWorkerFlagInput, y as REACTOR_WORKER_QUERY_KEY } from "./index-q4bHCPoJ.js";
1
+ import { A as isRuntimeFlagEnabled, C as REACTOR_WORKER_STORAGE_KEY, D as isMultiReactorEnabled, E as resolveReactorWorkerEnabled, O as RuntimeFlagInput, S as REACTOR_WORKER_QUERY_KEY, T as isReactorWorkerEnabled, _ as LocalReactorModule, a as getConnectPHStore, b as ReactorClientMode, c as LocalReactorFactory, d as getAppDrives, f as planReactorClient, g as LoadedDocumentModels, h as selectAppReactorClient, i as loadLocalPackage, j as resolveRuntimeFlag, k as RuntimeFlagKeys, l as ReactorClientPlan, m as relistAppDrives, n as LocalPackageSource, o as AppReactorClientParams, p as refreshAppDrives, r as ModuleNamespace, s as CreateAppReactorClientOptions, t as LocalPackage, u as createAppReactorClient, v as REACTOR_CLIENT_QUERY_KEY, w as ReactorWorkerFlagInput, x as resolveReactorClientMode, y as REACTOR_CLIENT_STORAGE_KEY } from "./index-BMlma368.js";
2
2
  import { i as primeRuntimeConfig, n as getRuntimeConfig, r as loadRuntimeConfig } from "./runtime-config-D3YPhYyj.js";
3
+ import { n as useBackendAuthGate, t as BackendAuthGateMode } from "./backend-auth-gate-DiFpY5rt.js";
3
4
  import { n as useIsEmbedded, t as getIsEmbedded } from "./embedded-DT1C47F0.js";
4
- export { type AppReactorClientParams, type CreateAppReactorClientOptions, type LoadedDocumentModels, type LocalPackage, type LocalPackageSource, type LocalReactorFactory, type LocalReactorModule, type ModuleNamespace, REACTOR_WORKER_QUERY_KEY, REACTOR_WORKER_STORAGE_KEY, type ReactorClientMode, type ReactorClientPlan, type ReactorWorkerFlagInput, type RuntimeFlagInput, type RuntimeFlagKeys, createAppReactorClient, getAppDrives, getConnectPHStore, getIsEmbedded, getRuntimeConfig, isMultiReactorEnabled, isReactorWorkerEnabled, isRuntimeFlagEnabled, loadLocalPackage, loadRuntimeConfig, planReactorClient, primeRuntimeConfig, refreshAppDrives, resolveReactorClientMode, resolveReactorWorkerEnabled, resolveRuntimeFlag, selectAppReactorClient, useIsEmbedded };
5
+ export { type AppReactorClientParams, type BackendAuthGateMode, type CreateAppReactorClientOptions, type LoadedDocumentModels, type LocalPackage, type LocalPackageSource, type LocalReactorFactory, type LocalReactorModule, type ModuleNamespace, REACTOR_CLIENT_QUERY_KEY, REACTOR_CLIENT_STORAGE_KEY, REACTOR_WORKER_QUERY_KEY, REACTOR_WORKER_STORAGE_KEY, type ReactorClientMode, type ReactorClientPlan, type ReactorWorkerFlagInput, type RuntimeFlagInput, type RuntimeFlagKeys, createAppReactorClient, getAppDrives, getConnectPHStore, getIsEmbedded, getRuntimeConfig, isMultiReactorEnabled, isReactorWorkerEnabled, isRuntimeFlagEnabled, loadLocalPackage, loadRuntimeConfig, planReactorClient, primeRuntimeConfig, refreshAppDrives, relistAppDrives, resolveReactorClientMode, resolveReactorWorkerEnabled, resolveRuntimeFlag, selectAppReactorClient, useBackendAuthGate, useIsEmbedded };
5
6
  export {};
package/dist/runtime.js CHANGED
@@ -1,5 +1,6 @@
1
- import { getRuntimeConfig, loadRuntimeConfig, primeRuntimeConfig } from "./runtime-config.js";
2
- import { n as resolveRuntimeFlag, t as isRuntimeFlagEnabled } from "./runtime-flag-U8y14Nth.js";
3
- import { a as planReactorClient, c as resolveReactorClientMode, d as isReactorWorkerEnabled, f as resolveReactorWorkerEnabled, i as getAppDrives, l as REACTOR_WORKER_QUERY_KEY, n as getConnectPHStore, o as refreshAppDrives, p as isMultiReactorEnabled, r as createAppReactorClient, s as selectAppReactorClient, t as loadLocalPackage, u as REACTOR_WORKER_STORAGE_KEY } from "./runtime-D5hpzfgT.js";
4
1
  import { getIsEmbedded, useIsEmbedded } from "./embedded.js";
5
- export { REACTOR_WORKER_QUERY_KEY, REACTOR_WORKER_STORAGE_KEY, createAppReactorClient, getAppDrives, getConnectPHStore, getIsEmbedded, getRuntimeConfig, isMultiReactorEnabled, isReactorWorkerEnabled, isRuntimeFlagEnabled, loadLocalPackage, loadRuntimeConfig, planReactorClient, primeRuntimeConfig, refreshAppDrives, resolveReactorClientMode, resolveReactorWorkerEnabled, resolveRuntimeFlag, selectAppReactorClient, useIsEmbedded };
2
+ import { getRuntimeConfig, loadRuntimeConfig, primeRuntimeConfig } from "./runtime-config.js";
3
+ import { n as resolveRuntimeFlag, t as isRuntimeFlagEnabled } from "./runtime-flag-DvxW_SD4.js";
4
+ import { a as planReactorClient, c as selectAppReactorClient, d as resolveReactorClientMode, f as REACTOR_WORKER_QUERY_KEY, g as isMultiReactorEnabled, h as resolveReactorWorkerEnabled, i as getAppDrives, l as REACTOR_CLIENT_QUERY_KEY, m as isReactorWorkerEnabled, n as getConnectPHStore, o as refreshAppDrives, p as REACTOR_WORKER_STORAGE_KEY, r as createAppReactorClient, s as relistAppDrives, t as loadLocalPackage, u as REACTOR_CLIENT_STORAGE_KEY } from "./runtime-D20_LlB_.js";
5
+ import { t as useBackendAuthGate } from "./backend-auth-gate-DNKg_v4Y.js";
6
+ export { REACTOR_CLIENT_QUERY_KEY, REACTOR_CLIENT_STORAGE_KEY, REACTOR_WORKER_QUERY_KEY, REACTOR_WORKER_STORAGE_KEY, createAppReactorClient, getAppDrives, getConnectPHStore, getIsEmbedded, getRuntimeConfig, isMultiReactorEnabled, isReactorWorkerEnabled, isRuntimeFlagEnabled, loadLocalPackage, loadRuntimeConfig, planReactorClient, primeRuntimeConfig, refreshAppDrives, relistAppDrives, resolveReactorClientMode, resolveReactorWorkerEnabled, resolveRuntimeFlag, selectAppReactorClient, useBackendAuthGate, useIsEmbedded };
@@ -1,4 +1,5 @@
1
1
  import { d as useRoutesById } from "./route-tree-D0aszMrk.js";
2
+ import { useIsEmbedded } from "./embedded.js";
2
3
  import { OPENED_FROM_STATE_KEY, SETTINGS_PATH, getDriveName, peekDriveNode, useDriveNode, useDrives } from "@powerhousedao/reactor-browser";
3
4
  import { Outlet, notFound, redirect, useMatch, useMatches, useRouter, useRouterState } from "@tanstack/react-router";
4
5
  import { useMemo, useState } from "react";
@@ -206,7 +207,17 @@ function useNavRoutes() {
206
207
  function useSidebarMode() {
207
208
  return useMatches({ select: (matches) => matches.findLast((match) => match.staticData.sidebar !== void 0)?.staticData.sidebar });
208
209
  }
210
+ /** The chrome Connect's `_app` layout shows, for a layout that replaces it. */
211
+ function useConnectChrome() {
212
+ const embedded = useIsEmbedded();
213
+ const mode = useSidebarMode();
214
+ return {
215
+ embedded,
216
+ sidebarHidden: embedded || mode === "hidden",
217
+ sidebarCollapsed: !embedded && mode === "collapsed"
218
+ };
219
+ }
209
220
  //#endregion
210
- export { ContentOutlet as C, useIsColdStart as E, CONTENT_VIEW_ID as S, DrivePending as T, useForgetSettingsOpenedFrom as _, DRIVE_ROUTE_ID as a, driveTitle as b, useDriveRouteIds as c, redirectToSettingsTab as d, setSettingsRoute as f, showsSettingsTab as g, settingsTitle as h, useCreateDocumentTarget as i, SETTINGS_ROUTE_ID as l, settingsTabsOf as m, useNavRoutes as n, NODE_ROUTE_ID as o, settingsTabs as p, useSidebarMode as r, useDriveRouteData as s, navRoutes as t, keepOpenedFrom as u, useSettingsTabs as v, DRIVE_PENDING_MS as w, notFoundTitle as x, appTitle as y };
221
+ export { CONTENT_VIEW_ID as C, useIsColdStart as D, DrivePending as E, notFoundTitle as S, DRIVE_PENDING_MS as T, showsSettingsTab as _, useCreateDocumentTarget as a, appTitle as b, useDriveRouteData as c, keepOpenedFrom as d, redirectToSettingsTab as f, settingsTitle as g, settingsTabsOf as h, useSidebarMode as i, useDriveRouteIds as l, settingsTabs as m, useConnectChrome as n, DRIVE_ROUTE_ID as o, setSettingsRoute as p, useNavRoutes as r, NODE_ROUTE_ID as s, navRoutes as t, SETTINGS_ROUTE_ID as u, useForgetSettingsOpenedFrom as v, ContentOutlet as w, driveTitle as x, useSettingsTabs as y };
211
222
 
212
- //# sourceMappingURL=sidebar-CRv_DvBV.js.map
223
+ //# sourceMappingURL=sidebar-C2y4mZnX.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar-CRv_DvBV.js","names":[],"sources":["../src/router/cold-start.ts","../src/router/route-components.tsx","../src/router/titles.ts","../src/router/settings.ts","../src/router/route-data.ts","../src/router/create-target.ts","../src/router/sidebar.ts"],"sourcesContent":["import { useRouterState } from \"@tanstack/react-router\";\n\n/** True until the router has resolved its first location. */\nexport function useIsColdStart() {\n return useRouterState({ select: (state) => !state.resolvedLocation });\n}\n","import type {\n AnyRoute,\n AnyRouter,\n RouteComponent,\n} from \"@tanstack/react-router\";\nimport { Outlet, useMatch, useRouter } from \"@tanstack/react-router\";\nimport type { ReactNode } from \"react\";\nimport type {\n DocumentDriveDocument,\n FolderNode,\n} from \"@powerhousedao/shared/document-drive\";\nimport type { PHDocument } from \"@powerhousedao/shared/document-model\";\nimport { useIsColdStart } from \"./cold-start.js\";\n\n/** A drive view's props: drive apps and core's directory listing get the same. */\nexport type DriveViewProps = {\n drive: DocumentDriveDocument;\n /** The open folder; undefined at the drive's root. */\n folder?: FolderNode;\n /** The open document's view, when one is open. */\n children?: ReactNode;\n};\n\n/** A document view's props, e.g. core's JSON view or revision history. */\nexport type DocumentViewProps = { document: PHDocument };\n\n// Open views stay while a navigation loads; the pending view only after this.\nexport const DRIVE_PENDING_MS = 200;\n\n/** The scroll container of the content view. */\nexport const CONTENT_VIEW_ID = \"content-view\";\n\n/** A layout's content area: the matched route, in the container whose scroll\n * Connect resets on new views and restores on back and forward. */\nexport function ContentOutlet(props: { className?: string }) {\n return (\n <div\n className={props.className ?? \"flex h-full flex-col overflow-auto\"}\n id={CONTENT_VIEW_ID}\n data-scroll-restoration-id={CONTENT_VIEW_ID}\n >\n {useRouter().options.context.shell ? <LayoutPending /> : <Outlet />}\n </div>\n );\n}\n\n// The loading shell: the layout's own pendingComponent stands in for its page.\nfunction LayoutPending() {\n const router = useRouter<AnyRouter>();\n const routeId = useMatch({ strict: false, select: (match) => match.routeId });\n const routesById = router.routesById as Partial<Record<string, AnyRoute>>;\n const route = routeId ? routesById[routeId] : undefined;\n const Pending = route?.options.pendingComponent as RouteComponent | undefined;\n return Pending ? <Pending /> : null;\n}\n\n/** A note on a cold start; otherwise the open view stays while loading. */\nexport function DrivePending() {\n const isColdStart = useIsColdStart();\n if (!isColdStart) return null;\n return (\n <p role=\"status\" className=\"p-6 text-core-muted\">\n Loading drive…\n </p>\n );\n}\n","// Document titles for Connect's routes; route heads set them through <HeadContent />.\nimport type { PHStore } from \"@powerhousedao/reactor-browser\";\nimport { getDriveName, peekDriveNode } from \"@powerhousedao/reactor-browser\";\nimport type { DriveRouteData } from \"./drive-route-loader.js\";\n\nconst DEFAULT_APP_NAME = \"Connect\";\n\n/** The configured app name, else \"Connect\". */\nexport function appTitle(appName?: string) {\n return appName || DEFAULT_APP_NAME;\n}\n\n/** `Not found · <app>`. */\nexport function notFoundTitle(appName?: string) {\n return `Not found · ${appTitle(appName)}`;\n}\n\n/** `<node> · <drive> · <app>`, from the store so renames show. */\nexport function driveTitle(\n store: PHStore,\n data: DriveRouteData | undefined,\n appName?: string,\n) {\n const drive = data && store.drives?.find((d) => d.header.id === data.driveId);\n if (!drive) return appTitle(appName);\n const node = data.nodeId\n ? peekDriveNode(drive.header.id, data.nodeId, store)\n : undefined;\n return [node?.name, getDriveName(drive)]\n .filter(Boolean)\n .concat(appTitle(appName))\n .join(\" · \");\n}\n","// The settings page: its tabs are the /settings route's children with `staticData.settings`.\nimport {\n OPENED_FROM_STATE_KEY,\n SETTINGS_PATH,\n} from \"@powerhousedao/reactor-browser\";\nimport type {\n AnyRoute,\n AnyRouter,\n HistoryState,\n StaticDataRouteOption,\n} from \"@tanstack/react-router\";\nimport { notFound, redirect, useRouter } from \"@tanstack/react-router\";\nimport type { ComponentType } from \"react\";\nimport { useMemo } from \"react\";\nimport { useRoutesById } from \"./route-tree.js\";\nimport type { ConnectRouterContext } from \"./drive-route-loader.js\";\nimport { appTitle } from \"./titles.js\";\n\n/** A settings tab, as a child route of /settings declares it in `staticData`. */\nexport type ConnectSettingsTab = {\n label: string;\n /** Ascending; Connect's own tabs use multiples of 10. */\n order: number;\n icon?: ComponentType<{ size?: number; className?: string }>;\n /** Marks the tab as destructive, e.g. the danger zone. */\n destructive?: boolean;\n /** Hidden, and redirected away from, when this returns false. */\n enabled?: (context: ConnectRouterContext) => boolean;\n};\n\ndeclare module \"@tanstack/react-router\" {\n interface StaticDataRouteOption {\n settings?: ConnectSettingsTab;\n }\n}\n\n/** The settings layout route's id. */\nexport const SETTINGS_ROUTE_ID = `/_app${SETTINGS_PATH}`;\n\n// Structural, not the router's match union: route options typed with it are circular.\ntype SettingsMatch = { routeId: string; staticData: StaticDataRouteOption };\n\n/** A listed tab: its id is its path below /settings. */\nexport type SettingsTabEntry = ConnectSettingsTab & {\n id: string;\n routeId: string;\n to: string;\n};\n\n/** The enabled tabs below `settingsRoute`, in order. */\nexport function settingsTabsOf(\n settingsRoute: AnyRoute,\n context: ConnectRouterContext,\n): SettingsTabEntry[] {\n const children = (settingsRoute.children ?? []) as AnyRoute[];\n const prefix = `${settingsRoute.fullPath}/`;\n return children\n .flatMap((route) => {\n const tab = (\n route.options as { staticData?: { settings?: ConnectSettingsTab } }\n ).staticData?.settings;\n if (!tab || tab.enabled?.(context) === false) return [];\n const { id: routeId, fullPath } = route as {\n id: string;\n fullPath: string;\n };\n const to = fullPath.replace(/\\/$/, \"\");\n return [{ ...tab, id: to.slice(prefix.length), routeId, to }];\n })\n .sort((a, b) => a.order - b.order || a.label.localeCompare(b.label));\n}\n\n/** The settings tabs of `router`'s tree; empty without a settings route. */\nexport function settingsTabs(router: AnyRouter): SettingsTabEntry[] {\n const settingsRoute = router.routesById[SETTINGS_ROUTE_ID] as\n | AnyRoute\n | undefined;\n if (!settingsRoute) return [];\n return settingsTabsOf(\n settingsRoute,\n router.options.context as ConnectRouterContext,\n );\n}\n\nexport function useSettingsTabs() {\n const router = useRouter();\n const routesById = useRoutesById();\n return useMemo(() => settingsTabs(router), [router, routesById]);\n}\n\n/** Whether `matches` show a tab that may stay: not bare /settings, not a disabled tab. */\nexport function showsSettingsTab(\n matches: readonly SettingsMatch[],\n context: ConnectRouterContext,\n) {\n const leaf = matches.at(-1);\n if (!leaf || leaf.routeId === SETTINGS_ROUTE_ID) return false;\n return leaf.staticData.settings?.enabled?.(context) !== false;\n}\n\nlet routerSettingsRoute: AnyRoute | undefined;\n\n/** The router's settings route: core's, or a project's override of it. */\nexport function setSettingsRoute(route?: AnyRoute) {\n routerSettingsRoute = route;\n}\n\n/** Sends bare /settings and a disabled tab to the first tab. */\nexport function redirectToSettingsTab({\n matches,\n context,\n}: {\n matches: readonly SettingsMatch[];\n context: ConnectRouterContext;\n}): void {\n if (showsSettingsTab(matches, context)) return;\n const first =\n routerSettingsRoute && settingsTabsOf(routerSettingsRoute, context).at(0);\n // oxlint-disable-next-line typescript/only-throw-error -- router control flow\n if (!first) throw notFound();\n // oxlint-disable-next-line typescript/only-throw-error -- router control flow\n throw redirect<AnyRouter>({ to: first.to, replace: true, state: true });\n}\n\n/** `Settings · <tab> · <app>`. */\nexport function settingsTitle(tabLabel: string | undefined, appName?: string) {\n return [\"Settings\", tabLabel, appTitle(appName)].filter(Boolean).join(\" · \");\n}\n\n/** Makes closing settings go home, not back to an entry that may be gone,\n * e.g. a deleted drive. */\nexport function useForgetSettingsOpenedFrom() {\n const router = useRouter();\n return () =>\n router.navigate({\n to: \".\",\n search: true,\n replace: true,\n resetScroll: false,\n state: (prev) =>\n ({ ...prev, [OPENED_FROM_STATE_KEY]: undefined }) as HistoryState,\n });\n}\n\n/** History state that keeps where the settings page was opened from. */\nexport function keepOpenedFrom(state: object) {\n const from: unknown = Reflect.get(state, OPENED_FROM_STATE_KEY);\n return { [OPENED_FROM_STATE_KEY]: from } as unknown as HistoryState;\n}\n","import { useDriveNode, useDrives } from \"@powerhousedao/reactor-browser\";\nimport type {\n DocumentDriveDocument,\n FolderNode,\n} from \"@powerhousedao/shared/document-drive\";\nimport { useRouterState } from \"@tanstack/react-router\";\nimport type { DriveRouteData } from \"./drive-route-loader.js\";\n\n/** The drive layout route's id. */\nexport const DRIVE_ROUTE_ID = \"/_app/d/$driveSlug\";\n/** The node route's id: below the drive layout, with an optional node. */\nexport const NODE_ROUTE_ID = \"/_app/d/$driveSlug/{-$nodeSlug}\";\n\n/** The open drive, folder and document, from the node route's loader data.\n * Live: a drive's edits re-render the caller. */\nexport type DriveRouteView = {\n drive: DocumentDriveDocument | undefined;\n /** The open folder; undefined at the drive's root or on a document. */\n folder: FolderNode | undefined;\n /** The open document's id; undefined on a folder or the drive's root. */\n documentId: string | undefined;\n};\n\n/** The ids the node route resolved, or undefined outside a drive. */\nexport function useDriveRouteIds(): DriveRouteData | undefined {\n return useRouterState({\n select: (state) =>\n state.matches.find((match) => match.routeId === NODE_ROUTE_ID)\n ?.loaderData as DriveRouteData | undefined,\n });\n}\n\n/** What the drive routes show: the drive, and the folder or document open in it. */\nexport function useDriveRouteData(): DriveRouteView {\n const ids = useDriveRouteIds();\n const drives = useDrives();\n const drive = ids && drives?.find((d) => d.header.id === ids.driveId);\n const node = useDriveNode(drive?.header.id, ids?.nodeId);\n const folder = node?.kind === \"folder\" ? node : undefined;\n // A node the listed drive doesn't have yet was a document the loader fetched.\n return { drive, folder, documentId: folder ? undefined : ids?.nodeId };\n}\n","import { useDrives } from \"@powerhousedao/reactor-browser\";\nimport type { DocumentDriveDocument } from \"@powerhousedao/shared/document-drive\";\nimport { useState } from \"react\";\nimport { useDriveRouteData } from \"./route-data.js\";\n\nexport type CreateDocumentTarget = {\n /** The drive the document goes in; undefined until one is picked. */\n drive: DocumentDriveDocument | undefined;\n /** The drives to pick from, or undefined when the drive is given. */\n choices: DocumentDriveDocument[] | undefined;\n pick: (driveId: string) => void;\n /** Whether it is the open drive, whose open folder the document goes in. */\n isOpenDrive: boolean;\n};\n\n/** Where a new document goes: the modal's `driveId`, else the open drive, else\n * one the user picks, the only drive preselected. */\nexport function useCreateDocumentTarget(\n driveId: string | undefined,\n): CreateDocumentTarget {\n const { drive: openDrive } = useDriveRouteData();\n const drives = useDrives();\n const [picked, setPicked] = useState<string>();\n const given = driveId ?? openDrive?.header.id;\n const only = drives?.length === 1 ? drives[0].header.id : undefined;\n const id = given ?? picked ?? only;\n return {\n drive: drives?.find((d) => d.header.id === id),\n choices: given ? undefined : (drives ?? []),\n pick: setPicked,\n isOpenDrive: !!openDrive && openDrive.header.id === id,\n };\n}\n","// What routes tell the sidebar layout: nav items and whether to show the sidebar.\nimport type { AnyRoute, AnyRouter } from \"@tanstack/react-router\";\nimport { useMatches, useRouter } from \"@tanstack/react-router\";\nimport type { ComponentType } from \"react\";\nimport { useMemo } from \"react\";\nimport { useRoutesById } from \"./route-tree.js\";\n\n/** A sidebar link to the route that declares it in `staticData.nav`. */\nexport type ConnectNavItem = {\n label: string;\n icon?: ComponentType<{ size?: number; className?: string }>;\n /** Ascending, after the drives. */\n order: number;\n};\n\n/** \"hidden\" drops the sidebar; \"collapsed\" keeps only its header and footer. */\nexport type SidebarMode = \"hidden\" | \"collapsed\";\n\ndeclare module \"@tanstack/react-router\" {\n interface StaticDataRouteOption {\n nav?: ConnectNavItem;\n sidebar?: SidebarMode;\n }\n}\n\nexport type NavRouteEntry = ConnectNavItem & { routeId: string; to: string };\n\n/** The routes with `staticData.nav`, in order; routes with params can't be listed. */\nexport function navRoutes(router: AnyRouter): NavRouteEntry[] {\n return Object.values(router.routesById as Record<string, AnyRoute>)\n .flatMap((route) => {\n const nav = (route.options as { staticData?: { nav?: ConnectNavItem } })\n .staticData?.nav;\n const { id: routeId, fullPath } = route as {\n id: string;\n fullPath: string;\n };\n if (!nav || fullPath.includes(\"$\")) return [];\n return [{ ...nav, routeId, to: fullPath.replace(/(.)\\/$/, \"$1\") }];\n })\n .sort((a, b) => a.order - b.order || a.label.localeCompare(b.label));\n}\n\nexport function useNavRoutes() {\n const router = useRouter();\n const routesById = useRoutesById();\n return useMemo(() => navRoutes(router), [router, routesById]);\n}\n\n/** The deepest match's `staticData.sidebar`, if any declares one. */\nexport function useSidebarMode(): SidebarMode | undefined {\n return useMatches({\n select: (matches) =>\n matches.findLast((match) => match.staticData.sidebar !== undefined)\n ?.staticData.sidebar,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sidebar-C2y4mZnX.js","names":[],"sources":["../src/router/cold-start.ts","../src/router/route-components.tsx","../src/router/titles.ts","../src/router/settings.ts","../src/router/route-data.ts","../src/router/create-target.ts","../src/router/sidebar.ts"],"sourcesContent":["import { useRouterState } from \"@tanstack/react-router\";\n\n/** True until the router has resolved its first location. */\nexport function useIsColdStart() {\n return useRouterState({ select: (state) => !state.resolvedLocation });\n}\n","import type {\n AnyRoute,\n AnyRouter,\n RouteComponent,\n} from \"@tanstack/react-router\";\nimport { Outlet, useMatch, useRouter } from \"@tanstack/react-router\";\nimport type { ReactNode } from \"react\";\nimport type {\n DocumentDriveDocument,\n FolderNode,\n} from \"@powerhousedao/shared/document-drive\";\nimport type { PHDocument } from \"@powerhousedao/shared/document-model\";\nimport { useIsColdStart } from \"./cold-start.js\";\n\n/** A drive view's props: drive apps and core's directory listing get the same. */\nexport type DriveViewProps = {\n drive: DocumentDriveDocument;\n /** The open folder; undefined at the drive's root. */\n folder?: FolderNode;\n /** The open document's view, when one is open. */\n children?: ReactNode;\n};\n\n/** A document view's props, e.g. core's JSON view or revision history. */\nexport type DocumentViewProps = { document: PHDocument };\n\n// Open views stay while a navigation loads; the pending view only after this.\nexport const DRIVE_PENDING_MS = 200;\n\n/** The scroll container of the content view. */\nexport const CONTENT_VIEW_ID = \"content-view\";\n\n/** A layout's content area: the matched route, in the container whose scroll\n * Connect resets on new views and restores on back and forward. */\nexport function ContentOutlet(props: { className?: string }) {\n return (\n <div\n className={props.className ?? \"flex h-full flex-col overflow-auto\"}\n id={CONTENT_VIEW_ID}\n data-scroll-restoration-id={CONTENT_VIEW_ID}\n >\n {useRouter().options.context.shell ? <LayoutPending /> : <Outlet />}\n </div>\n );\n}\n\n// The loading shell: the layout's own pendingComponent stands in for its page.\nfunction LayoutPending() {\n const router = useRouter<AnyRouter>();\n const routeId = useMatch({ strict: false, select: (match) => match.routeId });\n const routesById = router.routesById as Partial<Record<string, AnyRoute>>;\n const route = routeId ? routesById[routeId] : undefined;\n const Pending = route?.options.pendingComponent as RouteComponent | undefined;\n return Pending ? <Pending /> : null;\n}\n\n/** A note on a cold start; otherwise the open view stays while loading. */\nexport function DrivePending() {\n const isColdStart = useIsColdStart();\n if (!isColdStart) return null;\n return (\n <p role=\"status\" className=\"p-6 text-core-muted\">\n Loading drive…\n </p>\n );\n}\n","// Document titles for Connect's routes; route heads set them through <HeadContent />.\nimport type { PHStore } from \"@powerhousedao/reactor-browser\";\nimport { getDriveName, peekDriveNode } from \"@powerhousedao/reactor-browser\";\nimport type { DriveRouteData } from \"./drive-route-loader.js\";\n\nconst DEFAULT_APP_NAME = \"Connect\";\n\n/** The configured app name, else \"Connect\". */\nexport function appTitle(appName?: string) {\n return appName || DEFAULT_APP_NAME;\n}\n\n/** `Not found · <app>`. */\nexport function notFoundTitle(appName?: string) {\n return `Not found · ${appTitle(appName)}`;\n}\n\n/** `<node> · <drive> · <app>`, from the store so renames show. */\nexport function driveTitle(\n store: PHStore,\n data: DriveRouteData | undefined,\n appName?: string,\n) {\n const drive = data && store.drives?.find((d) => d.header.id === data.driveId);\n if (!drive) return appTitle(appName);\n const node = data.nodeId\n ? peekDriveNode(drive.header.id, data.nodeId, store)\n : undefined;\n return [node?.name, getDriveName(drive)]\n .filter(Boolean)\n .concat(appTitle(appName))\n .join(\" · \");\n}\n","// The settings page: its tabs are the /settings route's children with `staticData.settings`.\nimport {\n OPENED_FROM_STATE_KEY,\n SETTINGS_PATH,\n} from \"@powerhousedao/reactor-browser\";\nimport type {\n AnyRoute,\n AnyRouter,\n HistoryState,\n StaticDataRouteOption,\n} from \"@tanstack/react-router\";\nimport { notFound, redirect, useRouter } from \"@tanstack/react-router\";\nimport type { ComponentType } from \"react\";\nimport { useMemo } from \"react\";\nimport { useRoutesById } from \"./route-tree.js\";\nimport type { ConnectRouterContext } from \"./drive-route-loader.js\";\nimport { appTitle } from \"./titles.js\";\n\n/** A settings tab, as a child route of /settings declares it in `staticData`. */\nexport type ConnectSettingsTab = {\n label: string;\n /** Ascending; Connect's own tabs use multiples of 10. */\n order: number;\n icon?: ComponentType<{ size?: number; className?: string }>;\n /** Marks the tab as destructive, e.g. the danger zone. */\n destructive?: boolean;\n /** Hidden, and redirected away from, when this returns false. */\n enabled?: (context: ConnectRouterContext) => boolean;\n};\n\ndeclare module \"@tanstack/react-router\" {\n interface StaticDataRouteOption {\n settings?: ConnectSettingsTab;\n }\n}\n\n/** The settings layout route's id. */\nexport const SETTINGS_ROUTE_ID = `/_app${SETTINGS_PATH}`;\n\n// Structural, not the router's match union: route options typed with it are circular.\ntype SettingsMatch = { routeId: string; staticData: StaticDataRouteOption };\n\n/** A listed tab: its id is its path below /settings. */\nexport type SettingsTabEntry = ConnectSettingsTab & {\n id: string;\n routeId: string;\n to: string;\n};\n\n/** The enabled tabs below `settingsRoute`, in order. */\nexport function settingsTabsOf(\n settingsRoute: AnyRoute,\n context: ConnectRouterContext,\n): SettingsTabEntry[] {\n const children = (settingsRoute.children ?? []) as AnyRoute[];\n const prefix = `${settingsRoute.fullPath}/`;\n return children\n .flatMap((route) => {\n const tab = (\n route.options as { staticData?: { settings?: ConnectSettingsTab } }\n ).staticData?.settings;\n if (!tab || tab.enabled?.(context) === false) return [];\n const { id: routeId, fullPath } = route as {\n id: string;\n fullPath: string;\n };\n const to = fullPath.replace(/\\/$/, \"\");\n return [{ ...tab, id: to.slice(prefix.length), routeId, to }];\n })\n .sort((a, b) => a.order - b.order || a.label.localeCompare(b.label));\n}\n\n/** The settings tabs of `router`'s tree; empty without a settings route. */\nexport function settingsTabs(router: AnyRouter): SettingsTabEntry[] {\n const settingsRoute = router.routesById[SETTINGS_ROUTE_ID] as\n | AnyRoute\n | undefined;\n if (!settingsRoute) return [];\n return settingsTabsOf(\n settingsRoute,\n router.options.context as ConnectRouterContext,\n );\n}\n\nexport function useSettingsTabs() {\n const router = useRouter();\n const routesById = useRoutesById();\n return useMemo(() => settingsTabs(router), [router, routesById]);\n}\n\n/** Whether `matches` show a tab that may stay: not bare /settings, not a disabled tab. */\nexport function showsSettingsTab(\n matches: readonly SettingsMatch[],\n context: ConnectRouterContext,\n) {\n const leaf = matches.at(-1);\n if (!leaf || leaf.routeId === SETTINGS_ROUTE_ID) return false;\n return leaf.staticData.settings?.enabled?.(context) !== false;\n}\n\nlet routerSettingsRoute: AnyRoute | undefined;\n\n/** The router's settings route: core's, or a project's override of it. */\nexport function setSettingsRoute(route?: AnyRoute) {\n routerSettingsRoute = route;\n}\n\n/** Sends bare /settings and a disabled tab to the first tab. */\nexport function redirectToSettingsTab({\n matches,\n context,\n}: {\n matches: readonly SettingsMatch[];\n context: ConnectRouterContext;\n}): void {\n if (showsSettingsTab(matches, context)) return;\n const first =\n routerSettingsRoute && settingsTabsOf(routerSettingsRoute, context).at(0);\n // oxlint-disable-next-line typescript/only-throw-error -- router control flow\n if (!first) throw notFound();\n // oxlint-disable-next-line typescript/only-throw-error -- router control flow\n throw redirect<AnyRouter>({ to: first.to, replace: true, state: true });\n}\n\n/** `Settings · <tab> · <app>`. */\nexport function settingsTitle(tabLabel: string | undefined, appName?: string) {\n return [\"Settings\", tabLabel, appTitle(appName)].filter(Boolean).join(\" · \");\n}\n\n/** Makes closing settings go home, not back to an entry that may be gone,\n * e.g. a deleted drive. */\nexport function useForgetSettingsOpenedFrom() {\n const router = useRouter();\n return () =>\n router.navigate({\n to: \".\",\n search: true,\n replace: true,\n resetScroll: false,\n state: (prev) =>\n ({ ...prev, [OPENED_FROM_STATE_KEY]: undefined }) as HistoryState,\n });\n}\n\n/** History state that keeps where the settings page was opened from. */\nexport function keepOpenedFrom(state: object) {\n const from: unknown = Reflect.get(state, OPENED_FROM_STATE_KEY);\n return { [OPENED_FROM_STATE_KEY]: from } as unknown as HistoryState;\n}\n","import { useDriveNode, useDrives } from \"@powerhousedao/reactor-browser\";\nimport type {\n DocumentDriveDocument,\n FolderNode,\n} from \"@powerhousedao/shared/document-drive\";\nimport { useRouterState } from \"@tanstack/react-router\";\nimport type { DriveRouteData } from \"./drive-route-loader.js\";\n\n/** The drive layout route's id. */\nexport const DRIVE_ROUTE_ID = \"/_app/d/$driveSlug\";\n/** The node route's id: below the drive layout, with an optional node. */\nexport const NODE_ROUTE_ID = \"/_app/d/$driveSlug/{-$nodeSlug}\";\n\n/** The open drive, folder and document, from the node route's loader data.\n * Live: a drive's edits re-render the caller. */\nexport type DriveRouteView = {\n drive: DocumentDriveDocument | undefined;\n /** The open folder; undefined at the drive's root or on a document. */\n folder: FolderNode | undefined;\n /** The open document's id; undefined on a folder or the drive's root. */\n documentId: string | undefined;\n};\n\n/** The ids the node route resolved, or undefined outside a drive. */\nexport function useDriveRouteIds(): DriveRouteData | undefined {\n return useRouterState({\n select: (state) =>\n state.matches.find((match) => match.routeId === NODE_ROUTE_ID)\n ?.loaderData as DriveRouteData | undefined,\n });\n}\n\n/** What the drive routes show: the drive, and the folder or document open in it. */\nexport function useDriveRouteData(): DriveRouteView {\n const ids = useDriveRouteIds();\n const drives = useDrives();\n const drive = ids && drives?.find((d) => d.header.id === ids.driveId);\n const node = useDriveNode(drive?.header.id, ids?.nodeId);\n const folder = node?.kind === \"folder\" ? node : undefined;\n // A node the listed drive doesn't have yet was a document the loader fetched.\n return { drive, folder, documentId: folder ? undefined : ids?.nodeId };\n}\n","import { useDrives } from \"@powerhousedao/reactor-browser\";\nimport type { DocumentDriveDocument } from \"@powerhousedao/shared/document-drive\";\nimport { useState } from \"react\";\nimport { useDriveRouteData } from \"./route-data.js\";\n\nexport type CreateDocumentTarget = {\n /** The drive the document goes in; undefined until one is picked. */\n drive: DocumentDriveDocument | undefined;\n /** The drives to pick from, or undefined when the drive is given. */\n choices: DocumentDriveDocument[] | undefined;\n pick: (driveId: string) => void;\n /** Whether it is the open drive, whose open folder the document goes in. */\n isOpenDrive: boolean;\n};\n\n/** Where a new document goes: the modal's `driveId`, else the open drive, else\n * one the user picks, the only drive preselected. */\nexport function useCreateDocumentTarget(\n driveId: string | undefined,\n): CreateDocumentTarget {\n const { drive: openDrive } = useDriveRouteData();\n const drives = useDrives();\n const [picked, setPicked] = useState<string>();\n const given = driveId ?? openDrive?.header.id;\n const only = drives?.length === 1 ? drives[0].header.id : undefined;\n const id = given ?? picked ?? only;\n return {\n drive: drives?.find((d) => d.header.id === id),\n choices: given ? undefined : (drives ?? []),\n pick: setPicked,\n isOpenDrive: !!openDrive && openDrive.header.id === id,\n };\n}\n","// What routes tell the sidebar layout: nav items and whether to show the sidebar.\nimport type { AnyRoute, AnyRouter } from \"@tanstack/react-router\";\nimport { useMatches, useRouter } from \"@tanstack/react-router\";\nimport type { ComponentType } from \"react\";\nimport { useMemo } from \"react\";\nimport { useIsEmbedded } from \"../runtime/embedded.js\";\nimport { useRoutesById } from \"./route-tree.js\";\n\n/** A sidebar link to the route that declares it in `staticData.nav`. */\nexport type ConnectNavItem = {\n label: string;\n icon?: ComponentType<{ size?: number; className?: string }>;\n /** Ascending, after the drives. */\n order: number;\n};\n\n/** \"hidden\" drops the sidebar; \"collapsed\" keeps only its header and footer. */\nexport type SidebarMode = \"hidden\" | \"collapsed\";\n\ndeclare module \"@tanstack/react-router\" {\n interface StaticDataRouteOption {\n nav?: ConnectNavItem;\n sidebar?: SidebarMode;\n }\n}\n\nexport type NavRouteEntry = ConnectNavItem & { routeId: string; to: string };\n\n/** The routes with `staticData.nav`, in order; routes with params can't be listed. */\nexport function navRoutes(router: AnyRouter): NavRouteEntry[] {\n return Object.values(router.routesById as Record<string, AnyRoute>)\n .flatMap((route) => {\n const nav = (route.options as { staticData?: { nav?: ConnectNavItem } })\n .staticData?.nav;\n const { id: routeId, fullPath } = route as {\n id: string;\n fullPath: string;\n };\n if (!nav || fullPath.includes(\"$\")) return [];\n return [{ ...nav, routeId, to: fullPath.replace(/(.)\\/$/, \"$1\") }];\n })\n .sort((a, b) => a.order - b.order || a.label.localeCompare(b.label));\n}\n\nexport function useNavRoutes() {\n const router = useRouter();\n const routesById = useRoutesById();\n return useMemo(() => navRoutes(router), [router, routesById]);\n}\n\n/** The deepest match's `staticData.sidebar`, if any declares one. */\nexport function useSidebarMode(): SidebarMode | undefined {\n return useMatches({\n select: (matches) =>\n matches.findLast((match) => match.staticData.sidebar !== undefined)\n ?.staticData.sidebar,\n });\n}\n\n/** What a layout shows of Connect's chrome on the matched route. */\nexport type ConnectChrome = {\n /** Connect runs inside another app (`?embed=1`): no sidebar, header or footer. */\n embedded: boolean;\n /** No sidebar: embedded, or the route's `staticData.sidebar` is \"hidden\". */\n sidebarHidden: boolean;\n /** The route's `staticData.sidebar` is \"collapsed\". */\n sidebarCollapsed: boolean;\n};\n\n/** The chrome Connect's `_app` layout shows, for a layout that replaces it. */\nexport function useConnectChrome(): ConnectChrome {\n const embedded = useIsEmbedded();\n const mode = useSidebarMode();\n return {\n embedded,\n sidebarHidden: embedded || mode === \"hidden\",\n sidebarCollapsed: !embedded && mode === \"collapsed\",\n };\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { f as CORE_MODAL_VIEWS, h as useModalFrame, m as ModalDialog, p as ModalOutlet } from "./route-tree-D0aszMrk.js";
2
- import { C as ContentOutlet, i as useCreateDocumentTarget, n as useNavRoutes, r as useSidebarMode, s as useDriveRouteData, v as useSettingsTabs, y as appTitle } from "./sidebar-CRv_DvBV.js";
2
+ import { a as useCreateDocumentTarget, b as appTitle, c as useDriveRouteData, i as useSidebarMode, r as useNavRoutes, w as ContentOutlet, y as useSettingsTabs } from "./sidebar-C2y4mZnX.js";
3
3
  import { useIsEmbedded } from "./embedded.js";
4
- import { DRIVE_DOCUMENT_TYPES, DriveProvider, EditorDocumentProvider, addDocument, closePHModal, deleteNode, exportFile, getDocumentUpgradePreview, getDriveName, getNodeTarget, hideRevisionHistory, initTheme, logout, makeDriveSlug, makeNodeSlug, openRenown, openTarget, preloadDrive, setRevisionHistoryPage, setSelectedNode, showPHModal, upgradeDocument, useAppModuleById, useDispatch, useDocument, useDocumentById, useDocumentModelModuleById, useDriveNodes, useDrives, useEditorModuleFor, useLinkProps, useModelRegistry, useNodeById, useNodeParentFolderById, usePHModal, usePHNavigation, useReactorClient, useRenownAuth, useRenownLoginMethods, useRevisionHistoryPage, useRevisionHistoryVisible, useTheme, useUser, useVetraPackageManager, useVetraPackages, validateDocument } from "@powerhousedao/reactor-browser";
4
+ import { t as useBackendAuthGate } from "./backend-auth-gate-DNKg_v4Y.js";
5
+ import { DRIVE_DOCUMENT_TYPES, DriveProvider, EditorDocumentProvider, addDocument, closePHModal, deleteNode, exportFile, getDocumentUpgradePreview, getDriveName, getNodeTarget, hideRevisionHistory, initTheme, logout, makeDriveSlug, makeNodeSlug, openRenown, openTarget, preloadDrive, setRevisionHistoryPage, setSelectedNode, showPHModal, upgradeDocument, useAppModuleById, useBackendAuth, useDispatch, useDocument, useDocumentById, useDocumentModelModuleById, useDriveNodes, useDrives, useEditorModuleFor, useLinkProps, useModelRegistry, useNodeById, useNodeParentFolderById, usePHModal, usePHNavigation, useReactorClient, useRenownAuth, useRenownLoginMethods, useRevisionHistoryPage, useRevisionHistoryVisible, useTheme, useUser, useVetraPackageManager, useVetraPackages, validateDocument } from "@powerhousedao/reactor-browser";
5
6
  import { HeadContent, Link, Outlet, useRouter, useRouterState } from "@tanstack/react-router";
6
7
  import { Component, Fragment, Suspense, createElement, useEffect, useId, useMemo, useState } from "react";
7
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -618,6 +619,75 @@ function DriveIndex() {
618
619
  });
619
620
  }
620
621
  //#endregion
622
+ //#region src/views/backend-auth-gate.tsx
623
+ /**
624
+ * Full-page gate rendered by the root view in place of the routes when the
625
+ * backend refused the caller. Signing in goes through Renown's redirect, so
626
+ * the page comes back with the user and boots again as them.
627
+ */
628
+ function BackendAuthGate({ mode }) {
629
+ const user = useUser();
630
+ const refusal = useBackendAuth();
631
+ const where = refusal?.backend ? ` (${refusal.backend})` : "";
632
+ return /* @__PURE__ */ jsx("div", {
633
+ role: "dialog",
634
+ "aria-labelledby": "backend-auth-gate-title",
635
+ className: "flex h-full min-h-[60vh] items-center justify-center p-6",
636
+ children: /* @__PURE__ */ jsx("div", {
637
+ className: "flex w-full max-w-md flex-col gap-4 rounded-lg border border-core-border bg-core-bg p-6 text-core-fg",
638
+ children: mode === "login" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
639
+ /* @__PURE__ */ jsx("h1", {
640
+ id: "backend-auth-gate-title",
641
+ className: "text-lg font-semibold",
642
+ children: "Sign in to continue"
643
+ }),
644
+ /* @__PURE__ */ jsxs("p", {
645
+ className: "text-sm text-core-muted",
646
+ children: [
647
+ "This server",
648
+ where,
649
+ " only answers signed-in users. Sign in with Renown to open it."
650
+ ]
651
+ }),
652
+ /* @__PURE__ */ jsx("button", {
653
+ type: "button",
654
+ className: "self-start rounded border border-core-border px-3 py-1 hover:bg-core-hover",
655
+ onClick: () => openRenown(),
656
+ children: "Sign in with Renown"
657
+ })
658
+ ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
659
+ /* @__PURE__ */ jsx("h1", {
660
+ id: "backend-auth-gate-title",
661
+ className: "text-lg font-semibold",
662
+ children: "This account is not allowed here"
663
+ }),
664
+ /* @__PURE__ */ jsxs("p", {
665
+ className: "text-sm text-core-muted",
666
+ children: [
667
+ "You are signed in as",
668
+ " ",
669
+ /* @__PURE__ */ jsx("code", {
670
+ className: "break-all",
671
+ children: user?.address
672
+ }),
673
+ ", but this server",
674
+ where,
675
+ " does not accept that account. Ask its admin for access, or switch to another account."
676
+ ]
677
+ }),
678
+ /* @__PURE__ */ jsx("button", {
679
+ type: "button",
680
+ className: "self-start rounded border border-core-border px-3 py-1 hover:bg-core-hover",
681
+ onClick: () => {
682
+ logout().then(() => openRenown());
683
+ },
684
+ children: "Switch account"
685
+ })
686
+ ] })
687
+ })
688
+ });
689
+ }
690
+ //#endregion
621
691
  //#region src/views/theme-toggle.tsx
622
692
  /** Flips between light and dark; the first flip pins the system theme's opposite. */
623
693
  function ThemeToggle({ className }) {
@@ -931,17 +1001,20 @@ function DriveAuthRequiredModal() {
931
1001
  //#endregion
932
1002
  //#region src/views/layout.tsx
933
1003
  const coreModals = Object.fromEntries(Object.entries(CORE_MODAL_VIEWS).map(([type, name]) => [type, modals_exports[name]]));
934
- /** Core's root: the page, in the theme. */
1004
+ /** Core's root: the page, in the theme. When the app's backend refused the
1005
+ * caller, the sign-in gate stands in for every route, so a shell that
1006
+ * replaces core's layout still gets it. */
935
1007
  function CoreRoot() {
936
1008
  initTheme();
937
1009
  useTheme();
1010
+ const gate = useBackendAuthGate();
938
1011
  return /* @__PURE__ */ jsxs("div", {
939
1012
  className: "h-screen overflow-auto bg-core-bg text-core-fg",
940
1013
  children: [
941
1014
  /* @__PURE__ */ jsx(HeadContent, {}),
942
1015
  /* @__PURE__ */ jsx(Suspense, {
943
1016
  name: "CoreRoot",
944
- children: /* @__PURE__ */ jsx(Outlet, {})
1017
+ children: gate ? /* @__PURE__ */ jsx(BackendAuthGate, { mode: gate }) : /* @__PURE__ */ jsx(Outlet, {})
945
1018
  }),
946
1019
  /* @__PURE__ */ jsx(ModalOutlet, { modals: coreModals })
947
1020
  ]
@@ -1294,6 +1367,6 @@ function PackagesTab() {
1294
1367
  });
1295
1368
  }
1296
1369
  //#endregion
1297
- export { DocumentJson as C, formatModified as D, DirectoryListing as E, __exportAll as O, EditorHost as S, HISTORY_PAGE_SIZE as T, ThemeToggle as _, DriveNotFoundPage as a, DriveLayout as b, CoreLayout as c, CreateDocumentModal as d, DeleteItemModal as f, LoginModal as g, DriveAuthRequiredModal as h, CoreRouteError as i, CoreRoot as l, DownloadDocumentWithErrorsModal as m, AppearanceTab as n, NotFoundPage as o, DocumentVersionUnsupportedModal as p, CoreSettingsPage as r, CoreHeader as s, PackagesTab as t, ConfirmDocumentUpgradeModal as u, DriveIndex as v, DocumentHistory as w, NodeView as x, DocumentView as y };
1370
+ export { EditorHost as C, DirectoryListing as D, HISTORY_PAGE_SIZE as E, formatModified as O, NodeView as S, DocumentHistory as T, ThemeToggle as _, DriveNotFoundPage as a, DocumentView as b, CoreLayout as c, CreateDocumentModal as d, DeleteItemModal as f, LoginModal as g, DriveAuthRequiredModal as h, CoreRouteError as i, __exportAll as k, CoreRoot as l, DownloadDocumentWithErrorsModal as m, AppearanceTab as n, NotFoundPage as o, DocumentVersionUnsupportedModal as p, CoreSettingsPage as r, CoreHeader as s, PackagesTab as t, ConfirmDocumentUpgradeModal as u, BackendAuthGate as v, DocumentJson as w, DriveLayout as x, DriveIndex as y };
1298
1371
 
1299
- //# sourceMappingURL=views-anOyTI7u.js.map
1372
+ //# sourceMappingURL=views-BtUVboYl.js.map