@dxos/app-framework 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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.
- package/dist/lib/browser/{capability-BBBBAPDI.mjs → capability-Q5XRXRD2.mjs} +10 -10
- package/dist/lib/browser/{capability-OP63CD5N.mjs → capability-V7LR4LQN.mjs} +11 -11
- package/dist/lib/browser/capability-V7LR4LQN.mjs.map +7 -0
- package/dist/lib/browser/{chunk-T3Y4AEKX.mjs → chunk-23D4SJUE.mjs} +3 -3
- package/dist/lib/browser/{chunk-T3Y4AEKX.mjs.map → chunk-23D4SJUE.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-2CKCJ6PN.mjs → chunk-3JWJXGLK.mjs} +1 -1
- package/dist/lib/browser/{chunk-2CKCJ6PN.mjs.map → chunk-3JWJXGLK.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-GX4TUNM6.mjs → chunk-3ZS2A3DN.mjs} +170 -226
- package/dist/lib/browser/chunk-3ZS2A3DN.mjs.map +7 -0
- package/dist/lib/browser/{chunk-I34GF4NG.mjs → chunk-45CHLTBV.mjs} +2 -2
- package/dist/lib/browser/chunk-5LAIGWLU.mjs +467 -0
- package/dist/lib/browser/chunk-5LAIGWLU.mjs.map +7 -0
- package/dist/lib/browser/{chunk-QSXYHXCE.mjs → chunk-66IXTIVK.mjs} +1 -1
- package/dist/lib/browser/{chunk-QSXYHXCE.mjs.map → chunk-66IXTIVK.mjs.map} +2 -2
- package/dist/lib/browser/{chunk-TGX63LTL.mjs → chunk-FJ4765WW.mjs} +1 -1
- package/dist/lib/browser/{chunk-TGX63LTL.mjs.map → chunk-FJ4765WW.mjs.map} +2 -2
- package/dist/lib/browser/chunk-G7SDBRKH.mjs +1 -0
- package/dist/lib/browser/chunk-JXCBZSBJ.mjs +372 -0
- package/dist/lib/browser/chunk-JXCBZSBJ.mjs.map +7 -0
- package/dist/lib/browser/chunk-MX5DKEJH.mjs +584 -0
- package/dist/lib/browser/chunk-MX5DKEJH.mjs.map +7 -0
- package/dist/lib/browser/{chunk-JKWMHZP6.mjs → chunk-WBHCSOBW.mjs} +2 -2
- package/dist/lib/browser/chunk-WBHCSOBW.mjs.map +7 -0
- package/dist/lib/browser/{chunk-FU4GAFUQ.mjs → chunk-Z55LVAGN.mjs} +80 -15
- package/dist/lib/browser/chunk-Z55LVAGN.mjs.map +7 -0
- package/dist/lib/browser/{chunk-F7FW2RK2.mjs → chunk-ZGJAZSNE.mjs} +7 -32
- package/dist/lib/browser/chunk-ZGJAZSNE.mjs.map +7 -0
- package/dist/lib/browser/cli/index.mjs +11 -27
- package/dist/lib/browser/cli/index.mjs.map +2 -2
- package/dist/lib/browser/common/activation-events.mjs +7 -7
- package/dist/lib/browser/common/capabilities.mjs +7 -7
- package/dist/lib/browser/core/activation-event.mjs +1 -1
- package/dist/lib/browser/core/capability.mjs +1 -1
- package/dist/lib/browser/core/plugin-manager.mjs +6 -4
- package/dist/lib/browser/core/plugin.mjs +10 -2
- package/dist/lib/browser/core/url-loader.mjs +13 -5
- package/dist/lib/browser/index.mjs +22 -18
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/{invoker-capability-H5PPENOC.mjs → invoker-capability-LNX4CGIV.mjs} +12 -11
- package/dist/lib/browser/invoker-capability-LNX4CGIV.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +144 -27
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/browser/testing/react.mjs +78 -0
- package/dist/lib/browser/testing/react.mjs.map +7 -0
- package/dist/lib/browser/ui/index.mjs +18 -14
- package/dist/lib/node-esm/{capability-AWBEMRYR.mjs → capability-EW5GJCI6.mjs} +10 -10
- package/dist/lib/node-esm/{capability-WFEG6CIZ.mjs → capability-YKBMMD53.mjs} +11 -11
- package/dist/lib/node-esm/capability-YKBMMD53.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-FKE4Z3D6.mjs → chunk-37Z53PXZ.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-FKE4Z3D6.mjs.map → chunk-37Z53PXZ.mjs.map} +2 -2
- package/dist/lib/node-esm/{chunk-WZCSOX5Q.mjs → chunk-6XW6LET6.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-URWHJQT2.mjs → chunk-D347W3KO.mjs} +7 -32
- package/dist/lib/node-esm/chunk-D347W3KO.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-D5PO2WXX.mjs +373 -0
- package/dist/lib/node-esm/chunk-D5PO2WXX.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ULUEXB7Q.mjs → chunk-HTBJU5FX.mjs} +80 -15
- package/dist/lib/node-esm/chunk-HTBJU5FX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-KM2F6GH6.mjs +468 -0
- package/dist/lib/node-esm/chunk-KM2F6GH6.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-EL3R25OQ.mjs → chunk-OZ7DZA5Z.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-BCEOLX47.mjs → chunk-Q7XBFII4.mjs} +170 -226
- package/dist/lib/node-esm/chunk-Q7XBFII4.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-VKHGNEDB.mjs → chunk-SBS2YMPT.mjs} +3 -3
- package/dist/lib/node-esm/{chunk-VKHGNEDB.mjs.map → chunk-SBS2YMPT.mjs.map} +1 -1
- package/dist/lib/node-esm/{chunk-42KBWDE4.mjs → chunk-SDJ4B2LU.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-42KBWDE4.mjs.map → chunk-SDJ4B2LU.mjs.map} +1 -1
- package/dist/lib/node-esm/{chunk-G3RTFSNG.mjs → chunk-WFSRZKBP.mjs} +2 -2
- package/dist/lib/node-esm/chunk-WFSRZKBP.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WKTLE7MG.mjs +585 -0
- package/dist/lib/node-esm/chunk-WKTLE7MG.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ZZ7CKK6W.mjs → chunk-XOCUANHO.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-ZZ7CKK6W.mjs.map → chunk-XOCUANHO.mjs.map} +2 -2
- package/dist/lib/node-esm/cli/index.mjs +11 -27
- package/dist/lib/node-esm/cli/index.mjs.map +2 -2
- package/dist/lib/node-esm/common/activation-events.mjs +7 -7
- package/dist/lib/node-esm/common/capabilities.mjs +7 -7
- package/dist/lib/node-esm/core/activation-event.mjs +1 -1
- package/dist/lib/node-esm/core/capability.mjs +1 -1
- package/dist/lib/node-esm/core/plugin-manager.mjs +6 -4
- package/dist/lib/node-esm/core/plugin.mjs +10 -2
- package/dist/lib/node-esm/core/url-loader.mjs +13 -5
- package/dist/lib/node-esm/index.mjs +22 -18
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/{invoker-capability-S3ZA527J.mjs → invoker-capability-O4T5PHLA.mjs} +12 -11
- package/dist/lib/node-esm/invoker-capability-O4T5PHLA.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +144 -27
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/lib/node-esm/testing/react.mjs +79 -0
- package/dist/lib/node-esm/testing/react.mjs.map +7 -0
- package/dist/lib/node-esm/ui/index.mjs +18 -14
- package/dist/plugin/node-esm/index.mjs +480 -32
- package/dist/plugin/node-esm/index.mjs.map +4 -4
- package/dist/plugin/node-esm/meta.json +1 -1
- package/dist/types/src/common/capabilities.d.ts +2 -1
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/common/operations.d.ts +1 -1
- package/dist/types/src/common/operations.d.ts.map +1 -1
- package/dist/types/src/core/activation-event.d.ts +4 -4
- package/dist/types/src/core/activation-event.d.ts.map +1 -1
- package/dist/types/src/core/capability-manager.d.ts.map +1 -1
- package/dist/types/src/core/capability.d.ts +2 -2
- package/dist/types/src/core/capability.d.ts.map +1 -1
- package/dist/types/src/core/index.d.ts +2 -0
- package/dist/types/src/core/index.d.ts.map +1 -1
- package/dist/types/src/core/plugin-asset-cache.d.ts +71 -0
- package/dist/types/src/core/plugin-asset-cache.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager.d.ts +51 -2
- package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manifest.d.ts +76 -0
- package/dist/types/src/core/plugin-manifest.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manifest.test.d.ts +2 -0
- package/dist/types/src/core/plugin-manifest.test.d.ts.map +1 -0
- package/dist/types/src/core/plugin.d.ts +107 -6
- package/dist/types/src/core/plugin.d.ts.map +1 -1
- package/dist/types/src/core/url-loader.d.ts +90 -3
- package/dist/types/src/core/url-loader.d.ts.map +1 -1
- package/dist/types/src/helpers.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/capability.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/errors.d.ts +6 -6
- package/dist/types/src/plugin-operation/history/errors.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/history-tracker.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/history-tracker.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/types.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/types.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/undo-mapping.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/undo-mapping.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/undo-registry.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/undo-registry.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/invoker-capability.d.ts +1 -1
- package/dist/types/src/plugin-operation/invoker-capability.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/testing.d.ts +2 -1
- package/dist/types/src/plugin-operation/testing.d.ts.map +1 -1
- package/dist/types/src/plugin-runtime/capability.d.ts +1 -1
- package/dist/types/src/plugin-runtime/capability.d.ts.map +1 -1
- package/dist/types/src/testing/harness.d.ts +67 -0
- package/dist/types/src/testing/harness.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/react.d.ts +27 -0
- package/dist/types/src/testing/react.d.ts.map +1 -0
- package/dist/types/src/testing/react.test.d.ts +2 -0
- package/dist/types/src/testing/react.test.d.ts.map +1 -0
- package/dist/types/src/testing/service.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/App.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/App.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts +64 -0
- package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts.map +1 -0
- package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts +19 -0
- package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts.map +1 -0
- package/dist/types/src/ui/components/Placeholder/index.d.ts +2 -0
- package/dist/types/src/ui/components/Placeholder/index.d.ts.map +1 -0
- package/dist/types/src/ui/components/PluginManager/PluginManagerContext.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +16 -4
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/index.d.ts +16 -6
- package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/types.d.ts +110 -9
- package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/types.test.d.ts +2 -0
- package/dist/types/src/ui/components/Surface/types.test.d.ts.map +1 -0
- package/dist/types/src/ui/components/index.d.ts +1 -0
- package/dist/types/src/ui/components/index.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useApp.d.ts +29 -3
- package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useLoading.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useSettingsState.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts +34 -0
- package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/boot-loader/index.d.ts +52 -0
- package/dist/types/src/vite-plugin/boot-loader/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/composer/index.d.ts +34 -0
- package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/import-map/index.d.ts +28 -0
- package/dist/types/src/vite-plugin/import-map/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/index.d.ts +4 -2
- package/dist/types/src/vite-plugin/index.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/manifest.d.ts +37 -0
- package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/manifest.test.d.ts +2 -0
- package/dist/types/src/vite-plugin/manifest.test.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/packages.d.ts +10 -4
- package/dist/types/src/vite-plugin/packages.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/moon.yml +1 -0
- package/package.json +33 -59
- package/src/common/capabilities.ts +2 -1
- package/src/common/operations.ts +1 -1
- package/src/core/capability.ts +1 -1
- package/src/core/index.ts +2 -0
- package/src/core/plugin-asset-cache.ts +60 -0
- package/src/core/plugin-manager.test.ts +246 -5
- package/src/core/plugin-manager.ts +167 -25
- package/src/core/plugin-manifest.test.ts +48 -0
- package/src/core/plugin-manifest.ts +102 -0
- package/src/core/plugin.ts +135 -10
- package/src/core/url-loader.test.ts +104 -5
- package/src/core/url-loader.ts +226 -37
- package/src/plugin-operation/OperationPlugin.ts +2 -2
- package/src/plugin-operation/history/capability.ts +1 -1
- package/src/plugin-operation/history/history-tracker.test.ts +2 -1
- package/src/plugin-operation/history/history-tracker.ts +1 -1
- package/src/plugin-operation/history/types.ts +1 -1
- package/src/plugin-operation/history/undo-mapping.ts +1 -1
- package/src/plugin-operation/history/undo-registry.ts +1 -1
- package/src/plugin-operation/invoker-capability.ts +2 -1
- package/src/plugin-operation/testing.ts +2 -1
- package/src/plugin-runtime/RuntimePlugin.ts +2 -2
- package/src/testing/harness.ts +229 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/react.test.tsx +48 -0
- package/src/testing/react.tsx +113 -0
- package/src/testing/withPluginManager.stories.tsx +1 -1
- package/src/ui/components/App/App.stories.tsx +1 -1
- package/src/ui/components/App/App.tsx +25 -2
- package/src/ui/components/Placeholder/Placeholder.stories.tsx +77 -0
- package/src/ui/components/Placeholder/Placeholder.tsx +155 -0
- package/src/ui/components/Placeholder/index.ts +5 -0
- package/src/ui/components/PluginManager/PluginManagerContext.stories.tsx +4 -2
- package/src/ui/components/Surface/SurfaceComponent.stories.tsx +1 -1
- package/src/ui/components/Surface/SurfaceComponent.tsx +83 -46
- package/src/ui/components/Surface/index.ts +20 -1
- package/src/ui/components/Surface/types.test.ts +126 -0
- package/src/ui/components/Surface/types.ts +164 -12
- package/src/ui/components/index.ts +1 -0
- package/src/ui/hooks/useApp.tsx +165 -41
- package/src/ui/hooks/useLoading.tsx +14 -6
- package/src/vite-plugin/boot-loader/BootLoader.stories.tsx +263 -0
- package/src/vite-plugin/boot-loader/boot-loader.css +294 -0
- package/src/vite-plugin/boot-loader/boot-loader.js +274 -0
- package/src/vite-plugin/boot-loader/index.ts +112 -0
- package/src/vite-plugin/composer/index.ts +277 -0
- package/src/vite-plugin/import-map/index.ts +524 -0
- package/src/vite-plugin/index.ts +6 -2
- package/src/vite-plugin/manifest.test.ts +24 -0
- package/src/vite-plugin/manifest.ts +50 -0
- package/src/vite-plugin/packages.ts +169 -10
- package/tsconfig.json +9 -0
- package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
- package/dist/lib/browser/capability-OP63CD5N.mjs.map +0 -7
- package/dist/lib/browser/chunk-F7FW2RK2.mjs.map +0 -7
- package/dist/lib/browser/chunk-FU4GAFUQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-GX4TUNM6.mjs.map +0 -7
- package/dist/lib/browser/chunk-JKWMHZP6.mjs.map +0 -7
- package/dist/lib/browser/chunk-LVJW5EFU.mjs +0 -157
- package/dist/lib/browser/chunk-LVJW5EFU.mjs.map +0 -7
- package/dist/lib/browser/chunk-RFSO3JRG.mjs +0 -1
- package/dist/lib/browser/chunk-WPE6AL7I.mjs +0 -905
- package/dist/lib/browser/chunk-WPE6AL7I.mjs.map +0 -7
- package/dist/lib/browser/invoker-capability-H5PPENOC.mjs.map +0 -7
- package/dist/lib/node-esm/capability-WFEG6CIZ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-4A3ZCMI3.mjs +0 -158
- package/dist/lib/node-esm/chunk-4A3ZCMI3.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-BCEOLX47.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-G3RTFSNG.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-LQKOTNJW.mjs +0 -906
- package/dist/lib/node-esm/chunk-LQKOTNJW.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ULUEXB7Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-URWHJQT2.mjs.map +0 -7
- package/dist/lib/node-esm/invoker-capability-S3ZA527J.mjs.map +0 -7
- package/dist/types/src/vite-plugin/composer-plugin.d.ts +0 -18
- package/dist/types/src/vite-plugin/composer-plugin.d.ts.map +0 -1
- package/dist/types/src/vite-plugin/import-map-plugin.d.ts +0 -16
- package/dist/types/src/vite-plugin/import-map-plugin.d.ts.map +0 -1
- package/src/vite-plugin/composer-plugin.ts +0 -128
- package/src/vite-plugin/import-map-plugin.ts +0 -314
- /package/dist/lib/browser/{capability-BBBBAPDI.mjs.map → capability-Q5XRXRD2.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-I34GF4NG.mjs.map → chunk-45CHLTBV.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-RFSO3JRG.mjs.map → chunk-G7SDBRKH.mjs.map} +0 -0
- /package/dist/lib/node-esm/{capability-AWBEMRYR.mjs.map → capability-EW5GJCI6.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-WZCSOX5Q.mjs.map → chunk-6XW6LET6.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-EL3R25OQ.mjs.map → chunk-OZ7DZA5Z.mjs.map} +0 -0
|
@@ -6,8 +6,63 @@ import type { FC, PropsWithChildren, ReactNode, RefCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import type { MakeOptional, Position } from '@dxos/util';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* A typed role token. Carries a role string for runtime dispatch plus a phantom
|
|
11
|
+
* data type so consumers using `type={SomeToken}` and providers using
|
|
12
|
+
* `AppSurface.object(SomeToken, ...)` share a single type-level contract.
|
|
13
|
+
*
|
|
14
|
+
* Mint via {@link makeType}.
|
|
15
|
+
*/
|
|
16
|
+
export type RoleToken<TData> = {
|
|
17
|
+
readonly role: string;
|
|
18
|
+
/** Covariant phantom; never accessed at runtime. */
|
|
19
|
+
readonly _phantom?: TData;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* One entry in a {@link SurfaceFilter} — a role plus the guard that validates
|
|
24
|
+
* the data shape when the Surface dispatcher is matching that role.
|
|
25
|
+
*/
|
|
26
|
+
export type SurfaceBinding = {
|
|
27
|
+
readonly role: string;
|
|
28
|
+
readonly guard: (data: unknown) => boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Typed filter binding for {@link create}. Combines one or more `(role, guard)`
|
|
33
|
+
* pairs so a provider can register against multiple roles while keeping the
|
|
34
|
+
* role and its data shape in a single expression.
|
|
35
|
+
*/
|
|
36
|
+
export type SurfaceFilter<TData> = {
|
|
37
|
+
readonly bindings: ReadonlyArray<SurfaceBinding>;
|
|
38
|
+
/** Covariant phantom; never accessed at runtime. */
|
|
39
|
+
readonly _phantom?: TData;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Narrow data type carried by a role token.
|
|
44
|
+
*/
|
|
45
|
+
export type TokenData<T> = T extends RoleToken<infer D> ? D : never;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Runtime guard for {@link SurfaceFilter}. Distinguishes new-style filter
|
|
49
|
+
* bindings from legacy predicate filters.
|
|
50
|
+
*/
|
|
51
|
+
export const isSurfaceFilter = (value: unknown): value is SurfaceFilter<any> =>
|
|
52
|
+
typeof value === 'object' && value !== null && Array.isArray((value as { bindings?: unknown }).bindings);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Mints a typed role token. Identity is structural; all tokens with the same
|
|
56
|
+
* `role` string are interchangeable at runtime.
|
|
57
|
+
*/
|
|
58
|
+
export const makeType = <TData>(role: string): RoleToken<TData> => ({ role });
|
|
59
|
+
|
|
9
60
|
/**
|
|
10
61
|
* Props that are passed to the Surface component.
|
|
62
|
+
*
|
|
63
|
+
* The role can be provided either as a string via `role` or as a typed
|
|
64
|
+
* {@link RoleToken} via `type`. The typed form narrows `data` to the token's
|
|
65
|
+
* declared shape.
|
|
11
66
|
*/
|
|
12
67
|
export type Props<T extends Record<string, any> = Record<string, unknown>> = {
|
|
13
68
|
/**
|
|
@@ -21,16 +76,46 @@ export type Props<T extends Record<string, any> = Record<string, unknown>> = {
|
|
|
21
76
|
* The placeholder component will be rendered while the surface component is loading.
|
|
22
77
|
*/
|
|
23
78
|
placeholder?: ReactNode;
|
|
24
|
-
} & MakeOptional<CoreProps<T>, 'id' | 'data'> &
|
|
25
|
-
|
|
79
|
+
} & MakeOptional<CoreProps<T>, 'id' | 'data' | 'role'> & {
|
|
80
|
+
/**
|
|
81
|
+
* Explicitly disallow `type` on the untyped Props overload — if the caller
|
|
82
|
+
* provides `type={AppSurface.X}`, TypeScript must route to the typed
|
|
83
|
+
* overload (where `data` is narrowed by the token). Without this, the
|
|
84
|
+
* catch-all index signature below would accept any `type` value and mask
|
|
85
|
+
* the typed overload entirely.
|
|
86
|
+
*/
|
|
87
|
+
type?: undefined;
|
|
88
|
+
} /**
|
|
26
89
|
* Additional props to pass to the component.
|
|
27
90
|
* These props are not used by Surface itself but may be used by components which resolve the surface.
|
|
28
91
|
* Exclude known prop names to prevent overriding well-defined props.
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
[K in keyof Record<string, any>]: K extends keyof CoreProps<T> | 'fallback' | 'placeholder' ? never : any;
|
|
92
|
+
*/ & {
|
|
93
|
+
[K in keyof Record<string, any>]: K extends keyof CoreProps<T> | 'fallback' | 'placeholder' | 'type' ? never : any;
|
|
32
94
|
};
|
|
33
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Typed Surface consumer props — carries the role/data-shape contract via a
|
|
98
|
+
* {@link RoleToken}. Available as a typed overload of the Surface component so
|
|
99
|
+
* ad-hoc `type` fields on arbitrary spread props don't conflict with the
|
|
100
|
+
* untyped consumer form.
|
|
101
|
+
*/
|
|
102
|
+
export type TypedProps<TToken extends RoleToken<any>> = {
|
|
103
|
+
fallback?: FC<{ error: Error; data?: any }>;
|
|
104
|
+
placeholder?: ReactNode;
|
|
105
|
+
id?: string;
|
|
106
|
+
type: TToken;
|
|
107
|
+
data?: TokenData<TToken>;
|
|
108
|
+
limit?: number | undefined;
|
|
109
|
+
} & {
|
|
110
|
+
/**
|
|
111
|
+
* Additional pass-through props. Known prop names are excluded so the
|
|
112
|
+
* catch-all doesn't widen `data` / `type` / etc. to `any` at the intersection.
|
|
113
|
+
*/
|
|
114
|
+
[K in keyof Record<string, any>]: K extends 'fallback' | 'placeholder' | 'id' | 'type' | 'data' | 'limit'
|
|
115
|
+
? never
|
|
116
|
+
: any;
|
|
117
|
+
};
|
|
118
|
+
|
|
34
119
|
/**
|
|
35
120
|
* NOTE: If `[key: string]: unknown` is included in shared types, when re-used other fields become unknown as well.
|
|
36
121
|
*/
|
|
@@ -41,7 +126,10 @@ export type CoreProps<T extends Record<string, any> = Record<string, unknown>> =
|
|
|
41
126
|
id: string;
|
|
42
127
|
|
|
43
128
|
/**
|
|
44
|
-
* Role defines how the data should be rendered.
|
|
129
|
+
* Role defines how the data should be rendered. For new code, prefer the
|
|
130
|
+
* typed overload of the Surface component which takes a {@link RoleToken}
|
|
131
|
+
* via the `type` prop instead — it enforces a role/data contract at compile
|
|
132
|
+
* time.
|
|
45
133
|
*/
|
|
46
134
|
role: string;
|
|
47
135
|
|
|
@@ -71,6 +159,10 @@ export type ComponentFunction<T extends Record<string, any> = Record<string, any
|
|
|
71
159
|
|
|
72
160
|
/**
|
|
73
161
|
* Definition of when a React component surface should be rendered.
|
|
162
|
+
*
|
|
163
|
+
* The optional `filter` receives the consumer-supplied `role` as its second
|
|
164
|
+
* argument so a single definition can encode role-specific guards (used by the
|
|
165
|
+
* {@link SurfaceFilter}-based `create` overload).
|
|
74
166
|
*/
|
|
75
167
|
export type ReactDefinition<T extends Record<string, any> = any> = Readonly<{
|
|
76
168
|
kind: 'react';
|
|
@@ -78,7 +170,7 @@ export type ReactDefinition<T extends Record<string, any> = any> = Readonly<{
|
|
|
78
170
|
role: string | string[];
|
|
79
171
|
position?: Position;
|
|
80
172
|
component: ComponentFunction<T>;
|
|
81
|
-
filter?: (data: Record<string, unknown
|
|
173
|
+
filter?: (data: Record<string, unknown>, role?: string) => data is T;
|
|
82
174
|
}>;
|
|
83
175
|
|
|
84
176
|
/**
|
|
@@ -94,7 +186,7 @@ export type WebComponentDefinition<T extends Record<string, any> = any> = Readon
|
|
|
94
186
|
* The Web Component will receive the same props as React surfaces via properties/attributes.
|
|
95
187
|
*/
|
|
96
188
|
tagName: string;
|
|
97
|
-
filter?: (data: Record<string, unknown
|
|
189
|
+
filter?: (data: Record<string, unknown>, role?: string) => data is T;
|
|
98
190
|
}>;
|
|
99
191
|
|
|
100
192
|
/**
|
|
@@ -102,16 +194,76 @@ export type WebComponentDefinition<T extends Record<string, any> = any> = Readon
|
|
|
102
194
|
*/
|
|
103
195
|
export type Definition<T extends Record<string, any> = any> = ReactDefinition<T> | WebComponentDefinition<T>;
|
|
104
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Typed React surface definition — role is derived from the filter's bindings.
|
|
199
|
+
*/
|
|
200
|
+
export type TypedReactDefinition<T extends Record<string, any> = any> = Readonly<{
|
|
201
|
+
id: string;
|
|
202
|
+
filter: SurfaceFilter<T>;
|
|
203
|
+
component: ComponentFunction<T>;
|
|
204
|
+
position?: Position;
|
|
205
|
+
}>;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Typed Web Component surface definition.
|
|
209
|
+
*/
|
|
210
|
+
export type TypedWebComponentDefinition<T extends Record<string, any> = any> = Readonly<{
|
|
211
|
+
id: string;
|
|
212
|
+
filter: SurfaceFilter<T>;
|
|
213
|
+
tagName: string;
|
|
214
|
+
position?: Position;
|
|
215
|
+
}>;
|
|
216
|
+
|
|
217
|
+
const expandBindings = <T extends Record<string, any>>(
|
|
218
|
+
filter: SurfaceFilter<T>,
|
|
219
|
+
): { role: string | string[]; guard: (data: Record<string, unknown>, role?: string) => data is T } => {
|
|
220
|
+
const bindings = filter.bindings;
|
|
221
|
+
const roles = Array.from(new Set(bindings.map((binding) => binding.role)));
|
|
222
|
+
const guard = (data: Record<string, unknown>, role?: string): data is T => {
|
|
223
|
+
if (role != null) {
|
|
224
|
+
// Multiple bindings may share a role (e.g. via `oneOf` of same-role filters);
|
|
225
|
+
// the role matches if ANY of them passes.
|
|
226
|
+
return bindings.some((entry) => entry.role === role && entry.guard(data));
|
|
227
|
+
}
|
|
228
|
+
return bindings.some((entry) => entry.guard(data));
|
|
229
|
+
};
|
|
230
|
+
return { role: roles.length === 1 ? roles[0] : roles, guard };
|
|
231
|
+
};
|
|
232
|
+
|
|
105
233
|
/**
|
|
106
234
|
* Creates a React surface definition.
|
|
107
235
|
*/
|
|
108
|
-
export
|
|
236
|
+
export function create<T extends Record<string, any> = any>(definition: TypedReactDefinition<T>): ReactDefinition<T>;
|
|
237
|
+
export function create<T extends Record<string, any> = any>(
|
|
109
238
|
definition: Omit<ReactDefinition<T>, 'kind'>,
|
|
110
|
-
): ReactDefinition<T
|
|
239
|
+
): ReactDefinition<T>;
|
|
240
|
+
export function create<T extends Record<string, any> = any>(
|
|
241
|
+
definition: TypedReactDefinition<T> | Omit<ReactDefinition<T>, 'kind'>,
|
|
242
|
+
): ReactDefinition<T> {
|
|
243
|
+
if (isSurfaceFilter(definition.filter)) {
|
|
244
|
+
const { id, filter, component, position } = definition as TypedReactDefinition<T>;
|
|
245
|
+
const { role, guard } = expandBindings(filter);
|
|
246
|
+
return { kind: 'react', id, role, position, component, filter: guard };
|
|
247
|
+
}
|
|
248
|
+
return { ...(definition as Omit<ReactDefinition<T>, 'kind'>), kind: 'react' };
|
|
249
|
+
}
|
|
111
250
|
|
|
112
251
|
/**
|
|
113
252
|
* Creates a Web Component surface definition.
|
|
114
253
|
*/
|
|
115
|
-
export
|
|
254
|
+
export function createWeb<T extends Record<string, any> = any>(
|
|
255
|
+
definition: TypedWebComponentDefinition<T>,
|
|
256
|
+
): WebComponentDefinition<T>;
|
|
257
|
+
export function createWeb<T extends Record<string, any> = any>(
|
|
116
258
|
definition: Omit<WebComponentDefinition<T>, 'kind'>,
|
|
117
|
-
): WebComponentDefinition<T
|
|
259
|
+
): WebComponentDefinition<T>;
|
|
260
|
+
export function createWeb<T extends Record<string, any> = any>(
|
|
261
|
+
definition: TypedWebComponentDefinition<T> | Omit<WebComponentDefinition<T>, 'kind'>,
|
|
262
|
+
): WebComponentDefinition<T> {
|
|
263
|
+
if (isSurfaceFilter(definition.filter)) {
|
|
264
|
+
const { id, filter, tagName, position } = definition as TypedWebComponentDefinition<T>;
|
|
265
|
+
const { role, guard } = expandBindings(filter);
|
|
266
|
+
return { kind: 'web-component', id, role, position, tagName, filter: guard };
|
|
267
|
+
}
|
|
268
|
+
return { ...(definition as Omit<WebComponentDefinition<T>, 'kind'>), kind: 'web-component' };
|
|
269
|
+
}
|
package/src/ui/hooks/useApp.tsx
CHANGED
|
@@ -30,8 +30,33 @@ export type StartupProgress = {
|
|
|
30
30
|
total: number;
|
|
31
31
|
/** Fractional progress (0-1). */
|
|
32
32
|
progress: number;
|
|
33
|
-
/**
|
|
34
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Raw activation event key (e.g. `org.dxos.app-framework.event.startup`).
|
|
35
|
+
* Set on event-level transitions, *and* on module-level transitions where
|
|
36
|
+
* it carries the parent activation event that first triggered the
|
|
37
|
+
* module's load (plumbed through `_loadCapabilitiesForModules` →
|
|
38
|
+
* `_loadModule`). Consumers can use this either as the primary id (when
|
|
39
|
+
* {@link module} is absent) or as an extra "context" field alongside
|
|
40
|
+
* {@link module}.
|
|
41
|
+
*/
|
|
42
|
+
event?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Raw module id (e.g. `org.dxos.plugin.observability.module.ReactSurface`)
|
|
45
|
+
* when the in-flight activation is module-level. When present,
|
|
46
|
+
* {@link event} may also be set, identifying the parent activation that
|
|
47
|
+
* triggered this module's load.
|
|
48
|
+
*/
|
|
49
|
+
module?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Pre-humanized label for the currently surfaced transition (module
|
|
52
|
+
* label if {@link module} is set, otherwise the event label), supplied
|
|
53
|
+
* for consumers that want a sensible default. Hosts that prefer to
|
|
54
|
+
* render their own label can read the raw {@link event}/{@link module}
|
|
55
|
+
* fields and ignore this — the framework leaves the policy choice
|
|
56
|
+
* (which transitions to surface, how to format them, whether to drop
|
|
57
|
+
* sub-modules entirely) to the host's `Placeholder`.
|
|
58
|
+
*/
|
|
59
|
+
humanizedName?: string;
|
|
35
60
|
};
|
|
36
61
|
|
|
37
62
|
export type PlaceholderProps = {
|
|
@@ -42,6 +67,7 @@ export type PlaceholderProps = {
|
|
|
42
67
|
export type UseAppOptions = {
|
|
43
68
|
pluginManager?: PluginManager.PluginManager;
|
|
44
69
|
pluginLoader?: PluginManager.ManagerOptions['pluginLoader'];
|
|
70
|
+
onPluginRemove?: PluginManager.ManagerOptions['onRemove'];
|
|
45
71
|
plugins?: Plugin.Plugin[];
|
|
46
72
|
core?: string[];
|
|
47
73
|
defaults?: string[];
|
|
@@ -86,6 +112,7 @@ export type UseAppOptions = {
|
|
|
86
112
|
export const useApp = ({
|
|
87
113
|
pluginManager,
|
|
88
114
|
pluginLoader: pluginLoaderProp,
|
|
115
|
+
onPluginRemove,
|
|
89
116
|
plugins: pluginsProp,
|
|
90
117
|
core: coreProp,
|
|
91
118
|
defaults: defaultsProp,
|
|
@@ -131,10 +158,10 @@ export const useApp = ({
|
|
|
131
158
|
);
|
|
132
159
|
const isExternalManager = !!pluginManager;
|
|
133
160
|
const manager = useMemo(() => {
|
|
134
|
-
const mgr = pluginManager ?? PluginManager.make({ pluginLoader, plugins, core, enabled });
|
|
161
|
+
const mgr = pluginManager ?? PluginManager.make({ pluginLoader, plugins, core, enabled, onRemove: onPluginRemove });
|
|
135
162
|
log('useApp: useMemo created/reused manager', { provided: !!pluginManager });
|
|
136
163
|
return mgr;
|
|
137
|
-
}, [pluginManager, pluginLoader, plugins, core, enabled]);
|
|
164
|
+
}, [pluginManager, pluginLoader, plugins, core, enabled, onPluginRemove]);
|
|
138
165
|
|
|
139
166
|
useEffect(() => {
|
|
140
167
|
if (!cacheEnabled) {
|
|
@@ -164,38 +191,89 @@ export const useApp = ({
|
|
|
164
191
|
module: 'org.dxos.app-framework.atom-registry',
|
|
165
192
|
});
|
|
166
193
|
|
|
167
|
-
// Poll manager atoms for progress (avoids PubSub subscription race).
|
|
168
|
-
const progressInterval = setInterval(() => {
|
|
169
|
-
if (readyRef.current) {
|
|
170
|
-
clearInterval(progressInterval);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const active = manager.getActive();
|
|
174
|
-
const modules = manager.getModules();
|
|
175
|
-
const total = modules.length;
|
|
176
|
-
const activated = active.length;
|
|
177
|
-
const lastModule = active.length > 0 ? active[active.length - 1] : undefined;
|
|
178
|
-
setStartupProgress({
|
|
179
|
-
activated,
|
|
180
|
-
total,
|
|
181
|
-
progress: total > 0 ? activated / total : 0,
|
|
182
|
-
status: lastModule ? humanizeModuleId(lastModule) : undefined,
|
|
183
|
-
});
|
|
184
|
-
}, 100);
|
|
185
|
-
|
|
186
194
|
const fiber = Effect.gen(function* () {
|
|
187
195
|
const queue = yield* PubSub.subscribe(manager.activation);
|
|
188
196
|
const listener = yield* Effect.forkDaemon(
|
|
189
197
|
Queue.take(queue).pipe(
|
|
190
|
-
Effect.tap(({ event, state, error: error$ }) =>
|
|
198
|
+
Effect.tap(({ event, state, module, error: error$ }) =>
|
|
191
199
|
Effect.sync(() => {
|
|
192
|
-
|
|
200
|
+
// Event-level Startup activated (no `module` field) fires once,
|
|
201
|
+
// after every module triggered by Startup has finished. Module
|
|
202
|
+
// activations now also carry their parent event id (so the trace
|
|
203
|
+
// can attribute a module to its triggering event), which means
|
|
204
|
+
// each module activated under Startup publishes
|
|
205
|
+
// `{ event: 'startup', state: 'activated', module: <id> }`. Without
|
|
206
|
+
// the `!module` guard the listener would mark the app ready on
|
|
207
|
+
// the *first* such module rather than waiting for the event-level
|
|
208
|
+
// completion — leaving downstream capabilities (operation-invoker,
|
|
209
|
+
// app-graph, …) un-registered when the placeholder dismisses.
|
|
210
|
+
if (event === ActivationEvents.Startup.id && state === 'activated' && !module) {
|
|
193
211
|
clearTimeout(timeoutId);
|
|
194
|
-
clearInterval(progressInterval);
|
|
195
212
|
setReady(true);
|
|
196
213
|
readyRef.current = true;
|
|
197
214
|
// Trigger startup profiler dump if available.
|
|
198
215
|
(globalThis as any).composer?.profiler?.dump();
|
|
216
|
+
// Notify any host observability layer that startup completed.
|
|
217
|
+
// A `CustomEvent` keeps this generic — app-framework doesn't
|
|
218
|
+
// import a provider, and consumers can capture the startup
|
|
219
|
+
// summary without us picking one.
|
|
220
|
+
if (typeof window !== 'undefined') {
|
|
221
|
+
window.dispatchEvent(new CustomEvent('app-framework:startup-activated'));
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// `activating` is the start-of-load signal. Surface the raw
|
|
226
|
+
// `module` (or `event`) plus a pre-humanized label so the
|
|
227
|
+
// host placeholder can decide what to render — show
|
|
228
|
+
// everything, suppress noisy sub-modules, group by plugin,
|
|
229
|
+
// or apply its own formatting. We intentionally do NOT touch
|
|
230
|
+
// these fields on `activated`: pairing the two would cause
|
|
231
|
+
// back-to-back identical updates to the host's effect, which
|
|
232
|
+
// the boot loader treats as a re-trigger because
|
|
233
|
+
// `progress.progress` moved. Leaving the label alone on
|
|
234
|
+
// completion keeps it accurate ("now activating X") until
|
|
235
|
+
// the next module starts.
|
|
236
|
+
if (module && state === 'activating' && !readyRef.current) {
|
|
237
|
+
setStartupProgress((current) => ({
|
|
238
|
+
...current,
|
|
239
|
+
// `event` here is the activation event that first
|
|
240
|
+
// triggered this module load (plumbed through
|
|
241
|
+
// `_loadCapabilitiesForModules` → `_loadModule`).
|
|
242
|
+
// Falsy/empty falls back to undefined so consumers can
|
|
243
|
+
// tell "no parent context" from "parent context: <X>".
|
|
244
|
+
event: event || undefined,
|
|
245
|
+
module,
|
|
246
|
+
humanizedName: humanizeModuleId(module),
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
// Update the activation count when a module commits. The
|
|
250
|
+
// ring's fraction comes from this; `event`/`module`/
|
|
251
|
+
// `humanizedName` were set by the matching `activating`
|
|
252
|
+
// message above and are left alone so the count can advance
|
|
253
|
+
// without re-firing the host's status callback.
|
|
254
|
+
if (module && state === 'activated' && !readyRef.current) {
|
|
255
|
+
const active = manager.getActive();
|
|
256
|
+
const total = manager.getModules().length;
|
|
257
|
+
setStartupProgress((current) => ({
|
|
258
|
+
...current,
|
|
259
|
+
activated: active.length,
|
|
260
|
+
total,
|
|
261
|
+
progress: total > 0 ? active.length / total : 0,
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
// Event-level `activating` (no `module`) — fired at the start
|
|
265
|
+
// of `_activateModulesForEvent` and recursively for each
|
|
266
|
+
// before/after event. Surfaces a label during the gap before
|
|
267
|
+
// the first module-level message lands; subsequent module
|
|
268
|
+
// updates immediately overwrite this with a more specific
|
|
269
|
+
// label.
|
|
270
|
+
if (event && !module && state === 'activating' && !readyRef.current) {
|
|
271
|
+
setStartupProgress((current) => ({
|
|
272
|
+
...current,
|
|
273
|
+
event,
|
|
274
|
+
module: undefined,
|
|
275
|
+
humanizedName: humanizeEventKey(event),
|
|
276
|
+
}));
|
|
199
277
|
}
|
|
200
278
|
if (error$ && !readyRef.current) {
|
|
201
279
|
setError(error$);
|
|
@@ -232,7 +310,6 @@ export const useApp = ({
|
|
|
232
310
|
return () => {
|
|
233
311
|
log('useApp: effect cleanup');
|
|
234
312
|
clearTimeout(timeoutId);
|
|
235
|
-
clearInterval(progressInterval);
|
|
236
313
|
void runAndForwardErrors(Fiber.interrupt(fiber));
|
|
237
314
|
if (!isExternalManager) {
|
|
238
315
|
void runAndForwardErrors(manager.shutdown());
|
|
@@ -240,6 +317,9 @@ export const useApp = ({
|
|
|
240
317
|
};
|
|
241
318
|
}, [manager]);
|
|
242
319
|
|
|
320
|
+
const progressRef = useRef(startupProgress);
|
|
321
|
+
progressRef.current = startupProgress;
|
|
322
|
+
|
|
243
323
|
return useCallback(
|
|
244
324
|
() => (
|
|
245
325
|
<ErrorBoundary name='app' FallbackComponent={fallback}>
|
|
@@ -251,7 +331,7 @@ export const useApp = ({
|
|
|
251
331
|
ready={ready}
|
|
252
332
|
error={error}
|
|
253
333
|
debounce={debounce}
|
|
254
|
-
progress={
|
|
334
|
+
progress={progressRef.current}
|
|
255
335
|
/>
|
|
256
336
|
</RegistryContext.Provider>
|
|
257
337
|
</ContextProtocolProvider>
|
|
@@ -269,21 +349,65 @@ const setupDevtools = (manager: PluginManager.PluginManager) => {
|
|
|
269
349
|
|
|
270
350
|
/**
|
|
271
351
|
* Extracts a human-readable label from a module ID.
|
|
272
|
-
*
|
|
352
|
+
*
|
|
353
|
+
* Module IDs follow `org.dxos.plugin.<plugin-slug>.module.<module-name>`,
|
|
354
|
+
* where `<plugin-slug>` is kebab-case and `<module-name>` is either an
|
|
355
|
+
* explicit string (often kebab-case, e.g. `'observability'`, `'namespace'`)
|
|
356
|
+
* or a capability tag in PascalCase from `Capability.getModuleTag(...)`
|
|
357
|
+
* (e.g. `'ReactSurface'`, `'AppGraphBuilder'`). The output is
|
|
358
|
+
* `"Title Case Plugin: kebab-module"` so the visible status names both the
|
|
359
|
+
* plugin and the aspect being activated, helping disambiguate the multiple
|
|
360
|
+
* modules a plugin contributes.
|
|
361
|
+
*
|
|
362
|
+
* Examples:
|
|
363
|
+
* - "org.dxos.plugin.markdown.module.ReactSurface" → "Markdown: react-surface"
|
|
364
|
+
* - "org.dxos.plugin.observability.module.AppGraphBuilder" → "Observability: app-graph-builder"
|
|
365
|
+
* - "org.dxos.plugin.observability.module.observability" → "Observability"
|
|
366
|
+
* (the module name matches the plugin slug — collapsed to avoid
|
|
367
|
+
* "Observability: observability" noise.)
|
|
273
368
|
*/
|
|
274
369
|
const humanizeModuleId = (moduleId: string): string => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
return name
|
|
281
|
-
.split('-')
|
|
282
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
283
|
-
.join(' ');
|
|
370
|
+
const match = moduleId.match(/\.plugin\.([^.]+)\.module\.(.+)$/);
|
|
371
|
+
if (!match) {
|
|
372
|
+
// Fallback: use the last segment.
|
|
373
|
+
const parts = moduleId.split('.');
|
|
374
|
+
return parts[parts.length - 1];
|
|
284
375
|
}
|
|
376
|
+
const [, pluginSlug, moduleName] = match;
|
|
377
|
+
const pluginLabel = pluginSlug
|
|
378
|
+
.split('-')
|
|
379
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
380
|
+
.join(' ');
|
|
381
|
+
// Normalise the module name to kebab-case so PascalCase capability tags
|
|
382
|
+
// ("ReactSurface") read consistently with explicit kebab IDs
|
|
383
|
+
// ("operation-handler"). The two-step substitution handles consecutive
|
|
384
|
+
// uppercase runs (`URLLoader` → `url-loader`) without splitting them
|
|
385
|
+
// mid-acronym.
|
|
386
|
+
const moduleLabel = moduleName
|
|
387
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
388
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
389
|
+
.toLowerCase();
|
|
390
|
+
// Capability modules whose name matches the plugin slug (e.g. the
|
|
391
|
+
// observability plugin's `observability` capability module) would render
|
|
392
|
+
// as "Observability: observability" — drop the redundant suffix.
|
|
393
|
+
if (moduleLabel === pluginSlug) {
|
|
394
|
+
return pluginLabel;
|
|
395
|
+
}
|
|
396
|
+
return `${pluginLabel}: ${moduleLabel}`;
|
|
397
|
+
};
|
|
285
398
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
399
|
+
/**
|
|
400
|
+
* Extracts a human-readable label from an activation event key.
|
|
401
|
+
* E.g., "org.dxos.app-framework.event.setup-react-surface" → "Setup React Surface".
|
|
402
|
+
*/
|
|
403
|
+
const humanizeEventKey = (eventKey: string): string => {
|
|
404
|
+
// Strip a leading specifier (composite key form: "<id>:<specifier>").
|
|
405
|
+
const id = eventKey.split(':')[0];
|
|
406
|
+
// Match the trailing segment after `.event.`.
|
|
407
|
+
const match = id.match(/\.event\.(.+)$/);
|
|
408
|
+
const slug = match ? match[1] : (id.split('.').pop() ?? id);
|
|
409
|
+
return slug
|
|
410
|
+
.split('-')
|
|
411
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
412
|
+
.join(' ');
|
|
289
413
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { useEffect, useState } from 'react';
|
|
5
|
+
import { useEffect, useRef, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
export enum LoadingState {
|
|
8
8
|
Loading = 0,
|
|
@@ -23,6 +23,14 @@ export enum LoadingState {
|
|
|
23
23
|
*/
|
|
24
24
|
export const useLoading = (ready: boolean, debounce = 0) => {
|
|
25
25
|
const [stage, setStage] = useState<LoadingState>(LoadingState.Loading);
|
|
26
|
+
// Mirror `ready` into a ref so the interval's `setStage` callback can read
|
|
27
|
+
// the latest value without depending on the effect re-running. The pure
|
|
28
|
+
// closure-capture pattern (with `ready` in deps) sticks the FSM at
|
|
29
|
+
// `FadeIn` whenever HMR doesn't propagate the dep change end-to-end —
|
|
30
|
+
// a ref sidesteps that entirely and fires the read on every tick.
|
|
31
|
+
const readyRef = useRef(ready);
|
|
32
|
+
readyRef.current = ready;
|
|
33
|
+
|
|
26
34
|
useEffect(() => {
|
|
27
35
|
if (!debounce) {
|
|
28
36
|
return;
|
|
@@ -30,18 +38,18 @@ export const useLoading = (ready: boolean, debounce = 0) => {
|
|
|
30
38
|
|
|
31
39
|
const i = setInterval(() => {
|
|
32
40
|
setStage((stage) => {
|
|
41
|
+
const isReady = readyRef.current;
|
|
33
42
|
switch (stage) {
|
|
34
43
|
case LoadingState.Loading: {
|
|
35
|
-
if (!
|
|
44
|
+
if (!isReady) {
|
|
36
45
|
return LoadingState.FadeIn;
|
|
37
|
-
} else {
|
|
38
|
-
clearInterval(i);
|
|
39
|
-
return LoadingState.Done;
|
|
40
46
|
}
|
|
47
|
+
clearInterval(i);
|
|
48
|
+
return LoadingState.Done;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
case LoadingState.FadeIn: {
|
|
44
|
-
if (
|
|
52
|
+
if (isReady) {
|
|
45
53
|
return LoadingState.FadeOut;
|
|
46
54
|
}
|
|
47
55
|
break;
|