From 15c54522bc65d1309334b950cc6e988ad090842a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 14:35:54 +0000 Subject: [PATCH 1/3] refactor(ui)!: fold the providers upload context into fields and data-objectstack, retire the package UploadProvider and useUpload move into @object-ui/fields, next to the file, image and avatar widgets that read them. createObjectStackUploadAdapter and its options move into @object-ui/data-objectstack, beside the other fetch logic against an ObjectStack server. The UploadAdapter, UploadOptions and UploadResult contract both sides share goes into @object-ui/types, as DataSource does, because the two packages do not depend on each other. createS3Adapter and createAzureBlobAdapter had no consumer in the workspace or in hotcrm and retire with the package. createObjectUrlAdapter stays as the module-private fallback useUpload answers with when no provider is mounted. Every importer, alias line, mock and the fixed release group are re-pointed; the lockfile is regenerated. Co-Authored-By: Claude --- .changeset/25921-providers-upload-fold.md | 19 + .changeset/25921-upload-adapter-contract.md | 10 + .changeset/config.json | 1 - CONTRIBUTING.md | 1 - pnpm-lock.yaml | 34 - ui/apps/console/package.json | 1 - ui/apps/console/src/App.tsx | 3 +- .../App.uploadAltitude-10131.test.tsx | 8 +- .../FormPage.avatarUpload-10785.test.tsx | 2 +- .../FormPage.chromeI18n-11071.test.tsx | 2 +- .../components/FormPage.fileField.test.tsx | 2 +- .../console/src/pages/system/ProfilePage.tsx | 2 +- .../__tests__/ProfilePage.access.test.tsx | 2 +- .../ProfilePage.appLock-29748.test.tsx | 2 +- .../__tests__/ProfilePage.language.test.tsx | 2 +- .../ProfilePage.pageHeader-12553.test.tsx | 2 +- .../ProfilePage.sharedSelect-11865.test.tsx | 2 +- ui/apps/console/vite.config.ts | 1 - ui/packages/app-shell/package.json | 1 - .../settings-slug-environments-11720.test.tsx | 4 +- .../organizations/manage/SettingsPage.tsx | 2 +- ...ionParamDialog.avatarUpload-10785.test.tsx | 2 +- .../src/views/RecordAttachmentsPanel.tsx | 4 +- .../FlowRunner.fileField-25801.test.tsx | 2 +- ...hmentsPanel.affordanceGrant-12047.test.tsx | 2 +- .../src/views/record-attachments-renderer.tsx | 2 +- .../cli/src/__tests__/workspace-vite.test.ts | 8 +- ui/packages/data-objectstack/src/index.ts | 6 + .../objectstack-upload-scope-12055.test.ts | 9 +- .../src/upload-adapter.ts} | 236 +-- ui/packages/fields/package.json | 1 - .../fileRestrictedState-12339.test.tsx | 3 +- ui/packages/fields/src/index.tsx | 5 + .../widgets/AvatarField.fileId-10785.test.tsx | 3 +- .../fields/src/widgets/AvatarField.tsx | 2 +- ui/packages/fields/src/widgets/FileField.tsx | 2 +- .../fields/src/widgets/GridField.test.tsx | 2 +- .../ImageField.imageAlt-10637.test.tsx | 3 +- .../src/widgets/ImageField.maxSize.test.tsx | 3 +- ui/packages/fields/src/widgets/ImageField.tsx | 2 +- .../widgets/ImageField.uploadFailure.test.tsx | 3 +- .../fields/src/widgets/UploadProvider.tsx | 95 + .../fileSubmitRequiresId-7699.test.tsx | 3 +- .../nativeImagePick.shellPhoto.test.tsx | 3 +- .../src/widgets/uploadingScope.hold.test.tsx | 2 +- ui/packages/plugin-editor/vite.config.ts | 1 - .../src/WizardForm.nextMidUpload.test.tsx | 69 +- ui/packages/plugin-markdown/vite.config.ts | 1 - ui/packages/providers/CHANGELOG.md | 1524 ----------------- ui/packages/providers/LICENSE | 21 - ui/packages/providers/README.md | 65 - ui/packages/providers/package.json | 69 - ui/packages/providers/src/index.ts | 28 - ui/packages/providers/tsconfig.json | 57 - ui/packages/providers/tsconfig.test.json | 23 - ui/packages/runner/vite.config.ts | 2 - ui/packages/types/src/index.ts | 4 + ui/packages/types/src/upload.ts | 63 + ...de-effects-declaration-consistency.test.ts | 1 - ui/vitest.config.mts | 1 - 60 files changed, 304 insertions(+), 2131 deletions(-) create mode 100644 .changeset/25921-providers-upload-fold.md create mode 100644 .changeset/25921-upload-adapter-contract.md rename ui/packages/{providers/src/__tests__ => data-objectstack/src}/objectstack-upload-scope-12055.test.ts (95%) rename ui/packages/{providers/src/UploadProvider.tsx => data-objectstack/src/upload-adapter.ts} (51%) create mode 100644 ui/packages/fields/src/widgets/UploadProvider.tsx delete mode 100644 ui/packages/providers/CHANGELOG.md delete mode 100644 ui/packages/providers/LICENSE delete mode 100644 ui/packages/providers/README.md delete mode 100644 ui/packages/providers/package.json delete mode 100644 ui/packages/providers/src/index.ts delete mode 100644 ui/packages/providers/tsconfig.json delete mode 100644 ui/packages/providers/tsconfig.test.json create mode 100644 ui/packages/types/src/upload.ts diff --git a/.changeset/25921-providers-upload-fold.md b/.changeset/25921-providers-upload-fold.md new file mode 100644 index 00000000000..3da1b4bb0b4 --- /dev/null +++ b/.changeset/25921-providers-upload-fold.md @@ -0,0 +1,19 @@ +--- +'@object-ui/fields': patch +'@object-ui/data-objectstack': patch +--- + +refactor(fields, data-objectstack)!: `UploadProvider` moves into `@object-ui/fields`, the ObjectStack upload adapter into `@object-ui/data-objectstack`, and `@object-ui/providers` is retired + +Clause-②: yes (widening) +Metadata-protocol: none + + + +**BREAKING.** `@object-ui/providers` is retired: `18.3.0` is its last published version, and it gets no further release. Its one remaining job was the upload context the file, image and avatar widgets of `@object-ui/fields` upload through, so that context now lives in `@object-ui/fields` itself, and the adapter that talks to an ObjectStack server's storage service lives in `@object-ui/data-objectstack`, beside the other fetch logic against that server. The upload contract the two share is `@object-ui/types`'. + +What leaves, and what to do: + +- FROM `@object-ui/providers` TO `@object-ui/fields` and `@object-ui/data-objectstack`: import `UploadProvider`, `useUpload` and the `UploadProviderProps`, `UploadAdapter`, `UploadOptions` and `UploadResult` types from `@object-ui/fields` (the three contract types also from `@object-ui/types`), and `createObjectStackUploadAdapter` with `ObjectStackUploadAdapterOptions` from `@object-ui/data-objectstack`, then drop the `@object-ui/providers` dependency. `createS3Adapter`, `createAzureBlobAdapter`, `S3AdapterOptions` and `AzureBlobAdapterOptions` had no consumer and leave with no replacement: a host that uploads to its own bucket writes an object with a `name` and an `upload(file, options)` method against the `UploadAdapter` type of `@object-ui/types` and hands it to `UploadProvider`. `createObjectUrlAdapter` is no longer exported: it stays the local `blob:` adapter `useUpload` falls back to when no `UploadProvider` is mounted. + +What does not change: `UploadProvider`, `useUpload` and `createObjectStackUploadAdapter` keep their signatures and behaviour, the presigned upload request is the same, and the widgets read the same context. diff --git a/.changeset/25921-upload-adapter-contract.md b/.changeset/25921-upload-adapter-contract.md new file mode 100644 index 00000000000..1dbff6edfdc --- /dev/null +++ b/.changeset/25921-upload-adapter-contract.md @@ -0,0 +1,10 @@ +--- +'@object-ui/types': patch +--- + +feat(types): `@object-ui/types` publishes the upload adapter contract, `UploadAdapter`, `UploadOptions` and `UploadResult` + +Clause-②: yes (widening) +Metadata-protocol: none + +The three interfaces the file, image and avatar widgets upload through, unchanged in shape from the `@object-ui/providers` package they leave. They live here, as `DataSource` does, because `@object-ui/fields` (whose `UploadProvider` reads an adapter) and `@object-ui/data-objectstack` (whose `createObjectStackUploadAdapter` builds one) do not depend on each other. A host that writes its own adapter types it from here. Nothing existing changes. diff --git a/.changeset/config.json b/.changeset/config.json index 252e5999292..245b5b985fc 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -114,7 +114,6 @@ "@object-ui/plugin-timeline", "@object-ui/plugin-tree", "@object-ui/plugin-view", - "@object-ui/providers", "@object-ui/react", "@object-ui/react-runtime", "@object-ui/types" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa6111250aa..47bad097678 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -467,7 +467,6 @@ Atoms and fields stay light; heavy widgets live in lazy-loaded plugins. | Package | Description | | :--- | :--- | | [`@object-ui/app-shell`](ui/packages/app-shell) | The Console's shell, views and Studio; a private workspace package, last published to npm as 18.2.0 | -| [`@object-ui/providers`](ui/packages/providers) | The upload adapter context the file and image fields upload through | | [`@object-ui/auth`](ui/packages/auth) | `AuthProvider`, `useAuth`, `AuthGuard`, sign-in forms | | [`@object-ui/permissions`](ui/packages/permissions) | Object / field / row-level permission guards and hooks | | [`@object-ui/i18n`](ui/packages/i18n) | Language packs, RTL layout, date and currency formatting | diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9da1e2dd6f2..6538f5b1cea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3757,9 +3757,6 @@ importers: '@object-ui/plugin-view': specifier: workspace:* version: link:../../packages/plugin-view - '@object-ui/providers': - specifier: workspace:* - version: link:../../packages/providers '@object-ui/react': specifier: workspace:* version: link:../../packages/react @@ -3983,9 +3980,6 @@ importers: '@object-ui/plugin-editor': specifier: workspace:* version: link:../plugin-editor - '@object-ui/providers': - specifier: workspace:* - version: link:../providers '@object-ui/react': specifier: workspace:* version: link:../react @@ -4507,9 +4501,6 @@ importers: '@object-ui/permissions': specifier: workspace:* version: link:../permissions - '@object-ui/providers': - specifier: workspace:* - version: link:../providers '@object-ui/react': specifier: workspace:* version: link:../react @@ -5941,31 +5932,6 @@ importers: specifier: ^4.1.10 version: 4.1.12(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@4.1.12)(@vitest/ui@4.1.10)(happy-dom@21.0.4)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.15.0(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.5(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) - ui/packages/providers: - dependencies: - '@object-ui/types': - specifier: workspace:* - version: link:../types - react: - specifier: 19.3.0 - version: 19.3.0 - react-dom: - specifier: 19.3.0 - version: 19.3.0(react@19.3.0) - devDependencies: - '@types/react': - specifier: 19.3.0 - version: 19.3.0 - '@types/react-dom': - specifier: 19.3.0 - version: 19.3.0(@types/react@19.3.0) - typescript: - specifier: ^6.0.3 - version: 6.0.3 - vitest: - specifier: ^4.1.10 - version: 4.1.12(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@4.1.12)(@vitest/ui@4.1.10)(happy-dom@21.0.4)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.15.0(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.5(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) - ui/packages/react: dependencies: '@object-ui/core': diff --git a/ui/apps/console/package.json b/ui/apps/console/package.json index a6215b2aa89..c19548be069 100644 --- a/ui/apps/console/package.json +++ b/ui/apps/console/package.json @@ -84,7 +84,6 @@ "@object-ui/plugin-timeline": "workspace:*", "@object-ui/plugin-tree": "workspace:*", "@object-ui/plugin-view": "workspace:*", - "@object-ui/providers": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/test-support": "workspace:*", "@object-ui/types": "workspace:*", diff --git a/ui/apps/console/src/App.tsx b/ui/apps/console/src/App.tsx index de1d9967c83..bd0ede3de42 100644 --- a/ui/apps/console/src/App.tsx +++ b/ui/apps/console/src/App.tsx @@ -16,7 +16,8 @@ import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react'; import { BrowserRouter, Routes, Route, Navigate, Outlet, matchPath, useLocation, useNavigate } from 'react-router-dom'; import { useNativeHostRouting } from '@object-ui/mobile'; import { AuthProvider, useAuth, createAuthenticatedFetch } from '@object-ui/auth'; -import { UploadProvider, createObjectStackUploadAdapter } from '@object-ui/providers'; +import { UploadProvider } from '@object-ui/fields'; +import { createObjectStackUploadAdapter } from '@object-ui/data-objectstack'; import { DevMasterDetail } from './dev/DevMasterDetail'; import { DevLists } from './dev/DevLists'; import { DevModal } from './dev/DevModal'; diff --git a/ui/apps/console/src/__tests__/App.uploadAltitude-10131.test.tsx b/ui/apps/console/src/__tests__/App.uploadAltitude-10131.test.tsx index 2a74f1e5760..2f8d8437093 100644 --- a/ui/apps/console/src/__tests__/App.uploadAltitude-10131.test.tsx +++ b/ui/apps/console/src/__tests__/App.uploadAltitude-10131.test.tsx @@ -81,10 +81,10 @@ const { adapterOptions, uploadSpy, passthrough, stub, MINTED_FILE_ID } = vi.hois return { adapterOptions, uploadSpy, passthrough, stub, MINTED_FILE_ID }; }); -// The console's OWN adapter factory, replaced by a spy that mints a `sys_file` -// id. `UploadProvider` / `useUpload` stay real — the context lookup is the -// thing under test. -vi.mock('@object-ui/providers', async (importOriginal) => ({ +// The console's OWN adapter factory (`@object-ui/data-objectstack`), replaced by +// a spy that mints a `sys_file` id. `UploadProvider` / `useUpload` +// (`@object-ui/fields`) stay real — the context lookup is the thing under test. +vi.mock('@object-ui/data-objectstack', async (importOriginal) => ({ ...(await importOriginal>()), createObjectStackUploadAdapter: (opts: Record) => { adapterOptions.push(opts); diff --git a/ui/apps/console/src/components/FormPage.avatarUpload-10785.test.tsx b/ui/apps/console/src/components/FormPage.avatarUpload-10785.test.tsx index 586bdb7a4b5..347bd8e2069 100644 --- a/ui/apps/console/src/components/FormPage.avatarUpload-10785.test.tsx +++ b/ui/apps/console/src/components/FormPage.avatarUpload-10785.test.tsx @@ -30,7 +30,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, waitFor, cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { UploadProvider } from '@object-ui/providers'; +import { UploadProvider } from '@object-ui/fields'; import { createI18n } from '@object-ui/i18n'; import { FormPage } from './FormPage'; diff --git a/ui/apps/console/src/components/FormPage.chromeI18n-11071.test.tsx b/ui/apps/console/src/components/FormPage.chromeI18n-11071.test.tsx index b84e62f35b8..352410743de 100644 --- a/ui/apps/console/src/components/FormPage.chromeI18n-11071.test.tsx +++ b/ui/apps/console/src/components/FormPage.chromeI18n-11071.test.tsx @@ -31,7 +31,7 @@ import { render, screen, waitFor, cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n } from '@object-ui/i18n'; -import { UploadProvider } from '@object-ui/providers'; +import { UploadProvider } from '@object-ui/fields'; import { FormPage } from './FormPage'; const { toastError } = vi.hoisted(() => ({ toastError: vi.fn() })); diff --git a/ui/apps/console/src/components/FormPage.fileField.test.tsx b/ui/apps/console/src/components/FormPage.fileField.test.tsx index 5c69a9d1ee7..95be9c273fb 100644 --- a/ui/apps/console/src/components/FormPage.fileField.test.tsx +++ b/ui/apps/console/src/components/FormPage.fileField.test.tsx @@ -42,7 +42,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, waitFor, cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { UploadProvider } from '@object-ui/providers'; +import { UploadProvider } from '@object-ui/fields'; import { createI18n } from '@object-ui/i18n'; import { FormPage } from './FormPage'; diff --git a/ui/apps/console/src/pages/system/ProfilePage.tsx b/ui/apps/console/src/pages/system/ProfilePage.tsx index 2329bfbba02..c4c7b68f076 100644 --- a/ui/apps/console/src/pages/system/ProfilePage.tsx +++ b/ui/apps/console/src/pages/system/ProfilePage.tsx @@ -35,7 +35,7 @@ import { SelectTrigger, SelectValue, } from '@object-ui/components'; -import { useUpload } from '@object-ui/providers'; +import { useUpload } from '@object-ui/fields'; import { ConsolePageHeader } from '@object-ui/app-shell'; import { useDisplayLocale, useObjectTranslation, type TranslateFn } from '@object-ui/i18n'; import { useAdapter, extractFieldErrors, extractWriteErrorMessage } from '@object-ui/react'; diff --git a/ui/apps/console/src/pages/system/__tests__/ProfilePage.access.test.tsx b/ui/apps/console/src/pages/system/__tests__/ProfilePage.access.test.tsx index 9ddddbe55b9..7f9f2dca81e 100644 --- a/ui/apps/console/src/pages/system/__tests__/ProfilePage.access.test.tsx +++ b/ui/apps/console/src/pages/system/__tests__/ProfilePage.access.test.tsx @@ -43,7 +43,7 @@ const { translate, findOne } = vi.hoisted(() => ({ findOne: vi.fn(async () => ({ id: 'usr_admin' })), })); -vi.mock('@object-ui/providers', async (importOriginal) => ({ +vi.mock('@object-ui/fields', async (importOriginal) => ({ ...(await importOriginal>()), useUpload: () => ({ upload: async () => ({ url: '' }) }), })); diff --git a/ui/apps/console/src/pages/system/__tests__/ProfilePage.appLock-29748.test.tsx b/ui/apps/console/src/pages/system/__tests__/ProfilePage.appLock-29748.test.tsx index 8f54300e56b..06bb75472db 100644 --- a/ui/apps/console/src/pages/system/__tests__/ProfilePage.appLock-29748.test.tsx +++ b/ui/apps/console/src/pages/system/__tests__/ProfilePage.appLock-29748.test.tsx @@ -47,7 +47,7 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({ }), })); -vi.mock('@object-ui/providers', async (importOriginal) => ({ +vi.mock('@object-ui/fields', async (importOriginal) => ({ ...(await importOriginal>()), useUpload: () => ({ upload: async () => ({ url: '' }) }), })); diff --git a/ui/apps/console/src/pages/system/__tests__/ProfilePage.language.test.tsx b/ui/apps/console/src/pages/system/__tests__/ProfilePage.language.test.tsx index fb227bb49a8..6a4b7ae8002 100644 --- a/ui/apps/console/src/pages/system/__tests__/ProfilePage.language.test.tsx +++ b/ui/apps/console/src/pages/system/__tests__/ProfilePage.language.test.tsx @@ -79,7 +79,7 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({ }), })); -vi.mock('@object-ui/providers', async (importOriginal) => ({ +vi.mock('@object-ui/fields', async (importOriginal) => ({ ...(await importOriginal>()), useUpload: () => ({ upload: async () => ({ url: '' }) }), })); diff --git a/ui/apps/console/src/pages/system/__tests__/ProfilePage.pageHeader-12553.test.tsx b/ui/apps/console/src/pages/system/__tests__/ProfilePage.pageHeader-12553.test.tsx index ee5d6aea746..3c9f8e53157 100644 --- a/ui/apps/console/src/pages/system/__tests__/ProfilePage.pageHeader-12553.test.tsx +++ b/ui/apps/console/src/pages/system/__tests__/ProfilePage.pageHeader-12553.test.tsx @@ -41,7 +41,7 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({ }), })); -vi.mock('@object-ui/providers', async (importOriginal) => ({ +vi.mock('@object-ui/fields', async (importOriginal) => ({ ...(await importOriginal>()), useUpload: () => ({ upload: async () => ({ url: '' }) }), })); diff --git a/ui/apps/console/src/pages/system/__tests__/ProfilePage.sharedSelect-11865.test.tsx b/ui/apps/console/src/pages/system/__tests__/ProfilePage.sharedSelect-11865.test.tsx index e578fbcf5f6..9e53b8be751 100644 --- a/ui/apps/console/src/pages/system/__tests__/ProfilePage.sharedSelect-11865.test.tsx +++ b/ui/apps/console/src/pages/system/__tests__/ProfilePage.sharedSelect-11865.test.tsx @@ -55,7 +55,7 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({ }), })); -vi.mock('@object-ui/providers', async (importOriginal) => ({ +vi.mock('@object-ui/fields', async (importOriginal) => ({ ...(await importOriginal>()), useUpload: () => ({ upload: async () => ({ url: '' }) }), })); diff --git a/ui/apps/console/vite.config.ts b/ui/apps/console/vite.config.ts index 760050c271b..f3277f2fbc9 100644 --- a/ui/apps/console/vite.config.ts +++ b/ui/apps/console/vite.config.ts @@ -640,7 +640,6 @@ const workspaceAliases: Record = { '@object-ui/data-objectstack': path.resolve(import.meta.dirname, '../../packages/data-objectstack/src'), '@object-ui/auth': path.resolve(import.meta.dirname, '../../packages/auth/src'), '@object-ui/permissions': path.resolve(import.meta.dirname, '../../packages/permissions/src'), - '@object-ui/providers': path.resolve(import.meta.dirname, '../../packages/providers/src'), '@object-ui/collaboration': path.resolve(import.meta.dirname, '../../packages/collaboration/src'), '@object-ui/i18n': path.resolve(import.meta.dirname, '../../packages/i18n/src'), '@object-ui/mobile': path.resolve(import.meta.dirname, '../../packages/mobile/src'), diff --git a/ui/packages/app-shell/package.json b/ui/packages/app-shell/package.json index c97cdd7f2bb..790188706ac 100644 --- a/ui/packages/app-shell/package.json +++ b/ui/packages/app-shell/package.json @@ -86,7 +86,6 @@ "@object-ui/mobile": "workspace:*", "@object-ui/permissions": "workspace:*", "@object-ui/plugin-editor": "workspace:*", - "@object-ui/providers": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", "@objectstack/formula": "workspace:^", diff --git a/ui/packages/app-shell/src/console/organizations/__tests__/settings-slug-environments-11720.test.tsx b/ui/packages/app-shell/src/console/organizations/__tests__/settings-slug-environments-11720.test.tsx index f5c5b403321..255e64c1ddb 100644 --- a/ui/packages/app-shell/src/console/organizations/__tests__/settings-slug-environments-11720.test.tsx +++ b/ui/packages/app-shell/src/console/organizations/__tests__/settings-slug-environments-11720.test.tsx @@ -72,8 +72,8 @@ vi.mock('@object-ui/i18n', async (importActual) => ({ }), })); -vi.mock('@object-ui/providers', async (importActual) => ({ - ...(await importActual()), +vi.mock('@object-ui/fields', async (importActual) => ({ + ...(await importActual()), useUpload: () => ({ upload: vi.fn() }), })); diff --git a/ui/packages/app-shell/src/console/organizations/manage/SettingsPage.tsx b/ui/packages/app-shell/src/console/organizations/manage/SettingsPage.tsx index fbf7960a002..87cfd0267a0 100644 --- a/ui/packages/app-shell/src/console/organizations/manage/SettingsPage.tsx +++ b/ui/packages/app-shell/src/console/organizations/manage/SettingsPage.tsx @@ -41,7 +41,7 @@ import { import { useAuth } from '@object-ui/auth'; import type { AuthOrganizationMember } from '@object-ui/auth'; import { useObjectTranslation } from '@object-ui/i18n'; -import { useUpload } from '@object-ui/providers'; +import { useUpload } from '@object-ui/fields'; import { Loader2, Upload, X } from 'lucide-react'; import { toast } from 'sonner'; import { useNavigate } from 'react-router-dom'; diff --git a/ui/packages/app-shell/src/views/ActionParamDialog.avatarUpload-10785.test.tsx b/ui/packages/app-shell/src/views/ActionParamDialog.avatarUpload-10785.test.tsx index 67df87d5df4..3ed374f3fa1 100644 --- a/ui/packages/app-shell/src/views/ActionParamDialog.avatarUpload-10785.test.tsx +++ b/ui/packages/app-shell/src/views/ActionParamDialog.avatarUpload-10785.test.tsx @@ -23,7 +23,7 @@ */ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react'; -import { UploadProvider, type UploadResult } from '@object-ui/providers'; +import { UploadProvider, type UploadResult } from '@object-ui/fields'; import type { ActionParamDef } from '@object-ui/core'; import { ActionParamDialog } from './ActionParamDialog'; diff --git a/ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx b/ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx index 477f942ab53..357174d5f02 100644 --- a/ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx +++ b/ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx @@ -19,7 +19,7 @@ import { Ban, RefreshCw, } from 'lucide-react'; -import { createObjectStackUploadAdapter } from '@object-ui/providers'; +import { createObjectStackUploadAdapter } from '@object-ui/data-objectstack'; import { createAuthenticatedFetch } from '@object-ui/auth'; import { useObjectTranslation, isPermissionError, classifyLoadError } from '@object-ui/react'; import { usePermissions } from '@object-ui/permissions'; @@ -45,7 +45,7 @@ import { RECORD_PAGE_ATTACHMENT_ROWS, recordAttachmentsCountField } from './reco * and the enforcement seam always agree). * * Storage model: one `sys_file` row per uploaded blob (three-step - * presigned upload via @object-ui/providers' ObjectStack adapter), one + * presigned upload via @object-ui/data-objectstack's ObjectStack adapter), one * `sys_attachment` join row linking it to `(parent_object, parent_id)`. * Downloads fetch a short-lived signed URL from `/storage/files/:fileId/url` * with the console's Bearer token (the endpoint requires an authenticated diff --git a/ui/packages/app-shell/src/views/__tests__/FlowRunner.fileField-25801.test.tsx b/ui/packages/app-shell/src/views/__tests__/FlowRunner.fileField-25801.test.tsx index 866d11b2575..4dc62eef5c1 100644 --- a/ui/packages/app-shell/src/views/__tests__/FlowRunner.fileField-25801.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/FlowRunner.fileField-25801.test.tsx @@ -20,7 +20,7 @@ import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/re // Module scope, per AGENTS.md 测试纪律: the file leg reaches `FileField` // through a `React.lazy` factory inside `@object-ui/fields`. import '@object-ui/fields'; -import { UploadProvider, type UploadResult } from '@object-ui/providers'; +import { UploadProvider, type UploadResult } from '@object-ui/fields'; import { FlowRunner, type ScreenFlowState } from '../FlowRunner'; import { screenResumeValues } from '../ScreenView'; diff --git a/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx b/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx index 4bb9ce62b65..46bf4020a2a 100644 --- a/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx @@ -39,7 +39,7 @@ import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from ' /** The fake presigned adapter: `upload` resolving means a `sys_file` was committed. */ const adapterUpload = vi.fn(); -vi.mock('@object-ui/providers', async (importOriginal) => ({ +vi.mock('@object-ui/data-objectstack', async (importOriginal) => ({ ...(await importOriginal>()), createObjectStackUploadAdapter: () => ({ upload: adapterUpload }), })); diff --git a/ui/packages/app-shell/src/views/record-attachments-renderer.tsx b/ui/packages/app-shell/src/views/record-attachments-renderer.tsx index 4a974d51b73..a038606189e 100644 --- a/ui/packages/app-shell/src/views/record-attachments-renderer.tsx +++ b/ui/packages/app-shell/src/views/record-attachments-renderer.tsx @@ -12,7 +12,7 @@ * `RecordChatterPanel` (plugin-detail/renderers/record-chatter.tsx). * * Registered here (app-shell) rather than in plugin-detail because the panel - * depends on `@object-ui/providers` (presigned upload adapter) and + * depends on `@object-ui/data-objectstack` (presigned upload adapter) and * `@object-ui/auth` (Bearer fetch), which plugin-detail deliberately does not * pull in. The side-effect registration is imported from the app-shell barrel * (`src/index.ts`), so any host that mounts the shell — the console runtime diff --git a/ui/packages/cli/src/__tests__/workspace-vite.test.ts b/ui/packages/cli/src/__tests__/workspace-vite.test.ts index db91346bd93..05fcf02e66b 100644 --- a/ui/packages/cli/src/__tests__/workspace-vite.test.ts +++ b/ui/packages/cli/src/__tests__/workspace-vite.test.ts @@ -143,18 +143,20 @@ describe('workspace alias table (objectui#3890)', () => { } }); - it('aliases the five the dependency scan could not reach past them', () => { + it('aliases the four the dependency scan could not reach past them', () => { // A Vite dependency scan reports only the first layer it fails on, so the // card's list of four was the visible half. These sat behind them in the // same import graph and would have stayed broken under a backfill. The // sixth, `@object-ui/sdui-parser`, was retired for `@objectstack/sdui-parser` // at the repo root (objectstack#25712 item 3), which resolves through its - // built `dist` like `@objectstack/spec` and is outside this table. + // built `dist` like `@objectstack/spec` and is outside this table. The + // fifth, `@object-ui/providers`, was retired into `@object-ui/fields` and + // `@object-ui/data-objectstack` (objectstack#25921), so no package is left + // for it to alias. for (const name of [ '@object-ui/data-objectstack', '@object-ui/i18n', '@object-ui/plugin-map', - '@object-ui/providers', '@object-ui/react-runtime' ]) { expect(aliases[name]).toBeDefined(); diff --git a/ui/packages/data-objectstack/src/index.ts b/ui/packages/data-objectstack/src/index.ts index 2098bca38af..e01ee112c92 100644 --- a/ui/packages/data-objectstack/src/index.ts +++ b/ui/packages/data-objectstack/src/index.ts @@ -7998,3 +7998,9 @@ export type { MetadataAuditEntry, MetadataAuditResponse, } from './metadata-client'; +// The browser-side upload adapter for an ObjectStack server's storage service: +// fetch logic, so it lives in this package. It implements `@object-ui/types`'s +// `UploadAdapter`, which `UploadProvider` from `@object-ui/fields` makes +// ambient for the file, image and avatar widgets. +export { createObjectStackUploadAdapter } from './upload-adapter'; +export type { ObjectStackUploadAdapterOptions } from './upload-adapter'; diff --git a/ui/packages/providers/src/__tests__/objectstack-upload-scope-12055.test.ts b/ui/packages/data-objectstack/src/objectstack-upload-scope-12055.test.ts similarity index 95% rename from ui/packages/providers/src/__tests__/objectstack-upload-scope-12055.test.ts rename to ui/packages/data-objectstack/src/objectstack-upload-scope-12055.test.ts index 0ca8947304e..8cb8601c03d 100644 --- a/ui/packages/providers/src/__tests__/objectstack-upload-scope-12055.test.ts +++ b/ui/packages/data-objectstack/src/objectstack-upload-scope-12055.test.ts @@ -11,7 +11,7 @@ * * The adapter used to send `scope: opts.scope ?? options.path`, so an adapter * built without a scope turned an upload's generic `path` (a key prefix for - * the S3/Azure presign callbacks) into the storage scope. The server takes + * adapters that key objects by path) into the storage scope. The server takes * only its own scope vocabulary and refuses any other value, so such an * upload failed at the presign step. * @@ -21,13 +21,14 @@ * The option's TYPE is the spec's upload-scope vocabulary, `UploadScope` from * `@objectstack/spec/api`, reached through `@object-ui/types`'s `API` * namespace. The compile-time pins at the end of this file are checked by - * `tsconfig.test.json`, which `type-check` chains after the source program. + * this package's `tsconfig.json`, whose program includes its tests + * (`type-check`). */ import { describe, it, expect } from 'vitest'; import type { API } from '@object-ui/types'; -import { createObjectStackUploadAdapter, type ObjectStackUploadAdapterOptions } from '../UploadProvider'; +import { createObjectStackUploadAdapter, type ObjectStackUploadAdapterOptions } from './upload-adapter'; const FILE_ID = 'f-12055'; @@ -113,7 +114,7 @@ describe('the in-repo upload shapes send the same request as before (objectui#12 }); /* -------------------------------------------------------------------------- */ -/* Compile-time pins — compiled by tsconfig.test.json, chained off type-check. */ +/* Compile-time pins — compiled by this package's tsconfig.json (type-check). */ /* -------------------------------------------------------------------------- */ type Assert = T; diff --git a/ui/packages/providers/src/UploadProvider.tsx b/ui/packages/data-objectstack/src/upload-adapter.ts similarity index 51% rename from ui/packages/providers/src/UploadProvider.tsx rename to ui/packages/data-objectstack/src/upload-adapter.ts index 67799d83319..4cb6f33668c 100644 --- a/ui/packages/providers/src/UploadProvider.tsx +++ b/ui/packages/data-objectstack/src/upload-adapter.ts @@ -1,192 +1,21 @@ /** - * UploadProvider — pluggable file upload context for ObjectUI. + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. * - * Why: `FileField` and `ImageField` historically used `URL.createObjectURL(file)` - * which only works for the lifetime of the browser tab and never persists the - * file anywhere. Production deployments need to push the bytes to S3, Azure - * Blob, or another object store and replace the temporary blob URL with a - * canonical one. - * - * Design: - * - `UploadProvider` exposes an `upload(file, opts)` function via context. - * - Apps inject an adapter (`createS3Adapter`, `createAzureBlobAdapter`, or any - * custom one implementing `UploadAdapter`). - * - The default adapter (`createObjectUrlAdapter`) mints a tab-local `blob:` - * URL and no `sys_file` id; it keeps the field widgets from crashing when no - * provider is mounted, and they refuse every pick through it (objectui#7699). - * - Upload progress and resumable retries are first-class concerns: each - * adapter implementation can chunk + resume; the public API surfaces - * `onProgress` and an `abortSignal`. - */ -import React, { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - type ReactNode, -} from 'react'; -import type { API } from '@object-ui/types'; - -export interface UploadResult { - /** Canonical, shareable URL (e.g. https://my-bucket.s3.amazonaws.com/avatars/abc.png). */ - url: string; - /** Stored object name / key. */ - name: string; - size: number; - mimeType: string; - /** Adapter-specific metadata (etag, version, etc.). */ - meta?: Record; -} - -export interface UploadOptions { - /** - * Logical folder / key prefix (e.g. "avatars/"), handed to the presign - * callback of `createS3Adapter` and `createAzureBlobAdapter`. - * `createObjectStackUploadAdapter` never sends it: that server derives the - * object key itself, and a path is not a storage scope (objectui#12055). - */ - path?: string; - /** Progress callback in 0–1. */ - onProgress?: (ratio: number) => void; - /** External abort signal — cancels the upload (and any retry attempts). */ - signal?: AbortSignal; - /** Number of retry attempts on network failure. @default 3 */ - maxRetries?: number; - /** Initial backoff in ms; exponential. @default 500 */ - retryDelayMs?: number; -} - -export interface UploadAdapter { - readonly name: string; - upload(file: File | Blob, opts?: UploadOptions): Promise; -} - -interface UploadContextValue { - upload: (file: File | Blob, opts?: UploadOptions) => Promise; - adapter: UploadAdapter; -} - -const UploadContext = createContext(null); - -/** - * Default adapter — wraps the file in an object URL. It persists nothing and - * returns no `meta.fileId`, so the `@object-ui/fields` file and image widgets - * refuse its result (`UploadIncompleteError`); it is a no-crash default, not - * an upload destination. - */ -export function createObjectUrlAdapter(): UploadAdapter { - return { - name: 'object-url', - async upload(file) { - const f = file as File; - const name = ('name' in f && f.name) || 'upload'; - return { - url: URL.createObjectURL(file), - name, - size: file.size, - mimeType: file.type || 'application/octet-stream', - }; - }, - }; -} - -export interface S3AdapterOptions { - /** - * URL of an endpoint that returns `{ uploadUrl, publicUrl, headers? }` - * for a given `{ name, type, size }`. Implementations typically presign an - * S3 PUT URL server-side. - */ - getPresignedUrl: (input: { name: string; type: string; size: number; path?: string }) => Promise<{ - uploadUrl: string; - publicUrl: string; - headers?: Record; - }>; - /** Override fetch (useful for tests and ServiceWorker offline queues). */ - fetchImpl?: typeof fetch; -} - -/** - * S3-style adapter — relies on the app to provide a presign endpoint so that - * AWS credentials never leak to the browser. Works with any S3-compatible - * object store (AWS S3, Cloudflare R2, MinIO, …). + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. */ -export function createS3Adapter(opts: S3AdapterOptions): UploadAdapter { - const fetchFn = opts.fetchImpl ?? fetch; - return { - name: 's3', - async upload(file, options = {}) { - const f = file as File; - const name = ('name' in f && f.name) || 'upload'; - const presigned = await opts.getPresignedUrl({ - name, - type: file.type || 'application/octet-stream', - size: file.size, - path: options.path, - }); - await uploadWithProgress(fetchFn, presigned.uploadUrl, file, { - method: 'PUT', - headers: presigned.headers, - ...options, - }); - return { - url: presigned.publicUrl, - name, - size: file.size, - mimeType: file.type || 'application/octet-stream', - }; - }, - }; -} - -export interface AzureBlobAdapterOptions { - /** - * URL of an endpoint that returns `{ uploadUrl, publicUrl }` — typically a - * short-lived SAS URL minted server-side. - */ - getSasUrl: (input: { name: string; type: string; size: number; path?: string }) => Promise<{ - uploadUrl: string; - publicUrl: string; - }>; - fetchImpl?: typeof fetch; -} /** - * Azure Blob Storage adapter using SAS tokens. Single PUT — for files larger - * than ~256 MiB, callers should issue chunked SAS URLs (block list + commit) - * via their backend. + * The browser-side upload adapter for `@objectstack/service-storage`. + * + * It implements the `UploadAdapter` contract `@object-ui/types` declares, and + * `UploadProvider` from `@object-ui/fields` makes it ambient for the file, + * image and avatar widgets. It lives in this package because it is fetch + * logic against an ObjectStack server, and every such fetch belongs to the + * data adapter. */ -export function createAzureBlobAdapter(opts: AzureBlobAdapterOptions): UploadAdapter { - const fetchFn = opts.fetchImpl ?? fetch; - return { - name: 'azure-blob', - async upload(file, options = {}) { - const f = file as File; - const name = ('name' in f && f.name) || 'upload'; - const sas = await opts.getSasUrl({ - name, - type: file.type || 'application/octet-stream', - size: file.size, - path: options.path, - }); - await uploadWithProgress(fetchFn, sas.uploadUrl, file, { - method: 'PUT', - headers: { - 'x-ms-blob-type': 'BlockBlob', - 'Content-Type': file.type || 'application/octet-stream', - }, - ...options, - }); - return { - url: sas.publicUrl, - name, - size: file.size, - mimeType: file.type || 'application/octet-stream', - }; - }, - }; -} +import type { API, UploadAdapter, UploadOptions } from '@object-ui/types'; export interface ObjectStackUploadAdapterOptions { /** @@ -271,8 +100,8 @@ export function createObjectStackUploadAdapter( mimeType, size: file.size, // The adapter's own scope, or none. `options.path` is never sent - // as the scope: it is a key prefix for the S3/Azure presign - // callbacks, and this server refuses any scope outside its + // as the scope: it is a key prefix for adapters that key objects + // by path, and this server refuses any scope outside its // vocabulary (objectui#12055). scope: opts.scope, }), @@ -411,40 +240,3 @@ function xhrUpload(url: string, body: Blob, options: UploadFetchOptions): Promis xhr.send(body); }); } - -export interface UploadProviderProps { - adapter?: UploadAdapter; - children: ReactNode; -} - -export const UploadProvider: React.FC = ({ adapter, children }) => { - const adapterRef = useRef(adapter ?? createObjectUrlAdapter()); - useEffect(() => { - if (adapter) adapterRef.current = adapter; - }, [adapter]); - - const upload = useCallback( - (file: File | Blob, opts?: UploadOptions) => adapterRef.current.upload(file, opts), - [], - ); - - const value = useMemo( - () => ({ upload, adapter: adapterRef.current }), - [upload], - ); - return {children}; -}; - -/** - * Hook returning the configured upload function. Falls back to a fresh - * object-URL adapter so widgets never crash when used outside a provider. - */ -export function useUpload(): UploadContextValue { - const ctx = useContext(UploadContext); - // Always call useMemo to honour the rules of hooks; cheap when ctx is set. - const fallback = useMemo(() => { - const a = createObjectUrlAdapter(); - return { upload: (f: File | Blob, o?: UploadOptions) => a.upload(f, o), adapter: a }; - }, []); - return ctx ?? fallback; -} diff --git a/ui/packages/fields/package.json b/ui/packages/fields/package.json index 6065cda8360..2b094ba6942 100644 --- a/ui/packages/fields/package.json +++ b/ui/packages/fields/package.json @@ -35,7 +35,6 @@ "@object-ui/i18n": "workspace:*", "@object-ui/mobile": "workspace:*", "@object-ui/permissions": "workspace:*", - "@object-ui/providers": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", "@objectstack/spec": "workspace:^", diff --git a/ui/packages/fields/src/__tests__/fileRestrictedState-12339.test.tsx b/ui/packages/fields/src/__tests__/fileRestrictedState-12339.test.tsx index 7c20d593f5a..0a3c66dfc20 100644 --- a/ui/packages/fields/src/__tests__/fileRestrictedState-12339.test.tsx +++ b/ui/packages/fields/src/__tests__/fileRestrictedState-12339.test.tsx @@ -36,7 +36,8 @@ import { render, cleanup, within, fireEvent, waitFor } from '@testing-library/re import '@testing-library/jest-dom'; import { I18nProvider } from '@object-ui/i18n'; import { en, zh } from '@object-ui/i18n/locales'; -import { UploadProvider, type UploadAdapter, type UploadResult } from '@object-ui/providers'; +import { UploadProvider } from '../widgets/UploadProvider'; +import type { UploadAdapter, UploadResult } from '@object-ui/types'; import { FileRefusedValueSchema } from '@objectstack/spec/data'; import { getCellRenderer, readFileValue } from '../index'; diff --git a/ui/packages/fields/src/index.tsx b/ui/packages/fields/src/index.tsx index a6e0b25b115..080b1985fea 100644 --- a/ui/packages/fields/src/index.tsx +++ b/ui/packages/fields/src/index.tsx @@ -5462,6 +5462,11 @@ export * from './widgets/RecordPickerDialog.js'; // lookup picker and a related-list Add dialog of the same object agree on // columns (#3365). export * from './widgets/deriveLookupColumns.js'; +// The ambient upload adapter the file, image and avatar widgets upload through. +// The adapter contract is `@object-ui/types`'s, re-exported here so a host wires +// `UploadProvider` and types its own adapter from one import. +export { UploadProvider, useUpload, type UploadProviderProps } from './widgets/UploadProvider.js'; +export type { UploadAdapter, UploadOptions, UploadResult } from '@object-ui/types'; export * from './widgets/FileField.js'; export * from './widgets/ImageField.js'; export { ImageCropperDialog } from './widgets/ImageCropperDialog.js'; diff --git a/ui/packages/fields/src/widgets/AvatarField.fileId-10785.test.tsx b/ui/packages/fields/src/widgets/AvatarField.fileId-10785.test.tsx index 743179c499f..3c42d9548b7 100644 --- a/ui/packages/fields/src/widgets/AvatarField.fileId-10785.test.tsx +++ b/ui/packages/fields/src/widgets/AvatarField.fileId-10785.test.tsx @@ -41,7 +41,8 @@ */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react'; -import { UploadProvider, type UploadAdapter, type UploadResult } from '@object-ui/providers'; +import { UploadProvider } from './UploadProvider'; +import type { UploadAdapter, UploadResult } from '@object-ui/types'; import type { FieldMetadata } from '@object-ui/types'; import { AvatarField } from './AvatarField'; import { FileField } from './FileField'; diff --git a/ui/packages/fields/src/widgets/AvatarField.tsx b/ui/packages/fields/src/widgets/AvatarField.tsx index 464533d124c..9b202ddb072 100644 --- a/ui/packages/fields/src/widgets/AvatarField.tsx +++ b/ui/packages/fields/src/widgets/AvatarField.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Avatar, AvatarFallback, AvatarImage, Button } from '@object-ui/components'; -import { useUpload } from '@object-ui/providers'; +import { useUpload } from './UploadProvider.js'; import { useObjectTranslation } from '@object-ui/i18n'; import { Upload, X, Loader2, Lock } from 'lucide-react'; import { FieldWidgetComponentProps } from './types.js'; diff --git a/ui/packages/fields/src/widgets/FileField.tsx b/ui/packages/fields/src/widgets/FileField.tsx index b3db284bbea..e31a5e79569 100644 --- a/ui/packages/fields/src/widgets/FileField.tsx +++ b/ui/packages/fields/src/widgets/FileField.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState, useCallback } from 'react'; import { Button, EmptyValue, cn } from '@object-ui/components'; -import { useUpload } from '@object-ui/providers'; +import { useUpload } from './UploadProvider.js'; import { useObjectTranslation } from '@object-ui/i18n'; import { Upload, X, File as FileIcon, ImageIcon, Images, Camera, Loader2, Lock } from 'lucide-react'; import { FieldWidgetComponentProps } from './types.js'; diff --git a/ui/packages/fields/src/widgets/GridField.test.tsx b/ui/packages/fields/src/widgets/GridField.test.tsx index 1af72ae2c28..c84b0ad3348 100644 --- a/ui/packages/fields/src/widgets/GridField.test.tsx +++ b/ui/packages/fields/src/widgets/GridField.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; -import { UploadProvider } from '@object-ui/providers'; +import { UploadProvider } from './UploadProvider'; import { formatDate, formatDateTime } from '@object-ui/core'; import { GridField, LineItemsField, sumColumn, lookupAutofillPatch } from './GridField'; diff --git a/ui/packages/fields/src/widgets/ImageField.imageAlt-10637.test.tsx b/ui/packages/fields/src/widgets/ImageField.imageAlt-10637.test.tsx index 7bd54d92b59..e027539ebec 100644 --- a/ui/packages/fields/src/widgets/ImageField.imageAlt-10637.test.tsx +++ b/ui/packages/fields/src/widgets/ImageField.imageAlt-10637.test.tsx @@ -35,7 +35,8 @@ import { render, cleanup, fireEvent, screen, waitFor } from '@testing-library/re import '@testing-library/jest-dom'; import { I18nProvider } from '@object-ui/i18n'; import { en, zh } from '@object-ui/i18n/locales'; -import { UploadProvider, type UploadAdapter } from '@object-ui/providers'; +import { UploadProvider } from './UploadProvider'; +import type { UploadAdapter } from '@object-ui/types'; import { ImageField } from './ImageField'; import type { FieldWidgetComponentProps } from './types'; diff --git a/ui/packages/fields/src/widgets/ImageField.maxSize.test.tsx b/ui/packages/fields/src/widgets/ImageField.maxSize.test.tsx index 34694b4bd96..a1f59b9556d 100644 --- a/ui/packages/fields/src/widgets/ImageField.maxSize.test.tsx +++ b/ui/packages/fields/src/widgets/ImageField.maxSize.test.tsx @@ -22,7 +22,8 @@ */ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { UploadProvider, type UploadAdapter } from '@object-ui/providers'; +import { UploadProvider } from './UploadProvider'; +import type { UploadAdapter } from '@object-ui/types'; import { ImageField } from './ImageField'; // Imported at module scope (not awaited in a hook) so the widget's // `React.lazy(() => import('./ImageCropperDialog'))` resolves from the ESM diff --git a/ui/packages/fields/src/widgets/ImageField.tsx b/ui/packages/fields/src/widgets/ImageField.tsx index 7b0b1123ecc..043fa421ba4 100644 --- a/ui/packages/fields/src/widgets/ImageField.tsx +++ b/ui/packages/fields/src/widgets/ImageField.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState, useCallback, lazy, Suspense } from 'react'; import { Button, EmptyValue } from '@object-ui/components'; -import { useUpload } from '@object-ui/providers'; +import { useUpload } from './UploadProvider.js'; import { useObjectTranslation } from '@object-ui/i18n'; import { X, Image as ImageIcon, Images, Camera, Crop as CropIcon, Loader2 } from 'lucide-react'; import { FieldWidgetComponentProps } from './types.js'; diff --git a/ui/packages/fields/src/widgets/ImageField.uploadFailure.test.tsx b/ui/packages/fields/src/widgets/ImageField.uploadFailure.test.tsx index 9fd34f1af2d..770776dba6f 100644 --- a/ui/packages/fields/src/widgets/ImageField.uploadFailure.test.tsx +++ b/ui/packages/fields/src/widgets/ImageField.uploadFailure.test.tsx @@ -17,7 +17,8 @@ */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { UploadProvider, type UploadAdapter } from '@object-ui/providers'; +import { UploadProvider } from './UploadProvider'; +import type { UploadAdapter } from '@object-ui/types'; import { ImageField } from './ImageField'; // Imported at module scope (not awaited in a hook) so the widget's // `React.lazy(() => import('./ImageCropperDialog'))` resolves from the ESM diff --git a/ui/packages/fields/src/widgets/UploadProvider.tsx b/ui/packages/fields/src/widgets/UploadProvider.tsx new file mode 100644 index 00000000000..f68380d043b --- /dev/null +++ b/ui/packages/fields/src/widgets/UploadProvider.tsx @@ -0,0 +1,95 @@ +/** + * UploadProvider — pluggable file upload context for the file, image and + * avatar widgets of this package. + * + * Why: the widgets need to push picked bytes to a store and replace the + * temporary blob URL with a canonical one; `URL.createObjectURL(file)` alone + * lives only as long as the browser tab and persists nothing. + * + * Design: + * - `UploadProvider` exposes an `upload(file, opts)` function via context. + * - Apps inject an `UploadAdapter` (the contract `@object-ui/types` declares): + * `createObjectStackUploadAdapter` from `@object-ui/data-objectstack`, or any + * object with a `name` and an `upload(file, opts)` method. + * - The default adapter (`createObjectUrlAdapter`) mints a tab-local `blob:` + * URL and no `sys_file` id; it keeps the widgets from crashing when no + * provider is mounted, and they refuse every pick through it (objectui#7699). + * - Upload progress and cancellation are part of the contract: an adapter + * receives `onProgress` and an abort `signal` in `UploadOptions`. + */ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + type ReactNode, +} from 'react'; +import type { UploadAdapter, UploadOptions, UploadResult } from '@object-ui/types'; + +interface UploadContextValue { + upload: (file: File | Blob, opts?: UploadOptions) => Promise; + adapter: UploadAdapter; +} + +const UploadContext = createContext(null); + +/** + * Default adapter — wraps the file in an object URL. It persists nothing and + * returns no `meta.fileId`, so the file and image widgets refuse its result + * (`UploadIncompleteError`); it is a no-crash default, not an upload + * destination, so it is not exported. + */ +function createObjectUrlAdapter(): UploadAdapter { + return { + name: 'object-url', + async upload(file) { + const f = file as File; + const name = ('name' in f && f.name) || 'upload'; + return { + url: URL.createObjectURL(file), + name, + size: file.size, + mimeType: file.type || 'application/octet-stream', + }; + }, + }; +} + +export interface UploadProviderProps { + adapter?: UploadAdapter; + children: ReactNode; +} + +export const UploadProvider: React.FC = ({ adapter, children }) => { + const adapterRef = useRef(adapter ?? createObjectUrlAdapter()); + useEffect(() => { + if (adapter) adapterRef.current = adapter; + }, [adapter]); + + const upload = useCallback( + (file: File | Blob, opts?: UploadOptions) => adapterRef.current.upload(file, opts), + [], + ); + + const value = useMemo( + () => ({ upload, adapter: adapterRef.current }), + [upload], + ); + return {children}; +}; + +/** + * Hook returning the configured upload function. Falls back to a fresh + * object-URL adapter so widgets never crash when used outside a provider. + */ +export function useUpload(): UploadContextValue { + const ctx = useContext(UploadContext); + // Always call useMemo to honour the rules of hooks; cheap when ctx is set. + const fallback = useMemo(() => { + const a = createObjectUrlAdapter(); + return { upload: (f: File | Blob, o?: UploadOptions) => a.upload(f, o), adapter: a }; + }, []); + return ctx ?? fallback; +} diff --git a/ui/packages/fields/src/widgets/fileSubmitRequiresId-7699.test.tsx b/ui/packages/fields/src/widgets/fileSubmitRequiresId-7699.test.tsx index 9b22b3ffe5c..0c201c8ff80 100644 --- a/ui/packages/fields/src/widgets/fileSubmitRequiresId-7699.test.tsx +++ b/ui/packages/fields/src/widgets/fileSubmitRequiresId-7699.test.tsx @@ -44,7 +44,8 @@ */ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react'; -import { UploadProvider, type UploadAdapter, type UploadResult } from '@object-ui/providers'; +import { UploadProvider } from './UploadProvider'; +import type { UploadAdapter, UploadResult } from '@object-ui/types'; import { FileField, FileCell } from './FileField'; import { ImageField } from './ImageField'; // Imported at module scope (not awaited in a hook) so the widget's diff --git a/ui/packages/fields/src/widgets/nativeImagePick.shellPhoto.test.tsx b/ui/packages/fields/src/widgets/nativeImagePick.shellPhoto.test.tsx index a354f02be95..b35beaabbe7 100644 --- a/ui/packages/fields/src/widgets/nativeImagePick.shellPhoto.test.tsx +++ b/ui/packages/fields/src/widgets/nativeImagePick.shellPhoto.test.tsx @@ -25,7 +25,8 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, act, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { toast } from '@object-ui/components'; -import { UploadProvider, type UploadAdapter } from '@object-ui/providers'; +import { UploadProvider } from './UploadProvider'; +import type { UploadAdapter } from '@object-ui/types'; import { FileField } from './FileField'; import { ImageField } from './ImageField'; import './ImageCropperDialog'; diff --git a/ui/packages/fields/src/widgets/uploadingScope.hold.test.tsx b/ui/packages/fields/src/widgets/uploadingScope.hold.test.tsx index 419d4e67f78..12d8d450228 100644 --- a/ui/packages/fields/src/widgets/uploadingScope.hold.test.tsx +++ b/ui/packages/fields/src/widgets/uploadingScope.hold.test.tsx @@ -17,7 +17,7 @@ import { describe, it, expect, vi } from 'vitest'; import React from 'react'; import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; -import { UploadProvider } from '@object-ui/providers'; +import { UploadProvider } from './UploadProvider'; import { useUploadingScope, UploadingScopeProvider } from './uploadingScope.js'; import { FileField } from './FileField.js'; import { ImageField } from './ImageField.js'; diff --git a/ui/packages/plugin-editor/vite.config.ts b/ui/packages/plugin-editor/vite.config.ts index 4005fd1ae3d..dba8cddabcb 100644 --- a/ui/packages/plugin-editor/vite.config.ts +++ b/ui/packages/plugin-editor/vite.config.ts @@ -70,7 +70,6 @@ export default defineConfig({ '@object-ui/types': resolve(__dirname, '../types/src'), '@object-ui/react': resolve(__dirname, '../react/src'), '@object-ui/components': resolve(__dirname, '../components/src'), - '@object-ui/providers': resolve(__dirname, '../providers/src'), '@object-ui/fields': resolve(__dirname, '../fields/src'), '@object-ui/plugin-dashboard': resolve(__dirname, '../plugin-dashboard/src'), '@object-ui/plugin-grid': resolve(__dirname, '../plugin-grid/src'), diff --git a/ui/packages/plugin-form/src/WizardForm.nextMidUpload.test.tsx b/ui/packages/plugin-form/src/WizardForm.nextMidUpload.test.tsx index c630b06e644..0780ed8bf1e 100644 --- a/ui/packages/plugin-form/src/WizardForm.nextMidUpload.test.tsx +++ b/ui/packages/plugin-form/src/WizardForm.nextMidUpload.test.tsx @@ -32,13 +32,11 @@ * * ## What is real and what is faked * - * Only the upload TRANSPORT is faked — `useUpload` answers an upload whose - * promise this test settles — because that is what makes the mid-upload window - * deterministic. It is swapped with `vi.mock` rather than mounted as an - * `UploadProvider` because `@object-ui/providers` is not a dependency of this - * package: the widgets reach it through `@object-ui/fields`, and importing it - * here would be an undeclared import. The rest of that module is the real one. - * The widgets are the shipping `FileField` / `ImageField`, driven through their + * Only the upload TRANSPORT is faked — the ambient adapter answers an upload + * whose promise this test settles — because that is what makes the mid-upload + * window deterministic. It is mounted through the real `UploadProvider`, which + * `@object-ui/fields` (a dependency of this package) exports beside the widgets + * that read it. The widgets are the shipping `FileField` / `ImageField`, driven through their * real file input; the form renderer, react-hook-form and the wizard are real. * Both widgets are registered directly rather than through their lazy entries * so no module load races the assertions, and the lazy entries are put back @@ -48,23 +46,24 @@ import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import React from 'react'; import { ComponentRegistry } from '@object-ui/core'; -import { registerAllFields, FileField, ImageField } from '@object-ui/fields'; +import { + registerAllFields, + FileField, + ImageField, + UploadProvider, + type UploadAdapter, +} from '@object-ui/fields'; import { WizardForm } from './WizardForm'; /** The upload the current row installs; see `deferredUpload`. */ -const transport = vi.hoisted(() => ({ - upload: (_f: File | Blob): Promise => Promise.reject(new Error('no upload installed')), -})); -vi.mock('@object-ui/providers', async (importOriginal) => { - const actual = await importOriginal>(); - return { - ...actual, - useUpload: () => ({ - upload: (f: File | Blob) => transport.upload(f), - adapter: { name: 'deferred', upload: (f: File | Blob) => transport.upload(f) }, - }), - }; -}); +const transport: { upload: UploadAdapter['upload'] } = { + upload: () => Promise.reject(new Error('no upload installed')), +}; +/** The ambient adapter: it hands every pick to the current row's `transport`. */ +const deferredAdapter: UploadAdapter = { + name: 'deferred', + upload: (f, opts) => transport.upload(f, opts), +}; registerAllFields(); @@ -149,19 +148,21 @@ describe('WizardForm — Next pressed while an upload is in flight (objectui#101 const { created, ds } = makeDataSource(); const { upload, settle } = deferredUpload(); render( - , + + + , ); // ── Step 1: a value typed, a file picked through the widget's real input, diff --git a/ui/packages/plugin-markdown/vite.config.ts b/ui/packages/plugin-markdown/vite.config.ts index a3e9adba3e3..bcb25276b35 100644 --- a/ui/packages/plugin-markdown/vite.config.ts +++ b/ui/packages/plugin-markdown/vite.config.ts @@ -70,7 +70,6 @@ export default defineConfig({ '@object-ui/types': resolve(__dirname, '../types/src'), '@object-ui/react': resolve(__dirname, '../react/src'), '@object-ui/components': resolve(__dirname, '../components/src'), - '@object-ui/providers': resolve(__dirname, '../providers/src'), '@object-ui/fields': resolve(__dirname, '../fields/src'), '@object-ui/plugin-dashboard': resolve(__dirname, '../plugin-dashboard/src'), '@object-ui/plugin-grid': resolve(__dirname, '../plugin-grid/src'), diff --git a/ui/packages/providers/CHANGELOG.md b/ui/packages/providers/CHANGELOG.md deleted file mode 100644 index 584241a06c1..00000000000 --- a/ui/packages/providers/CHANGELOG.md +++ /dev/null @@ -1,1524 +0,0 @@ -# @object-ui/providers — Changelog - -## 18.3.0 - -### Patch Changes - -- 4daad78: docs: the README's Contributing Guide and Roadmap links point at the ObjectStack repository (objectstack#26080) - - Every package README linked its "Contributing Guide" and "Roadmap" to `CONTRIBUTING.md` and `ROADMAP.md` in the archived `objectstack-ai/objectui` repository. Those documents now live at the root of `objectstack-ai/objectstack`, which is where the links point; the `@object-ui/console` README's roadmap links move there too. No code, export or behaviour changes. - - Clause-②: no - Metadata-protocol: none -- 7587fd4: docs(ui): package homepages and README links point at objectstack.ai/docs; objectui.org is retired - - Clause-②: no - Metadata-protocol: none - - The ObjectUI documentation site at objectui.org is retired. Its pages now live in the ObjectStack docs, mostly under [User Interface](https://objectstack.ai/docs/frontend). Every `@object-ui/*` package's `homepage` and README links now go to the matching page there, and the old objectui.org addresses answer with a permanent redirect to the same page. The `description` fields spell the product name "ObjectUI". The `@objectstack/skills` bundle's ObjectUI skill links to the same pages. Nothing else changes in what these packages ship. -- Updated dependencies [f486e9c] -- Updated dependencies [3eeb81d] -- Updated dependencies [c59b1bf] -- Updated dependencies [8a12a17] -- Updated dependencies [d8a9cc5] -- Updated dependencies [b53d9f3] -- Updated dependencies [a260556] -- Updated dependencies [8eeb3b3] -- Updated dependencies [607d3bb] -- Updated dependencies [4daad78] -- Updated dependencies [6866e2e] -- Updated dependencies [eab4bba] -- Updated dependencies [a122269] -- Updated dependencies [17b5152] -- Updated dependencies [fbe344b] -- Updated dependencies [6dbff8e] -- Updated dependencies [b5e4ce6] -- Updated dependencies [7587fd4] -- Updated dependencies [2655ebd] -- Updated dependencies [a33fce2] -- Updated dependencies [9c51314] -- Updated dependencies [10755db] -- Updated dependencies [ad2c286] -- Updated dependencies [9462d8a] -- Updated dependencies [9ab75ee] -- Updated dependencies [0ac6dab] -- Updated dependencies [72b08ee] -- Updated dependencies [bf658d1] -- Updated dependencies [0027c4a] -- Updated dependencies [2e20454] -- Updated dependencies [71de060] -- Updated dependencies [bed8306] -- Updated dependencies [ea1751b] -- Updated dependencies [bede9de] -- Updated dependencies [044d201] -- Updated dependencies [82b29fe] -- Updated dependencies [e7a6cc7] -- Updated dependencies [462b4f9] -- Updated dependencies [53dda03] -- Updated dependencies [c268b72] -- Updated dependencies [5651647] - - @object-ui/types@18.3.0 - -## 18.2.0 - -### Minor Changes - -- 9a0af4e: **BREAKING** Object UI's changes from its 18.1.0 release to the day it moved into this repository, released here as one entry. They are objectui commits `a3c0da9`..`9e1fe03`, the release commit to the freeze. Their 65 changesets came across with the code under `ui/.changeset/`, which `changeset version` does not read, so this entry carries them into the release. Each of those 65 files stays in `ui/.changeset/` unchanged, as the full record of its change. Five of the changes are breaking retirements; each has a paragraph below and points at the file that holds its migration table. - - - - ### The raw-HTML `html` node type is retired (objectui#13449, PR #13475) - - `@object-ui/components` no longer registers `html` or `ui:html`, `@object-ui/core` drops it from the public blocks, and `@object-ui/types` no longer exports `HtmlSchema`. Its renderer wrote the authored string into the page with no sanitiser. A page that still names the node draws the unknown-component panel, and `objectui validate` refuses `type: "html"` by name. The `html` field type is a different layer and stays. Migration: `ui/.changeset/13449-retire-html-renderer.md`. - - ### The `module` widget source is retired (objectui#13450, PR #13473) - - `WidgetRegistry` no longer loads a widget's code from a URL at runtime, and `@object-ui/types` no longer exports `WidgetSourceModule`. That loader was a runtime dynamic import and needed `unsafe-eval` under a strict content security policy. A manifest that still declares the `module` source is refused before anything is fetched, with a message that names both replacement sources. Migration: `ui/.changeset/13450-retire-widget-module-source.md`. - - ### Nine zero-consumer modules are retired and the runner is private (objectui#13453 part 1, PR #13506) - - `@object-ui/core` drops the fluent schema builders and the drag-and-drop, keyboard and sharing protocol bridges. `@object-ui/react` drops `useFocusTrap`, `useFocusReturn`, `useRovingFocus`, `usePageTransition`, `useSchemaPersistence` and `createLocalStorageAdapter`. `@object-ui/collaboration` drops `LiveCursors`. The test-only reset helpers are no longer on the package roots of `@object-ui/core`, `@object-ui/app-shell`, `@object-ui/types` and `@object-ui/plugin-detail`. Nothing in ObjectUI read any of them. `@object-ui/runner` is now private. Details: `ui/.changeset/13453-core-retire-dead-modules.md`, `13453-react-retire-dead-hooks.md`, `13453-collaboration-retire-live-cursors.md` and `13453-test-helpers-off-barrels.md`. - - ### The providers trio is retired (objectui#13453 part 2, PR #13507) - - `@object-ui/providers` no longer exports `DataSourceProvider` / `useDataSource`, `MetadataProvider` / `useMetadata` or `ThemeProvider` / `useTheme`. They duplicated names that `@object-ui/react` and `@object-ui/app-shell` publish with different implementations. `UploadProvider` and the upload adapters are unchanged. Migration: `ui/.changeset/13453-providers-retire-trio.md`. - - ### Nine duration keys carry their unit in their name (objectstack#25762, PR #13515) - - Nine keys on `carousel`, the three chatbot nodes, `ActionSchema`, `ActionSchema.retry`, `toast`, `tooltip`, `hover-card` and `report` now end in `Ms` or `Seconds`. The value and its unit do not change. An old key is refused at its own path with a message that names the new key, and its TypeScript member is `never`. Migration table: `ui/.changeset/25762-duration-keys-carry-unit.md`. - - ### Also in this range - - - Every package's `react` / `react-dom` peer range is `^19.0.0` (was `^18.0.0 || ^19.0.0`): a React 18 host is no longer supported (objectui#13465, PR #13481). - - The `react-pages` capability (`kind: 'react'` pages) now defaults to off in `@object-ui/core`; a host turns it on. The Console sets zod's `jitless` before any module runs, so zod no longer probes for `eval` (PR #13526). - - An invalid `gantt` block is refused with each schema issue named, instead of drawn (objectui#13459, PR #13490). An action's `confirmText` or `successMessage` with no template hole is shown exactly as written (objectui#13476, PR #13482). - - A record section whose fields are all empty folds into one "Show N empty fields" row (objectui#13386, PR #13483). The list, gallery, map and timeline record preview mounts `RecordDetailPanel` (PR #13486). - - Bulk actions keep each failed record's id and reason, and refresh the bound list once per run (objectui#13442, PR #13474). The AI build chat's plan card says how the build will land (objectui#12724, PR #13508). Assistant answers render text and charts in stream order (objectui#13492, PR #13500). - - Inline locale-map labels and descriptions on reports and pages show in the viewer's language (objectui#13470, #13488; PRs #13487, #13501). Positions show by label in a permission set's assigned users (objectui#13477, PR #13493). The translation preview draws the `positions` and `permissions` groups (objectui#13478, PR #13485). Calendar events with a lookup `titleField` show the record's display name (PR #13480). Unlabeled datasource enum selects get readable option labels (objectui#13353, PR #13499). - - More console pages are translated (objectui#13460, PRs #13489, #13503). Icon-only buttons have accessible names and clickable controls are keyboard-reachable (objectui#13461, PR #13494). - - Every published package declares `sideEffects` (objectui#13464, PR #13495). One `react` 19.3.0 line and one `zod` `^4.6.5` range across the packages, and single-copy pins for `@ai-sdk/react`, `ai` and `better-auth` (objectstack#25685, #25727; PRs #13472, #13496, #13512). - - Tooling and test-only changes for the merge, which publish nothing: workspace-root readers and test assumptions (objectstack#25723, #25733; PRs #13484, #13498) and warm test imports (objectstack#25763, PR #13513). - - Clause-②: yes (narrowing) - Metadata-protocol: narrowed - -### Patch Changes - -- 6ea429d: chore(ui): every ui package whose `test` script calls `vitest` now declares `vitest` as a devDependency, so `pnpm --filter @object-ui/PKG test` runs from the repository root - - Clause-②: no - Metadata-protocol: none - - The package `test` scripts run `vitest run --root ../.. packages/PKG/`. A pnpm script resolves its binaries from the package's own `node_modules/.bin` and the workspace root's, and vitest was installed only under `ui/node_modules/.bin`, so 24 of the 41 ui packages with a `test` script failed with `vitest: not found` from both the repository root and `ui/`. Each of them now declares `vitest: ^4.1.10`, the range `ui/package.json` and the other 17 packages already declare; the lockfile resolves every new entry to the snapshot those packages already use, so no package gains a copy. Nothing a consumer installs changes: only the `devDependencies` field of the published `package.json` moves. -- Updated dependencies [dc71845] -- Updated dependencies [9a0af4e] -- Updated dependencies [1bbb7bf] -- Updated dependencies [e843b5b] -- Updated dependencies [0b76a1f] -- Updated dependencies [6ea429d] - - @object-ui/types@18.2.0 - -## 18.1.0 - -### Patch Changes - -- Updated dependencies [8556295] -- Updated dependencies [cd14d8e] -- Updated dependencies [f1a4b08] -- Updated dependencies [1d178e0] -- Updated dependencies [6fb31dc] - - @object-ui/types@18.1.0 - -## 18.0.0 - -### Major Changes - -- 6f3299f: objectui moves to major 18, following `@objectstack/*` from 17 to 18 (objectui#12334). objectui's major is not a count of its own breaking changes: it tracks the major of the `@objectstack/spec` its packages depend on, so that the same major means compatible across the two projects. These packages already declare `@objectstack/*` on the 18 line (objectui#12030), so every package in the fixed release group crosses to 18 together. It is the one `major` that rule admits: objectui's own breaking changes ship as `minor`, described in their entries. - - The 18 line is released from Changesets pre mode on the `next` tag (`.changeset/pre.json`), as `@objectstack/*` 18 is: these releases are `18.0.0-next.N` on the npm `next` dist-tag, and they do not move `latest`. The first `18.0.0` on `latest` comes when `@objectstack` 18 is generally available and this repository leaves pre mode. - - What a consumer must do to take it is in the `@objectstack/*` 18 entry of this release (objectui#12030): move its own `@objectstack/*` packages to the 18 line at the same time. - -### Minor Changes - -- 7282c6a: `createObjectStackUploadAdapter` no longer sends an upload's `path` as the storage scope (objectui#12055). - - The adapter used to send `scope: opts.scope ?? options.path` in its presigned upload request, so an adapter built without a `scope` turned the `path` a caller passed to `upload(file, { path })` into the scope. A path is a key prefix for the S3 and Azure presign callbacks, not a scope: the ObjectStack storage service accepts only the values of the `scope` select on its `sys_file` object and refuses any other, so such an upload failed at the presign step. The adapter now sends only the `scope` it was built with, or none, and the server applies its default. `UploadResult.meta.scope` reports the same value. - - The `scope` option's documentation no longer teaches free key prefixes ("avatars", "logos", "attachments/case"). It names the server's `sys_file` scope select as the vocabulary. - - BREAKING (`@object-ui/providers`), for a caller that relied on `path` to choose the scope of an adapter built without one: the upload is now filed under the server's default scope. Remedy: pass one of the server's scopes as the adapter's `scope`, or none to take the server's default. (The bump is `minor` by this repo's release model: objectui's major follows the `@objectstack` family major, and its own breaking changes ship as `minor` with the breaking semantics stated here.) - - **Clause-②: no (narrowing)**: no export, type member or option is added, and no type changes. The request the adapter sends narrows: a `path` no longer reaches it. -- 24804dc: `createObjectStackUploadAdapter`'s `scope` option is typed from the spec's upload-scope vocabulary (objectui#12055). - - `ObjectStackUploadAdapterOptions.scope` was an open `string`, so a caller could build an adapter with a scope the ObjectStack storage service refuses with `400`. It is now `UploadScope` from `@objectstack/spec/api`, the one list the spec's upload request, the `scope` select on `sys_file` and the upload doors share. `@object-ui/providers` reaches it through the `API` namespace `@object-ui/types` already re-exports, so no dependency and no export is added. Omitting `scope` still sends none, and the server applies the default the spec's upload request declares. - - BREAKING (`@object-ui/providers`), TypeScript only, for a caller that builds the adapter with a scope outside the vocabulary (for example `'avatars'` or `'attachments/case'`): it no longer compiles. The server already refused such a request at runtime. Remedy: pass one of the vocabulary's values, or omit `scope` to take the server's default. (The bump is `minor` by this repo's release model: objectui's major follows the `@objectstack` family major, and its own breaking changes ship as `minor` with the breaking semantics stated here.) - - **Clause-②: no (narrowing)**: the option's accepted values narrow from any string to the spec's upload scopes. No export, option or type member is added; the declaration file gains a type-only import of `API` from `@object-ui/types`, a package `@object-ui/providers` already depends on. -- 12ff256: The props of this package's `ThemeProvider` are declared and exported as `ThemePreferenceProviderProps` instead of `ThemeProviderProps` (objectui#6349, batch 10). `@object-ui/react` publishes a different `ThemeProviderProps`: the props of its theme-system `ThemeProvider`, which registers whole `Theme` documents, takes a theme name as `defaultTheme` and the mode as `defaultMode`, and writes the active theme as CSS variables. This package's provider stores one `ThemePreference` (`auto | light | dark | system`) and takes that mode as `defaultTheme`. One exported name stood for two props types across the two packages, and react keeps it. - - **Type change, breaking for some consumers.** `ThemeProviderProps` is no longer exported from `@object-ui/providers`. Replace `import type { ThemeProviderProps } from '@object-ui/providers'` with `ThemePreferenceProviderProps`; the members (`defaultTheme`, `storageKey`, `children`) are unchanged. The old import fails to compile with TS2724 ("has no exported member named 'ThemeProviderProps'"). That error's "Did you mean" names the `ThemeProvider` component, not the replacement; using the component's name as a type fails too (TS2749), so write `ThemePreferenceProviderProps`. The `ThemeProvider` component and `useTheme` keep their names. - - No runtime behaviour changes. - -### Patch Changes - -- Updated dependencies [18d7b48] -- Updated dependencies [172acc3] -- Updated dependencies [f0496bd] -- Updated dependencies [c4c506b] -- Updated dependencies [9db9ff3] -- Updated dependencies [b10c68e] -- Updated dependencies [bdc9049] -- Updated dependencies [2e818d0] -- Updated dependencies [8b14aec] -- Updated dependencies [2abec3a] -- Updated dependencies [9dfaca6] -- Updated dependencies [96fcf30] -- Updated dependencies [89cc738] -- Updated dependencies [c0862c1] -- Updated dependencies [c0862c1] -- Updated dependencies [c0862c1] -- Updated dependencies [4c0de52] -- Updated dependencies [a80fef7] -- Updated dependencies [f34e877] -- Updated dependencies [aaba865] -- Updated dependencies [45d5853] -- Updated dependencies [2312bd9] -- Updated dependencies [c5406c2] -- Updated dependencies [2a48bd4] -- Updated dependencies [c7b30bd] -- Updated dependencies [69bca0a] -- Updated dependencies [c0c2f79] -- Updated dependencies [0665c3b] -- Updated dependencies [d2500d8] -- Updated dependencies [b231607] -- Updated dependencies [466a219] -- Updated dependencies [6fb281d] -- Updated dependencies [c08c933] -- Updated dependencies [c4dd707] -- Updated dependencies [923504f] -- Updated dependencies [10aba37] -- Updated dependencies [5d3f437] -- Updated dependencies [7af0ab3] -- Updated dependencies [fd4872f] -- Updated dependencies [f0532d8] -- Updated dependencies [3092aca] -- Updated dependencies [82e5f53] -- Updated dependencies [d81e460] -- Updated dependencies [8106db1] -- Updated dependencies [093d55b] -- Updated dependencies [a8051d6] -- Updated dependencies [524136e] -- Updated dependencies [6f3299f] -- Updated dependencies [f2b17c6] -- Updated dependencies [52d6194] -- Updated dependencies [23bf859] -- Updated dependencies [1c72d77] -- Updated dependencies [2b4d3f1] -- Updated dependencies [d75f212] -- Updated dependencies [ff9df88] -- Updated dependencies [a2225f9] -- Updated dependencies [21f9577] -- Updated dependencies [3eb3ec1] -- Updated dependencies [619e3f7] -- Updated dependencies [f62b6ff] -- Updated dependencies [d66d7e8] -- Updated dependencies [15d8ca6] -- Updated dependencies [378896d] -- Updated dependencies [4839f5c] -- Updated dependencies [c999d4c] -- Updated dependencies [26323c4] -- Updated dependencies [c219165] -- Updated dependencies [30083da] -- Updated dependencies [8aa8e3e] -- Updated dependencies [a928aaa] -- Updated dependencies [1b94d50] -- Updated dependencies [fb85637] -- Updated dependencies [df71568] -- Updated dependencies [cbac9d7] -- Updated dependencies [daeb77f] -- Updated dependencies [eb4552e] -- Updated dependencies [55e90fd] -- Updated dependencies [9b75509] -- Updated dependencies [b13ea3c] -- Updated dependencies [d7e9e9a] -- Updated dependencies [cf62edf] -- Updated dependencies [0253416] -- Updated dependencies [5d77c09] -- Updated dependencies [3fd8625] -- Updated dependencies [1473757] -- Updated dependencies [d73d987] -- Updated dependencies [12ff256] -- Updated dependencies [26b0bd2] -- Updated dependencies [d328698] -- Updated dependencies [b4e0787] -- Updated dependencies [088e0f7] -- Updated dependencies [b403bb3] - - @object-ui/types@18.0.0 - -## 17.7.0 - -### Minor Changes - -- c9327c9: Localize the theme document types: `@object-ui/types` now owns `Theme`, `ThemeMode` and `ColorPalette` (objectui#5716 ruling, 2026-08-23). The spec retired its theme module (objectstack `35ad101bc`) while ObjectUI retained the theme system, so the types are hand-written from the last-published `@objectstack/spec` 17.1.0 shapes instead of re-exported — a spec dependency refresh past the retirement no longer breaks these packages. - - Published-name REMOVALS from `@object-ui/types` (zero in-repo readers, deleted under the same ruling's rider): - - - `Typography` — the shape lives on as the inline `Theme['typography']` member. - - `BorderRadius` — lives on as inline `Theme['borderRadius']`. - - `Shadow` — lives on as inline `Theme['shadows']`. - - `ThemeDefinition` — the deprecated alias of `Theme`; use `Theme`. - - Also added: `THEME_MODES`, a runtime tuple witness of the theme mode vocabulary (`['auto', 'light', 'dark']`). - - The `UI` protocol namespace (`import { UI } from '@object-ui/types'`) now resolves `UI.Theme` / `UI.ThemeMode` / `UI.ColorPalette` to the local owners, so they survive the upcoming spec refresh; the rest of the namespace continues to track `@objectstack/spec/ui`. After that refresh, retired spec/ui members (`UI.ThemeSchema`, `UI.ThemeModeSchema`, `UI.ThemeParsed`, `UI.Typography`, `UI.BorderRadius`, `UI.Shadow`, `UI.defineTheme`) drop out of the namespace. - - `@object-ui/providers`: `ThemePreference` is now derived from `@object-ui/types`' `ThemeMode` instead of the retired spec `ThemeModeSchema` (same union: `'auto' | 'light' | 'dark' | 'system'`). - -### Patch Changes - -- a1c41c5: `@object-ui/providers` no longer declares `@objectstack/spec` as a dependency. Nothing - in the package imports it, so consumers stop installing it on this package's account - (objectui#5753). - - The edge was live for exactly one release cycle. It was promoted from `devDependencies` - to `dependencies` when `ThemePreference` was derived from the spec's `ThemeMode` union, - because the package's public `.d.ts` then referenced the spec. objectui#5716 re-pointed - that derivation at `@object-ui/types` (`ThemeMode` / `THEME_MODES`), which removed the - last three import sites — `src/types.ts` and the two retirement-era test files — and - left the declaration behind with no reader. - - Re-measured on `origin/main` at `ad0f5f11f` before removal, with a positive control so - the empty result is a real absence rather than a broken command: the import-shaped grep - (`from` / `require(` / `import(` / `vi.mock(` against `@objectstack/spec`, bare name and - every subpath) returns **0** hits under `packages/providers/` and **434** across - `packages/` + `apps/` — same command, same invocation. The only surviving mentions in - the package are the declaration itself, immutable `CHANGELOG.md` history, and a prose - comment in `tsconfig.test.json` that this change corrects. - - No API change and no behaviour change: `dist/types.d.ts` imports only `react` and - `@object-ui/types`, and no emitted file references a spec symbol. Consumers on an - isolated `node_modules` (pnpm) never had supported access to the spec through this - package, so nothing they could legitimately import goes away — the change is to the - install graph only, which is why it is scored `patch` rather than `minor`. -- c907a9c: fix(fields): a file or image upload that surfaces no `sys_file` id is refused by name, never submitted as an inline blob - - **BREAKING (`@object-ui/fields`), graded `minor` by this repo's release model** (AGENTS.md: objectui's major follows the `@objectstack` family major, so its own breaking changes ship as `minor` with the breaking semantics stated here, never `major`): - - - **Who breaks.** Two kinds of host. The first calls `fileValueForSubmit` itself. The second mounts an upload adapter whose result carries no id-shaped `meta.fileId`. That includes the object-URL default: `useUpload()` falls back to it when no `UploadProvider` is mounted, and `UploadProvider` uses it when given no `adapter`. It also includes any S3- or Azure-style adapter, `createS3Adapter` and `createAzureBlobAdapter` among them, because neither mints a `sys_file` row. This includes every host on a non-ObjectStack backend (the README's any-backend `DataSource` path) that stored the inline `UploadedFileMetadata` object: no adapter there mints a `sys_file` id. - - **What they see now.** `fileValueForSubmit` throws `UploadIncompleteError` (`code` `UPLOAD_INCOMPLETE`, `fileName` naming the pick) where it used to return the inline object, and its return type is `string`. In `FileField`, `FileCell` and `ImageField` the pick is refused with the translated "did not complete" row, and the field is not changed. - - **What to do.** Mount an `UploadProvider` whose adapter returns the `sys_file` id in `meta.fileId`. `createObjectStackUploadAdapter`, the ObjectStack presigned flow, does. A custom adapter has to register the file with the platform and return that id. A host that calls `fileValueForSubmit` directly catches `UploadIncompleteError` (or checks `code === 'UPLOAD_INCOMPLETE'`) where it used to accept an object. Such a host returns its own file id in `meta.fileId` (word characters and `-`, 1 to 64 long, as `isFileIdToken` requires) and has its read path return `{ id, name, url }`, because a bare id otherwise renders from ObjectStack's `/api/v1/storage/files/:id`. - - `Clause-②: yes (narrowing)` — `fileValueForSubmit`, a public export of `@object-ui/fields`, narrows its return type to `string` and throws where it used to return an object; `UploadIncompleteError` is a new public export. No field-value accept set widens. - - `fileValueForSubmit` used to fall back to the legacy inline `{ name, original_name, size, mime_type, url }` object when the upload adapter surfaced no `meta.fileId`. That was the file and image widgets' one path to the pre-D3 inline file object: the backend's stored contract has been the bare `sys_file` id since spec `17.0.0`, so the fallback turned a successful-looking upload into a save the engine refused (`invalid_type`) on every deployment that has verified its file-as-reference migration, and into a warned-about legacy value on the rest. ADR-0104's 2026-09-05 addendum fixes the physical column to the bare id as well. - - The fallback is retired. `FileField`, `FileCell` and both upload paths of `ImageField` now submit the id the adapter minted, or refuse the pick with the translated `fields.file.uploadIncomplete` message ("did not complete") in the same error row a transport failure lands in — the field is not changed and no new blob reaches `onChange`. This includes the object-URL default `useUpload()` falls open to when no `UploadProvider` is mounted, and any S3/Azure-style adapter that mints no `sys_file` row. Reading is unchanged: a legacy blob already on a record still renders, and on a `multiple` field it is passed through untouched beside a new id. - - `@object-ui/i18n` gains the `fields.file.uploadIncomplete` key in every locale pack. - - `@object-ui/providers`'s `UploadProvider.tsx` JSDoc for the object-URL default now states it persists nothing and returns no id. -- Updated dependencies [b46c58f] -- Updated dependencies [6f96fca] -- Updated dependencies [5f00ff4] -- Updated dependencies [c9e073a] -- Updated dependencies [7b395d8] -- Updated dependencies [0879812] -- Updated dependencies [2dd4d3f] -- Updated dependencies [e3ea4f9] -- Updated dependencies [8b1f066] -- Updated dependencies [af243c1] -- Updated dependencies [f3f4e4c] -- Updated dependencies [a05c350] -- Updated dependencies [8c10f4f] -- Updated dependencies [90dac98] -- Updated dependencies [6096f20] -- Updated dependencies [ea02938] -- Updated dependencies [a14fb23] -- Updated dependencies [ae98f1d] -- Updated dependencies [f98eddf] -- Updated dependencies [ce6bd99] -- Updated dependencies [a5b08c9] -- Updated dependencies [9b28151] -- Updated dependencies [1a5003f] -- Updated dependencies [d22b37b] -- Updated dependencies [1daf477] -- Updated dependencies [fb13e85] -- Updated dependencies [c2d8659] -- Updated dependencies [e0f8202] -- Updated dependencies [c3a26cc] -- Updated dependencies [a66e58e] -- Updated dependencies [d89492c] -- Updated dependencies [9327397] -- Updated dependencies [17cc3a3] -- Updated dependencies [9c78ebe] -- Updated dependencies [12809a5] -- Updated dependencies [f9c06ef] -- Updated dependencies [5ad3b88] -- Updated dependencies [f9d772b] -- Updated dependencies [97b6c21] -- Updated dependencies [29b45f6] -- Updated dependencies [b956e69] -- Updated dependencies [fec3b1a] -- Updated dependencies [b8e0941] -- Updated dependencies [0c50f18] -- Updated dependencies [1dae95a] -- Updated dependencies [e32dae1] -- Updated dependencies [4aebea0] -- Updated dependencies [f976774] -- Updated dependencies [25cb364] -- Updated dependencies [c6678b1] -- Updated dependencies [0638322] -- Updated dependencies [e3782d2] -- Updated dependencies [db0beb2] -- Updated dependencies [997ce38] -- Updated dependencies [ae0b9d3] -- Updated dependencies [3b469c8] -- Updated dependencies [6650259] -- Updated dependencies [4f8b7f8] -- Updated dependencies [f6ae5e2] -- Updated dependencies [7343376] -- Updated dependencies [b2683a2] -- Updated dependencies [dded788] -- Updated dependencies [b45d463] -- Updated dependencies [54a7830] -- Updated dependencies [f3135a4] -- Updated dependencies [b5696d3] -- Updated dependencies [3f9d926] -- Updated dependencies [e978ed5] -- Updated dependencies [6a7f24e] -- Updated dependencies [b3c96d6] -- Updated dependencies [8d0ca91] -- Updated dependencies [c30c8dd] -- Updated dependencies [24d3e65] -- Updated dependencies [95a7c8d] -- Updated dependencies [cc4e476] -- Updated dependencies [92970c4] -- Updated dependencies [d570eaa] -- Updated dependencies [42687ba] -- Updated dependencies [24a0f14] -- Updated dependencies [797a30f] -- Updated dependencies [b4075c0] -- Updated dependencies [9b85600] -- Updated dependencies [99878d8] -- Updated dependencies [3c13675] -- Updated dependencies [0eb9f36] -- Updated dependencies [ae582b7] -- Updated dependencies [db11afd] -- Updated dependencies [154075a] -- Updated dependencies [582edef] -- Updated dependencies [19f484f] -- Updated dependencies [0a78a20] -- Updated dependencies [615346d] -- Updated dependencies [75dcc81] -- Updated dependencies [55a12a8] -- Updated dependencies [edfcf5a] -- Updated dependencies [0a3e540] -- Updated dependencies [f61dab1] -- Updated dependencies [b0a05dd] -- Updated dependencies [dd5ff19] -- Updated dependencies [81f8498] -- Updated dependencies [c27b575] -- Updated dependencies [0e6e76b] -- Updated dependencies [cd5b19a] -- Updated dependencies [17dc167] -- Updated dependencies [20d23be] -- Updated dependencies [20d23be] -- Updated dependencies [e6bc087] -- Updated dependencies [a7557a7] -- Updated dependencies [7d074ba] -- Updated dependencies [6158e4c] -- Updated dependencies [6158e4c] -- Updated dependencies [52aad5c] -- Updated dependencies [58da8ae] -- Updated dependencies [138ad45] -- Updated dependencies [5262f7d] -- Updated dependencies [6aa029b] -- Updated dependencies [770cc5b] -- Updated dependencies [1a88ce2] -- Updated dependencies [a1a44d6] -- Updated dependencies [e0a9c67] -- Updated dependencies [5638529] -- Updated dependencies [c476be0] -- Updated dependencies [c82ff39] -- Updated dependencies [6c3da53] -- Updated dependencies [31987bd] -- Updated dependencies [3c3ce15] -- Updated dependencies [e100589] -- Updated dependencies [304f611] -- Updated dependencies [e46ee77] -- Updated dependencies [6e9c8d2] -- Updated dependencies [9547063] -- Updated dependencies [3f6efd6] -- Updated dependencies [c4ab6d0] -- Updated dependencies [0e9058b] -- Updated dependencies [5988b6b] -- Updated dependencies [00ccdf7] -- Updated dependencies [9d9ed54] -- Updated dependencies [ca3de72] -- Updated dependencies [83e3f83] -- Updated dependencies [401611b] -- Updated dependencies [2c0ddf2] -- Updated dependencies [4abc0aa] -- Updated dependencies [2b188fa] -- Updated dependencies [f68e0a0] -- Updated dependencies [aea682a] -- Updated dependencies [fcdc8ec] -- Updated dependencies [2d576e4] -- Updated dependencies [8366acc] -- Updated dependencies [95e58a3] -- Updated dependencies [9d7419b] -- Updated dependencies [fc7db05] -- Updated dependencies [9ed8d0f] -- Updated dependencies [c73cdb5] -- Updated dependencies [6f5719e] -- Updated dependencies [06a8af5] -- Updated dependencies [6a91586] -- Updated dependencies [a04d7c6] -- Updated dependencies [f3c2bb0] -- Updated dependencies [460575f] -- Updated dependencies [d88e20f] -- Updated dependencies [2d7304d] -- Updated dependencies [636b236] -- Updated dependencies [d6d8fb9] -- Updated dependencies [64d624d] -- Updated dependencies [95bad12] -- Updated dependencies [d2fb6ef] -- Updated dependencies [fda49e5] -- Updated dependencies [fc62bb4] -- Updated dependencies [41df893] -- Updated dependencies [0cba1b7] -- Updated dependencies [00f3eb5] -- Updated dependencies [1ec291c] -- Updated dependencies [453dbaa] -- Updated dependencies [69a2163] -- Updated dependencies [24e027e] -- Updated dependencies [2c3cd1b] -- Updated dependencies [90665e0] -- Updated dependencies [7e19d03] -- Updated dependencies [1e946c9] -- Updated dependencies [864154e] -- Updated dependencies [b023625] -- Updated dependencies [75bd83d] -- Updated dependencies [40c479a] -- Updated dependencies [971d387] -- Updated dependencies [ee851c3] -- Updated dependencies [6414dfd] -- Updated dependencies [a8d5c71] -- Updated dependencies [905b21f] -- Updated dependencies [88e9109] -- Updated dependencies [2c45966] -- Updated dependencies [db3a600] -- Updated dependencies [3a3db76] -- Updated dependencies [0d723a3] -- Updated dependencies [0c95d3d] -- Updated dependencies [3e4fa2c] -- Updated dependencies [b5b928a] -- Updated dependencies [52a43de] -- Updated dependencies [195052f] -- Updated dependencies [e4559d1] -- Updated dependencies [2c71482] -- Updated dependencies [5ef9c4f] -- Updated dependencies [46f0bb4] -- Updated dependencies [6f81384] -- Updated dependencies [8f1d995] -- Updated dependencies [dddb942] -- Updated dependencies [29754cf] -- Updated dependencies [b84dc18] -- Updated dependencies [ac8abb0] -- Updated dependencies [9d86e1d] -- Updated dependencies [3a5817f] -- Updated dependencies [99a3c2d] -- Updated dependencies [c8ea8af] -- Updated dependencies [3190414] -- Updated dependencies [4e480f5] -- Updated dependencies [38a123c] -- Updated dependencies [d7acad6] -- Updated dependencies [45a9aeb] -- Updated dependencies [713db46] -- Updated dependencies [bf3a03c] -- Updated dependencies [cb55718] -- Updated dependencies [29cb85b] -- Updated dependencies [3e028c8] -- Updated dependencies [ce503e5] -- Updated dependencies [f20dcf0] -- Updated dependencies [4ca30d0] -- Updated dependencies [7a5da14] -- Updated dependencies [2c1c967] -- Updated dependencies [d6ceb8d] -- Updated dependencies [2acd8e1] -- Updated dependencies [adb2a86] -- Updated dependencies [3561bd2] -- Updated dependencies [bf97b98] -- Updated dependencies [b0d308d] -- Updated dependencies [40f34b4] -- Updated dependencies [8063bcb] -- Updated dependencies [b74a859] -- Updated dependencies [d4493fd] -- Updated dependencies [240b80f] -- Updated dependencies [77cb489] -- Updated dependencies [bfaa158] -- Updated dependencies [777e5c6] -- Updated dependencies [0c386dd] -- Updated dependencies [9e37d9b] -- Updated dependencies [5ad86dd] -- Updated dependencies [16a725f] -- Updated dependencies [4dfdcc3] -- Updated dependencies [446d93d] -- Updated dependencies [ecd9cb2] -- Updated dependencies [98d4108] -- Updated dependencies [0e3b3be] -- Updated dependencies [a29ae2d] -- Updated dependencies [4388f71] -- Updated dependencies [0b1ac58] -- Updated dependencies [c93b4d5] -- Updated dependencies [c1fe272] -- Updated dependencies [8ad218d] -- Updated dependencies [3e41187] -- Updated dependencies [5f78953] -- Updated dependencies [639114c] -- Updated dependencies [1f31d3a] -- Updated dependencies [351eb31] -- Updated dependencies [20c04b2] -- Updated dependencies [b652514] -- Updated dependencies [adbda1b] -- Updated dependencies [e2b3826] -- Updated dependencies [2e32ed4] -- Updated dependencies [1bee5d0] -- Updated dependencies [858cd72] -- Updated dependencies [554f2b6] -- Updated dependencies [669d71b] -- Updated dependencies [ed27d7c] -- Updated dependencies [52c8cf7] -- Updated dependencies [2ceb43a] -- Updated dependencies [7cdd2b9] -- Updated dependencies [52c8cf7] -- Updated dependencies [caa0cd3] -- Updated dependencies [25c7d58] -- Updated dependencies [c6198c2] -- Updated dependencies [51eb515] -- Updated dependencies [c354ce5] -- Updated dependencies [8fe8e5c] -- Updated dependencies [feac439] -- Updated dependencies [efbd566] -- Updated dependencies [9587fc9] -- Updated dependencies [e62c44e] -- Updated dependencies [5d0876c] -- Updated dependencies [544ecba] -- Updated dependencies [bc640ec] -- Updated dependencies [3e377c9] -- Updated dependencies [a3eb5d0] -- Updated dependencies [4ce14f1] -- Updated dependencies [2af1fa7] -- Updated dependencies [a137d0c] -- Updated dependencies [caf477f] -- Updated dependencies [f6375da] -- Updated dependencies [967e5d8] -- Updated dependencies [a4611b3] -- Updated dependencies [20316ba] -- Updated dependencies [d3499b3] -- Updated dependencies [309c75e] -- Updated dependencies [c9f9bae] -- Updated dependencies [18897a4] -- Updated dependencies [8b7ea39] -- Updated dependencies [dcbf0b2] -- Updated dependencies [1422a92] -- Updated dependencies [d05fe17] -- Updated dependencies [a480f79] -- Updated dependencies [f08d1a8] -- Updated dependencies [64a252d] -- Updated dependencies [786bc91] -- Updated dependencies [75fca96] -- Updated dependencies [7ca6ddd] -- Updated dependencies [f1cd290] -- Updated dependencies [5a41ce7] -- Updated dependencies [8d50bc2] -- Updated dependencies [604476d] -- Updated dependencies [335abea] -- Updated dependencies [0f5cadf] -- Updated dependencies [4f9f1ee] -- Updated dependencies [66e8b2a] -- Updated dependencies [aa083cd] -- Updated dependencies [12b5992] -- Updated dependencies [b93e245] -- Updated dependencies [c842594] -- Updated dependencies [290de37] -- Updated dependencies [8c8da45] -- Updated dependencies [8cd8eb5] -- Updated dependencies [cf1d29e] -- Updated dependencies [af9e957] -- Updated dependencies [c974edf] -- Updated dependencies [ad852b6] -- Updated dependencies [ee4d19f] -- Updated dependencies [496d31d] -- Updated dependencies [9a853f2] -- Updated dependencies [cb847fd] -- Updated dependencies [ee70287] -- Updated dependencies [3e98e13] -- Updated dependencies [4eaa835] -- Updated dependencies [b1777ae] -- Updated dependencies [24845c4] -- Updated dependencies [6f864cf] -- Updated dependencies [24d1edd] -- Updated dependencies [645087c] -- Updated dependencies [33f4a19] -- Updated dependencies [5323168] -- Updated dependencies [841dd2b] -- Updated dependencies [3014fc0] -- Updated dependencies [dacb402] -- Updated dependencies [474797d] -- Updated dependencies [704e695] -- Updated dependencies [a407bd6] -- Updated dependencies [3a43a15] -- Updated dependencies [421544b] -- Updated dependencies [fb01022] -- Updated dependencies [e9d9212] -- Updated dependencies [ecfb693] -- Updated dependencies [81a51db] -- Updated dependencies [67749c7] -- Updated dependencies [507b61b] -- Updated dependencies [512c84b] -- Updated dependencies [d4733f2] -- Updated dependencies [8b532cb] -- Updated dependencies [c42554e] -- Updated dependencies [555b4ec] -- Updated dependencies [1ccfc23] -- Updated dependencies [542718f] -- Updated dependencies [7f27bc5] -- Updated dependencies [f95b140] -- Updated dependencies [541ce4e] -- Updated dependencies [6479086] -- Updated dependencies [d79f525] -- Updated dependencies [f1190b0] -- Updated dependencies [6a4680b] -- Updated dependencies [c3a4273] -- Updated dependencies [093af32] -- Updated dependencies [1bd1be7] -- Updated dependencies [d234fa9] -- Updated dependencies [adf5812] -- Updated dependencies [5058336] -- Updated dependencies [2f6b2bf] -- Updated dependencies [2028b31] -- Updated dependencies [63601ab] -- Updated dependencies [8693b85] -- Updated dependencies [58b7b3d] -- Updated dependencies [681d3f1] -- Updated dependencies [f3bc481] -- Updated dependencies [93fc0e7] -- Updated dependencies [a4b723f] -- Updated dependencies [2b10ca0] -- Updated dependencies [7db4a81] -- Updated dependencies [526fc11] -- Updated dependencies [6732df4] -- Updated dependencies [fe9e0d0] -- Updated dependencies [63fb72c] -- Updated dependencies [279e48e] -- Updated dependencies [8700d6d] -- Updated dependencies [8db2a0f] -- Updated dependencies [30443fb] -- Updated dependencies [96919a4] -- Updated dependencies [2e471dc] -- Updated dependencies [be50942] -- Updated dependencies [775e079] -- Updated dependencies [7e8b3c0] -- Updated dependencies [53374dc] -- Updated dependencies [f6fb83f] -- Updated dependencies [2049b03] -- Updated dependencies [7cbc724] -- Updated dependencies [fb91ac9] -- Updated dependencies [8524372] -- Updated dependencies [7cbefa5] -- Updated dependencies [72d6587] -- Updated dependencies [a272a4f] -- Updated dependencies [55f39ee] -- Updated dependencies [0970a0e] -- Updated dependencies [e427e9c] -- Updated dependencies [bbc9dc3] -- Updated dependencies [02f1813] -- Updated dependencies [ac716ff] -- Updated dependencies [f0f3cd5] -- Updated dependencies [20f3e65] -- Updated dependencies [bbba098] -- Updated dependencies [87af769] -- Updated dependencies [3be720e] -- Updated dependencies [c3df43a] -- Updated dependencies [d16d0e9] -- Updated dependencies [bbe57fd] -- Updated dependencies [272a530] -- Updated dependencies [1779e8d] -- Updated dependencies [4128188] -- Updated dependencies [b253c4e] -- Updated dependencies [78a9c67] -- Updated dependencies [4a7ef0d] -- Updated dependencies [dea17b4] -- Updated dependencies [89bb77a] -- Updated dependencies [06611e4] -- Updated dependencies [dc3893d] -- Updated dependencies [1bbaa16] -- Updated dependencies [6ee259a] -- Updated dependencies [e708426] -- Updated dependencies [3b6d53b] -- Updated dependencies [a8198de] -- Updated dependencies [a78cd37] -- Updated dependencies [5ea623e] -- Updated dependencies [ca5d671] -- Updated dependencies [32bf2d6] -- Updated dependencies [af4fb29] -- Updated dependencies [9a97800] -- Updated dependencies [6bca0e4] -- Updated dependencies [2fcefb9] -- Updated dependencies [b55a346] -- Updated dependencies [065bba7] -- Updated dependencies [100547e] -- Updated dependencies [6d1c155] -- Updated dependencies [d7573b3] -- Updated dependencies [0e05aac] -- Updated dependencies [18a8e7d] -- Updated dependencies [e7957ab] -- Updated dependencies [f7e34ca] -- Updated dependencies [f9e4f91] -- Updated dependencies [6ef48b1] -- Updated dependencies [fa429cf] -- Updated dependencies [ed8df3e] -- Updated dependencies [8b446f5] -- Updated dependencies [7357447] -- Updated dependencies [199d31b] -- Updated dependencies [3e01cb5] -- Updated dependencies [4e8622b] -- Updated dependencies [dffd752] -- Updated dependencies [105f3c5] -- Updated dependencies [3ccd9e8] -- Updated dependencies [689b979] -- Updated dependencies [e546222] -- Updated dependencies [fd13f52] -- Updated dependencies [fb336df] -- Updated dependencies [0fce2ef] -- Updated dependencies [0e2ddd4] -- Updated dependencies [b7479ab] -- Updated dependencies [b2ea297] -- Updated dependencies [5b5a5c3] -- Updated dependencies [14582b8] -- Updated dependencies [51e144e] -- Updated dependencies [a691c0b] -- Updated dependencies [515f171] -- Updated dependencies [258d264] -- Updated dependencies [93127bd] -- Updated dependencies [51f3d8d] -- Updated dependencies [78cbdb5] -- Updated dependencies [b7543a9] -- Updated dependencies [ca39427] -- Updated dependencies [c9327c9] -- Updated dependencies [920165d] -- Updated dependencies [968dc1e] -- Updated dependencies [3c73d99] -- Updated dependencies [1170ed1] -- Updated dependencies [4d73b07] - - @object-ui/types@17.7.0 - -## 17.6.0 - -### Patch Changes - -- 5458414: Publish relative import specifiers with explicit `.js` extensions so these six packages load under plain Node ESM. - - Node's ESM resolver does not extension-search relative specifiers and `tsc` never rewrites them, so an extensionless `./Foo` in the source shipped as an extensionless `./Foo` in `dist` and importing the package entry outside a bundler failed with `ERR_MODULE_NOT_FOUND`. Bundled consumers were unaffected. Unbundled consumers — plain Node ESM, an SSR host importing the package directly, anyone running the published tarball without a build step — can now import these entries, and so can the downstream `@object-ui/plugin-*` packages that evaluate through `mobile`, `permissions` and `providers`. -- Updated dependencies [88085e3] -- Updated dependencies [279fb13] -- Updated dependencies [1184192] -- Updated dependencies [a2a9747] -- Updated dependencies [af5e292] -- Updated dependencies [7f96b10] -- Updated dependencies [f1d4748] -- Updated dependencies [578e025] -- Updated dependencies [598c89a] -- Updated dependencies [b8b9af4] -- Updated dependencies [97abb24] -- Updated dependencies [deb157a] -- Updated dependencies [d2ce342] -- Updated dependencies [9695da7] -- Updated dependencies [58b8346] -- Updated dependencies [3cf4de0] -- Updated dependencies [c9dc811] -- Updated dependencies [a0b9e91] -- Updated dependencies [99bd015] - - @object-ui/types@17.6.0 - -## 17.5.0 - -### Patch Changes - -- Updated dependencies [f650253] -- Updated dependencies [3d9769a] -- Updated dependencies [92876f0] -- Updated dependencies [d9d3463] -- Updated dependencies [2a40f69] -- Updated dependencies [bec3e14] -- Updated dependencies [1f9b905] -- Updated dependencies [abb0f81] -- Updated dependencies [38ab505] -- Updated dependencies [7e4f0e5] -- Updated dependencies [c1d939f] -- Updated dependencies [bb68488] -- Updated dependencies [ab04728] - - @object-ui/types@17.5.0 - -## 17.4.0 - -### Patch Changes - -- Updated dependencies [d229dfa] -- Updated dependencies [c2fd122] -- Updated dependencies [48132f7] -- Updated dependencies [7e5bb5d] -- Updated dependencies [e6fdbdc] -- Updated dependencies [7e2b7e9] -- Updated dependencies [c1e1e6b] - - @object-ui/types@17.4.0 - -## 17.3.0 - -### Patch Changes - -- Updated dependencies [d915c47] -- Updated dependencies [9e9e9a9] -- Updated dependencies [23018cc] -- Updated dependencies [f44d872] -- Updated dependencies [f833d3a] -- Updated dependencies [d22ae31] - - @object-ui/types@17.3.0 - -## 17.2.0 - -### Minor Changes - -- 4a51e77: Stop declaring 14 symbols across ten packages under names `@objectstack/spec` - owns (objectui#3161, objectstack#4115 batch 7 — the long tail, one or two - entries per package). All ten packages leave the ledger, which drops from 17 - collisions across 11 packages to 3 across 1. - - **Renamed exports** — in every case the spec exports the same name for a - _different_ thing, so the old name was a mis-description rather than a dialect: - - | package | was | now | what the spec's same-named export is | - | :------------------------- | :--------------------------------- | :--------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | - | `@object-ui/fields` | `FieldWidgetProps` | `FieldWidgetComponentProps` | the DECLARED field-widget plugin props contract (a zod object; `field.type` is the `FieldType` enum, `readonly`/`required` carry defaults) | - | `@object-ui/layout` | `PageHeaderProps` | `PageHeaderComponentProps` | the authored `page:header` node — a zod schema of `title`, `subtitle`, an icon NAME, `breadcrumb`, `actions: string[]` | - | `@object-ui/layout` | `Page` | `PageNodeRenderer` | the authored page metadata DOCUMENT (`name`, `label`, `type`, `regions`) | - | `@object-ui/plugin-detail` | `ObjectFieldLike` | `ObjectDefFieldLike` | the i18n duck type `translateObject` walks (`help`/`description`, plus `[key: string]: any`) | - | `@object-ui/plugin-grid` | `ColumnSummaryConfig` | `ColumnSummarySetting` | the OBJECT form of `ListColumn.summary` **only** — the local one was the whole union, shorthand included | - | `@object-ui/plugin-grid` | `isMultiValueField` | `hasMultiValueShape` | the spec's classifier, which requires a def with a `type`; the local one is called with `undefined` | - | `@object-ui/collaboration` | `RealtimeConfig` | `RealtimeSubscriptionConfig` | the app's realtime DECLARATION (`enabled`, `transport`, `subscriptions[]`) | - | `@object-ui/plugin-charts` | `ChartConfig` | `ChartContainerConfig` | the authored chart document (`type`, `xAxis`, `series`, `showLegend`, …) | - | `@object-ui/plugin-form` | `FormSection` / `FormSectionProps` | `FormSectionContainer` / `FormSectionContainerProps` | the authored form-section metadata (`name`, `pane`, `visibleWhen`, `fields`) | - | `@object-ui/providers` | `Theme` | `ThemePreference` | a whole theme DOCUMENT (`name`, `label`, `colors`, `typography`) | - | `@object-ui/runner` | `App` (default export) | `RunnerApp` | the authored application metadata type **and** the `App.create()` builder | - | `@object-ui/sdui-parser` | `ValidationResult` | `ManifestValidationResult` | plugin-manifest validation (`{ valid, errors?, warnings? }`), exported from both `kernel` and `contracts` | - - `ManifestValidationResult` follows the `Validation` - convention registered on objectstack#4115 (`@object-ui/core` took - `SchemaNodeValidationResult` in batch 4). `PageHeaderComponentProps` deliberately - reuses the name `@object-ui/app-shell` already chose for its own header props in - batch 3, so one concept does not acquire two dialect names one package apart. - - **Now derived from the spec instead of hand-written:** - - - `@object-ui/fields` — `isFileIdToken` is re-exported from - `@objectstack/spec/data`. The local copy was character-for-character identical - to the spec's function while its comment said it "mirrors" it, so every - behaviour test passed and only reference identity could tell the two apart. - The regex is a wire decision: widening it server-side while a copy here kept - the old bound would make every new id read as "not a reference", and the - widget would submit the legacy inline blob to a backend expecting a reference. - - `@object-ui/plugin-detail` — `FeedFilterMode` is re-exported from - `@objectstack/spec/data`, in a file that already imported the sibling - `FeedItemType` from the spec. - - `@object-ui/plugin-grid` — the eleven-member aggregation union is now the - spec's `ColumnSummary` enum, so the total `Record` - label map turns a member the spec adds into a compile error instead of a - blank footer cell. `ColumnSummarySetting` is `NonNullable`, - i.e. whatever forms the spec itself accepts. `hasMultiValueShape` delegates to - the spec's `isMultiValueField` rather than re-deriving it from - `MULTI_OPTION_TYPES` / `MULTI_CAPABLE_TYPES`. - - `@object-ui/providers` — `ThemePreference` is the spec's `ThemeMode` union - plus the one legacy `'system'` spelling this provider still honours for stored - preferences, read off the schema's own `_zod` carrier so the package takes no - zod dependency. - - `@objectstack/spec` moves from `devDependencies` to `dependencies` in - `@object-ui/fields` (it re-exports a runtime function) and `@object-ui/providers` - (its public `.d.ts` now references the spec). - - Scored `minor`, not `major`, per this repo's fixed-group rule — objectui's major - tracks `@objectstack`, so breaking changes of our own ship as minor with the - semantics spelled out above (see AGENTS.md §版本号策略). A `major` here would carry - all 39 packages of the fixed group to `18.0.0` and off objectstack's 17.x line. - -### Patch Changes - -- Updated dependencies [4ae0ac4] -- Updated dependencies [696e3c1] -- Updated dependencies [4bf612c] -- Updated dependencies [cb82705] -- Updated dependencies [f572849] -- Updated dependencies [444457c] -- Updated dependencies [022e4c3] -- Updated dependencies [009e25d] -- Updated dependencies [726b89c] - - @object-ui/types@17.2.0 - -## 17.1.0 - -### Patch Changes - -- Updated dependencies [9e7349e] -- Updated dependencies [8864971] -- Updated dependencies [b41f401] -- Updated dependencies [19e9fa0] -- Updated dependencies [38ca8be] -- Updated dependencies [4952edf] -- Updated dependencies [7f0252e] -- Updated dependencies [7639a61] -- Updated dependencies [94e63ef] -- Updated dependencies [c4db402] -- Updated dependencies [5319bf1] -- Updated dependencies [49e5671] -- Updated dependencies [b5b97e2] -- Updated dependencies [f59f2c1] -- Updated dependencies [4874117] -- Updated dependencies [ce08d55] -- Updated dependencies [2374a49] -- Updated dependencies [ea7f477] -- Updated dependencies [7f23cd0] -- Updated dependencies [24e0e0a] -- Updated dependencies [3a6cf24] -- Updated dependencies [aa35561] -- Updated dependencies [03bd53b] -- Updated dependencies [3c1f321] -- Updated dependencies [a045a32] -- Updated dependencies [912496d] -- Updated dependencies [9867281] - - @object-ui/types@17.1.0 - -## 17.0.0 - -### Patch Changes - -- Updated dependencies [1767124] -- Updated dependencies [8ecf5a6] -- Updated dependencies [dfd3705] -- Updated dependencies [6dee2cb] -- Updated dependencies [c7cff19] -- Updated dependencies [cd09a7b] -- Updated dependencies [f1abf0e] -- Updated dependencies [f05b84e] -- Updated dependencies [662bdf9] -- Updated dependencies [059a052] -- Updated dependencies [53642d4] -- Updated dependencies [8aae006] -- Updated dependencies [d147a13] - - @object-ui/types@17.0.0 - -## 16.1.0 - -### Patch Changes - -- Updated dependencies [7cf4051] -- Updated dependencies [94d4876] -- Updated dependencies [2b17339] -- Updated dependencies [31b77d4] -- Updated dependencies [6d4fbe6] -- Updated dependencies [62b9ab5] -- Updated dependencies [29c6040] -- Updated dependencies [faebac3] -- Updated dependencies [199fa83] - - @object-ui/types@16.1.0 - -## 16.0.0 - -### Patch Changes - -- Updated dependencies [210806a] -- Updated dependencies [b4ef588] -- Updated dependencies [5534535] -- Updated dependencies [9b8f978] - - @object-ui/types@16.0.0 - -## 15.0.0 - -### Patch Changes - -- @object-ui/types@15.0.0 - -## 14.1.0 - -### Patch Changes - -- Updated dependencies [2ded18c] -- Updated dependencies [e628d1f] -- Updated dependencies [887062c] -- Updated dependencies [9e2d58f] -- Updated dependencies [d5b1bc0] -- Updated dependencies [f0f10f5] - - @object-ui/types@14.1.0 - -## 14.0.0 - -### Patch Changes - -- Updated dependencies [86c69c3] -- Updated dependencies [6a74160] - - @object-ui/types@14.0.0 - -## 13.2.0 - -### Patch Changes - -- @object-ui/types@13.2.0 - -## 13.1.0 - -### Patch Changes - -- @object-ui/types@13.1.0 - -## 13.0.0 - -### Patch Changes - -- Updated dependencies [619097e] - - @object-ui/types@13.0.0 - -## 12.1.0 - -### Patch Changes - -- Updated dependencies [c31874d] - - @object-ui/types@12.1.0 - -## 12.0.0 - -### Patch Changes - -- Updated dependencies [226fde9] -- Updated dependencies [e4de456] - - @object-ui/types@12.0.0 - -## 11.5.0 - -### Patch Changes - -- Updated dependencies [9255686] -- Updated dependencies [1072701] - - @object-ui/types@11.5.0 - -## 11.4.0 - -### Patch Changes - -- Updated dependencies [8bf6295] -- Updated dependencies [1948c5b] -- Updated dependencies [c38d107] - - @object-ui/types@11.4.0 - -## 11.3.0 - -### Patch Changes - -- @object-ui/types@11.3.0 - -## 11.2.0 - -### Patch Changes - -- @object-ui/types@11.2.0 - -## 11.1.0 - -### Patch Changes - -- @object-ui/types@11.1.0 - -## 7.3.0 - -### Patch Changes - -- @object-ui/types@7.3.0 - -## 7.2.0 - -### Patch Changes - -- Updated dependencies [d23db5c] - - @object-ui/types@7.2.0 - -## 7.1.0 - -### Patch Changes - -- Updated dependencies [677f7ed] -- Updated dependencies [a71be60] -- Updated dependencies [cb03bc3] - - @object-ui/types@7.1.0 - -## 7.0.0 - -### Patch Changes - -- Updated dependencies [ddbe4a2] -- Updated dependencies [9049bbe] -- Updated dependencies [cb2fdb1] -- Updated dependencies [6cfa330] -- Updated dependencies [ad8ade6] -- Updated dependencies [3870c20] -- Updated dependencies [b88c560] -- Updated dependencies [d16566f] -- Updated dependencies [300d755] -- Updated dependencies [4eb9cb6] -- Updated dependencies [858ad94] - - @object-ui/types@7.0.0 - -## 6.2.3 - -### Patch Changes - -- @object-ui/types@6.2.3 - -## 6.2.2 - -### Patch Changes - -- @object-ui/types@6.2.2 - -## 6.2.1 - -### Patch Changes - -- @object-ui/types@6.2.1 - -## 6.2.0 - -### Patch Changes - -- @object-ui/types@6.2.0 - -## 6.1.0 - -### Patch Changes - -- Updated dependencies [991b62d] - - @object-ui/types@6.1.0 - -## 6.0.4 - -### Patch Changes - -- @object-ui/types@6.0.4 - -## 6.0.3 - -### Patch Changes - -- @object-ui/types@6.0.3 - -## 6.0.2 - -### Patch Changes - -- @object-ui/types@6.0.2 - -## 6.0.1 - -### Patch Changes - -- @object-ui/types@6.0.1 - -## 6.0.0 - -### Patch Changes - -- @object-ui/types@6.0.0 - -## 5.4.2 - -### Patch Changes - -- @object-ui/types@5.4.2 - -## 5.4.1 - -### Patch Changes - -- @object-ui/types@5.4.1 - -## 5.4.0 - -### Patch Changes - -- Updated dependencies [3a8c754] - - @object-ui/types@5.4.0 - -## 5.3.2 - -### Patch Changes - -- @object-ui/types@5.3.2 - -## 5.3.1 - -### Patch Changes - -- @object-ui/types@5.3.1 - -## 5.3.0 - -### Patch Changes - -- @object-ui/types@5.3.0 - -## 5.2.1 - -### Patch Changes - -- @object-ui/types@5.2.1 - -## 5.2.0 - -### Patch Changes - -- Updated dependencies [de0c5e6] -- Updated dependencies [9997cae] -- Updated dependencies [70b5570] - - @object-ui/types@5.2.0 - -## 5.1.1 - -### Patch Changes - -- @object-ui/types@5.1.1 - -## 5.1.0 - -### Patch Changes - -- Updated dependencies [cf30cc2] -- Updated dependencies [5b80cfd] - - @object-ui/types@5.1.0 - -## 5.0.2 - -### Patch Changes - -- @object-ui/types@5.0.2 - -## 5.0.1 - -### Patch Changes - -- @object-ui/types@5.0.1 - -## 5.0.0 - -### Patch Changes - -- Updated dependencies [7213027] - - @object-ui/types@5.0.0 - -## 4.8.0 - -### Patch Changes - -- @object-ui/types@4.8.0 - -## 4.7.0 - -### Patch Changes - -- @object-ui/types@4.7.0 - -## 4.6.0 - -### Patch Changes - -- @object-ui/types@4.6.0 - -## 4.5.0 - -### Patch Changes - -- Updated dependencies [ab5e281] - - @object-ui/types@4.5.0 - -## 4.4.0 - -### Patch Changes - -- @object-ui/types@4.4.0 - -## 4.3.1 - -### Patch Changes - -- @object-ui/types@4.3.1 - -## 4.3.0 - -### Patch Changes - -- @object-ui/types@4.3.0 - -## 4.2.1 - -### Patch Changes - -- @object-ui/types@4.2.1 - -## 4.2.0 - -### Patch Changes - -- @object-ui/types@4.2.0 - -## 4.1.0 - -### Patch Changes - -- @object-ui/types@4.1.0 - -## 4.0.12 - -### Patch Changes - -- @object-ui/types@4.0.12 - -## 4.0.11 - -### Patch Changes - -- @object-ui/types@4.0.11 - -## 4.0.10 - -### Patch Changes - -- @object-ui/types@4.0.10 - -## 4.0.9 - -### Patch Changes - -- @object-ui/types@4.0.9 - -## 4.0.8 - -### Patch Changes - -- @object-ui/types@4.0.8 - -## 4.0.7 - -### Patch Changes - -- @object-ui/types@4.0.7 - -## 4.0.6 - -### Patch Changes - -- @object-ui/types@4.0.6 - -## 4.0.5 - -### Patch Changes - -- @object-ui/types@4.0.5 - -## 4.0.4 - -### Patch Changes - -- @object-ui/types@4.0.4 - -## 4.0.3 - -### Patch Changes - -- 4be43e2: **Page-mode record forms (`editMode: 'page'`).** New per-object metadata flag that opts a record's create/edit form into a dedicated full-screen route (`/apps/:appName/:objectName/new`, `/apps/:appName/:objectName/record/:recordId/edit`). Two new declarative actions `navigate_create` and `navigate_edit` open these routes from JSON action buttons. Default modal behavior is preserved for objects that do not set `editMode`. - - **`@object-ui/plugin-list` & `@object-ui/plugin-detail`: `ComponentRegistry` singleton fix.** Both plugins' Vite configs now mark all `@object-ui/*` packages as external so each plugin no longer bundles its own private copy of `@object-ui/core`. Cross-plugin component lookups now resolve correctly from the same singleton registry. `plugin-list` dist shrank from multi-MB to 67 kB (gzip 16 kB); `plugin-detail` to 124 kB (gzip 28 kB). - - **`@object-ui/app-shell` `CreateViewDialog` churn fix.** `existingSet` is now memoised on the joined string key of `existingLabels` rather than the raw array reference, preventing the name-suggest `useEffect` from re-firing on every parent render. - - **CI fixes.** `ReportViewer` conditional-formatting test now accepts both `rgb(...)` and hex color representations. `ObjectView` i18n mocks rewritten to mirror the real hook shapes (`useObjectTranslation`, `useObjectLabel`). - -- Updated dependencies [4be43e2] - - @object-ui/types@4.0.3 - -## 4.0.1 - -### Patch Changes - -- @object-ui/types@4.0.1 - -## 4.0.0 - -### Patch Changes - -- Updated dependencies - - @object-ui/types@4.0.0 - -## 3.3.3 - -### Patch Changes - -- a2d7023: End-user feature batch — forms, designer history, import/export, and PWA offline sync. - - **Forms (`@object-ui/fields`, `@object-ui/providers`)** - - - `FileField`: native `` camera capture for mobile devices, plus a uploading-progress indicator driven by `UploadProvider`. - - `ImageField`: per-image inline crop/rotate via the lazy-loaded `ImageCropperDialog` (canvas-based, zero new deps). - - New `UploadProvider` in `@object-ui/providers` with pluggable adapters for S3 and Azure Blob (plus the default object-URL adapter for local previews). XHR-based with progress, abort, and retry. - - `LookupField`: `lookup.dependsOn: string | string[]` to chain dependent lookups (e.g. State depends on Country); the trigger is gated until parent values are present and the OData `$filter` is built automatically. - - **Container-aware widget widths (`@object-ui/components`)** - - - New `useResizeObserver(ref)` hook exposing `{ width, height }` of any element. SSR-safe; reads the initial size via `getBoundingClientRect`. - - `plugin-gantt` and `plugin-kanban` now react to their container size instead of `window.innerWidth`, so they behave correctly inside split panels and dashboards. - - **Designer history (`@object-ui/plugin-designer`)** - - - `useUndoRedo` (and therefore `useDesignerHistory`) gains `persistKey` + `storage` options to round-trip the undo/redo stack through `sessionStorage`, plus a `clearPersisted()` cleanup helper. Drafts now survive accidental tab refreshes. - - New `` component renders the timeline visually with one-click jump-to-checkpoint via the new `jumpTo(index)` API. - - **Import wizard (`@object-ui/plugin-grid`)** - - - Saved column-mapping templates: name, save, re-apply, and delete via a new template bar in the mapping step. Persisted under `objectui:import-templates:${objectName}` (override via `templateStorageKey` / `templateStorage`). - - Inline validation correction: cells with errors in the preview step are now editable; corrections feed straight into the import without requiring a re-upload, with green-bar status indicators for fixed rows. - - **PWA offline sync (`@object-ui/mobile`)** - - - New `MemoryOfflineQueue` / `IndexedDbOfflineQueue` (`createOfflineQueue()` picks the best backend) backed by IndexedDB. - - `createOfflineDataSource(inner, { queue })` wraps any DataSource so mutations issued while offline (or that fail with a network-style error) are queued and replayed in order on reconnect. Includes `replay()`, `drop()`, `clear()`, `pending()`, an `onChange` notifier, and an opt-in `resolveConflict` hook for stale-write conflicts. - - New `useOfflineSync(source)` hook exposes `{ isOnline, pending, isReplaying, replay, drop, clear }` and auto-replays on the browser's `online` event. - - `getServiceWorkerSource(opts)` emits a customisable Service Worker that pre-caches the app shell, applies network-first to API requests, and broadcasts `REPLAY_QUEUE` to clients on Background Sync. `requestBackgroundSync(tag)` registers a one-shot sync from the page. - -- Updated dependencies [f1ca238] -- Updated dependencies [de881ef] - - @object-ui/types@3.4.0 - -## 3.3.2 - -### Patch Changes - -- @object-ui/types@3.3.2 - -## 3.3.1 - -### Patch Changes - -- @object-ui/types@3.3.1 - -All notable changes to this package will be documented in this file. -See the [monorepo CHANGELOG](../../CHANGELOG.md) for cross-package release notes. diff --git a/ui/packages/providers/LICENSE b/ui/packages/providers/LICENSE deleted file mode 100644 index cf2ca28574c..00000000000 --- a/ui/packages/providers/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 ObjectQL - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/ui/packages/providers/README.md b/ui/packages/providers/README.md deleted file mode 100644 index 92569d24de2..00000000000 --- a/ui/packages/providers/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# @object-ui/providers - -**Reusable Context Providers for ObjectUI** - -React context providers for ObjectUI applications that third-party systems can use without console dependencies — today, the upload adapter context. - -## Installation - -```bash -pnpm add @object-ui/providers -``` - -## Providers - -Every example below compiles against this package's built types. The values your -own app supplies are written as `declare const` stand-ins so each block stands -alone. - -### UploadProvider - -The ambient upload adapter the `@object-ui/fields` file, image and avatar widgets -upload through (`useUpload()` reads it). Without a provider they fall back to -`createObjectUrlAdapter()`, which persists nothing and is a no-crash default, not -an upload destination. - -```tsx -import type { ReactNode } from 'react'; -import { UploadProvider, createObjectStackUploadAdapter } from '@object-ui/providers'; - -declare const App: () => ReactNode; - -const adapter = createObjectStackUploadAdapter({ baseUrl: 'https://api.example.com' }); - - - - -``` - -`createS3Adapter` and `createAzureBlobAdapter` build adapters for presigned -S3 and Azure Blob uploads; any object with a `name` and an `upload(file, opts)` -method is an `UploadAdapter`. - -## Retired providers - -`DataSourceProvider` / `useDataSource`, `MetadataProvider` / `useMetadata` and -`ThemeProvider` / `useTheme` are no longer exported from this package. Use the -ones the rest of ObjectUI reads instead: - -- the data source: `SchemaRendererProvider` (its `dataSource` prop) from - `@object-ui/react`; -- metadata: `MetadataProvider` / `useMetadata` from `@object-ui/app-shell`; -- theme: `ThemeProvider` / `useTheme` from `@object-ui/react` (`defaultMode` - takes `auto | light | dark`; the old `system` spelling is `auto`). - -## Links - -- 📦 [npm package](https://www.npmjs.com/package/@object-ui/providers) -- 📝 [Changelog](./CHANGELOG.md) -- 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues) -- 🤝 [Contributing Guide](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md) -- 🗺️ [Roadmap](https://github.com/objectstack-ai/objectstack/blob/main/ROADMAP.md) - -## License - -MIT — see [LICENSE](./LICENSE). diff --git a/ui/packages/providers/package.json b/ui/packages/providers/package.json deleted file mode 100644 index 88b3a1cb7bc..00000000000 --- a/ui/packages/providers/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@object-ui/providers", - "version": "18.3.0", - "type": "module", - "license": "MIT", - "description": "Reusable context providers for ObjectUI applications", - "homepage": "https://objectstack.ai/docs/frontend", - "repository": { - "type": "git", - "url": "git+https://github.com/objectstack-ai/objectstack.git", - "directory": "ui/packages/providers" - }, - "bugs": { - "url": "https://github.com/objectstack-ai/objectui/issues" - }, - "main": "./dist/index.js", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "sideEffects": false, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc && node ../../scripts/check-dist-completeness.mjs", - "test": "vitest run --root ../.. packages/providers/", - "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", - "lint": "eslint ." - }, - "dependencies": { - "@object-ui/types": "workspace:*" - }, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "@types/react": "19.3.0", - "@types/react-dom": "19.3.0", - "typescript": "^6.0.3", - "vitest": "^4.1.10" - }, - "keywords": [ - "objectui", - "sdui", - "schema-driven-ui", - "react", - "tailwind", - "shadcn", - "objectstack", - "providers", - "context", - "data-source", - "theme" - ], - "author": "ObjectStack Team ", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist", - "README.md", - "CHANGELOG.md", - "LICENSE" - ] -} diff --git a/ui/packages/providers/src/index.ts b/ui/packages/providers/src/index.ts deleted file mode 100644 index eeab3e9c2ac..00000000000 --- a/ui/packages/providers/src/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @object-ui/providers - * - * Reusable context providers for ObjectUI applications - */ - -// `DataSourceProvider` / `useDataSource`, `MetadataProvider` / `useMetadata` -// and `ThemeProvider` / `useTheme` retired in objectui#13453: they duplicated -// names `@object-ui/react` and `@object-ui/app-shell` publish with different -// implementations, and nothing in the repo but one example read them. -export { - UploadProvider, - useUpload, - createObjectUrlAdapter, - createS3Adapter, - createAzureBlobAdapter, - createObjectStackUploadAdapter, -} from './UploadProvider.js'; - -export type { - UploadAdapter, - UploadOptions, - UploadResult, - UploadProviderProps, - S3AdapterOptions, - AzureBlobAdapterOptions, - ObjectStackUploadAdapterOptions, -} from './UploadProvider.js'; diff --git a/ui/packages/providers/tsconfig.json b/ui/packages/providers/tsconfig.json deleted file mode 100644 index 685a6a22fc3..00000000000 --- a/ui/packages/providers/tsconfig.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - "lib": ["ES2020", "DOM"], - - // Node's ESM resolver does NOT extension-search relative specifiers, so an - // extensionless `./Foo` in the emitted dist is unloadable under plain Node - // (`ERR_MODULE_NOT_FOUND`) — objectui#5214, same defect and same remedy as - // objectui#4538. `tsc` never rewrites specifiers, so what the source writes - // is what dist ships; the only way to KEEP the extensions is to have the - // compiler reject a missing one (TS2835, and TS2834 for a bare directory - // import) instead of leaving it to review. - // - // These override the ROOT tsconfig's `"module": "ESNext"` / - // `"moduleResolution": "bundler"` for THIS package only — the root is - // shared by 38 packages and flipping it there is a different, much larger - // change. Emit is unchanged: package.json is `"type": "module"`, so - // `nodenext` emits the same ESM `ESNext` did. - // - // Runtime loadability is pinned separately by `pnpm check:node-esm-load`, - // which actually imports the built entry under plain Node. - "module": "nodenext", - "moduleResolution": "nodenext", - "noEmit": false, - "declaration": true, - // The incremental build RECORD, pinned OUTSIDE the published build output. - // Left unpinned, TypeScript derives it from `outDir`/`rootDir`: a package with - // no `rootDir` gets `dist/tsconfig.tsbuildinfo` -- inside what `files: ["dist", - // ...]` publishes -- and `pnpm check:published-dist` then reds on a byte-identical - // tree as soon as anything runs `tsc` (objectui#9189). Pinned in every composite - // package so the location is a decision, not a side effect of an unrelated option. - "tsBuildInfoFile": "./tsconfig.tsbuildinfo", - "composite": true - }, - "include": ["src"], - // Tooling is excluded by DIRECTORY, not just by file NAME. A helper under - // `__tests__/` that is not itself called `*.test.ts` is otherwise a program - // input, and an EMITTING program writes it into the published `dist` - // (objectui#4006 / #4836 / #6943 -- the same defect three times, each found - // by hand). Enforced repo-wide by `pnpm check:published-tsconfig-exclude` - // (objectui#7212), which derives these directory names from `TOOLING_FILE` - // in scripts/check-phantom-dependencies.mjs. - "exclude": [ - "node_modules", - "dist", - "**/__tests__/**", - "**/__mocks__/**", - "**/__benchmarks__/**", - "**/*.test.ts", - "**/*.test.tsx" - ], - "references": [ - { "path": "../types" } - ] -} diff --git a/ui/packages/providers/tsconfig.test.json b/ui/packages/providers/tsconfig.test.json deleted file mode 100644 index ddbfa982654..00000000000 --- a/ui/packages/providers/tsconfig.test.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - // Type-checks this package's TESTS, which `tsconfig.json` excludes. - // See `packages/types/tsconfig.test.json` for why that exclusion was a hole: - // the build correctly keeps tests out of `dist`, but nothing else compiled - // them, so a test could assert a contract the compiler never checked. - "extends": "../../tsconfig.json", - "compilerOptions": { - // objectui#8691: test programs only. One notch above the package build's - // ES2020, mirroring packages/app-shell. The package SOURCE tsconfig is - // untouched, so what this package SHIPS still compiles at the ES2020 - // baseline; nothing here is emitted. - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "noEmit": true, - // The package build emits `dist`; this project emits nothing, so it must - // not inherit `composite` / `declaration` from the build config. - "composite": false, - // Drop the root tsconfig's source-tree `paths` so `@object-ui/*` resolves - // through the workspace dependency's built `.d.ts` instead of pulling - // sibling sources in as program inputs (TS6059). - "paths": {} - }, - "include": ["src/**/*.test.ts", "src/**/*.test.tsx"] -} diff --git a/ui/packages/runner/vite.config.ts b/ui/packages/runner/vite.config.ts index f58dfa22447..df407aea3b5 100644 --- a/ui/packages/runner/vite.config.ts +++ b/ui/packages/runner/vite.config.ts @@ -116,8 +116,6 @@ export default defineConfig({ "@object-ui/fields": path.resolve(__dirname, "../../packages/fields/src"), // `@object-ui/plugin-detail` <- plugin-kanban/src/ObjectKanban.tsx "@object-ui/plugin-detail": path.resolve(__dirname, "../../packages/plugin-detail/src"), - // `@object-ui/providers` <- fields/src/widgets/{FileField,ImageField}.tsx (2nd layer) - "@object-ui/providers": path.resolve(__dirname, "../../packages/providers/src"), // `@object-ui/permissions` <- plugin-detail/src/DetailView.tsx et al. (2nd layer) "@object-ui/permissions": path.resolve(__dirname, "../../packages/permissions/src"), // `@object-ui/data-objectstack` <- react/src/context/AppShellContext.tsx, `import type` diff --git a/ui/packages/types/src/index.ts b/ui/packages/types/src/index.ts index baf0622bf6c..70f02ccfeee 100644 --- a/ui/packages/types/src/index.ts +++ b/ui/packages/types/src/index.ts @@ -493,6 +493,10 @@ export type { ExportDownloadRequest, } from './data.js'; +// The upload adapter contract: `UploadProvider` (`@object-ui/fields`) reads it, +// `createObjectStackUploadAdapter` (`@object-ui/data-objectstack`) implements it. +export type { UploadResult, UploadOptions, UploadAdapter } from './upload.js'; + // ============================================================================ // CRUD Components - Create, Read, Update, Delete Operations // ============================================================================ diff --git a/ui/packages/types/src/upload.ts b/ui/packages/types/src/upload.ts new file mode 100644 index 00000000000..7e34de8b522 --- /dev/null +++ b/ui/packages/types/src/upload.ts @@ -0,0 +1,63 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @object-ui/types - Upload adapter contract + * + * The contract between the upload context that the file, image and avatar + * widgets read (`UploadProvider` / `useUpload` in `@object-ui/fields`) and an + * upload destination (`createObjectStackUploadAdapter` in + * `@object-ui/data-objectstack`, or a host's own adapter). It lives here, as + * the `DataSource` interface does, because those two packages do not depend on + * each other and both read it from this one. + * + * @module upload + * @packageDocumentation + */ + +/** What an {@link UploadAdapter} answers for one stored file. */ +export interface UploadResult { + /** Canonical, shareable URL (e.g. https://my-bucket.s3.amazonaws.com/avatars/abc.png). */ + url: string; + /** Stored object name / key. */ + name: string; + size: number; + mimeType: string; + /** Adapter-specific metadata (etag, version, etc.). */ + meta?: Record; +} + +/** Per-upload options an {@link UploadAdapter} receives. */ +export interface UploadOptions { + /** + * Logical folder / key prefix (e.g. "avatars/") for an adapter that keys + * objects by path. `createObjectStackUploadAdapter` never sends it: that + * server derives the object key itself, and a path is not a storage scope + * (objectui#12055). + */ + path?: string; + /** Progress callback in 0–1. */ + onProgress?: (ratio: number) => void; + /** External abort signal — cancels the upload (and any retry attempts). */ + signal?: AbortSignal; + /** Number of retry attempts on network failure. @default 3 */ + maxRetries?: number; + /** Initial backoff in ms; exponential. @default 500 */ + retryDelayMs?: number; +} + +/** + * A file upload destination: any object with a `name` and an `upload` method. + * `UploadProvider` (`@object-ui/fields`) makes one ambient for the widgets; + * `createObjectStackUploadAdapter` (`@object-ui/data-objectstack`) builds the + * one for an ObjectStack server's storage service. + */ +export interface UploadAdapter { + readonly name: string; + upload(file: File | Blob, opts?: UploadOptions): Promise; +} diff --git a/ui/scripts/__tests__/side-effects-declaration-consistency.test.ts b/ui/scripts/__tests__/side-effects-declaration-consistency.test.ts index 79a339c257b..5c33755671a 100644 --- a/ui/scripts/__tests__/side-effects-declaration-consistency.test.ts +++ b/ui/scripts/__tests__/side-effects-declaration-consistency.test.ts @@ -1110,7 +1110,6 @@ describe('`sideEffects` declarations match load-time behaviour (objectui#3943)', '@object-ui/plugin-timeline', '@object-ui/plugin-tree', '@object-ui/plugin-view', - '@object-ui/providers', '@object-ui/react', '@object-ui/react-runtime', '@object-ui/types', diff --git a/ui/vitest.config.mts b/ui/vitest.config.mts index 9e6d00040eb..658bd69c0f1 100644 --- a/ui/vitest.config.mts +++ b/ui/vitest.config.mts @@ -529,7 +529,6 @@ export default defineConfig({ '@object-ui/react': path.resolve(__dirname, './packages/react/src'), '@object-ui/protocol': path.resolve(__dirname, './packages/core/src'), '@object-ui/components': path.resolve(__dirname, './packages/components/src'), - '@object-ui/providers': path.resolve(__dirname, './packages/providers/src'), '@object-ui/fields': path.resolve(__dirname, './packages/fields/src'), '@object-ui/plugin-dashboard': path.resolve(__dirname, './packages/plugin-dashboard/src'), '@object-ui/plugin-grid': path.resolve(__dirname, './packages/plugin-grid/src'), From 4e1e0e0e34e03e0fa80cd6b6e9ae1588162209e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 15:13:34 +0000 Subject: [PATCH 2/3] test(ui): the doc-links README floor moves down by the two links the retired providers README carried Co-Authored-By: Claude --- ui/scripts/__tests__/check-doc-links.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/scripts/__tests__/check-doc-links.test.ts b/ui/scripts/__tests__/check-doc-links.test.ts index c69b9fbecfe..2923ed5cd04 100644 --- a/ui/scripts/__tests__/check-doc-links.test.ts +++ b/ui/scripts/__tests__/check-doc-links.test.ts @@ -1624,7 +1624,10 @@ describe('packages/*/README.md joined the disk surface — objectui#3622', () => // the VS Code extension package, whose README carried 12 / 8 of them, and // objectstack#27042 (c59b1bfa) retired `@object-ui/plugin-ai`, whose README // carried 3 / 1 more. Both floors move down by exactly those deletions. - expect(decidable.length).toBeGreaterThanOrEqual(126); + // 125 measured on objectstack#25921's providers fold: retiring + // `@object-ui/providers` deleted its README, which carried 2 / 0 of them + // (127 on the merge base), so only the decidable floor moves. + expect(decidable.length).toBeGreaterThanOrEqual(125); expect(siteAbsolute.length).toBeGreaterThanOrEqual(39); }); }); From 6f59446163b3e4b6f22017eb7b7be12a2eecb7f5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 15:53:47 +0000 Subject: [PATCH 3/3] docs(ui): the fields and data-objectstack READMEs carry the upload wiring the retired providers README held The changeset now declares the fold as a diff that widens two entries and narrows by retiring a package. Co-Authored-By: Claude --- .changeset/25921-providers-upload-fold.md | 2 +- ui/packages/data-objectstack/README.md | 13 ++++++++++ ui/packages/fields/README.md | 29 +++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.changeset/25921-providers-upload-fold.md b/.changeset/25921-providers-upload-fold.md index 3da1b4bb0b4..48667720096 100644 --- a/.changeset/25921-providers-upload-fold.md +++ b/.changeset/25921-providers-upload-fold.md @@ -5,7 +5,7 @@ refactor(fields, data-objectstack)!: `UploadProvider` moves into `@object-ui/fields`, the ObjectStack upload adapter into `@object-ui/data-objectstack`, and `@object-ui/providers` is retired -Clause-②: yes (widening) +Clause-②: yes (narrowing) — `@object-ui/fields` gains `UploadProvider`, `useUpload`, `UploadProviderProps` and the three upload contract types, and `@object-ui/data-objectstack` gains `createObjectStackUploadAdapter` and `ObjectStackUploadAdapterOptions`; the whole `@object-ui/providers` package is retired, which takes `createS3Adapter`, `createAzureBlobAdapter`, their option types and the exported `createObjectUrlAdapter` with it. Metadata-protocol: none diff --git a/ui/packages/data-objectstack/README.md b/ui/packages/data-objectstack/README.md index 34c3909f7a8..90f511bfebb 100644 --- a/ui/packages/data-objectstack/README.md +++ b/ui/packages/data-objectstack/README.md @@ -1097,6 +1097,19 @@ its own row. Only rows whose value is a JSON object are returned. A failed read loads as `{}`, and a failed write is reported to `onError` and swallowed. A key outside the family is refused and nothing is written. +## File Uploads + +`createObjectStackUploadAdapter` is the browser-side client for the server's +storage service (`/api/v1/storage` unless `basePath` says otherwise). It +requests a presigned upload, PUTs the bytes, and completes the upload, which +registers the `sys_file` row; it answers the stable `/files/FILE_ID` download +URL, with the `sys_file` id in `meta.fileId`. Hand it to `UploadProvider` from +`@object-ui/fields`, whose README shows the wiring. Its `scope` option is the +storage scope every upload from that adapter is filed under, typed by the +spec's upload-scope vocabulary (`API.UploadScope` from `@object-ui/types`; +`attachments` is the scope of files joined to records). Leave it out and the +request carries no scope, so the server applies its default. + ## API Reference ### ObjectStackAdapter diff --git a/ui/packages/fields/README.md b/ui/packages/fields/README.md index 963ac486842..06847e0fa8f 100644 --- a/ui/packages/fields/README.md +++ b/ui/packages/fields/README.md @@ -295,6 +295,35 @@ function PointerValue({ field, row }: { field: { name: string; type: string; ref } ``` +### Where file, image and avatar uploads go (`UploadProvider`) + +The file, image and avatar widgets upload through the adapter the nearest +`UploadProvider` holds, which `useUpload()` reads. Mount it above every place +those widgets render, dialogs included. An app on an ObjectStack server uses +`createObjectStackUploadAdapter` from `@object-ui/data-objectstack`: + +```tsx +import type { ReactNode } from 'react'; +import { UploadProvider } from '@object-ui/fields'; +import { createObjectStackUploadAdapter } from '@object-ui/data-objectstack'; + +declare const App: () => ReactNode; + +const adapter = createObjectStackUploadAdapter({ baseUrl: 'https://api.example.com' }); + + + + +``` + +Any other destination is an object with a `name` and an `upload(file, options)` +method, typed by `UploadAdapter` from `@object-ui/types` (this package +re-exports it with `UploadOptions` and `UploadResult`). With no provider +mounted, `useUpload()` falls back to a local adapter that keeps the file in a +`blob:` URL and stores nothing. The widgets refuse that result: it carries no +`sys_file` id, so the pick shows an "upload did not complete" error and the +field stores nothing. + ### File uploads in line-item grids `GridField` (the master-detail line-items grid) supports `type: 'file'` columns: