diff --git a/.changeset/25921-providers-upload-fold.md b/.changeset/25921-providers-upload-fold.md new file mode 100644 index 00000000000..48667720096 --- /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 (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 + + + +**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/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/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/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: 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__/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); }); }); 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'),