@dxos/app-framework 0.8.4-main.d05539e30a → 0.8.4-main.d9fc60f731

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 (177) hide show
  1. package/dist/lib/browser/{capability-K5XIVCQU.mjs → capability-JCUNWZ66.mjs} +7 -7
  2. package/dist/lib/browser/{chunk-5AHASNDW.mjs → chunk-2ZIYRDAF.mjs} +1 -1
  3. package/dist/lib/browser/{chunk-5AHASNDW.mjs.map → chunk-2ZIYRDAF.mjs.map} +1 -1
  4. package/dist/lib/browser/{chunk-QLML5QFJ.mjs → chunk-7MBI66M7.mjs} +12 -6
  5. package/dist/lib/browser/chunk-7MBI66M7.mjs.map +7 -0
  6. package/dist/lib/browser/{chunk-VJ5PFAWC.mjs → chunk-BMLCIFZW.mjs} +37 -16
  7. package/dist/lib/browser/chunk-BMLCIFZW.mjs.map +7 -0
  8. package/dist/lib/browser/{chunk-SLX73WRZ.mjs → chunk-DC3WRPBV.mjs} +14 -5
  9. package/dist/lib/browser/chunk-DC3WRPBV.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-BRK6GYNB.mjs → chunk-E5DTDNDY.mjs} +20 -19
  11. package/dist/lib/browser/chunk-E5DTDNDY.mjs.map +7 -0
  12. package/dist/lib/browser/{chunk-KLHQNYJ2.mjs → chunk-KZUDO43J.mjs} +2 -2
  13. package/dist/lib/browser/chunk-Q2GLJTVV.mjs +12 -0
  14. package/dist/lib/browser/chunk-Q2GLJTVV.mjs.map +7 -0
  15. package/dist/lib/browser/{chunk-UVTGHZQF.mjs → chunk-VBG5F5UF.mjs} +51 -88
  16. package/dist/lib/browser/chunk-VBG5F5UF.mjs.map +7 -0
  17. package/dist/lib/browser/cli/index.mjs +5 -5
  18. package/dist/lib/browser/common/activation-events.mjs +3 -3
  19. package/dist/lib/browser/common/capabilities.mjs +4 -4
  20. package/dist/lib/browser/core/plugin-manager.mjs +2 -2
  21. package/dist/lib/browser/core/plugin.mjs +3 -1
  22. package/dist/lib/browser/core/url-loader.mjs +2 -2
  23. package/dist/lib/browser/index.mjs +13 -14
  24. package/dist/lib/browser/index.mjs.map +3 -3
  25. package/dist/lib/browser/meta.json +1 -1
  26. package/dist/lib/browser/{process-manager-capability-JIWLN7SU.mjs → process-manager-capability-4FUA4TRP.mjs} +6 -6
  27. package/dist/lib/browser/testing/index.mjs +11 -10
  28. package/dist/lib/browser/testing/index.mjs.map +3 -3
  29. package/dist/lib/browser/testing/react.mjs +6 -6
  30. package/dist/lib/browser/ui/index.mjs +7 -9
  31. package/dist/lib/node-esm/{capability-RLKFFLTB.mjs → capability-7TCQK7ZP.mjs} +7 -7
  32. package/dist/lib/node-esm/{chunk-SFYCO3PT.mjs → chunk-4BX2AE47.mjs} +37 -16
  33. package/dist/lib/node-esm/chunk-4BX2AE47.mjs.map +7 -0
  34. package/dist/lib/node-esm/chunk-FPW45EZH.mjs +14 -0
  35. package/dist/lib/node-esm/chunk-FPW45EZH.mjs.map +7 -0
  36. package/dist/lib/node-esm/{chunk-LJNUFNDO.mjs → chunk-GEWHMHM7.mjs} +12 -6
  37. package/dist/lib/node-esm/chunk-GEWHMHM7.mjs.map +7 -0
  38. package/dist/lib/node-esm/{chunk-BYHYYJZH.mjs → chunk-KSH4OVMY.mjs} +20 -19
  39. package/dist/lib/node-esm/chunk-KSH4OVMY.mjs.map +7 -0
  40. package/dist/lib/node-esm/{chunk-PW2VYGOS.mjs → chunk-QBUGLR4H.mjs} +1 -1
  41. package/dist/lib/node-esm/{chunk-PW2VYGOS.mjs.map → chunk-QBUGLR4H.mjs.map} +1 -1
  42. package/dist/lib/node-esm/{chunk-42UNAKYO.mjs → chunk-XGBUKRCK.mjs} +2 -2
  43. package/dist/lib/node-esm/{chunk-6S45OMUP.mjs → chunk-XIGPWXCQ.mjs} +14 -5
  44. package/dist/lib/node-esm/chunk-XIGPWXCQ.mjs.map +7 -0
  45. package/dist/lib/node-esm/{chunk-JNT72ZCN.mjs → chunk-Y7JKXNDU.mjs} +51 -88
  46. package/dist/lib/node-esm/chunk-Y7JKXNDU.mjs.map +7 -0
  47. package/dist/lib/node-esm/cli/index.mjs +5 -5
  48. package/dist/lib/node-esm/common/activation-events.mjs +3 -3
  49. package/dist/lib/node-esm/common/capabilities.mjs +4 -4
  50. package/dist/lib/node-esm/core/plugin-manager.mjs +2 -2
  51. package/dist/lib/node-esm/core/plugin.mjs +3 -1
  52. package/dist/lib/node-esm/core/url-loader.mjs +2 -2
  53. package/dist/lib/node-esm/index.mjs +13 -14
  54. package/dist/lib/node-esm/index.mjs.map +3 -3
  55. package/dist/lib/node-esm/meta.json +1 -1
  56. package/dist/lib/node-esm/{process-manager-capability-PHKLO2BL.mjs → process-manager-capability-F23D62RN.mjs} +6 -6
  57. package/dist/lib/node-esm/testing/index.mjs +11 -10
  58. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  59. package/dist/lib/node-esm/testing/react.mjs +6 -6
  60. package/dist/lib/node-esm/ui/index.mjs +7 -9
  61. package/dist/plugin/node-esm/index.mjs +155 -77
  62. package/dist/plugin/node-esm/index.mjs.map +4 -4
  63. package/dist/plugin/node-esm/meta.json +1 -1
  64. package/dist/types/src/common/capabilities.d.ts +1 -1
  65. package/dist/types/src/common/index.d.ts +0 -1
  66. package/dist/types/src/common/index.d.ts.map +1 -1
  67. package/dist/types/src/common/translations.d.ts +1 -1
  68. package/dist/types/src/core/capability-manager.d.ts +4 -0
  69. package/dist/types/src/core/capability-manager.d.ts.map +1 -1
  70. package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
  71. package/dist/types/src/core/plugin.d.ts +33 -6
  72. package/dist/types/src/core/plugin.d.ts.map +1 -1
  73. package/dist/types/src/plugin-process-manager/history/history-tracker.d.ts +15 -1
  74. package/dist/types/src/plugin-process-manager/history/history-tracker.d.ts.map +1 -1
  75. package/dist/types/src/plugin-process-manager/history/undo-mapping.d.ts +1 -1
  76. package/dist/types/src/plugin-process-manager/history/undo-mapping.d.ts.map +1 -1
  77. package/dist/types/src/plugin-process-manager/meta.d.ts +1 -1
  78. package/dist/types/src/plugin-process-manager/meta.d.ts.map +1 -1
  79. package/dist/types/src/plugin-process-manager/testing.d.ts.map +1 -1
  80. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  81. package/dist/types/src/ui/components/App/App.d.ts +34 -2
  82. package/dist/types/src/ui/components/App/App.d.ts.map +1 -1
  83. package/dist/types/src/ui/components/App/App.stories.d.ts.map +1 -1
  84. package/dist/types/src/ui/components/PluginManager/PluginManagerContext.stories.d.ts.map +1 -1
  85. package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
  86. package/dist/types/src/ui/components/index.d.ts +0 -1
  87. package/dist/types/src/ui/components/index.d.ts.map +1 -1
  88. package/dist/types/src/ui/hooks/useApp.d.ts +1 -7
  89. package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
  90. package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
  91. package/dist/types/src/ui/hooks/useProcessManagerRuntime.d.ts +6 -0
  92. package/dist/types/src/ui/hooks/useProcessManagerRuntime.d.ts.map +1 -1
  93. package/dist/types/src/vite-plugin/boot-loader/loader.d.ts +26 -23
  94. package/dist/types/src/vite-plugin/boot-loader/loader.d.ts.map +1 -1
  95. package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -1
  96. package/dist/types/tsconfig.tsbuildinfo +1 -1
  97. package/package.json +40 -35
  98. package/src/common/capabilities.ts +1 -1
  99. package/src/common/index.ts +0 -1
  100. package/src/common/translations.ts +1 -1
  101. package/src/core/capability-manager.test.ts +3 -0
  102. package/src/core/capability-manager.ts +22 -1
  103. package/src/core/plugin-manager.test.ts +51 -21
  104. package/src/core/plugin-manager.ts +10 -5
  105. package/src/core/plugin.ts +44 -8
  106. package/src/core/url-loader.test.ts +2 -1
  107. package/src/plugin-process-manager/history/history-tracker.test.ts +39 -60
  108. package/src/plugin-process-manager/history/history-tracker.ts +32 -16
  109. package/src/plugin-process-manager/history/undo-mapping.ts +1 -1
  110. package/src/plugin-process-manager/history/undo-registry.test.ts +3 -1
  111. package/src/plugin-process-manager/meta.ts +6 -4
  112. package/src/plugin-process-manager/testing.ts +6 -5
  113. package/src/testing/react.test.tsx +3 -1
  114. package/src/testing/withPluginManager.tsx +4 -3
  115. package/src/ui/components/App/App.stories.tsx +9 -11
  116. package/src/ui/components/App/App.tsx +80 -17
  117. package/src/ui/components/PluginManager/PluginManagerContext.stories.tsx +9 -14
  118. package/src/ui/components/Surface/SurfaceComponent.stories.tsx +1 -1
  119. package/src/ui/components/Surface/SurfaceComponent.tsx +1 -1
  120. package/src/ui/components/Surface/types.test.ts +2 -2
  121. package/src/ui/components/Surface/types.ts +21 -0
  122. package/src/ui/components/index.ts +0 -1
  123. package/src/ui/hooks/useApp.test.tsx +3 -1
  124. package/src/ui/hooks/useApp.tsx +4 -18
  125. package/src/ui/hooks/useCapabilities.ts +1 -0
  126. package/src/ui/hooks/useProcessManagerRuntime.ts +12 -2
  127. package/src/vite-plugin/boot-loader/BootLoader.solid-stories.tsx +198 -0
  128. package/src/vite-plugin/boot-loader/loader-app/Loader.tsx +166 -0
  129. package/src/vite-plugin/boot-loader/loader-app/boot-loader.css +311 -0
  130. package/src/vite-plugin/boot-loader/loader-app/bridge.ts +25 -0
  131. package/src/vite-plugin/boot-loader/loader-app/entry.tsx +21 -0
  132. package/src/vite-plugin/boot-loader/loader-app/mount.tsx +70 -0
  133. package/src/vite-plugin/boot-loader/loader-app/store.test.ts +137 -0
  134. package/src/vite-plugin/boot-loader/loader-app/store.ts +149 -0
  135. package/src/vite-plugin/boot-loader/loader-app/tsconfig.json +11 -0
  136. package/src/vite-plugin/boot-loader/loader-app/types.ts +78 -0
  137. package/src/vite-plugin/boot-loader/loader.ts +133 -63
  138. package/src/vite-plugin/manifest.test.ts +19 -10
  139. package/src/vite-plugin/manifest.ts +9 -3
  140. package/tsconfig.json +6 -7
  141. package/dist/lib/browser/chunk-BRK6GYNB.mjs.map +0 -7
  142. package/dist/lib/browser/chunk-KFDF7KR3.mjs +0 -42
  143. package/dist/lib/browser/chunk-KFDF7KR3.mjs.map +0 -7
  144. package/dist/lib/browser/chunk-QLML5QFJ.mjs.map +0 -7
  145. package/dist/lib/browser/chunk-SLX73WRZ.mjs.map +0 -7
  146. package/dist/lib/browser/chunk-UVTGHZQF.mjs.map +0 -7
  147. package/dist/lib/browser/chunk-VJ5PFAWC.mjs.map +0 -7
  148. package/dist/lib/node-esm/chunk-6S45OMUP.mjs.map +0 -7
  149. package/dist/lib/node-esm/chunk-BYHYYJZH.mjs.map +0 -7
  150. package/dist/lib/node-esm/chunk-JNT72ZCN.mjs.map +0 -7
  151. package/dist/lib/node-esm/chunk-LJNUFNDO.mjs.map +0 -7
  152. package/dist/lib/node-esm/chunk-OUEMWPIW.mjs +0 -43
  153. package/dist/lib/node-esm/chunk-OUEMWPIW.mjs.map +0 -7
  154. package/dist/lib/node-esm/chunk-SFYCO3PT.mjs.map +0 -7
  155. package/dist/types/src/common/operations.d.ts +0 -19
  156. package/dist/types/src/common/operations.d.ts.map +0 -1
  157. package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts +0 -64
  158. package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts.map +0 -1
  159. package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts +0 -19
  160. package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts.map +0 -1
  161. package/dist/types/src/ui/components/Placeholder/index.d.ts +0 -2
  162. package/dist/types/src/ui/components/Placeholder/index.d.ts.map +0 -1
  163. package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts +0 -34
  164. package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts.map +0 -1
  165. package/src/common/operations.ts +0 -35
  166. package/src/ui/components/Placeholder/Placeholder.stories.tsx +0 -77
  167. package/src/ui/components/Placeholder/Placeholder.tsx +0 -155
  168. package/src/ui/components/Placeholder/index.ts +0 -5
  169. package/src/vite-plugin/boot-loader/BootLoader.stories.tsx +0 -270
  170. package/src/vite-plugin/boot-loader/boot-loader.css +0 -320
  171. package/src/vite-plugin/boot-loader/boot-loader.js +0 -325
  172. /package/dist/lib/browser/{capability-K5XIVCQU.mjs.map → capability-JCUNWZ66.mjs.map} +0 -0
  173. /package/dist/lib/browser/{chunk-KLHQNYJ2.mjs.map → chunk-KZUDO43J.mjs.map} +0 -0
  174. /package/dist/lib/browser/{process-manager-capability-JIWLN7SU.mjs.map → process-manager-capability-4FUA4TRP.mjs.map} +0 -0
  175. /package/dist/lib/node-esm/{capability-RLKFFLTB.mjs.map → capability-7TCQK7ZP.mjs.map} +0 -0
  176. /package/dist/lib/node-esm/{chunk-42UNAKYO.mjs.map → chunk-XGBUKRCK.mjs.map} +0 -0
  177. /package/dist/lib/node-esm/{process-manager-capability-PHKLO2BL.mjs.map → process-manager-capability-F23D62RN.mjs.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/testing/harness.ts", "../../../../src/testing/service.ts", "../../../../src/testing/withPluginManager.tsx"],
4
- "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Registry } from '@effect-atom/atom-react';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as PubSub from 'effect/PubSub';\nimport * as Queue from 'effect/Queue';\n\nimport { type Operation } from '@dxos/compute';\nimport { runAndForwardErrors } from '@dxos/effect';\nimport { invariant } from '@dxos/invariant';\n\nimport { ActivationEvents, Capabilities } from '../common';\nimport { ActivationEvent, type Capability, type CapabilityManager, type Plugin, PluginManager } from '../core';\n\nexport type TestAppOptions = {\n /**\n * Plugins to register. Plugins whose `meta.tags` includes `'system'` are treated as core\n * (force-enabled). For test convenience, plugins without a `'system'` tag are enabled by\n * default unless `enabled` is provided.\n */\n plugins: Plugin.Plugin[];\n /** Plugin ids that are enabled by default in addition to core. Defaults to all non-system plugin ids. */\n enabled?: string[];\n /** Additional activation events fired alongside SetupReactSurface. */\n setupEvents?: ActivationEvent.ActivationEvent[];\n /**\n * Whether to automatically fire SetupReactSurface + Startup during setup.\n * Defaults to true.\n */\n autoStart?: boolean;\n /**\n * Whether to register the PluginManager + AtomRegistry framework capabilities.\n * Defaults to true.\n */\n registerFrameworkCapabilities?: boolean;\n};\n\n/**\n * A running plugin manager plus helpers for driving it in tests.\n */\nexport interface TestHarness {\n readonly manager: PluginManager.PluginManager;\n readonly capabilities: CapabilityManager.CapabilityManager;\n readonly registry: Registry.Registry;\n\n /** Activate the given event. Equivalent to `manager.activate(event)`. */\n fire(event: ActivationEvent.ActivationEvent | string): Promise<boolean>;\n /** Re-activate all modules that were activated by the given event. */\n reset(event: ActivationEvent.ActivationEvent | string): Promise<boolean>;\n\n /** Returns the first contributed capability for the given interface. Throws if none are present. */\n get<T>(iface: Capability.InterfaceDef<T>): T;\n /** Returns all contributed capabilities for the given interface. */\n getAll<T>(iface: Capability.InterfaceDef<T>): T[];\n /** Waits until at least one capability is contributed for the given interface. */\n waitForCapability<T>(iface: Capability.InterfaceDef<T>, opts?: { timeout?: number }): Promise<T>;\n /** Waits until the given activation event has completed. */\n waitForEvent(event: ActivationEvent.ActivationEvent | string, opts?: { timeout?: number }): Promise<void>;\n\n /** Invokes an operation through the `Capabilities.OperationInvoker` capability. */\n invoke<I, O>(op: Operation.Definition<I, O>, ...args: void extends I ? [input?: I] : [input: I]): Promise<O>;\n\n /**\n * Waits for `Capabilities.ProcessManagerRuntime` and runs the given effect on it.\n * Convenience around `waitForCapability(ProcessManagerRuntime).runPromise(effect)`.\n */\n runPromise<A, E>(\n effect: Effect.Effect<A, E, Capabilities.ProcessManagerRuntimeServices>,\n options?: { readonly timeout?: number; readonly signal?: AbortSignal },\n ): Promise<A>;\n\n enable(id: string): Promise<boolean>;\n disable(id: string): Promise<boolean>;\n\n /** Shuts down the underlying plugin manager. */\n dispose(): Promise<void>;\n\n /** Async-disposable support so tests can use `await using harness = ...`. */\n [Symbol.asyncDispose](): Promise<void>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\n/**\n * Creates a TestHarness with the same bootstrap sequence that `useApp` performs,\n * minus the React provider tree.\n *\n * For Node-only tests, this is enough to fire activation events, read\n * capabilities, and invoke operations.\n *\n * For React tests, pass the returned harness to `render` or `renderSurface`\n * from `@dxos/app-framework/testing-react`.\n */\nexport const createTestApp = async (opts: TestAppOptions): Promise<TestHarness> => {\n const {\n plugins,\n enabled = plugins.filter(({ meta }) => !meta.tags?.includes('system')).map((plugin) => plugin.meta.id),\n setupEvents = [],\n autoStart = true,\n registerFrameworkCapabilities = true,\n } = opts;\n\n const pluginLoader = (id: string) =>\n Effect.sync(() => {\n const plugin = plugins.find((plugin) => plugin.meta.id === id);\n invariant(plugin, `Plugin not found: ${id}`);\n return { plugin };\n });\n\n const manager = PluginManager.make({ pluginLoader, plugins, enabled });\n\n if (registerFrameworkCapabilities) {\n manager.capabilities.contribute({\n interface: Capabilities.PluginManager,\n implementation: manager,\n module: 'org.dxos.app-framework.plugin-manager',\n });\n manager.capabilities.contribute({\n interface: Capabilities.AtomRegistry,\n implementation: manager.registry,\n module: 'org.dxos.app-framework.atom-registry',\n });\n }\n\n if (autoStart) {\n try {\n await runAndForwardErrors(\n Effect.all([\n ...setupEvents.map((event) => manager.activate(event)),\n manager.activate(ActivationEvents.SetupReactSurface),\n manager.activate(ActivationEvents.Startup),\n ]),\n );\n } catch (err) {\n await runAndForwardErrors(manager.shutdown()).catch(() => undefined);\n throw err;\n }\n }\n\n return new TestHarnessImpl(manager);\n};\n\nclass TestHarnessImpl implements TestHarness {\n constructor(readonly manager: PluginManager.PluginManager) {}\n\n get capabilities(): CapabilityManager.CapabilityManager {\n return this.manager.capabilities;\n }\n\n get registry(): Registry.Registry {\n return this.manager.registry;\n }\n\n fire(event: ActivationEvent.ActivationEvent | string): Promise<boolean> {\n return runAndForwardErrors(this.manager.activate(event));\n }\n\n reset(event: ActivationEvent.ActivationEvent | string): Promise<boolean> {\n return runAndForwardErrors(this.manager.reset(event));\n }\n\n get<T>(iface: Capability.InterfaceDef<T>): T {\n return this.manager.capabilities.get(iface);\n }\n\n getAll<T>(iface: Capability.InterfaceDef<T>): T[] {\n return this.manager.capabilities.getAll(iface);\n }\n\n waitForCapability<T>(iface: Capability.InterfaceDef<T>, opts?: { timeout?: number }): Promise<T> {\n const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n return runAndForwardErrors(\n this.manager.capabilities.waitFor(iface).pipe(\n Effect.timeoutFail({\n duration: Duration.millis(timeout),\n onTimeout: () => timeoutError(iface.identifier),\n }),\n ),\n );\n }\n\n waitForEvent(event: ActivationEvent.ActivationEvent | string, opts?: { timeout?: number }): Promise<void> {\n const key = typeof event === 'string' ? event : ActivationEvent.eventKey(event);\n const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n\n const program = Effect.gen(this, function* () {\n const queue = yield* PubSub.subscribe(this.manager.activation);\n // Re-check after subscribing to avoid a race where the event fires\n // between the caller invoking this and the subscription being installed.\n if (this.manager.getEventsFired().includes(key)) {\n return;\n }\n while (true) {\n const message = yield* Queue.take(queue);\n if (message.event === key && message.state === 'activated') {\n return;\n }\n }\n }).pipe(\n Effect.scoped,\n Effect.timeoutFail({\n duration: Duration.millis(timeout),\n onTimeout: () => timeoutError(key),\n }),\n );\n\n return runAndForwardErrors(program);\n }\n\n async invoke<I, O>(op: Operation.Definition<I, O>, ...args: [input?: I]): Promise<O> {\n const invoker = await this.waitForCapability(Capabilities.OperationInvoker);\n const result = await invoker.invokePromise(op as any, ...(args as [any]));\n if (result.error) {\n throw result.error;\n }\n return result.data as O;\n }\n\n async runPromise<A, E>(\n effect: Effect.Effect<A, E, Capabilities.ProcessManagerRuntimeServices>,\n options?: { readonly timeout?: number; readonly signal?: AbortSignal },\n ): Promise<A> {\n const runtime = await this.waitForCapability(Capabilities.ProcessManagerRuntime, { timeout: options?.timeout });\n return runtime.runPromise(effect, options?.signal ? { signal: options.signal } : undefined);\n }\n\n enable(id: string): Promise<boolean> {\n return runAndForwardErrors(this.manager.enable(id));\n }\n\n disable(id: string): Promise<boolean> {\n return runAndForwardErrors(this.manager.disable(id));\n }\n\n async dispose(): Promise<void> {\n await runAndForwardErrors(this.manager.shutdown());\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n}\n\nconst timeoutError = (id: string) => new Error(`Timed out waiting for ${id}`);\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nimport { invariant } from '@dxos/invariant';\n\nimport { ActivationEvents, Capabilities } from '../common';\nimport { Plugin, PluginManager } from '../core';\n\n/**\n * Creates a Plugin.Service layer from a list of plugins.\n * This is primarily used for testing.\n */\nexport const fromPlugins = (plugins: Plugin.Plugin[]) =>\n Layer.effect(\n Plugin.Service,\n Effect.gen(function* () {\n // TODO(wittjosiah): Try to dedupe logic between here, createCliApp and useApp.\n\n const pluginLoader = (id: string) =>\n Effect.sync(() => {\n const plugin = plugins.find((plugin) => plugin.meta.id === id);\n invariant(plugin, `Plugin not found: ${id}`);\n return { plugin };\n });\n\n const manager = PluginManager.make({\n pluginLoader,\n plugins,\n enabled: plugins.map((plugin) => plugin.meta.id),\n });\n\n manager.capabilities.contribute({\n interface: Capabilities.PluginManager,\n implementation: manager,\n module: 'org.dxos.app-framework.plugin-manager',\n });\n\n manager.capabilities.contribute({\n interface: Capabilities.AtomRegistry,\n implementation: manager.registry,\n module: 'org.dxos.app-framework.atom-registry',\n });\n\n yield* manager.activate(ActivationEvents.Startup);\n\n return manager;\n }),\n );\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Decorator, type StoryContext } from '@storybook/react';\nimport * as Effect from 'effect/Effect';\nimport React, { useEffect, useState } from 'react';\n\nimport { raise } from '@dxos/debug';\nimport { runAndForwardErrors } from '@dxos/effect';\nimport { useAsyncEffect } from '@dxos/react-hooks';\nimport { type MaybeProvider, getProviderValue } from '@dxos/util';\n\nimport { ActivationEvents, Capabilities } from '../common';\nimport { type ActivationEvent, Capability, type CapabilityManager, Plugin, PluginManager } from '../core';\nimport { type UseAppOptions, useApp } from '../ui';\n\n/**\n * @internal\n */\nexport const setupPluginManager = ({\n capabilities,\n plugins = [],\n ...options\n}: UseAppOptions & Pick<WithPluginManagerOptions, 'capabilities'> = {}) => {\n // Auto-enable every non-system plugin so stories don't have to spell out\n // enablement. System-tagged plugins are force-enabled by the manager.\n const enabled = plugins.filter(({ meta }) => !meta.tags?.includes('system')).map(({ meta }) => meta.id);\n const pluginManager = PluginManager.make({\n pluginLoader: () => raise(new Error('Not implemented')),\n plugins: [StoryPlugin, ...plugins],\n enabled,\n ...options,\n });\n\n if (capabilities) {\n getProviderValue(capabilities, pluginManager.capabilities).forEach((capability) => {\n pluginManager.capabilities.contribute({\n interface: capability.interface,\n implementation: capability.implementation,\n module: 'story',\n });\n });\n }\n\n return pluginManager;\n};\n\ntype ManagedPluginManagerState = {\n fireEvents?: (ActivationEvent.ActivationEvent | string)[];\n pluginManager: PluginManager.PluginManager;\n setupEvents?: ActivationEvent.ActivationEvent[];\n storyId: string;\n};\n\nexport type WithPluginManagerOptions = UseAppOptions & {\n /** @deprecated */\n capabilities?: MaybeProvider<Capability.Any[], CapabilityManager.CapabilityManager>;\n /** @deprecated */\n fireEvents?: (ActivationEvent.ActivationEvent | string)[];\n};\n\nexport type WithPluginManagerInitializer<Args = void> =\n | WithPluginManagerOptions\n | ((context: StoryContext<Args>) => WithPluginManagerOptions);\n\n/**\n * Wraps a story with a plugin manager.\n * NOTE: This builds up and tears down the plugin manager on every render.\n */\nexport const withPluginManager = <Args,>(init: WithPluginManagerInitializer<Args> = {}): Decorator => {\n return (Story, context) => {\n const storyId = context.id;\n const options = typeof init === 'function' ? init(context as any) : init;\n const [managerState, setManagerState] = useState<ManagedPluginManagerState>();\n\n // Storybook replaces the full context object often, so key manager ownership by story id.\n useEffect(() => {\n const pluginManager = setupPluginManager(options);\n const capability = Capability.contributes(Capabilities.ReactRoot, {\n id: storyId,\n root: () => <Story />,\n });\n\n pluginManager.capabilities.contribute({\n ...capability,\n module: 'org.dxos.app-framework.with-plugin-manager',\n });\n\n setManagerState({\n pluginManager,\n setupEvents: options.setupEvents,\n fireEvents: options.fireEvents,\n storyId,\n });\n\n return () => {\n pluginManager.capabilities.remove(capability.interface, capability.implementation);\n void runAndForwardErrors(pluginManager.shutdown());\n };\n }, [storyId, init]);\n\n // Avoid mounting useApp with a stale manager from the previous story.\n if (!managerState || managerState.storyId !== storyId) {\n return <></>;\n }\n\n return <WithPluginManagerApp {...managerState} />;\n };\n};\n\nconst WithPluginManagerApp = ({ fireEvents, pluginManager, setupEvents, storyId }: ManagedPluginManagerState) => {\n // Fire deprecated events only after the effect-owned manager for this story exists.\n useAsyncEffect(async () => {\n await Promise.all(fireEvents?.map((event) => pluginManager.activate(event)) ?? []);\n }, [fireEvents, pluginManager, storyId]);\n\n const App = useApp({ pluginManager, setupEvents });\n return <App />;\n};\n\nconst storyMeta = {\n id: 'org.dxos.app-framework.story',\n name: 'Story',\n tags: ['system'],\n};\n\n// No-op plugin to ensure there exists at least one plugin for the startup event.\n// This is necessary because `createApp` expects the startup event to complete before the app is ready.\nconst StoryPlugin = Plugin.define(storyMeta).pipe(\n Plugin.addModule({\n id: 'Story',\n activatesOn: ActivationEvents.Startup,\n activate: () => Effect.succeed([]),\n }),\n Plugin.make,\n)();\n"],
5
- "mappings": "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",
6
- "names": ["Duration", "Effect", "PubSub", "Queue", "runAndForwardErrors", "invariant", "DEFAULT_TIMEOUT_MS", "pluginLoader", "Effect", "sync", "plugin", "plugins", "meta", "id", "manager", "enabled", "registerFrameworkCapabilities", "PluginManager", "implementation", "module", "capabilities", "registry", "autoStart", "runAndForwardErrors", "ActivationEvents", "activate", "Startup", "err", "TestHarnessImpl", "fire", "reset", "iface", "getAll", "waitForCapability", "duration", "Duration", "timeout", "onTimeout", "timeoutError", "identifier", "waitForEvent", "opts", "program", "queue", "subscribe", "message", "key", "invoke", "result", "invoker", "error", "data", "runPromise", "effect", "options", "runtime", "undefined", "enable", "disable", "dispose", "Symbol", "asyncDispose", "Effect", "Layer", "invariant", "__dxlog_file", "pluginLoader", "id", "plugin", "manager", "plugins", "enabled", "capabilities", "contribute", "implementation", "module", "registry", "Effect", "React", "useEffect", "useState", "raise", "runAndForwardErrors", "useAsyncEffect", "getProviderValue", "setupPluginManager", "capabilities", "plugins", "options", "enabled", "filter", "meta", "tags", "includes", "map", "id", "pluginManager", "PluginManager", "make", "pluginLoader", "raise", "Error", "StoryPlugin", "getProviderValue", "forEach", "capability", "contribute", "interface", "implementation", "module", "withPluginManager", "init", "Story", "context", "storyId", "managerState", "setManagerState", "useState", "useEffect", "Capability", "contributes", "Capabilities", "ReactRoot", "root", "setupEvents", "fireEvents", "remove", "runAndForwardErrors", "shutdown", "WithPluginManagerApp", "useAsyncEffect", "Promise", "all", "event", "activate", "App", "useApp", "storyMeta", "name", "Plugin", "define", "pipe", "addModule", "activatesOn", "ActivationEvents", "Startup", "succeed"]
4
+ "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Registry } from '@effect-atom/atom-react';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as PubSub from 'effect/PubSub';\nimport * as Queue from 'effect/Queue';\n\nimport { type Operation } from '@dxos/compute';\nimport { runAndForwardErrors } from '@dxos/effect';\nimport { invariant } from '@dxos/invariant';\n\nimport { ActivationEvents, Capabilities } from '../common';\nimport { ActivationEvent, type Capability, type CapabilityManager, type Plugin, PluginManager } from '../core';\n\nexport type TestAppOptions = {\n /**\n * Plugins to register. Plugins whose `meta.tags` includes `'system'` are treated as core\n * (force-enabled). For test convenience, plugins without a `'system'` tag are enabled by\n * default unless `enabled` is provided.\n */\n plugins: Plugin.Plugin[];\n /** Plugin ids that are enabled by default in addition to core. Defaults to all non-system plugin ids. */\n enabled?: string[];\n /** Additional activation events fired alongside SetupReactSurface. */\n setupEvents?: ActivationEvent.ActivationEvent[];\n /**\n * Whether to automatically fire SetupReactSurface + Startup during setup.\n * Defaults to true.\n */\n autoStart?: boolean;\n /**\n * Whether to register the PluginManager + AtomRegistry framework capabilities.\n * Defaults to true.\n */\n registerFrameworkCapabilities?: boolean;\n};\n\n/**\n * A running plugin manager plus helpers for driving it in tests.\n */\nexport interface TestHarness {\n readonly manager: PluginManager.PluginManager;\n readonly capabilities: CapabilityManager.CapabilityManager;\n readonly registry: Registry.Registry;\n\n /** Activate the given event. Equivalent to `manager.activate(event)`. */\n fire(event: ActivationEvent.ActivationEvent | string): Promise<boolean>;\n /** Re-activate all modules that were activated by the given event. */\n reset(event: ActivationEvent.ActivationEvent | string): Promise<boolean>;\n\n /** Returns the first contributed capability for the given interface. Throws if none are present. */\n get<T>(iface: Capability.InterfaceDef<T>): T;\n /** Returns all contributed capabilities for the given interface. */\n getAll<T>(iface: Capability.InterfaceDef<T>): T[];\n /** Waits until at least one capability is contributed for the given interface. */\n waitForCapability<T>(iface: Capability.InterfaceDef<T>, opts?: { timeout?: number }): Promise<T>;\n /** Waits until the given activation event has completed. */\n waitForEvent(event: ActivationEvent.ActivationEvent | string, opts?: { timeout?: number }): Promise<void>;\n\n /** Invokes an operation through the `Capabilities.OperationInvoker` capability. */\n invoke<I, O>(op: Operation.Definition<I, O>, ...args: void extends I ? [input?: I] : [input: I]): Promise<O>;\n\n /**\n * Waits for `Capabilities.ProcessManagerRuntime` and runs the given effect on it.\n * Convenience around `waitForCapability(ProcessManagerRuntime).runPromise(effect)`.\n */\n runPromise<A, E>(\n effect: Effect.Effect<A, E, Capabilities.ProcessManagerRuntimeServices>,\n options?: { readonly timeout?: number; readonly signal?: AbortSignal },\n ): Promise<A>;\n\n enable(id: string): Promise<boolean>;\n disable(id: string): Promise<boolean>;\n\n /** Shuts down the underlying plugin manager. */\n dispose(): Promise<void>;\n\n /** Async-disposable support so tests can use `await using harness = ...`. */\n [Symbol.asyncDispose](): Promise<void>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\n/**\n * Creates a TestHarness with the same bootstrap sequence that `useApp` performs,\n * minus the React provider tree.\n *\n * For Node-only tests, this is enough to fire activation events, read\n * capabilities, and invoke operations.\n *\n * For React tests, pass the returned harness to `render` or `renderSurface`\n * from `@dxos/app-framework/testing-react`.\n */\nexport const createTestApp = async (opts: TestAppOptions): Promise<TestHarness> => {\n const {\n plugins,\n enabled = plugins.filter(({ meta }) => !meta.tags?.includes('system')).map((plugin) => plugin.meta.id),\n setupEvents = [],\n autoStart = true,\n registerFrameworkCapabilities = true,\n } = opts;\n\n const pluginLoader = (id: string) =>\n Effect.sync(() => {\n const plugin = plugins.find((plugin) => plugin.meta.id === id);\n invariant(plugin, `Plugin not found: ${id}`);\n return { plugin };\n });\n\n const manager = PluginManager.make({ pluginLoader, plugins, enabled });\n\n if (registerFrameworkCapabilities) {\n manager.capabilities.contribute({\n interface: Capabilities.PluginManager,\n implementation: manager,\n module: 'org.dxos.app-framework.plugin-manager',\n });\n manager.capabilities.contribute({\n interface: Capabilities.AtomRegistry,\n implementation: manager.registry,\n module: 'org.dxos.app-framework.atom-registry',\n });\n }\n\n if (autoStart) {\n try {\n await runAndForwardErrors(\n Effect.all([\n ...setupEvents.map((event) => manager.activate(event)),\n manager.activate(ActivationEvents.SetupReactSurface),\n manager.activate(ActivationEvents.Startup),\n ]),\n );\n } catch (err) {\n await runAndForwardErrors(manager.shutdown()).catch(() => undefined);\n throw err;\n }\n }\n\n return new TestHarnessImpl(manager);\n};\n\nclass TestHarnessImpl implements TestHarness {\n constructor(readonly manager: PluginManager.PluginManager) {}\n\n get capabilities(): CapabilityManager.CapabilityManager {\n return this.manager.capabilities;\n }\n\n get registry(): Registry.Registry {\n return this.manager.registry;\n }\n\n fire(event: ActivationEvent.ActivationEvent | string): Promise<boolean> {\n return runAndForwardErrors(this.manager.activate(event));\n }\n\n reset(event: ActivationEvent.ActivationEvent | string): Promise<boolean> {\n return runAndForwardErrors(this.manager.reset(event));\n }\n\n get<T>(iface: Capability.InterfaceDef<T>): T {\n return this.manager.capabilities.get(iface);\n }\n\n getAll<T>(iface: Capability.InterfaceDef<T>): T[] {\n return this.manager.capabilities.getAll(iface);\n }\n\n waitForCapability<T>(iface: Capability.InterfaceDef<T>, opts?: { timeout?: number }): Promise<T> {\n const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n return runAndForwardErrors(\n this.manager.capabilities.waitFor(iface).pipe(\n Effect.timeoutFail({\n duration: Duration.millis(timeout),\n onTimeout: () => timeoutError(iface.identifier),\n }),\n ),\n );\n }\n\n waitForEvent(event: ActivationEvent.ActivationEvent | string, opts?: { timeout?: number }): Promise<void> {\n const key = typeof event === 'string' ? event : ActivationEvent.eventKey(event);\n const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n\n const program = Effect.gen(this, function* () {\n const queue = yield* PubSub.subscribe(this.manager.activation);\n // Re-check after subscribing to avoid a race where the event fires\n // between the caller invoking this and the subscription being installed.\n if (this.manager.getEventsFired().includes(key)) {\n return;\n }\n while (true) {\n const message = yield* Queue.take(queue);\n if (message.event === key && message.state === 'activated') {\n return;\n }\n }\n }).pipe(\n Effect.scoped,\n Effect.timeoutFail({\n duration: Duration.millis(timeout),\n onTimeout: () => timeoutError(key),\n }),\n );\n\n return runAndForwardErrors(program);\n }\n\n async invoke<I, O>(op: Operation.Definition<I, O>, ...args: [input?: I]): Promise<O> {\n const invoker = await this.waitForCapability(Capabilities.OperationInvoker);\n const result = await invoker.invokePromise(op as any, ...(args as [any]));\n if (result.error) {\n throw result.error;\n }\n return result.data as O;\n }\n\n async runPromise<A, E>(\n effect: Effect.Effect<A, E, Capabilities.ProcessManagerRuntimeServices>,\n options?: { readonly timeout?: number; readonly signal?: AbortSignal },\n ): Promise<A> {\n const runtime = await this.waitForCapability(Capabilities.ProcessManagerRuntime, { timeout: options?.timeout });\n return runtime.runPromise(effect, options?.signal ? { signal: options.signal } : undefined);\n }\n\n enable(id: string): Promise<boolean> {\n return runAndForwardErrors(this.manager.enable(id));\n }\n\n disable(id: string): Promise<boolean> {\n return runAndForwardErrors(this.manager.disable(id));\n }\n\n async dispose(): Promise<void> {\n await runAndForwardErrors(this.manager.shutdown());\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n}\n\nconst timeoutError = (id: string) => new Error(`Timed out waiting for ${id}`);\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nimport { invariant } from '@dxos/invariant';\n\nimport { ActivationEvents, Capabilities } from '../common';\nimport { Plugin, PluginManager } from '../core';\n\n/**\n * Creates a Plugin.Service layer from a list of plugins.\n * This is primarily used for testing.\n */\nexport const fromPlugins = (plugins: Plugin.Plugin[]) =>\n Layer.effect(\n Plugin.Service,\n Effect.gen(function* () {\n // TODO(wittjosiah): Try to dedupe logic between here, createCliApp and useApp.\n\n const pluginLoader = (id: string) =>\n Effect.sync(() => {\n const plugin = plugins.find((plugin) => plugin.meta.id === id);\n invariant(plugin, `Plugin not found: ${id}`);\n return { plugin };\n });\n\n const manager = PluginManager.make({\n pluginLoader,\n plugins,\n enabled: plugins.map((plugin) => plugin.meta.id),\n });\n\n manager.capabilities.contribute({\n interface: Capabilities.PluginManager,\n implementation: manager,\n module: 'org.dxos.app-framework.plugin-manager',\n });\n\n manager.capabilities.contribute({\n interface: Capabilities.AtomRegistry,\n implementation: manager.registry,\n module: 'org.dxos.app-framework.atom-registry',\n });\n\n yield* manager.activate(ActivationEvents.Startup);\n\n return manager;\n }),\n );\n", "//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Decorator, type StoryContext } from '@storybook/react';\nimport * as Effect from 'effect/Effect';\nimport React, { useEffect, useState } from 'react';\n\nimport { raise } from '@dxos/debug';\nimport { runAndForwardErrors } from '@dxos/effect';\nimport { DXN } from '@dxos/keys';\nimport { useAsyncEffect } from '@dxos/react-hooks';\nimport { type MaybeProvider, getProviderValue } from '@dxos/util';\n\nimport { ActivationEvents, Capabilities } from '../common';\nimport { type ActivationEvent, Capability, type CapabilityManager, Plugin, PluginManager } from '../core';\nimport { type UseAppOptions, useApp } from '../ui';\n\n/**\n * @internal\n */\nexport const setupPluginManager = ({\n capabilities,\n plugins = [],\n ...options\n}: UseAppOptions & Pick<WithPluginManagerOptions, 'capabilities'> = {}) => {\n // Auto-enable every non-system plugin so stories don't have to spell out\n // enablement. System-tagged plugins are force-enabled by the manager.\n const enabled = plugins.filter(({ meta }) => !meta.tags?.includes('system')).map(({ meta }) => meta.id);\n const pluginManager = PluginManager.make({\n pluginLoader: () => raise(new Error('Not implemented')),\n plugins: [StoryPlugin, ...plugins],\n enabled,\n ...options,\n });\n\n if (capabilities) {\n getProviderValue(capabilities, pluginManager.capabilities).forEach((capability) => {\n pluginManager.capabilities.contribute({\n interface: capability.interface,\n implementation: capability.implementation,\n module: 'story',\n });\n });\n }\n\n return pluginManager;\n};\n\ntype ManagedPluginManagerState = {\n fireEvents?: (ActivationEvent.ActivationEvent | string)[];\n pluginManager: PluginManager.PluginManager;\n setupEvents?: ActivationEvent.ActivationEvent[];\n storyId: string;\n};\n\nexport type WithPluginManagerOptions = UseAppOptions & {\n /** @deprecated */\n capabilities?: MaybeProvider<Capability.Any[], CapabilityManager.CapabilityManager>;\n /** @deprecated */\n fireEvents?: (ActivationEvent.ActivationEvent | string)[];\n};\n\nexport type WithPluginManagerInitializer<Args = void> =\n | WithPluginManagerOptions\n | ((context: StoryContext<Args>) => WithPluginManagerOptions);\n\n/**\n * Wraps a story with a plugin manager.\n * NOTE: This builds up and tears down the plugin manager on every render.\n */\nexport const withPluginManager = <Args,>(init: WithPluginManagerInitializer<Args> = {}): Decorator => {\n return (Story, context) => {\n const storyId = context.id;\n const options = typeof init === 'function' ? init(context as any) : init;\n const [managerState, setManagerState] = useState<ManagedPluginManagerState>();\n\n // Storybook replaces the full context object often, so key manager ownership by story id.\n useEffect(() => {\n const pluginManager = setupPluginManager(options);\n const capability = Capability.contributes(Capabilities.ReactRoot, {\n id: storyId,\n root: () => <Story />,\n });\n\n pluginManager.capabilities.contribute({\n ...capability,\n module: 'org.dxos.app-framework.with-plugin-manager',\n });\n\n setManagerState({\n pluginManager,\n setupEvents: options.setupEvents,\n fireEvents: options.fireEvents,\n storyId,\n });\n\n return () => {\n pluginManager.capabilities.remove(capability.interface, capability.implementation);\n void runAndForwardErrors(pluginManager.shutdown());\n };\n }, [storyId, init]);\n\n // Avoid mounting useApp with a stale manager from the previous story.\n if (!managerState || managerState.storyId !== storyId) {\n return <></>;\n }\n\n return <WithPluginManagerApp {...managerState} />;\n };\n};\n\nconst WithPluginManagerApp = ({ fireEvents, pluginManager, setupEvents, storyId }: ManagedPluginManagerState) => {\n // Fire deprecated events only after the effect-owned manager for this story exists.\n useAsyncEffect(async () => {\n await Promise.all(fireEvents?.map((event) => pluginManager.activate(event)) ?? []);\n }, [fireEvents, pluginManager, storyId]);\n\n const App = useApp({ pluginManager, setupEvents });\n return <App />;\n};\n\nconst storyMeta = Plugin.makeMeta({\n key: DXN.make('org.dxos.appFramework.story'),\n name: 'Story',\n tags: ['system'],\n});\n\n// No-op plugin to ensure there exists at least one plugin for the startup event.\n// This is necessary because `createApp` expects the startup event to complete before the app is ready.\nconst StoryPlugin = Plugin.define(storyMeta).pipe(\n Plugin.addModule({\n id: 'Story',\n activatesOn: ActivationEvents.Startup,\n activate: () => Effect.succeed([]),\n }),\n Plugin.make,\n)();\n"],
5
+ "mappings": "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",
6
+ "names": ["Duration", "Effect", "PubSub", "Queue", "runAndForwardErrors", "invariant", "DEFAULT_TIMEOUT_MS", "pluginLoader", "Effect", "sync", "plugin", "plugins", "meta", "id", "manager", "enabled", "registerFrameworkCapabilities", "PluginManager", "implementation", "module", "capabilities", "registry", "autoStart", "runAndForwardErrors", "ActivationEvents", "activate", "Startup", "err", "TestHarnessImpl", "fire", "reset", "iface", "getAll", "waitForCapability", "duration", "Duration", "timeout", "onTimeout", "timeoutError", "identifier", "waitForEvent", "opts", "program", "queue", "subscribe", "message", "key", "invoke", "result", "invoker", "error", "data", "runPromise", "effect", "options", "runtime", "undefined", "enable", "disable", "dispose", "Symbol", "asyncDispose", "Effect", "Layer", "invariant", "__dxlog_file", "pluginLoader", "id", "plugin", "manager", "plugins", "enabled", "capabilities", "contribute", "implementation", "module", "registry", "Effect", "React", "useEffect", "useState", "raise", "runAndForwardErrors", "DXN", "useAsyncEffect", "getProviderValue", "setupPluginManager", "capabilities", "plugins", "options", "enabled", "filter", "meta", "tags", "includes", "map", "id", "pluginManager", "PluginManager", "make", "pluginLoader", "raise", "Error", "StoryPlugin", "getProviderValue", "forEach", "capability", "contribute", "interface", "implementation", "module", "withPluginManager", "init", "Story", "context", "storyId", "managerState", "setManagerState", "useState", "useEffect", "Capability", "contributes", "Capabilities", "ReactRoot", "root", "setupEvents", "fireEvents", "remove", "runAndForwardErrors", "shutdown", "WithPluginManagerApp", "useAsyncEffect", "Promise", "all", "event", "activate", "App", "useApp", "storyMeta", "Plugin", "makeMeta", "key", "DXN", "name", "define", "pipe", "addModule", "activatesOn", "ActivationEvents", "Startup", "succeed"]
7
7
  }
@@ -3,20 +3,20 @@ import {
3
3
  PluginManagerProvider,
4
4
  SurfaceComponent,
5
5
  topologicalSort
6
- } from "../chunk-LJNUFNDO.mjs";
6
+ } from "../chunk-GEWHMHM7.mjs";
7
7
  import {
8
8
  PluginManagerContext
9
9
  } from "../chunk-37Z53PXZ.mjs";
10
- import "../chunk-OUEMWPIW.mjs";
10
+ import "../chunk-FPW45EZH.mjs";
11
11
  import {
12
12
  capabilities_exports
13
- } from "../chunk-PW2VYGOS.mjs";
13
+ } from "../chunk-QBUGLR4H.mjs";
14
14
  import "../chunk-KFZEB6BV.mjs";
15
15
  import "../chunk-WK7OIQKI.mjs";
16
- import "../chunk-42UNAKYO.mjs";
17
- import "../chunk-SFYCO3PT.mjs";
16
+ import "../chunk-XGBUKRCK.mjs";
17
+ import "../chunk-4BX2AE47.mjs";
18
18
  import "../chunk-XOCUANHO.mjs";
19
- import "../chunk-6S45OMUP.mjs";
19
+ import "../chunk-XIGPWXCQ.mjs";
20
20
  import "../chunk-CTKEZHKF.mjs";
21
21
  import "../chunk-HSLMI22Q.mjs";
22
22
 
@@ -2,7 +2,6 @@ import { createRequire } from 'node:module';const require = createRequire(import
2
2
  import {
3
3
  App,
4
4
  LoadingState,
5
- Placeholder,
6
5
  useApp,
7
6
  useAtomCapability,
8
7
  useAtomCapabilityState,
@@ -14,28 +13,27 @@ import {
14
13
  useSettingsState,
15
14
  useSpaceCallback,
16
15
  useSpaceService
17
- } from "../chunk-JNT72ZCN.mjs";
16
+ } from "../chunk-Y7JKXNDU.mjs";
18
17
  import {
19
18
  PluginManagerProvider,
20
19
  Surface,
21
20
  usePluginManager,
22
21
  useSurface
23
- } from "../chunk-LJNUFNDO.mjs";
22
+ } from "../chunk-GEWHMHM7.mjs";
24
23
  import "../chunk-37Z53PXZ.mjs";
25
- import "../chunk-OUEMWPIW.mjs";
26
- import "../chunk-PW2VYGOS.mjs";
24
+ import "../chunk-FPW45EZH.mjs";
25
+ import "../chunk-QBUGLR4H.mjs";
27
26
  import "../chunk-KFZEB6BV.mjs";
28
27
  import "../chunk-WK7OIQKI.mjs";
29
- import "../chunk-42UNAKYO.mjs";
30
- import "../chunk-SFYCO3PT.mjs";
28
+ import "../chunk-XGBUKRCK.mjs";
29
+ import "../chunk-4BX2AE47.mjs";
31
30
  import "../chunk-XOCUANHO.mjs";
32
- import "../chunk-6S45OMUP.mjs";
31
+ import "../chunk-XIGPWXCQ.mjs";
33
32
  import "../chunk-CTKEZHKF.mjs";
34
33
  import "../chunk-HSLMI22Q.mjs";
35
34
  export {
36
35
  App,
37
36
  LoadingState,
38
- Placeholder,
39
37
  PluginManagerProvider,
40
38
  Surface,
41
39
  useApp,
@@ -1,25 +1,144 @@
1
1
  import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
2
 
3
- // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/boot-loader.css?raw
4
- var boot_loader_default = "/*\n * Boot loader stylesheet \u2014 injected into the host's `<head>` by `bootLoaderPlugin`.\n * The keyframe animation runs on the browser's compositor thread, so the bar\n * keeps moving even while the JS main thread is busy parsing modules.\n */\n\n/*\n * Defaults match `@dxos/ui-theme`'s `--color-base-surface`, which resolves to\n * Tailwind's `neutral-50` / `neutral-950` in light / dark mode respectively\n * (see `packages/ui/ui-theme/src/css/theme/semantic.css`). Hosts can override\n * with the `--boot-loader-bg-*` / `--boot-loader-fg-*` custom properties on\n * `:root` if they need a different palette. Keeping the boot loader's\n * background identical to the app's `body` eliminates the flash on handoff\n * to React's `<Placeholder>`.\n */\n#boot-loader {\n /*\n * Tunable sizes \u2014 change these in one place and every derived calc()\n * (mark size, dot orbit radius, status offset, \u2026) follows.\n */\n /*\n * Caps the disc / mark at the design pixel sizes on roomy viewports while\n * shrinking proportionally on narrow ones (`vmin` follows the smaller of\n * width / height) so neither the ring nor the mark crops on phones.\n */\n --boot-loader-disc-size: min(384px, 80vmin);\n /*\n * Match the React `Placeholder`'s logo box (`h-[300px] w-[300px]` at the\n * `stage >= 1` keyframe) so the brand mark doesn't visibly jump size when\n * the loader hands off \u2014 the responsive cap keeps the same 300/384 ratio\n * (\u224878%) at any viewport width.\n */\n --boot-loader-mark-size: min(300px, 62.5vmin);\n --boot-loader-dot-size: 6px;\n --boot-loader-line-thickness: 1px;\n --boot-loader-line-feather: 1px;\n --boot-loader-status-gap: 32px;\n\n position: fixed;\n inset: 0;\n z-index: 10;\n background: var(--boot-loader-bg-light, #fafafa);\n color: var(--boot-loader-fg-light, #0a0a0a);\n font-family:\n ui-sans-serif,\n system-ui,\n -apple-system,\n BlinkMacSystemFont,\n 'Segoe UI',\n sans-serif;\n font-size: 13px;\n}\n\n@media (prefers-color-scheme: dark) {\n #boot-loader {\n background: var(--boot-loader-bg-dark, #0a0a0a);\n color: var(--boot-loader-fg-dark, #fafafa);\n }\n}\n\n/*\n * Register the progress var as a typed custom property so the browser can\n * interpolate it. Both the conic-gradient arc and the orbiting dot derive\n * their angle from `--boot-loader-bar-progress`, so animating the var (rather\n * than the resulting `background` / `transform`) keeps them in lockstep \u2014\n * `transition: background` on a conic-gradient doesn't interpolate smoothly\n * in all engines, which previously caused the head of the arc to flicker\n * against the dot during each progress step.\n */\n@property --boot-loader-bar-progress {\n syntax: '<number>';\n inherits: true;\n initial-value: 0;\n}\n\n/*\n * Stack the ring and the brand mark in the same square so the mark sits\n * centered inside the circle. Grid stacking keeps the mark outside the ring's\n * mask (the mask would otherwise clip the SVG to the 1px-thick arc shape).\n */\n#boot-loader-disc {\n position: absolute;\n top: 50%;\n left: 50%;\n display: grid;\n place-items: center;\n width: var(--boot-loader-disc-size);\n height: var(--boot-loader-disc-size);\n transform: translate(-50%, -50%);\n transition: --boot-loader-bar-progress 240ms ease-out;\n}\n\n#boot-loader-disc > * {\n grid-column: 1;\n grid-row: 1;\n}\n\n/*\n * Default to grayscale so the mark reads as a quiet monochrome silhouette\n * before any host-driven progress lands; the brand palette swaps in once\n * the driver flips `data-host-driven` on the disc (state-2 entry \u2014 the\n * first `__bootLoader.progress()` call). Hover keeps a parallel reveal\n * affordance for inspection in the storybook. The 600ms ease lets the\n * grayscale \u2192 color transition play through the activation phase rather\n * than snapping.\n */\n#boot-loader-mark {\n width: var(--boot-loader-mark-size);\n height: var(--boot-loader-mark-size);\n display: flex;\n align-items: center;\n justify-content: center;\n filter: grayscale(100%);\n transition: filter 1000ms ease-out;\n}\n\n/*\n * `grayscale(0%)` (identity) rather than `none` \u2014 `filter: none` changes\n * the filter function list and most engines treat that as a discrete\n * transition (snap, not ease). Keeping `grayscale(...)` on both sides of\n * the rule lets the browser interpolate the percentage smoothly.\n */\n#boot-loader-disc[data-host-driven] #boot-loader-mark,\n#boot-loader-mark:hover {\n filter: grayscale(0%);\n}\n\n#boot-loader-mark svg {\n width: 100%;\n height: 100%;\n /*\n * The host SVG carries its own brand-palette fills, so we don't apply a\n * `color` cascade here. SVGs that intentionally use `fill=\"currentColor\"`\n * still pick up the loader's text colour via `#boot-loader`'s `color`.\n */\n}\n\n/*\n * Circular determinate progress \u2014 a 1px-thick ring carved out of a filled\n * disc by a radial-gradient mask. The mask applies to the element and its\n * descendants so the `::before` progress arc is clipped to the ring shape\n * too. There is no indeterminate state: the ring sits empty (0%) until the\n * host calls `__bootLoader.progress(fraction)`, then the arc grows in place.\n * The fill repaints on the compositor thread so progress remains visually\n * smooth even while the JS main thread is busy parsing modules.\n */\n#boot-loader-bar {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background: rgba(127, 127, 127, 0.18);\n opacity: 0.3;\n /*\n * Feather the inner edge so the thin line gets soft anti-aliased edges\n * instead of a hard pixel cutoff (which can look stair-stepped on\n * non-retina screens). The outer edge is clipped by `border-radius: 50%`,\n * so only the inner side of the ring needs the gradient feather. The\n * solid line lives in the outermost `--boot-loader-line-thickness` of the\n * disc; the feather extends `--boot-loader-line-feather` further inward.\n */\n mask: radial-gradient(\n circle closest-side,\n transparent calc(100% - var(--boot-loader-line-thickness) - var(--boot-loader-line-feather)),\n #000 calc(100% - var(--boot-loader-line-thickness))\n );\n -webkit-mask: radial-gradient(\n circle closest-side,\n transparent calc(100% - var(--boot-loader-line-thickness) - var(--boot-loader-line-feather)),\n #000 calc(100% - var(--boot-loader-line-thickness))\n );\n}\n\n/*\n * The host writes 0\u2013100 into `--boot-loader-bar-progress` via\n * `__bootLoader.progress(fraction)`. The `transition: background` smooths\n * the transitions between counter ticks so the arc grows continuously\n * rather than snapping. `conic-gradient` is clockwise-only, so the arc is\n * mirrored horizontally with `scaleX(-1)` to grow anticlockwise from\n * 12 o'clock.\n */\n#boot-loader-bar::before {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: 50%;\n background: conic-gradient(currentcolor calc(var(--boot-loader-bar-progress, 0) * 1%), transparent 0);\n transform: scaleX(-1);\n opacity: 0.5;\n}\n\n/*\n * A small dot at the leading edge of the arc. Lives as a sibling of the\n * masked ring (inside `#boot-loader-disc`) so the radial-gradient mask\n * doesn't clip it. The `rotate(...) translateY(-50%)` recipe orbits the\n * dot around the disc centre: at progress 0 it sits at 12 o'clock; the\n * negative rotation grows it anticlockwise to match the arc.\n */\n#boot-loader-dot {\n position: absolute;\n top: 50%;\n left: 50%;\n width: var(--boot-loader-dot-size);\n height: var(--boot-loader-dot-size);\n margin: calc(var(--boot-loader-dot-size) / -2) 0 0 calc(var(--boot-loader-dot-size) / -2);\n border-radius: 50%;\n background: currentcolor;\n opacity: 0.5;\n /*\n * Orbit radius = `disc/2 - line/2` so the dot's centre lands on the solid\n * line's centre rather than on the disc's outer edge (the line lives in\n * the outermost `--boot-loader-line-thickness` of the disc).\n */\n transform: rotate(calc(var(--boot-loader-bar-progress, 0) * -3.6deg))\n translateY(calc(var(--boot-loader-disc-size) / -2 + var(--boot-loader-line-thickness) / 2));\n transition: opacity 200ms ease-out;\n}\n\n/*\n * The driver sets `data-progress-active` on `#boot-loader-disc` while\n * progress is strictly between 0 and 1, hiding the dot at the empty (0%)\n * and full (100%) ring states where it would either sit on a bare ring or\n * coincide with the start of the arc.\n */\n#boot-loader-disc[data-progress-active] #boot-loader-dot {\n opacity: 0.5;\n}\n\n#boot-loader-status {\n position: absolute;\n top: 50%;\n left: 0;\n right: 0;\n /* \n * Sit just below the centered disc \u2014 half the disc plus the configured gap.\n */\n transform: translate(0, calc(var(--boot-loader-disc-size) / 2 + var(--boot-loader-status-gap)));\n /*\n * Slightly de-emphasised vs body text so the brand mark + ring stay the\n * dominant focus, but high enough to remain legible on both light and dark\n * backgrounds without a custom palette.\n */\n opacity: 0.5;\n /*\n * Terminal-style log viewport (3 rows at the default `line-height`) over\n * a vertically-stacked track of every status the driver has emitted. The\n * track is bottom-anchored via `justify-content: flex-end` so the newest\n * line always sits on the last visible row, older lines stack above it,\n * and anything that doesn't fit is clipped at the top by\n * `overflow: hidden`. The driver runs a FLIP-style transform animation\n * per append so each new line slides up from below the bottom edge \u2014\n * independent of any `scroll-behavior` smooth-scroll support, which\n * Playwright/headless Chrome strips even from spec-compliant\n * `scrollTo({ behavior: 'smooth' })` calls.\n */\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n height: 4.5em;\n line-height: 1.5;\n letter-spacing: 0.01em;\n /* \n * Fixed-width digits so the `(i/n)` counter on range-bearing status\n * updates (\"Loading plugins (12/80)\") doesn't jitter as the index\n * ticks through different glyph widths. \n */\n font-variant-numeric: tabular-nums;\n text-align: center;\n overflow: hidden;\n}\n\n#boot-loader-status-fade {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 32px;\n background: linear-gradient(to bottom, var(--boot-loader-bg-light, #fafafa), transparent);\n z-index: 1;\n}\n@media (prefers-color-scheme: dark) {\n #boot-loader-status-fade {\n background: linear-gradient(to bottom, var(--boot-loader-bg-dark, #0a0a0a), transparent);\n }\n}\n\n/*\n * Track of every appended `.boot-loader-status-line`. The driver writes\n * `--boot-loader-line` (0 = first line visible, 1 = second, \u2026) on this\n * element and the `transform` interpolates because `--boot-loader-line` is\n * registered as a typed `<number>` custom property below.\n */\n#boot-loader-status-track {\n display: flex;\n flex-direction: column;\n /*\n * Resting transform is `translateY(0)` \u2014 the bottom-anchored flex\n * parent already positions the newest line on the last visible row.\n * On each append the driver does a one-shot FLIP: snap to\n * `translateY(<lineHeight>px)` with no transition (placing the new\n * line just below the viewport), force a reflow, then animate back to\n * 0 so the track rises by one line-height and the new entry slides up\n * into the bottom row. See `boot-loader.js`.\n */\n transform: translateY(0);\n transition: transform 500ms ease-out;\n}\n\n/* Honour reduced-motion preferences explicitly \u2014 `transition` doesn't auto-respect it. */\n@media (prefers-reduced-motion: reduce) {\n #boot-loader-status-track {\n transition: none;\n }\n}\n\n.boot-loader-status-line {\n flex: 0 0 auto;\n height: 1.5em;\n white-space: nowrap;\n}\n";
3
+ // src/vite-plugin/boot-loader/loader.ts
4
+ import * as babel from "@babel/core";
5
+ import babelPresetTypeScript from "@babel/preset-typescript";
6
+ import babelPresetSolid from "babel-preset-solid";
7
+ import { build } from "esbuild";
8
+ import { dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/loader-app/boot-loader.css?raw
12
+ var boot_loader_default = "/*\n * Boot loader stylesheet \u2014 injected into the host's `<head>` by `bootLoaderPlugin`\n * so the full-screen backdrop paints before any JS bundle is fetched. The Solid\n * `Loader` renders the ring + status into the `#boot-loader` backdrop; this\n * sheet styles both. Class/id names here form a contract with `Loader.tsx`,\n * guarded by `BootLoader.solid-stories.tsx` (it mounts the real component).\n */\n\nbody {\n margin: 0;\n overflow: hidden;\n}\n\n/*\n * Defaults match `@dxos/ui-theme`'s `--color-base-surface`, which resolves to\n * Tailwind's `neutral-50` / `neutral-950` in light / dark mode respectively\n * (see `packages/ui/ui-theme/src/css/theme/semantic.css`). Hosts can override\n * with the `--boot-loader-bg-*` / `--boot-loader-fg-*` custom properties on\n * `:root` if they need a different palette. Keeping the boot loader's\n * background identical to the app's `body` eliminates the flash on handoff\n * to the rendered React shell.\n */\n#boot-loader {\n --color-composer-100: rgb(5 40 61);\n --color-composer-200: rgb(10 75 105);\n --color-composer-300: rgb(1 122 183);\n --color-composer-400: rgb(6 197 253);\n\n /*\n * Caps the disc / mark at the design pixel sizes on roomy viewports while\n * shrinking proportionally on narrow ones (`vmin` follows the smaller of\n * width / height) so neither the ring nor the mark crops on phones.\n */\n --boot-loader-disc-size: min(384px, 80vmin);\n /*\n * Match the React `Placeholder`'s logo box (`h-[300px] w-[300px]` at the\n * `stage >= 1` keyframe) so the brand mark doesn't visibly jump size when\n * the loader hands off \u2014 the responsive cap keeps the same 300/384 ratio\n * (\u224878%) at any viewport width.\n */\n --boot-loader-mark-size: min(300px, 62.5vmin);\n --boot-loader-status-gap: 32px;\n\n position: fixed;\n inset: 0;\n z-index: 10;\n font-size: 13px;\n font-family:\n ui-sans-serif,\n system-ui,\n -apple-system,\n BlinkMacSystemFont,\n 'Segoe UI',\n sans-serif;\n /*\n * Dismissal outro \u2014 `mountLoader` toggles `data-dismissing` on this element\n * when the host calls `__bootLoader.ready()`; the loader cross-fades out over\n * the real app shell rendering beneath it, and removes itself once this\n * opacity transition completes (a fallback timer covers reduced-motion).\n */\n transition: opacity 500ms ease-out;\n}\n\n#boot-loader {\n background: var(--boot-loader-bg-light, #fafafa);\n color: var(--boot-loader-fg-light, #0a0a0a);\n --boot-loader-accent: var(--boot-loader-accent-light, var(--color-composer-200));\n}\n\n@media (prefers-color-scheme: dark) {\n #boot-loader {\n background: var(--boot-loader-bg-dark, #0a0a0a);\n color: var(--boot-loader-fg-dark, #fafafa);\n --boot-loader-accent: var(--boot-loader-accent-dark, var(--color-composer-300));\n }\n}\n\n#boot-loader[data-dismissing] {\n opacity: 0;\n}\n\n/* Gently shrink the disc + mark as the loader fades, echoing the prior handoff. */\n#boot-loader[data-dismissing] #boot-loader-disc {\n transform: translate(-50%, -50%) scale(0.92);\n transition: transform 500ms ease-out;\n}\n\n/*\n * Stack the ring (the SVG `#boot-loader-ring`) and the brand mark in the same\n * grid cell so the mark sits centered inside the circle.\n */\n#boot-loader-disc {\n position: absolute;\n top: 50%;\n left: 50%;\n display: grid;\n place-items: center;\n width: var(--boot-loader-disc-size);\n height: var(--boot-loader-disc-size);\n transform: translate(-50%, -50%);\n}\n\n#boot-loader-disc > * {\n grid-column: 1;\n grid-row: 1;\n}\n\n/*\n * Default to grayscale so the mark reads as a quiet monochrome silhouette\n * before any host-driven progress lands; the brand palette swaps in once\n * `Loader.tsx` sets `data-host-driven` on the disc (the first\n * `__bootLoader.progress()` call). Hover keeps a parallel reveal affordance\n * for inspection in the storybook. The 1000ms ease lets the grayscale \u2192 color\n * transition play through the activation phase rather than snapping.\n */\n#boot-loader-mark {\n width: var(--boot-loader-mark-size);\n height: var(--boot-loader-mark-size);\n display: flex;\n align-items: center;\n justify-content: center;\n filter: grayscale(100%);\n transition: filter 1000ms ease-out;\n}\n\n/*\n * `grayscale(0%)` (identity) rather than `none` \u2014 `filter: none` changes\n * the filter function list and most engines treat that as a discrete\n * transition (snap, not ease). Keeping `grayscale(...)` on both sides of\n * the rule lets the browser interpolate the percentage smoothly.\n */\n#boot-loader-disc[data-host-driven] #boot-loader-mark,\n#boot-loader-mark:hover {\n filter: grayscale(0%);\n}\n\n#boot-loader-mark svg {\n width: 100%;\n height: 100%;\n /*\n * The host SVG carries its own brand-palette fills, so we don't apply a\n * `color` cascade here. SVGs that intentionally use `fill=\"currentColor\"`\n * still pick up the loader's text colour via `#boot-loader`'s `color`.\n */\n}\n\n/*\n * Circular determinate progress \u2014 a single SVG `<path>` arc that sweeps\n * anticlockwise from 12 o'clock, its angle growing with progress (the `d` is\n * recomputed by the component each tick). There is no static track;\n * `stroke-linecap: round` rounds the trailing edge, and `marker-end` paints the\n * leading-edge dot. Stroke width is in viewBox units so it scales with the disc.\n */\n#boot-loader-ring {\n width: var(--boot-loader-disc-size);\n height: var(--boot-loader-disc-size);\n overflow: visible;\n /*\n * Fade the arc linearly from the head (the marker, fully opaque) back to the\n * tail (12 o'clock, transparent). A conic-gradient mask does this along the\n * curve \u2014 `Loader.tsx` writes the eased progress into `--boot-loader-arc`\n * (0\u2013100), and the arc occupies the conic range `(100 - arc)%`\u2026`100%`, so the\n * head sits at `(100 - arc)%` (opaque) and the tail at `100%` (transparent).\n * NOTE: We start at -1.16deg so that we don't mask the marker.\n */\n --boot-loader-arc: 0;\n mask: conic-gradient(\n from -1.16deg at 50% 50%,\n transparent 0,\n transparent calc((100 - var(--boot-loader-arc)) * 1%),\n #000 calc((100 - var(--boot-loader-arc)) * 1%),\n transparent 100%\n );\n -webkit-mask: conic-gradient(\n from -1.16deg at 50% 50%,\n transparent 0,\n transparent calc((100 - var(--boot-loader-arc)) * 1%),\n #000 calc((100 - var(--boot-loader-arc)) * 1%),\n transparent 100%\n );\n}\n\n/*\n * The leading-edge dot's layer \u2014 same geometry as `#boot-loader-ring` but with no\n * mask, so the dot renders as a full circle (the ring's conic fade mask would\n * otherwise bisect it where it sits on the mask's transparent\u2192opaque edge).\n */\n#boot-loader-ring-head {\n width: var(--boot-loader-disc-size);\n height: var(--boot-loader-disc-size);\n overflow: visible;\n}\n\n.boot-loader-ring-progress {\n fill: none;\n stroke: var(--boot-loader-accent);\n /* viewBox units: ~1.9px on the 384px disc, scaling down on smaller viewports. */\n stroke-width: 0.5;\n opacity: 0.5;\n stroke-linecap: butt;\n overflow: visible;\n /* Dashed line: dash + gap lengths in viewBox units (~2 units \u2248 7.7px @384px). */\n /* stroke-dasharray: 2 1; */\n}\n\n/* The `marker-end` dot at the arc's leading edge. */\n.boot-loader-ring-marker {\n fill: var(--boot-loader-accent);\n stroke: none;\n overflow: visible;\n /* opacity: 0.5; */\n}\n\n#boot-loader-status {\n position: absolute;\n top: 50%;\n left: 0;\n right: 0;\n /* \n * Sit just below the centered disc \u2014 half the disc plus the configured gap.\n */\n transform: translate(0, calc(var(--boot-loader-disc-size) / 2 + var(--boot-loader-status-gap)));\n /*\n * Slightly de-emphasised vs body text so the brand mark + ring stay the\n * dominant focus, but high enough to remain legible on both light and dark\n * backgrounds without a custom palette.\n */\n opacity: 0.5;\n /*\n * Terminal-style log viewport (3 rows at the default `line-height`) over\n * a vertically-stacked track of every status the driver has emitted. The\n * track is bottom-anchored via `justify-content: flex-end` so the newest\n * line always sits on the last visible row, older lines stack above it,\n * and anything that doesn't fit is clipped at the top by\n * `overflow: hidden`. The driver runs a FLIP-style transform animation\n * per append so each new line slides up from below the bottom edge \u2014\n * independent of any `scroll-behavior` smooth-scroll support, which\n * Playwright/headless Chrome strips even from spec-compliant\n * `scrollTo({ behavior: 'smooth' })` calls.\n */\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n height: 4.5em;\n line-height: 1.5;\n letter-spacing: 0.01em;\n /* \n * Fixed-width digits so the `(i/n)` counter on range-bearing status\n * updates (\"Loading plugins (12/80)\") doesn't jitter as the index\n * ticks through different glyph widths. \n */\n font-variant-numeric: tabular-nums;\n text-align: center;\n overflow: hidden;\n}\n\n#boot-loader-status-fade {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 32px;\n background: linear-gradient(to bottom, var(--boot-loader-bg-light, #fafafa), transparent);\n z-index: 1;\n}\n@media (prefers-color-scheme: dark) {\n #boot-loader-status-fade {\n background: linear-gradient(to bottom, var(--boot-loader-bg-dark, #0a0a0a), transparent);\n }\n}\n\n#boot-loader-rss {\n background: var(--boot-loader-bg-light, #fafafa);\n text-wrap: balance;\n}\n@media (prefers-color-scheme: dark) {\n #boot-loader-rss {\n background: var(--boot-loader-bg-dark, #0a0a0a);\n }\n}\n\n/*\n * Track of every appended `.boot-loader-status-line`.\n */\n#boot-loader-status-track {\n display: flex;\n flex-direction: column;\n /*\n * Resting transform is `translateY(0)` \u2014 the bottom-anchored flex parent\n * already positions the newest line on the last visible row. On each append\n * `Loader.tsx` runs a one-shot FLIP: snap to `translateY(<lineHeight>px)` with\n * no transition (placing the new line just below the viewport), force a\n * reflow, then animate back to 0 so the track rises by one line-height and the\n * new entry slides up into the bottom row.\n */\n transform: translateY(0);\n transition: transform 500ms ease-out;\n}\n\n/* Honour reduced-motion preferences explicitly \u2014 `transition` doesn't auto-respect it. */\n@media (prefers-reduced-motion: reduce) {\n #boot-loader-status-track {\n transition: none;\n }\n}\n\n.boot-loader-status-line {\n flex: 0 0 auto;\n height: 1.5em;\n white-space: nowrap;\n}\n";
13
+
14
+ // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/loader-app/bridge.ts?raw
15
+ var bridge_default = "//\n// Copyright 2026 DXOS.org\n//\n\nimport { type LoaderStore } from './store';\nimport { type BootLoaderApi } from './types';\n\n/**\n * Wrap a reactive {@link LoaderStore} as the imperative `window.__bootLoader`\n * facade the host app drives. `status` / `progress` write into the store;\n * `ready` plays the graceful dismissal outro; `dismiss` is the immediate-remove\n * backstop for the fast-load path (and a no-op once an outro is already in\n * flight, so a late `dismiss()` from the framework never cuts the animation).\n */\nexport const createBridge = (store: LoaderStore, dismissNow: () => void): BootLoaderApi => ({\n status: (payload) => store.pushStatus(payload),\n progress: (fraction) => store.setProgress(fraction),\n ready: () => store.ready(),\n dismiss: () => {\n if (store.phase() === 'dismissing') {\n return;\n }\n dismissNow();\n },\n});\n";
16
+
17
+ // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/loader-app/entry.tsx?raw
18
+ var entry_default = "//\n// Copyright 2026 DXOS.org\n//\n\nimport { mountLoader } from './mount';\nimport { DEFAULT_ROOT_ID } from './types';\n\n//\n// Production entry \u2014 bundled to a self-contained IIFE by `bootLoaderPlugin` and\n// inlined into `index.html` ahead of the app bundle. Mounts the loader into the\n// static backdrop using the config the plugin baked in just before this script\n// (including the backdrop id, so the id has a single source). The storybook\n// does NOT use this entry; it imports `mountLoader` directly so Vite compiles\n// the Solid source with HMR.\n//\n\nconst config = window.__BOOT_LOADER_CONFIG__ ?? {};\nconst el = document.getElementById(config.rootId ?? DEFAULT_ROOT_ID);\nif (el) {\n mountLoader(el, config);\n}\n";
19
+
20
+ // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/loader-app/Loader.tsx?raw
21
+ var Loader_default = "//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Component, For, createEffect, createSignal, onCleanup, onMount } from 'solid-js';\n\nimport { type LoaderStore } from './store';\n\n// Ring geometry in the SVG's `0 0 100 100` viewBox. The radius leaves a couple\n// of units of padding so the stroke, its round cap, and the end marker aren't\n// clipped at the viewBox edge.\nconst RING_RADIUS = 48;\nconst RING_CENTER = 50;\n// Leading-edge marker: a small dot drawn at the arc's head in an unmasked layer.\nconst MARKER_RADIUS = 1; // viewBox units \u2192 ~3.8px on the 384px disc\n\n/**\n * Read an element's *current animated* translateY (px) from its live transform\n * matrix \u2014 the interpolated value mid-transition, not the last-written property.\n * Used by the status FLIP so successive appends chain off the in-flight position\n * instead of yanking the track back to a fixed invert target.\n */\nconst readTranslateY = (element: HTMLElement): number => {\n const computed = getComputedStyle(element).transform;\n if (!computed || computed === 'none') {\n return 0;\n }\n const match = computed.match(/matrix.*\\(([^)]+)\\)/);\n if (!match) {\n return 0;\n }\n const values = match[1].split(',');\n // 2D `matrix(a, b, c, d, tx, ty)` \u2192 ty at index 5; 3D `matrix3d(...)` \u2192 ty at 13.\n if (values.length === 6) {\n return Number.parseFloat(values[5]) || 0;\n }\n if (values.length === 16) {\n return Number.parseFloat(values[13]) || 0;\n }\n return 0;\n};\n\nexport type LoaderProps = {\n /** Reactive source of truth for progress, status lines, and lifecycle phase. */\n store: LoaderStore;\n /** Inline SVG markup for the brand mark rendered inside the ring. */\n markSvg?: string;\n};\n\n/**\n * The boot loader, authored as a single Solid component \u2014 the one source of\n * truth for the loader DOM. `bootLoaderPlugin` bundles this (Solid runtime\n * inlined) into `index.html`; the storybook mounts the very same component, so\n * the two can no longer drift. The DOM structure, ids, and classes mirror\n * `boot-loader.css`.\n *\n * The full-screen backdrop (`#boot-loader`) is injected as static markup so it\n * paints from CSS before this bundle executes; this component renders the disc\n * and status log *into* that backdrop. The dismissal outro (fading `#boot-loader`)\n * and its teardown are owned by {@link mountLoader}, which has the host element.\n */\nexport const Loader: Component<LoaderProps> = (props) => {\n let trackRef: HTMLDivElement | undefined;\n let previousCount = props.store.lines().length;\n\n // FLIP-style slide on each appended line: snap the track down by one\n // line-height (chained off any in-flight translate) with no transition, force\n // a reflow, then animate back to translateY(0) so the new entry rises from\n // below the bottom-anchored viewport. Range ticks (length unchanged) skip it.\n createEffect(() => {\n const count = props.store.lines().length;\n const track = trackRef;\n if (track && count > previousCount) {\n const currentY = readTranslateY(track);\n const lineHeight = track.lastElementChild?.getBoundingClientRect().height ?? 0;\n track.style.transition = 'none';\n track.style.transform = `translateY(${currentY + lineHeight}px)`;\n void track.offsetHeight;\n track.style.transition = '';\n track.style.transform = 'translateY(0)';\n }\n previousCount = count;\n });\n\n // Eased display progress. A `<path>`'s `d` can't be CSS-transitioned, so the\n // arc would snap on every store tick; instead we ease a displayed value toward\n // the store's progress with a requestAnimationFrame loop (~per-frame lerp) and\n // recompute the path each frame. This restores the compositor-smooth growth\n // the old `transition: stroke-dashoffset` gave, while keeping the path +\n // `marker-end` (the marker rides the arc's recomputed end automatically).\n const [shown, setShown] = createSignal(props.store.progress());\n let raf: number | undefined;\n const animate = () => {\n const target = props.store.progress();\n const current = shown();\n // Snap when within a hair to settle; otherwise lerp ~18% of the gap/frame.\n const next = Math.abs(target - current) < 0.05 ? target : current + (target - current) * 0.18;\n if (next !== current) {\n setShown(next);\n }\n raf = requestAnimationFrame(animate);\n };\n\n onMount(() => {\n raf = requestAnimationFrame(animate);\n });\n\n onCleanup(() => {\n if (raf != null) {\n cancelAnimationFrame(raf);\n }\n });\n\n // Determinate ring as an SVG `<path>` arc: an anticlockwise sweep starting at\n // 12 o'clock whose angle grows with the eased `shown()` progress. The sweep is\n // capped just shy of a full turn so the single arc command never degenerates at\n // 100%. The leading-edge dot is its `head` point, rendered separately (below) in\n // an *unmasked* layer \u2014 drawing it via `marker-end` on this path would put it\n // under the ring's conic fade mask, whose hard edge bisects the dot into a\n // half-circle.\n const arc = () => {\n const fraction = Math.min(shown() / 100, 0.9999);\n const sweep = fraction * 2 * Math.PI;\n const start = -Math.PI / 2; // 12 o'clock\n const end = start - sweep; // anticlockwise (decreasing angle)\n const x0 = RING_CENTER + RING_RADIUS * Math.cos(start);\n const y0 = RING_CENTER + RING_RADIUS * Math.sin(start);\n const headX = RING_CENTER + RING_RADIUS * Math.cos(end);\n const headY = RING_CENTER + RING_RADIUS * Math.sin(end);\n const largeArc = sweep > Math.PI ? 1 : 0;\n // sweep-flag 0 = anticlockwise (negative-angle) direction.\n return { d: `M ${x0} ${y0} A ${RING_RADIUS} ${RING_RADIUS} 0 ${largeArc} 0 ${headX} ${headY}`, headX, headY };\n };\n\n // Once host-driven, the brand mark eases grayscale \u2192 colour and stays there.\n const isHostDriven = () => props.store.phase() !== 'creep';\n\n return (\n <>\n <div id='boot-loader-disc' data-host-driven={isHostDriven() ? '' : undefined}>\n <svg\n id='boot-loader-ring'\n viewBox='0 0 100 100'\n aria-hidden='true'\n style={{ '--boot-loader-arc': String(shown()) }}\n >\n {shown() > 0 ? <path class='boot-loader-ring-progress' d={arc().d} /> : null}\n </svg>\n {/* Leading-edge dot in its own unmasked layer (see `arc()`), so the ring's fade mask\n never clips it. Stacked over `#boot-loader-ring` via the disc grid. */}\n {shown() > 0 ? (\n <svg id='boot-loader-ring-head' viewBox='0 0 100 100' aria-hidden='true'>\n <circle class='boot-loader-ring-marker' cx={arc().headX} cy={arc().headY} r={MARKER_RADIUS} />\n </svg>\n ) : null}\n {props.markSvg ? <div id='boot-loader-mark' innerHTML={props.markSvg} /> : null}\n </div>\n <div id='boot-loader-status'>\n <div id='boot-loader-status-fade' />\n <div id='boot-loader-status-track' ref={trackRef}>\n <For each={props.store.lines()}>{(line) => <div class='boot-loader-status-line'>{line.text}</div>}</For>\n </div>\n </div>\n </>\n );\n};\n";
5
22
 
6
- // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/boot-loader.js?raw
7
- var boot_loader_default2 = "//\n// Copyright 2026 DXOS.org\n//\n\n// Inline driver script \u2014 injected into the host's `<body>` by `bootLoaderPlugin`.\n// Runs before the module bundle is fetched, so the host can observe `boot:html-parsed`\n// and update the visible status from `main.tsx` between phases.\n//\n// The loader has two host-driven states (plus an unobserved idle state on\n// script entry that's promoted automatically \u2014 there's no API to opt out of\n// motion on cold load, by design):\n// - State 1 (slow tick): auto-entered the moment the inline script runs;\n// asymptotic creep toward `STATE_1_ASYMPTOTE`%.\n// - State 2 (progress): host-driven, value supplied to `progress(fraction)`.\n// The creep stays alive in state 2 too, with a\n// ceiling that climbs `STATE_2_BUMP` percent ahead\n// of the most recent `progress()` call (capped at\n// `ABSOLUTE_CEILING`). Between sparse host updates\n// the ring keeps inching forward toward that\n// ceiling, so a long activation gap doesn't read\n// as a frozen disc.\n//\n// `window.__bootLoader.status(text)` updates the visible status line.\n// `window.__bootLoader.progress(fraction)` enter state 2 with `fraction` \u2208 [0, 1].\n// `window.__bootLoader.dismiss()` remove the loader DOM (terminal).\n//\n// Kept ES5-flavoured so the bundler never has to touch it.\n\n(function () {\n performance.mark('boot:html-parsed');\n\n // Asymptotic creep \u2014 `next = raw + (ceiling - raw) * creepRate` per tick,\n // so the var eases toward `ceiling` and never quite reaches it.\n var CREEP_TICK_MS = 100;\n // State 1 creep is a \"we're alive\" hint before any real progress lands,\n // so it eases gently toward a low asymptote.\n var STATE_1_RATE = 0.04;\n var STATE_1_ASYMPTOTE = 20;\n // State 2 creep bridges the gap between sparse `progress()` calls.\n // Tuning is a trade-off:\n // - Larger `STATE_2_RATE` \u2192 fills faster, but reaches the ceiling\n // sooner and then sits idle (looks \"paused\").\n // - Larger `STATE_2_BUMP` \u2192 ring leads further ahead of the host's\n // last reported value, so subsequent host updates feel \"behind\"\n // until they cross the lead.\n // Current values aim for continuous-but-modest motion through a ~5 s\n // activation silence: 50 % \u2192 ~64 % over 5 s, ceiling at host + 15 % so\n // real progress can catch up via `Math.max` without big jumps.\n var STATE_2_RATE = 0.05;\n var STATE_2_BUMP = 15;\n // Ring never auto-creeps past this \u2014 keeps the user from interpreting the\n // ring as \"almost done\" while the host hasn't actually said so.\n var ABSOLUTE_CEILING = 90;\n\n // 0 = idle (transient \u2014 auto-promoted to 1 immediately below),\n // 1 = slow tick, 2 = host-driven progress.\n var state = 0;\n var creepHandle = null;\n var creepCeiling = STATE_1_ASYMPTOTE;\n var creepRate = STATE_1_RATE;\n var trace = /(?:^|[?&])trace=1(?:&|$)/.test(window.location.search);\n var start = Date.now();\n var last = Date.now();\n var timings = [];\n\n function ensureCreep() {\n if (creepHandle != null) {\n return;\n }\n\n creepHandle = setInterval(function () {\n var element = document.getElementById('boot-loader-disc');\n if (!element) {\n return;\n }\n var raw = parseFloat(element.style.getPropertyValue('--boot-loader-bar-progress')) || 0;\n // Already at (or above) the ceiling \u2014 nothing to do this tick.\n if (raw >= creepCeiling - 0.1) {\n return;\n }\n var next = raw + (creepCeiling - raw) * creepRate;\n element.style.setProperty('--boot-loader-bar-progress', String(next));\n if (next > 0 && next < 100) {\n element.setAttribute('data-progress-active', '');\n }\n }, CREEP_TICK_MS);\n }\n\n function stopCreep() {\n if (creepHandle != null) {\n clearInterval(creepHandle);\n creepHandle = null;\n }\n }\n\n // Read the element's *current animated* translateY in pixels \u2014 the\n // interpolated value mid-transition, not the property as last written.\n // `getComputedStyle(...).transform` returns a `matrix(...)` /\n // `matrix3d(...)` string with the live, interpolated translation.\n // Used by the status FLIP so successive appends chain off the in-flight\n // position instead of yanking the track back to a fixed invert target.\n function readTranslateY(element) {\n var computed = window.getComputedStyle(element).transform;\n if (!computed || computed === 'none') {\n return 0;\n }\n var match = computed.match(/matrix.*\\(([^)]+)\\)/);\n if (!match) {\n return 0;\n }\n var values = match[1].split(',');\n // 2D `matrix(a, b, c, d, tx, ty)` \u2192 ty at index 5.\n // 3D `matrix3d(...)` (16 entries) \u2192 ty at index 13.\n if (values.length === 6) {\n return parseFloat(values[5]) || 0;\n }\n if (values.length === 16) {\n return parseFloat(values[13]) || 0;\n }\n return 0;\n }\n\n window.__bootLoader = {\n /**\n * Update the visible status line. The caller owns formatting \u2014 the\n * loader just renders `humanized` and records the structured fields\n * for the trace. Idempotent on the full structured payload: equal\n * back-to-back updates are a no-op, but two updates with the same\n * visible text and different `event` / `module` ids still produce a\n * fresh trace entry so the timeline can distinguish those transitions.\n *\n * Payload: `{ event?, module?, humanized, range? }`.\n * - `humanized`: the exact string to display (e.g. \"Loading framework\u2026\",\n * \"Activating Observability: react-surface\").\n * - `event` / `module`: optional raw ids for the trace, when the\n * update originates from an activation transition.\n * - `range`: optional `{ index, total }` for ticking progress within\n * a single phase (e.g. \"Loading plugins (12/80)\"). When present,\n * the loader replaces the current line in place rather than\n * appending a new one \u2014 so 80 plugin loads produce one trace\n * entry with an updating suffix instead of 80 separate lines.\n */\n status: function (payload) {\n if (!payload || typeof payload !== 'object') {\n return;\n }\n var humanized = payload.humanized || '';\n var event = payload.event || null;\n var moduleId = payload.module || null;\n var range =\n payload.range && typeof payload.range.index === 'number' && typeof payload.range.total === 'number'\n ? payload.range\n : null;\n // Display text appends the (i/n) suffix when a range is provided so\n // the same `humanized` string can tick through a count without the\n // caller having to format it.\n var displayText = range ? humanized + ' (' + range.index + '/' + range.total + ')' : humanized;\n // Compare against the previous entry's structured fields, not the\n // DOM text alone \u2014 two different transitions can humanize to the same\n // visible string (e.g. an event-level \"Activating Foo\" followed by a\n // module-level \"Activating Foo: react-surface\" reduced to \"Activating\n // Foo\" by a custom formatter), and the trace should still carry both.\n var previous = timings.length > 0 ? timings[timings.length - 1] : null;\n if (\n previous &&\n previous.text === displayText &&\n (previous.event || null) === event &&\n (previous.module || null) === moduleId\n ) {\n return;\n }\n if (range && previous) {\n // Range tick \u2014 collapse into the previous trace entry instead of\n // appending. The trace timeline records one transition per phase,\n // not one per `(i/n)` step, keeping the boot trace compact.\n previous.text = displayText;\n } else {\n if (previous) {\n previous.duration = Date.now() - last;\n }\n var entry = { text: displayText };\n if (event) {\n entry.event = event;\n }\n if (moduleId) {\n entry.module = moduleId;\n }\n timings.push(entry);\n last = Date.now();\n }\n var element = document.getElementById('boot-loader-status');\n if (!element) {\n return;\n }\n // The track holds every appended status line. On the first\n // `status()` call we hoist any pre-existing lines (the initial\n // status injected by `bootLoaderPlugin`) into the track so the\n // CSS `transform` translation applies uniformly.\n var track = document.getElementById('boot-loader-status-track');\n var lastLine = track.lastElementChild;\n if (range && lastLine) {\n // Range tick \u2014 replace the current visible line's text in place,\n // no slide. The line keeps its position so the (i/n) counter just\n // updates over the same row.\n lastLine.textContent = displayText;\n } else {\n // Fresh phase (or first line) \u2014 append to the bottom-anchored track\n // (see `#boot-loader-status`'s `justify-content: flex-end` in\n // `boot-loader.css`) and run a FLIP-style slide so the new line\n // rises from below the viewport's bottom edge instead of snapping\n // into place. The flex layout handles which lines are visible at\n // rest (newest on the last row, older lines stacked above, anything\n // that overflows is clipped at the top by `overflow: hidden`), so\n // this code only owns the animation.\n var line = document.createElement('div');\n line.className = 'boot-loader-status-line';\n line.textContent = displayText;\n // Read the track's current animated translateY before mutating\n // anything. When a previous slide is still in flight this is\n // somewhere in (0, lineHeight]; when the track is at rest it's 0.\n // Reading via `getComputedStyle` (not `style.transform`) gives the\n // interpolated mid-transition value, which is what the chain math\n // below needs.\n var currentY = readTranslateY(track);\n // Measure the line height from a sibling when one exists \u2014 the\n // newly appended line may not have laid out yet at the moment of\n // the bounding-rect read in some engines. Pixels (not `em`) because\n // some engines (and headless Chromium) don't reliably interpolate\n // `transform` between `em`-valued translateY's.\n var probe = track.lastElementChild;\n track.appendChild(line);\n var lineHeight = (probe || line).getBoundingClientRect().height || 0;\n // Invert: snap the track down to `currentY + lineHeight` with no\n // transition. The `+ lineHeight` is the usual FLIP offset (the\n // track just grew by a row, so this keeps every existing line at\n // the pixel position it had before the append). The `+ currentY`\n // is the chain term \u2014 if a previous slide was still in flight we\n // pin the track to its in-flight position instead of yanking it\n // back to its pre-animation start, so the previous line keeps\n // rising smoothly through the append rather than jumping down.\n // Then force a reflow and animate to translateY(0); the track\n // covers the full `currentY + lineHeight` distance in one ride\n // and bursts of rapid updates read as a continuous scroll rather\n // than overlapping snaps.\n track.style.transition = 'none';\n track.style.transform = `translateY(${currentY + lineHeight}px)`;\n void track.offsetHeight;\n track.style.transition = '';\n track.style.transform = 'translateY(0)';\n }\n },\n\n /**\n * Enter state 2 (host-driven progress) \u2014 set the determinate ring to\n * `fraction` \u2208 [0, 1]. Invalid / negative / non-finite values clamp to\n * 0 (the empty ring) rather than letting `NaN`/`Infinity` slip into\n * `--boot-loader-bar-progress`, which CSS would treat as invalid and\n * silently reset to the 0% var() default. The state-1 creep timer is\n * left running with a new ceiling so the ring keeps inching forward\n * between host updates.\n *\n * The ring never regresses: if the requested value is below the\n * current var (e.g. the host's first call is `progress(0)` while the\n * creep had already reached ~12%), the existing value is held. The\n * host's progress catches up once it exceeds the held floor, so the\n * ring's motion stays monotonic across the state-1 \u2192 state-2 boundary.\n */\n progress: function (fraction) {\n state = 2;\n // Set the var on the disc so both the masked ring and the orbiting\n // dot (siblings inside `#boot-loader-disc`) inherit the same value.\n var element = document.getElementById('boot-loader-disc');\n if (!element) {\n return;\n }\n // Flip into \"host-driven\" mode the first time `progress()` lands \u2014\n // CSS keys the brand mark's grayscale\u2192color transition off this\n // attribute, so the mark eases from monochrome to full palette as\n // real progress starts arriving.\n element.setAttribute('data-host-driven', '');\n var clamped = typeof fraction !== 'number' || !isFinite(fraction) || fraction < 0 ? 0 : Math.min(1, fraction);\n var requestedPct = clamped * 100;\n var currentPct = parseFloat(element.style.getPropertyValue('--boot-loader-bar-progress')) || 0;\n var nextPct = Math.max(currentPct, requestedPct);\n element.style.setProperty('--boot-loader-bar-progress', String(nextPct));\n // Switch the ongoing creep to its state-2 cadence \u2014 faster ease and\n // a larger lead bump so the ring keeps moving while the host is\n // silent (e.g. composer-app's \"Starting Composer\u2026\" stretch while the\n // first slow plugin module activates).\n creepRate = STATE_2_RATE;\n var newCeiling = Math.min(nextPct + STATE_2_BUMP, ABSOLUTE_CEILING);\n if (newCeiling > creepCeiling) {\n creepCeiling = newCeiling;\n }\n ensureCreep();\n // Toggle the leading-edge dot only while progress is strictly in (0, 1).\n if (nextPct > 0 && nextPct < 100) {\n element.setAttribute('data-progress-active', '');\n } else {\n element.removeAttribute('data-progress-active');\n }\n },\n\n dismiss: function () {\n stopCreep();\n var element = document.getElementById('boot-loader');\n if (element && element.parentNode) {\n element.parentNode.removeChild(element);\n }\n\n if (trace) {\n if (timings.length > 0) {\n timings[timings.length - 1].duration = Date.now() - last;\n }\n console.log(JSON.stringify({ total: Date.now() - start, timings }, null, 2));\n }\n },\n };\n\n // Auto-promote idle \u2192 slow tick the moment this inline script executes,\n // so the disc starts moving on the very first frame the loader paints.\n // The host calls `progress()` later to take over.\n state = 1;\n ensureCreep();\n})();\n";
23
+ // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/loader-app/mount.tsx?raw
24
+ var mount_default = "//\n// Copyright 2026 DXOS.org\n//\n\nimport { createEffect } from 'solid-js';\nimport { render } from 'solid-js/web';\n\nimport { createBridge } from './bridge';\nimport { Loader } from './Loader';\nimport { createLoaderStore } from './store';\nimport { type BootLoaderConfig } from './types';\n\n/** Fallback teardown if the outro's `transitionend` never fires (e.g. no opacity transition). */\nconst OUTRO_FALLBACK_MS = 800;\n\n/**\n * Mount the boot loader into a host element (`#boot-loader`, the static backdrop\n * the plugin injects \u2014 or any container in the storybook) and install the\n * `window.__bootLoader` facade. Renders the disc + status into the host, toggles\n * `data-dismissing` on it for the outro, and removes everything once the outro\n * completes.\n *\n * Returns a disposer that tears the loader down immediately (idempotent).\n */\nexport const mountLoader = (el: HTMLElement, config: BootLoaderConfig = {}): (() => void) => {\n const store = createLoaderStore(config.status);\n\n let removed = false;\n // Forward-declared so `remove` can dispose the Solid render created below.\n let disposeRender: (() => void) | undefined;\n\n const remove = (): void => {\n if (removed) {\n return;\n }\n removed = true;\n el.removeEventListener('transitionend', handleTransitionEnd);\n store.dispose();\n disposeRender?.();\n el.remove();\n if (window.__bootLoader === api) {\n delete window.__bootLoader;\n }\n };\n\n const handleTransitionEnd = (event: TransitionEvent): void => {\n if (event.propertyName === 'opacity' && store.phase() === 'dismissing') {\n remove();\n }\n };\n el.addEventListener('transitionend', handleTransitionEnd);\n\n disposeRender = render(() => {\n // Drive the backdrop's outro from the host element (Solid owns the host's\n // children; the host attribute is toggled imperatively within this owner).\n createEffect(() => {\n const dismissing = store.phase() === 'dismissing';\n el.toggleAttribute('data-dismissing', dismissing);\n if (dismissing) {\n setTimeout(remove, OUTRO_FALLBACK_MS);\n }\n });\n return <Loader store={store} markSvg={config.markSvg} />;\n }, el);\n\n const api = createBridge(store, remove);\n window.__bootLoader = api;\n\n return remove;\n};\n";
25
+
26
+ // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/loader-app/store.ts?raw
27
+ var store_default = "//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Accessor, createSignal } from 'solid-js';\n\nimport { type StatusPayload } from './types';\n\n//\n// Creep tuning. The ring auto-creeps toward a moving ceiling so a long\n// activation silence never reads as a frozen disc. See the original\n// hand-written driver for the rationale behind each constant.\n//\n\n/** Creep timer cadence, in milliseconds. */\nexport const CREEP_TICK_MS = 100;\n/** State-1 ease rate \u2014 a gentle \"we're alive\" hint before real progress lands. */\nexport const STATE_1_RATE = 0.05;\n/** State-1 asymptote (percent) the creep eases toward before host progress. */\nexport const STATE_1_ASYMPTOTE = 40;\n/** State-2 ease rate \u2014 bridges the gap between sparse host `progress()` calls. */\nexport const STATE_2_RATE = 0.05;\n/** State-2 lead (percent) the ceiling sits ahead of the last host value. */\nexport const STATE_2_BUMP = 15;\n/** Hard ceiling the auto-creep never crosses (host must drive the rest). */\nexport const ABSOLUTE_CEILING = 90;\n\n/** Loader lifecycle phase. */\nexport type Phase = 'creep' | 'host' | 'dismissing';\n\n/** A single rendered status line; `id` keys the `<For>` and survives in-place range ticks. */\nexport type StatusLine = {\n id: number;\n text: string;\n event?: string;\n module?: string;\n};\n\n/**\n * Asymptotic ease \u2014 `next = raw + (ceiling - raw) * rate`, clamped so it never\n * overshoots (or regresses past) the ceiling. Pure; unit-tested.\n */\nexport const easeToward = (raw: number, ceiling: number, rate: number): number =>\n raw >= ceiling - 0.1 ? raw : raw + (ceiling - raw) * rate;\n\n/**\n * Clamp a host-supplied fraction to a percent in [0, 100]. Invalid / negative /\n * non-finite values collapse to 0 rather than poisoning the CSS var. Pure;\n * unit-tested.\n */\nexport const clampPercent = (fraction?: number): number =>\n typeof fraction !== 'number' || !Number.isFinite(fraction) || fraction < 0 ? 0 : Math.min(1, fraction) * 100;\n\n/** Display text for a payload \u2014 appends the `(index/total)` suffix when a range is present. */\nexport const displayText = (payload: StatusPayload): string =>\n payload.range ? `${payload.humanized} (${payload.range.index}/${payload.range.total})` : payload.humanized;\n\n/**\n * Reactive loader store \u2014 owns the progress percent, the status-line log, and\n * the lifecycle phase, plus the auto-creep timer. All DOM-free, so the creep\n * math and status reduction are testable as plain functions; `Loader.tsx` binds\n * the accessors to the DOM and `bridge.ts` wraps the mutators as the imperative\n * `window.__bootLoader` facade.\n */\nexport type LoaderStore = {\n /** Progress as a percent in [0, 100] (the CSS `--boot-loader-bar-progress` value). */\n progress: Accessor<number>;\n /** Appended status lines, newest last. */\n lines: Accessor<StatusLine[]>;\n /** Current lifecycle phase. */\n phase: Accessor<Phase>;\n /** Apply a status update (append, or replace-in-place for range ticks). */\n pushStatus: (payload: StatusPayload) => void;\n /** Enter host-driven progress with `fraction` \u2208 [0, 1]; never regresses. */\n setProgress: (fraction?: number) => void;\n /** Snap to 100%, stop the creep, and enter the dismissing phase. */\n ready: () => void;\n /** Stop the creep timer (call on teardown). */\n dispose: () => void;\n};\n\nexport const createLoaderStore = (initialStatus?: string): LoaderStore => {\n const [progress, setProgressPct] = createSignal(0);\n const [lines, setLines] = createSignal<StatusLine[]>(initialStatus ? [{ id: 0, text: initialStatus }] : []);\n const [phase, setPhase] = createSignal<Phase>('creep');\n\n let creepCeiling = STATE_1_ASYMPTOTE;\n let creepRate = STATE_1_RATE;\n let nextId = initialStatus ? 1 : 0;\n let timer: ReturnType<typeof setInterval> | null = null;\n\n const startCreep = (): void => {\n if (timer == null) {\n timer = setInterval(() => setProgressPct((raw) => easeToward(raw, creepCeiling, creepRate)), CREEP_TICK_MS);\n }\n };\n\n const stopCreep = (): void => {\n if (timer != null) {\n clearInterval(timer);\n timer = null;\n }\n };\n\n const pushStatus = (payload: StatusPayload): void => {\n const text = displayText(payload);\n setLines((current) => {\n const previous = current.at(-1);\n // Dedup back-to-back identical transitions (same text + structured fields).\n if (\n previous &&\n previous.text === text &&\n (previous.event ?? null) === (payload.event ?? null) &&\n (previous.module ?? null) === (payload.module ?? null)\n ) {\n return current;\n }\n // Range tick \u2014 replace the current line in place rather than appending,\n // so a counted phase produces one entry with an updating suffix.\n if (payload.range && previous) {\n return [...current.slice(0, -1), { ...previous, text }];\n }\n return [...current, { id: nextId++, text, event: payload.event, module: payload.module }];\n });\n };\n\n const setProgress = (fraction?: number): void => {\n setPhase('host');\n const pct = clampPercent(fraction);\n // The ring never regresses: hold the current value if the host reports lower.\n setProgressPct((current) => Math.max(current, pct));\n // Switch the creep to its state-2 cadence and lead the host value by a bump.\n creepRate = STATE_2_RATE;\n creepCeiling = Math.min(Math.max(creepCeiling, pct + STATE_2_BUMP), ABSOLUTE_CEILING);\n startCreep();\n };\n\n const ready = (): void => {\n stopCreep();\n setProgressPct(100);\n setPhase('dismissing');\n };\n\n const dispose = (): void => stopCreep();\n\n startCreep();\n\n return { progress, lines, phase, pushStatus, setProgress, ready, dispose };\n};\n";
28
+
29
+ // raw-loader:/__w/dxos/dxos/packages/sdk/app-framework/src/vite-plugin/boot-loader/loader-app/types.ts?raw
30
+ var types_default = "//\n// Copyright 2026 DXOS.org\n//\n\n/**\n * Status payload accepted by `window.__bootLoader.status(...)`. The caller owns\n * formatting \u2014 `humanized` is the exact text rendered \u2014 while `event` / `module`\n * carry the structured activation ids for the boot trace, and `range` drives an\n * in-place `(index/total)` counter for long counted phases.\n */\nexport type StatusPayload = {\n /**\n * Raw activation event key (e.g. `dxos.org/plugin/observability/activate`)\n * when the transition is event-level.\n */\n event?: string;\n\n /**\n * Raw module id (e.g. `org.dxos.plugin.observability.module.ReactSurface`)\n * when the transition is module-level.\n */\n module?: string;\n\n /** Exact text to display (e.g. \"Activating Observability: react-surface\"). */\n humanized: string;\n\n /**\n * Optional `(index/total)` tick. When present the loader replaces the current\n * line in place (\"Loading plugins (12/80)\") instead of appending a new entry \u2014\n * keeps the visible log compact during long counted phases.\n */\n range?: { index: number; total: number };\n};\n\n/**\n * Imperative facade exposed on `window.__bootLoader`, installed by the inlined\n * loader bundle and driven by the host app (the React relay forwards `useApp`'s\n * startup progress through it).\n */\nexport type BootLoaderApi = {\n /** Update the visible status line. */\n status: (payload: StatusPayload) => void;\n /** Enter host-driven progress \u2014 `fraction` \u2208 [0, 1]. */\n progress: (fraction?: number) => void;\n /** Play the dismissal outro, then remove the loader DOM (graceful path). */\n ready: () => void;\n /** Remove the loader DOM immediately (fast-load backstop / terminal). */\n dismiss: () => void;\n};\n\n/**\n * Config baked into `index.html` by `bootLoaderPlugin` ahead of the loader\n * bundle, so the compiled app stays static (compiled once, independent of the\n * host's brand mark / initial status).\n */\nexport type BootLoaderConfig = {\n /**\n * Id of the static backdrop element the plugin injects and the loader mounts\n * into \u2014 the single coupling between `transformIndexHtml` and the app. The\n * plugin owns the authoritative value and passes it here so `entry.tsx` reads\n * it rather than hardcoding (the CSS selector mirrors it \u2014 see `boot-loader.css`).\n */\n rootId?: string;\n /** Inline SVG markup for the brand mark rendered inside the ring. */\n markSvg?: string;\n /** Initial status text rendered before the host fires its first `status(...)`. */\n status?: string;\n};\n\n/** Fallback backdrop id when no config is present (kept in sync with `loader.ts` + the CSS). */\nexport const DEFAULT_ROOT_ID = 'boot-loader';\n\ndeclare global {\n interface Window {\n __bootLoader?: BootLoaderApi;\n __BOOT_LOADER_CONFIG__?: BootLoaderConfig;\n }\n}\n";
8
31
 
9
32
  // src/vite-plugin/boot-loader/loader.ts
33
+ var VIRTUAL_NS = "boot-loader-app";
34
+ var MODULES = {
35
+ entry: {
36
+ contents: entry_default,
37
+ tsx: true
38
+ },
39
+ mount: {
40
+ contents: mount_default,
41
+ tsx: true
42
+ },
43
+ Loader: {
44
+ contents: Loader_default,
45
+ tsx: true
46
+ },
47
+ bridge: {
48
+ contents: bridge_default,
49
+ tsx: false
50
+ },
51
+ store: {
52
+ contents: store_default,
53
+ tsx: false
54
+ },
55
+ types: {
56
+ contents: types_default,
57
+ tsx: false
58
+ }
59
+ };
60
+ var resolveDir = dirname(fileURLToPath(import.meta.url));
61
+ var compileLoaderBundle = async () => {
62
+ const virtualSolid = () => ({
63
+ name: "boot-loader-app-virtual",
64
+ setup: (esbuild) => {
65
+ esbuild.onResolve({
66
+ filter: /^\.\//
67
+ }, (args) => {
68
+ const name = args.path.replace(/^\.\//, "").replace(/\.(tsx?|jsx?)$/, "");
69
+ return name in MODULES ? {
70
+ path: name,
71
+ namespace: VIRTUAL_NS
72
+ } : void 0;
73
+ });
74
+ esbuild.onLoad({
75
+ filter: /.*/,
76
+ namespace: VIRTUAL_NS
77
+ }, async (args) => {
78
+ const module = MODULES[args.path];
79
+ const result2 = await babel.transformAsync(module.contents, {
80
+ filename: `${args.path}.${module.tsx ? "tsx" : "ts"}`,
81
+ babelrc: false,
82
+ configFile: false,
83
+ // Presets run last-to-first: TS strips first, then Solid sees plain JSX.
84
+ presets: [
85
+ [
86
+ babelPresetSolid,
87
+ {
88
+ generate: "dom",
89
+ hydratable: false
90
+ }
91
+ ],
92
+ [
93
+ babelPresetTypeScript,
94
+ {
95
+ allowDeclareFields: true
96
+ }
97
+ ]
98
+ ]
99
+ });
100
+ if (!result2?.code) {
101
+ throw new Error(`babel-preset-solid returned no code for ${args.path}`);
102
+ }
103
+ return {
104
+ contents: result2.code,
105
+ loader: "js",
106
+ resolveDir
107
+ };
108
+ });
109
+ }
110
+ });
111
+ const result = await build({
112
+ stdin: {
113
+ contents: "import './entry';",
114
+ loader: "ts",
115
+ resolveDir,
116
+ sourcefile: "boot-loader-entry.ts"
117
+ },
118
+ bundle: true,
119
+ format: "iife",
120
+ platform: "browser",
121
+ target: "es2020",
122
+ minify: true,
123
+ write: false,
124
+ legalComments: "none",
125
+ define: {
126
+ "process.env.NODE_ENV": '"production"'
127
+ },
128
+ plugins: [
129
+ virtualSolid()
130
+ ]
131
+ });
132
+ return result.outputFiles[0].text;
133
+ };
134
+ var bundlePromise;
135
+ var getLoaderBundle = () => bundlePromise ??= compileLoaderBundle();
10
136
  var bootLoaderPlugin = ({ status, markSvg } = {}) => {
11
137
  return {
12
138
  name: "app-framework:boot-loader",
13
- transformIndexHtml() {
14
- const markChildren = markSvg ? [
15
- {
16
- tag: "div",
17
- attrs: {
18
- id: "boot-loader-mark"
19
- },
20
- children: markSvg
21
- }
22
- ] : [];
139
+ async transformIndexHtml() {
140
+ const bundle = await getLoaderBundle();
141
+ const rootId = "boot-loader";
23
142
  return [
24
143
  {
25
144
  tag: "style",
@@ -30,70 +149,26 @@ var bootLoaderPlugin = ({ status, markSvg } = {}) => {
30
149
  tag: "div",
31
150
  injectTo: "body-prepend",
32
151
  attrs: {
33
- id: "boot-loader",
152
+ id: rootId,
34
153
  role: "status",
35
154
  "aria-live": "polite",
36
155
  "aria-label": "Initializing"
37
156
  },
38
- children: [
39
- {
40
- tag: "div",
41
- attrs: {
42
- id: "boot-loader-disc"
43
- },
44
- children: [
45
- {
46
- tag: "div",
47
- attrs: {
48
- id: "boot-loader-bar"
49
- },
50
- children: ""
51
- },
52
- {
53
- tag: "div",
54
- attrs: {
55
- id: "boot-loader-dot"
56
- },
57
- children: ""
58
- },
59
- ...markChildren
60
- ]
61
- },
62
- {
63
- tag: "div",
64
- attrs: {
65
- id: "boot-loader-status"
66
- },
67
- children: [
68
- {
69
- tag: "div",
70
- attrs: {
71
- id: "boot-loader-status-fade"
72
- }
73
- },
74
- {
75
- tag: "div",
76
- attrs: {
77
- id: "boot-loader-status-track"
78
- },
79
- children: status ? [
80
- {
81
- tag: "div",
82
- attrs: {
83
- class: "boot-loader-status-line"
84
- },
85
- children: status
86
- }
87
- ] : []
88
- }
89
- ]
90
- }
91
- ]
157
+ children: ""
158
+ },
159
+ {
160
+ tag: "script",
161
+ injectTo: "body-prepend",
162
+ children: `window.__BOOT_LOADER_CONFIG__=${JSON.stringify({
163
+ rootId,
164
+ markSvg,
165
+ status
166
+ })};`
92
167
  },
93
168
  {
94
169
  tag: "script",
95
170
  injectTo: "body-prepend",
96
- children: boot_loader_default2
171
+ children: bundle
97
172
  }
98
173
  ];
99
174
  }
@@ -124,13 +199,16 @@ var Manifest = Schema.Struct({
124
199
  import { PLUGIN_ENTRY_FILENAME as PLUGIN_ENTRY_FILENAME2 } from "@dxos/protocols";
125
200
  var MANIFEST_ASSET_NAME = "manifest.json";
126
201
  var ENTRY_FILENAME = PLUGIN_ENTRY_FILENAME2;
127
- var serializeManifest = (meta, { assets, devEntry }) => JSON.stringify({
128
- ...meta,
129
- assets,
130
- ...devEntry !== void 0 ? {
131
- devEntry
132
- } : {}
133
- }, null, 2);
202
+ var serializeManifest = (meta, { assets, devEntry }) => {
203
+ const { key: _key, ...rest } = meta;
204
+ return JSON.stringify({
205
+ ...rest,
206
+ assets,
207
+ ...devEntry !== void 0 ? {
208
+ devEntry
209
+ } : {}
210
+ }, null, 2);
211
+ };
134
212
 
135
213
  // src/vite-plugin/packages.ts
136
214
  var THIRD_PARTY_SHARED_PACKAGES = [