@ethisyscore/plugin-ui 1.136.0 → 1.137.0

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/README.md CHANGED
@@ -16,7 +16,7 @@ and the shared MUI component surface externalised to the host's instances.
16
16
  | Subpath | Key exports | Purpose |
17
17
  |---|---|---|
18
18
  | `.` | `createPortBridgeClient`, `BridgeClientContext` / `useBridgeClient`, `useBridgeTheme` / `useBridgeLocale`, `ExtensionRuntimeProvider`, `useMcpResource` / `useMcpTool` / `useMcpQuery` / `unwrapItems`, `useHostIdentity` / `useCurrentUser`, `definePlatformReactPage` / `emitNavigation` / `PlatformReactPageProps` | Bridge client + theme/locale context, brokered-MCP data hooks, host identity, and the base PlatformReact page contract |
19
- | `./platform-react` | `definePlatformReactPluginPage` / `createPluginPageDefiner`, `definePlatformReactPluginOverlay` / `useOverlayHost`, `useView` / `createUseView` / `ViewComponent` / `isComponent`, `BaseMcpService` / `useToolInvoker` / `useToolInvokerMap`, `useAuthenticatedQuery` / `useAuthenticatedQueries`, `injectPluginStyles` / `PluginStyleScope`, `createReactRouterShim`, `usePluginRealtime`, `useBackgroundOperation` / `useOperationsByParent` / `useLatestTerminalOperationByParent` / `hasKind`, `useMcpUpload`, `useManagedLifecycle` | The plugin authoring layer: page/overlay wrappers (react-query + manifest + style root), the static `useView` adapter pattern, the MCP service base + tool invokers, pre-authenticated react-query wrappers, scoped-style injection, router shim, realtime, following the plugin's own background operations, and file upload |
19
+ | `./platform-react` | `definePlatformReactPluginPage` / `createPluginPageDefiner`, `definePlatformReactPluginOverlay` / `useOverlayHost`, `useView` / `createUseView` / `ViewComponent` / `isComponent`, `BaseMcpService` / `useToolInvoker` / `useToolInvokerMap`, `useAuthenticatedQuery` / `useAuthenticatedQueries`, `injectPluginStyles` / `PluginStyleScope`, `createReactRouterShim`, `usePluginRealtime`, `useBackgroundOperation` / `useOperationsByParent` / `useLatestTerminalOperationByParent` / `hasKind`, `useUploadToStorage` / `PluginStorageUploadError` / `isPluginStorageUploadError` / `validatePluginStoragePrefix`, `useMcpUpload` (legacy CORE storage), `useManagedLifecycle` | The plugin authoring layer: page/overlay wrappers (react-query + manifest + style root), the static `useView` adapter pattern, the MCP service base + tool invokers, pre-authenticated react-query wrappers, scoped-style injection, router shim, realtime, following the plugin's own background operations, and file upload |
20
20
  | `./icons` | `NextureIcons`, `IconMap`, 470+ `Ni*` icon components, `sizeHelper` / `strokeSizeHelper`, `IconName` / `IconSize` / `IconVariant` | The gogo-ui "nexture" SVG icon set with props types and size/stroke helpers |
21
21
  | `./components/ui` | `Button`, `Card`, `Dialog`, `Table`, `TextField`, `Select`, `Tabs`, `Typography`, `Grid`, `Stack`, `Menu`, `DebouncedSearchInput`, … | MUI component re-exports plus the Dialog scroll-lock wrapper and the debounced search field. These are **host externals** — never bundled; production builds bind them to the host's MUI instance |
22
22
  | `./components/layout` | `PageHeader` / `usePageHeader` / `withPageHeader` / `buildBreadcrumbs`, `FillGrid` / `FillGridItem` / `FillGap` / `FillSpan`, sidebar-action context (`SidebarActionProvider`, `useSidebarAction` / `useSidebarActions` / `useTriggerSidebarAction`), `RouteMeta` / `RouteMetaEntry` | Layout primitives (flex-wrap grid), the page-header layer + breadcrumb builder, and the sidebar-action context/provider |
@@ -52,6 +52,27 @@ service extending `BaseMcpService` and consume it through `useToolInvoker`. See
52
52
  the top-level README §"services over MCP" and the
53
53
  [FE migration playbook](../../docs/app-migration/fe-migration-playbook.md).
54
54
 
55
+ ### Uploading a browser file
56
+
57
+ Browser uploads go through `useUploadToStorage` into the app's own plugin storage. Never base64 a file through a tool call.
58
+
59
+ ```tsx
60
+ import { useUploadToStorage, isPluginStorageUploadError } from "@ethisyscore/plugin-ui/platform-react";
61
+
62
+ const { upload, loading, error } = useUploadToStorage();
63
+
64
+ async function onFile(file: File) {
65
+ try {
66
+ const { path } = await upload(file, { pathPrefix: "cvs" });
67
+ await recordCv({ path }); // the app's own command; the backend reads `path` via IPluginStorage
68
+ } catch (err) {
69
+ if (isPluginStorageUploadError(err) && err.reason === "quota_exceeded") { /* ... */ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ The prefix is one or more `/`-separated `[A-Za-z0-9_-]` segments, at most 84 characters, with no leading or trailing `/`. It is refused, never repaired. Files are capped at 30 MB, and only allow-listed passive types (`PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES`) are accepted. See [CAPABILITIES.md](../../CAPABILITIES.md#browser-file-uploads) for the full rules and the error reasons.
75
+
55
76
  ## Build and test
56
77
 
57
78
  ```bash
@@ -985,6 +985,63 @@ function useMcpUpload(opts) {
985
985
  );
986
986
  return { upload, progress, loading, error };
987
987
  }
988
+ function useUploadToStorage(opts) {
989
+ const transport = extensionRuntime.useExtensionRuntimeTransport(opts?.transport);
990
+ const maxSizeBytes = opts?.maxSizeBytes;
991
+ const [progress, setProgress] = react.useState(0);
992
+ const [loading, setLoading] = react.useState(false);
993
+ const [error, setError] = react.useState(void 0);
994
+ const controllerRef = react.useRef(null);
995
+ const mountedRef = react.useRef(true);
996
+ react.useEffect(() => {
997
+ mountedRef.current = true;
998
+ return () => {
999
+ mountedRef.current = false;
1000
+ controllerRef.current?.abort();
1001
+ };
1002
+ }, []);
1003
+ const upload = react.useCallback(
1004
+ async (file, target) => {
1005
+ const controller = new AbortController();
1006
+ controllerRef.current?.abort();
1007
+ controllerRef.current = controller;
1008
+ const applyState = (fn) => {
1009
+ if (mountedRef.current && !controller.signal.aborted) {
1010
+ fn();
1011
+ }
1012
+ };
1013
+ applyState(() => {
1014
+ setLoading(true);
1015
+ setError(void 0);
1016
+ setProgress(0);
1017
+ });
1018
+ try {
1019
+ const result = await extensionRuntime.uploadFileToPluginStorage(transport, file, {
1020
+ pathPrefix: target.pathPrefix,
1021
+ fileName: target.fileName,
1022
+ contentType: target.contentType,
1023
+ maxSizeBytes,
1024
+ signal: controller.signal
1025
+ });
1026
+ applyState(() => {
1027
+ setProgress(1);
1028
+ setLoading(false);
1029
+ });
1030
+ return result;
1031
+ } catch (err) {
1032
+ const typed = extensionRuntime.toPluginStorageUploadError(err);
1033
+ applyState(() => {
1034
+ setError(typed);
1035
+ setLoading(false);
1036
+ setProgress(0);
1037
+ });
1038
+ throw typed;
1039
+ }
1040
+ },
1041
+ [transport, maxSizeBytes]
1042
+ );
1043
+ return { upload, progress, loading, error };
1044
+ }
988
1045
  var OVERLAY_HOST_CONTRACT_VERSION = "1.0.0";
989
1046
  var OverlayHostContext = react.createContext(null);
990
1047
  function useOverlayHost() {
@@ -1414,6 +1471,26 @@ function withActiveNavHref(input, activeNavHref) {
1414
1471
  return { ...input, activeNavHref };
1415
1472
  }
1416
1473
 
1474
+ Object.defineProperty(exports, "PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES", {
1475
+ enumerable: true,
1476
+ get: function () { return extensionRuntime.PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES; }
1477
+ });
1478
+ Object.defineProperty(exports, "PluginStorageUploadError", {
1479
+ enumerable: true,
1480
+ get: function () { return extensionRuntime.PluginStorageUploadError; }
1481
+ });
1482
+ Object.defineProperty(exports, "isPluginStorageUploadError", {
1483
+ enumerable: true,
1484
+ get: function () { return extensionRuntime.isPluginStorageUploadError; }
1485
+ });
1486
+ Object.defineProperty(exports, "resolvePluginStorageContentType", {
1487
+ enumerable: true,
1488
+ get: function () { return extensionRuntime.resolvePluginStorageContentType; }
1489
+ });
1490
+ Object.defineProperty(exports, "validatePluginStoragePrefix", {
1491
+ enumerable: true,
1492
+ get: function () { return extensionRuntime.validatePluginStoragePrefix; }
1493
+ });
1417
1494
  exports.BaseMcpService = BaseMcpService;
1418
1495
  exports.DEFAULT_MAX_UPLOAD_BYTES = DEFAULT_MAX_UPLOAD_BYTES;
1419
1496
  exports.EHX_PLUGIN_PORTAL_ATTR = EHX_PLUGIN_PORTAL_ATTR;
@@ -1480,6 +1557,7 @@ exports.useSurfaceUrl = useSurfaceUrl;
1480
1557
  exports.useToolInvoker = useToolInvoker;
1481
1558
  exports.useToolInvokerMap = useToolInvokerMap;
1482
1559
  exports.useTypedToolInvokerMap = useTypedToolInvokerMap;
1560
+ exports.useUploadToStorage = useUploadToStorage;
1483
1561
  exports.useView = useView;
1484
1562
  exports.withActiveNavHref = withActiveNavHref;
1485
1563
  //# sourceMappingURL=index.cjs.map