diff --git a/.changeset/13453-providers-retire-trio.md b/.changeset/13453-providers-retire-trio.md new file mode 100644 index 0000000000..f347ce2278 --- /dev/null +++ b/.changeset/13453-providers-retire-trio.md @@ -0,0 +1,10 @@ +--- +"@object-ui/providers": minor +--- + +Clause-②: no +Metadata-protocol: none + +**Breaking:** removed `DataSourceProvider` / `useDataSource`, `MetadataProvider` / `useMetadata`, `ThemeProvider` / `useTheme` and the types `DataSourceProviderProps`, `MetadataProviderProps`, `ThemePreferenceProviderProps`, `ThemePreference` (objectui#13453). They duplicated names other ObjectUI packages publish with different implementations. Declared `minor` per the version-alignment policy (a changeset never declares `major`). `UploadProvider`, `useUpload` and the upload adapters are unchanged. + +Migration: wrap the tree in `SchemaRendererProvider` from `@object-ui/react` (its `dataSource` prop) instead of `DataSourceProvider`, and pass the adapter to views as before; use `ThemeProvider` / `useTheme` from `@object-ui/react` (`defaultMode`, where the old `system` is `auto`; add `persist` to keep the choice in `localStorage`); use `MetadataProvider` / `useMetadata` from `@object-ui/app-shell`. diff --git a/.claude/skills/objectui-contributor/guides/console-development.md b/.claude/skills/objectui-contributor/guides/console-development.md index 6372632e82..a5f74d0098 100644 --- a/.claude/skills/objectui-contributor/guides/console-development.md +++ b/.claude/skills/objectui-contributor/guides/console-development.md @@ -404,11 +404,10 @@ prefix across the table. | `FavoritesProvider` | `packages/app-shell/src/context/FavoritesProvider.tsx` | User's starred/pinned items | | `ExpressionProvider` | `packages/app-shell/src/providers/ExpressionProvider.tsx` | Expression evaluator instance | -`MetadataProvider` is the one name with two hits in the repo. The console's is the -app-shell file above — it is what `ConsoleShell` / `ConnectedShell` mount and what -`@object-ui/app-shell` re-exports. `packages/providers/src/MetadataProvider.tsx` is an -unrelated, much smaller generic provider (static metadata or one fetch); nothing in the -console path uses it. +`MetadataProvider` is the app-shell file above — it is what `ConsoleShell` / +`ConnectedShell` mount and what `@object-ui/app-shell` re-exports. `@object-ui/providers` +used to publish an unrelated generic provider under the same name; it retired in +objectui#13453, so the app-shell one is the only `MetadataProvider` left. ## Key hooks diff --git a/README.md b/README.md index 14b46c6202..cd47b895cd 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Pick the path that matches where you are starting from: | You want to… | Start here | Install | |---|---|---| | Render a JSON schema inside your React app | [`examples/hello-world`](examples/hello-world) | `@object-ui/react @object-ui/components` | -| Embed views in an existing app, against **your own** backend | [`examples/byo-backend-console`](examples/byo-backend-console) | `@object-ui/app-shell @object-ui/plugin-view @object-ui/providers` | +| Embed views in an existing app, against **your own** backend | [`examples/byo-backend-console`](examples/byo-backend-console) | `@object-ui/app-shell @object-ui/plugin-view @object-ui/react` | | Stand up a complete console on an **ObjectStack** backend | [`examples/console-starter`](examples/console-starter) | fork the example | | Build a UI from a JSON file, no React code at all | [`@object-ui/cli`](packages/cli) | `npm install -g @object-ui/cli` | @@ -120,14 +120,14 @@ nothing thrown and one line on the console. Use the shell and views without the full console infrastructure — your routing, your auth, your API: ```bash -npm install @object-ui/app-shell @object-ui/plugin-view @object-ui/providers +npm install @object-ui/app-shell @object-ui/plugin-view @object-ui/react ``` ```tsx import type { FC } from 'react'; import { AppShell } from '@object-ui/app-shell'; import { ObjectView } from '@object-ui/plugin-view'; -import { ThemeProvider, DataSourceProvider, useDataSource } from '@object-ui/providers'; +import { ThemeProvider, SchemaRendererProvider } from '@object-ui/react'; import type { DataSource } from '@object-ui/types'; // The two pieces you bring: the backend adapter you implement (see "Custom @@ -138,19 +138,18 @@ declare const MySidebar: FC; function MyConsole() { return ( - + }> - + ); } function ContactList() { - const dataSource = useDataSource(); return ( - + ); } ``` @@ -380,7 +379,7 @@ Grouped by dependency weight — atoms and fields stay light, heavy widgets live | Package | Description | |---------|-------------| | **[@object-ui/app-shell](./packages/app-shell)** | Minimal application shell — the base of every console | -| **[@object-ui/providers](./packages/providers)** | Theme, data-source, and other reusable context providers | +| **[@object-ui/providers](./packages/providers)** | The upload adapter context the file and image fields upload through | | **[@object-ui/auth](./packages/auth)** | `AuthProvider`, `useAuth`, `AuthGuard`, sign-in forms | | **[@object-ui/permissions](./packages/permissions)** | Object / field / row-level permission guards and hooks | | **[@object-ui/i18n](./packages/i18n)** | Language packs, RTL layout, date and currency formatting | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cfc4a04e2d..62b9efc0dc 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -31,16 +31,18 @@ This document describes the refactored architecture that enables third-party sys ### @object-ui/providers -**Purpose**: Reusable context providers +**Purpose**: The upload adapter context **Exports**: -- `DataSourceProvider` - Generic data source context -- `MetadataProvider` - Schema/metadata management -- `ThemeProvider` - Theme management +- `UploadProvider` / `useUpload` - The ambient upload adapter the file and image fields upload through +- `createObjectStackUploadAdapter`, `createS3Adapter`, `createAzureBlobAdapter`, `createObjectUrlAdapter` -**Dependencies**: `@object-ui/types` +Its former `DataSourceProvider`, `MetadataProvider` and `ThemeProvider` retired +(objectui#13453). The data source is `SchemaRendererProvider` and the theme is +`ThemeProvider`, both from `@object-ui/react`; metadata is `MetadataProvider` from +`@object-ui/app-shell`. -**Bundle Size**: ~10KB +**Dependencies**: `@object-ui/types` ## Architecture Diagram @@ -64,15 +66,10 @@ This document describes the refactored architecture that enables third-party sys └────────────────┬────────────────────────┘ │ ┌────────────────┴────────────────────────┐ -│ @object-ui/providers │ -│ - DataSourceProvider │ -│ - MetadataProvider │ -│ - ThemeProvider │ -└────────────────┬────────────────────────┘ - │ -┌────────────────┴────────────────────────┐ │ @object-ui/react │ │ - SchemaRenderer │ +│ - SchemaRendererProvider (data source) │ +│ - ThemeProvider │ │ - useActionRunner │ │ - Component Registry │ └────────────────┬────────────────────────┘ @@ -115,10 +112,9 @@ apps/console (500KB+) ├── Page Rendering └── Form Rendering -@object-ui/providers (10KB) -├── Generic DataSource -├── Metadata Management -└── Theme System +@object-ui/react +├── SchemaRendererProvider (generic DataSource) +└── ThemeProvider Third-Party App ├── Custom Routing @@ -162,32 +158,30 @@ Third-Party App import { AppShell } from '@object-ui/app-shell'; import type { MinimalAppShellProps } from '@object-ui/app-shell'; import { ObjectView } from '@object-ui/plugin-view'; -import { DataSourceProvider, useDataSource } from '@object-ui/providers'; -import type { DataSourceProviderProps } from '@object-ui/providers'; +import { SchemaRendererProvider } from '@object-ui/react'; +import type { DataSource } from '@object-ui/types'; // The two things you bring. Both are typed from the surface these packages // ship, so this snippet compiles on its own: `myAPI` is your backend adapter -// (the "Custom Data Source Interface" section below is the shape it needs at -// runtime — `DataSourceProvider` declares the prop `any`, so the annotation -// records where the value goes rather than checking it), and `MySidebar` is -// your own component, returning whatever `AppShell` accepts for `sidebar`. -declare const myAPI: DataSourceProviderProps['dataSource']; +// (the "Custom Data Source Interface" section below is the shape it needs), +// and `MySidebar` is your own component, returning whatever `AppShell` +// accepts for `sidebar`. +declare const myAPI: DataSource; declare function MySidebar(): MinimalAppShellProps['sidebar']; function MyConsole() { return ( - + }> - + ); } function ContactList() { - const dataSource = useDataSource(); return ( - + ); } ``` @@ -198,8 +192,8 @@ Both files below open with `'use client'`. Under the Next.js App Router every module under `app/` is a **server** component by default, and the `@object-ui/*` packages carry no `'use client'` banner of their own — so the client boundary has to be declared here, in the files that import them. Without the directive the -layout's React context providers and the page's `useDataSource()` hook are both -asked to run on the server, where neither exists. +layout's React context providers and the page's client view are both asked to +run on the server, where neither exists. `app/layout.tsx`: @@ -208,7 +202,7 @@ asked to run on the server, where neither exists. import { AppShell } from '@object-ui/app-shell'; import type { MinimalAppShellProps } from '@object-ui/app-shell'; -import { ThemeProvider } from '@object-ui/providers'; +import { ThemeProvider } from '@object-ui/react'; export default function RootLayout({ children }: { children: MinimalAppShellProps['children'] }) { return ( @@ -225,11 +219,13 @@ export default function RootLayout({ children }: { children: MinimalAppShellProp 'use client'; import { ObjectView } from '@object-ui/plugin-view'; -import { useDataSource } from '@object-ui/providers'; +import type { DataSource } from '@object-ui/types'; + +// Your backend adapter, imported from your own module. +declare const myAPI: DataSource; export default function Page({ params }: { params: { object: string } }) { - const dataSource = useDataSource(); - return ; + return ; } ``` @@ -237,12 +233,12 @@ export default function Page({ params }: { params: { object: string } }) { ```tsx import { ObjectView } from '@object-ui/plugin-view'; -import { DataSourceProvider, useDataSource } from '@object-ui/providers'; -import type { DataSourceProviderProps } from '@object-ui/providers'; +import { SchemaRendererProvider } from '@object-ui/react'; +import type { DataSource } from '@object-ui/types'; // Your backend adapter, as in Example 1 — declared here because every block on // this page compiles on its own. -declare const myAPI: DataSourceProviderProps['dataSource']; +declare const myAPI: DataSource; function MyExistingApp() { return ( @@ -250,9 +246,9 @@ function MyExistingApp() {
My App Header
{/* Embed ObjectUI widget */} - + - +
My App Footer
@@ -260,8 +256,7 @@ function MyExistingApp() { } function ContactWidget() { - const dataSource = useDataSource(); - return ; + return ; } ``` diff --git a/docs/audits/2901-spec-enum-renderer-coverage.md b/docs/audits/2901-spec-enum-renderer-coverage.md index 4999ad3296..1e445cbb54 100644 --- a/docs/audits/2901-spec-enum-renderer-coverage.md +++ b/docs/audits/2901-spec-enum-renderer-coverage.md @@ -76,7 +76,7 @@ The #2897 shape. Validates, renders nothing, nobody knows it should be full. | 7 chart types draw an **empty plot** | [AdvancedChartImpl.tsx:343](../../packages/plugin-charts/src/AdvancedChartImpl.tsx) | Grid, axes, tooltip and legend all render; every series mark hits `return null`. Looks like an empty dataset. | | 6 of 9 animation presets do nothing | [useAnimation.ts:64](../../packages/react/src/hooks/useAnimation.ts) | `PRESET_CLASSES` is keyed in **hyphens** (`'slide-up'`), the spec enum in **underscores** (`slide_up`); `rotate`/`flip` absent entirely. Lookup misses → `''` → no animation. | | 3 of 6 easings emit **invalid CSS** | [useAnimation.ts:112](../../packages/react/src/hooks/useAnimation.ts) | Same split, but `EASING_MAP[easing] \|\| easing` falls through to the raw string. `animationTimingFunction: 'ease_in_out'` — browser drops it, devtools shows the attribute populated. | -| `mode: 'auto'` breaks theming | [ThemeProvider.tsx:38](../../packages/app-shell/src/chrome/ThemeProvider.tsx) + [providers/ThemeProvider.tsx:33](../../packages/providers/src/ThemeProvider.tsx) | Renderer branches on `"system"`; spec says `auto`. `root.classList.add('auto')` matches no Tailwind variant → light theme, OS preference no longer followed, no error. | +| `mode: 'auto'` breaks theming | [ThemeProvider.tsx:38](../../packages/app-shell/src/chrome/ThemeProvider.tsx) + `providers/ThemeProvider.tsx` (retired in objectui#13453) | Renderer branches on `"system"`; spec says `auto`. `root.classList.add('auto')` matches no Tailwind variant → light theme, OS preference no longer followed, no error. | | Export **PDF** downloads nothing | [ObjectGrid.tsx:1258](../../packages/plugin-grid/src/ObjectGrid.tsx), [ListView.tsx:1611](../../packages/plugin-list/src/ListView.tsx) | Excluded from `serverEligible` and absent from the client fallback. Popover closes, no file. `xlsx` does the same whenever the server path is unavailable. | | Timeline `scale` blanks the axis | [renderer.tsx:270](../../packages/plugin-timeline/src/renderer.tsx) | `hour`/`quarter`/`year` produce zero header columns. All six are ignored entirely on the default `vertical` variant. | | All 6 toast positions discarded | [toaster.tsx:15](../../packages/components/src/renderers/feedback/toaster.tsx) | `` with no props, `inputs: []`. | diff --git a/examples/README.md b/examples/README.md index 6b7f5648e7..d1ae422257 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,7 +7,7 @@ Runnable examples that show how to consume ObjectUI in different scenarios. Pick | Example | Use it when… | Backend | Bundle | What it gives you | |---|---|---|---|---| | [`hello-world/`](./hello-world) | You want the smallest possible `` demo to see the JSON → React pipeline. | None (in-memory schema) | Tiny | A single `App.tsx` rendering one schema. | -| [`byo-backend-console/`](./byo-backend-console) | You want to **embed** ObjectUI into your own app and connect it to **your own** REST/GraphQL backend. | Bring-your-own (mock REST shown) | ~50 KB | `@object-ui/app-shell` + `@object-ui/providers`, custom router, custom `DataSource`. ~100 lines of integration code, no auth, no plugins. | +| [`byo-backend-console/`](./byo-backend-console) | You want to **embed** ObjectUI into your own app and connect it to **your own** REST/GraphQL backend. | Bring-your-own (mock REST shown) | ~50 KB | `@object-ui/app-shell` + `@object-ui/react`, custom router, custom `DataSource`. ~100 lines of integration code, no auth, no plugins. | | [`console-starter/`](./console-starter) | You want a **fork-ready, opinionated console** with the full plugin set already wired against an **ObjectStack** backend. | ObjectStack (`/api/v1`) | Full | `@object-ui/app-shell` `ConsoleShell` + `AuthProvider` + every `@object-ui/plugin-*` (grid, kanban, calendar, charts, dashboard, designer, detail, form, list, report, view, chatbot). | > **Rule of thumb:** diff --git a/examples/byo-backend-console/README.md b/examples/byo-backend-console/README.md index 6a0cdfcc10..b4e05114bd 100644 --- a/examples/byo-backend-console/README.md +++ b/examples/byo-backend-console/README.md @@ -10,7 +10,7 @@ This example demonstrates how third-party systems can integrate ObjectUI compone - ✅ Custom data adapter (mock REST API, not ObjectStack) - ✅ Using `@object-ui/app-shell` for the page shell - ✅ Using `@object-ui/plugin-view` for object views (`ObjectView`) -- ✅ Using `@object-ui/providers` for context +- ✅ Using `@object-ui/react` for the theme and the ambient data source - ✅ No console dependencies - ✅ ~100 lines of integration code @@ -65,18 +65,18 @@ object view: import { Routes, Route, useParams } from 'react-router-dom'; import { AppShell } from '@object-ui/app-shell'; import { ObjectView } from '@object-ui/plugin-view'; -import { ThemeProvider, DataSourceProvider } from '@object-ui/providers'; +import { ThemeProvider, SchemaRendererProvider } from '@object-ui/react'; function App() { return ( - + }> } /> - + ); } @@ -101,7 +101,7 @@ function ObjectPage() { Full control over routes - no predefined structure: ```tsx -import { useDataSource } from '@object-ui/providers'; +import { mockDataSource } from './mockDataSource'; function AppRoutes() { return ( @@ -114,15 +114,13 @@ function AppRoutes() { ); } -// One small component per object. Inside `DataSourceProvider`, `useDataSource()` -// supplies the data source that `ObjectView` requires. +// One small component per object. `ObjectView` requires its `dataSource` +// prop, so pass your adapter in. function ObjectListPage({ objectName }: { objectName: string }) { - const dataSource = useDataSource(); - return ( ); } @@ -143,11 +141,9 @@ export default function RootLayout({ children }: { children: React.ReactNode }) // app/[object]/page.tsx 'use client'; import { ObjectView } from '@object-ui/plugin-view'; -import { useDataSource } from '@object-ui/providers'; +import { dataSource } from '../../lib/dataSource'; // your adapter export default function ObjectPage({ params }: { params: { object: string } }) { - const dataSource = useDataSource(); - return ( + }> {/* ... */} - + ); } ``` diff --git a/examples/byo-backend-console/package.json b/examples/byo-backend-console/package.json index 86c77da0fb..09c59c92b6 100644 --- a/examples/byo-backend-console/package.json +++ b/examples/byo-backend-console/package.json @@ -16,7 +16,6 @@ "@object-ui/components": "workspace:*", "@object-ui/plugin-form": "workspace:*", "@object-ui/plugin-view": "workspace:*", - "@object-ui/providers": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", "react": "19.3.0", diff --git a/examples/byo-backend-console/src/App.tsx b/examples/byo-backend-console/src/App.tsx index 4f72ed81b4..6c011dc7ae 100644 --- a/examples/byo-backend-console/src/App.tsx +++ b/examples/byo-backend-console/src/App.tsx @@ -2,7 +2,7 @@ import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom' import { AppShell } from '@object-ui/app-shell'; import { ObjectView } from '@object-ui/plugin-view'; import { ObjectForm } from '@object-ui/plugin-form'; -import { ThemeProvider, DataSourceProvider } from '@object-ui/providers'; +import { ThemeProvider, SchemaRendererProvider } from '@object-ui/react'; import { mockDataSource } from './mockDataSource'; /** @@ -14,8 +14,8 @@ import { mockDataSource } from './mockDataSource'; function App() { return ( - - + + }> @@ -26,7 +26,7 @@ function App() { - + ); } @@ -83,7 +83,7 @@ function Home() {

Welcome to Minimal Console

This is a demonstration of third-party ObjectUI integration using - @object-ui/app-shell and @object-ui/providers. + @object-ui/app-shell and @object-ui/react.

diff --git a/packages/providers/README.md b/packages/providers/README.md index 3e986a6de1..6082476467 100644 --- a/packages/providers/README.md +++ b/packages/providers/README.md @@ -2,7 +2,7 @@ **Reusable Context Providers for ObjectUI** -A collection of framework-agnostic React context providers that can be used by third-party systems without console dependencies. +React context providers for ObjectUI applications that third-party systems can use without console dependencies — today, the upload adapter context. ## Installation @@ -14,97 +14,43 @@ pnpm add @object-ui/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, and each stand-in is typed from the prop it is passed to — so the bound -the example teaches is the bound the package actually declares. +alone. -### DataSourceProvider +### UploadProvider -Generic data source context that decouples ObjectUI from ObjectStack. +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 { DataSourceProvider, type DataSourceProviderProps } from '@object-ui/providers'; +import { UploadProvider, createObjectStackUploadAdapter } from '@object-ui/providers'; -// `DataSourceProviderProps['dataSource']` is declared `any` today, so this -// stand-in inherits `any`: the compiler checks nothing about the adapter's -// shape here (objectui#8160, objectui#7912 track that laundering). -declare const myCustomDataSource: DataSourceProviderProps['dataSource']; declare const App: () => ReactNode; - - - -``` - -### MetadataProvider +const adapter = createObjectStackUploadAdapter({ baseUrl: 'https://api.example.com' }); -Schema/metadata management for objects, fields, and views. - -```tsx -import type { ReactNode } from 'react'; -import { MetadataProvider, type MetadataProviderProps } from '@object-ui/providers'; - -// `MetadataProviderProps['metadata']` is declared `any` today — same bound as -// `dataSource` above, so nothing about this object's shape is checked here. -declare const myMetadata: MetadataProviderProps['metadata']; -declare const App: () => ReactNode; - - + - + ``` -### ThemeProvider - -Theme management with system theme detection. - -`defaultTheme` takes a `ThemePreference` (`auto | light | dark | system`); both -props are optional. +`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`. -```tsx -import type { ReactNode } from 'react'; -import { ThemeProvider } from '@object-ui/providers'; - -declare const App: () => ReactNode; +## 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: -## Usage Example - -`DataSourceProvider` and `MetadataProvider` both declare `children` as required, -so each one needs a real element inside it — a placeholder comment is not a -child. - -```tsx -import type { ReactNode } from 'react'; -import { - DataSourceProvider, - MetadataProvider, - ThemeProvider, - type DataSourceProviderProps, - type MetadataProviderProps, -} from '@object-ui/providers'; - -declare const myDataSource: DataSourceProviderProps['dataSource']; -declare const myMetadata: MetadataProviderProps['metadata']; -// Your own component tree goes here. -declare const AppContent: () => ReactNode; - -function App() { - return ( - - - - - - - - ); -} -``` +- 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 diff --git a/packages/providers/src/DataSourceProvider.tsx b/packages/providers/src/DataSourceProvider.tsx deleted file mode 100644 index 78bcc41ee9..0000000000 --- a/packages/providers/src/DataSourceProvider.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { createContext, useContext } from 'react'; -import type { DataSourceProviderProps } from './types.js'; - -const DataSourceContext = createContext(null); - -/** - * DataSourceProvider - Generic data source context - * - * Provides data source to child components without coupling to ObjectStack. - * Third-party systems can inject their own data adapters. - */ -export function DataSourceProvider({ - dataSource, - children, -}: DataSourceProviderProps) { - return ( - - {children} - - ); -} - -/** - * Hook to access data source from context - */ -export function useDataSource() { - const context = useContext(DataSourceContext); - if (!context) { - throw new Error('useDataSource must be used within DataSourceProvider'); - } - return context; -} diff --git a/packages/providers/src/MetadataProvider.tsx b/packages/providers/src/MetadataProvider.tsx deleted file mode 100644 index 1e5c826f9e..0000000000 --- a/packages/providers/src/MetadataProvider.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { createContext, useContext, useState, useEffect } from 'react'; -import type { MetadataProviderProps } from './types.js'; - -interface MetadataContextValue { - metadata: any; - loading: boolean; - error: string | null; - refetch: () => void; -} - -const MetadataContext = createContext(null); - -/** - * MetadataProvider - Schema/metadata management - * - * Provides application metadata (objects, fields, views) to child components. - * Can work with static metadata or fetch from API. - */ -export function MetadataProvider({ - metadata: initialMetadata, - children, -}: MetadataProviderProps) { - const [metadata, setMetadata] = useState(initialMetadata || null); - const [loading, setLoading] = useState(!initialMetadata); - - const refetch = () => { - // Placeholder for refetch logic - setLoading(true); - // In real implementation, this would call the API - setTimeout(() => { - setLoading(false); - }, 100); - }; - - useEffect(() => { - if (initialMetadata) { - setMetadata(initialMetadata); - setLoading(false); - } - }, [initialMetadata]); - - const value: MetadataContextValue = { - metadata, - loading, - error: null, - refetch, - }; - - return ( - - {children} - - ); -} - -/** - * Hook to access metadata from context - */ -export function useMetadata() { - const context = useContext(MetadataContext); - if (!context) { - throw new Error('useMetadata must be used within MetadataProvider'); - } - return context; -} diff --git a/packages/providers/src/ThemeProvider.tsx b/packages/providers/src/ThemeProvider.tsx deleted file mode 100644 index da240aa495..0000000000 --- a/packages/providers/src/ThemeProvider.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { createContext, useContext, useEffect, useState } from 'react'; -import type { ThemePreference, ThemePreferenceProviderProps } from './types.js'; - -interface ThemeContextValue { - theme: ThemePreference; - setTheme: (theme: ThemePreference) => void; -} - -const ThemeContext = createContext(null); - -/** - * ThemeProvider - Theme management - * - * Provides theme context and handles system theme detection. - * Extracted from console for reuse in third-party applications. - */ -export function ThemeProvider({ - defaultTheme = 'system', - storageKey = 'ui-theme', - children, -}: ThemePreferenceProviderProps) { - const [theme, setTheme] = useState(() => { - if (typeof window !== 'undefined') { - return (localStorage.getItem(storageKey) as ThemePreference) || defaultTheme; - } - return defaultTheme; - }); - - useEffect(() => { - const root = window.document.documentElement; - root.classList.remove('light', 'dark'); - - // The spec's OS-following mode is `auto` (ThemeModeSchema, ui/theme.zod.ts); - // `system` is this provider's pre-spec spelling, kept for stored values. - // Branching on `system` alone sent `auto` into `classList.add('auto')` — - // a class no Tailwind variant matches, locking the light theme (#2942). - if (theme === 'auto' || theme === 'system') { - const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') - .matches - ? 'dark' - : 'light'; - root.classList.add(systemTheme); - } else { - root.classList.add(theme); - } - }, [theme]); - - const value: ThemeContextValue = { - theme, - setTheme: (newTheme: ThemePreference) => { - localStorage.setItem(storageKey, newTheme); - setTheme(newTheme); - }, - }; - - return ( - {children} - ); -} - -/** - * Hook to access theme from context - */ -export function useTheme() { - const context = useContext(ThemeContext); - if (!context) { - throw new Error('useTheme must be used within ThemeProvider'); - } - return context; -} diff --git a/packages/providers/src/__tests__/spec-symbol-batch7.test.ts b/packages/providers/src/__tests__/spec-symbol-batch7.test.ts deleted file mode 100644 index a68d43412f..0000000000 --- a/packages/providers/src/__tests__/spec-symbol-batch7.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * 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. - */ - -/** - * `ThemePreference` derivation pin - * (objectui#3161, objectstack#4115 ledger batch 7; re-homed by objectui#5716). - * - * `Theme` → `ThemePreference`, derived from the theme MODE vocabulary plus the - * one legacy spelling this provider still honours. The batch-7 naming - * judgement is recorded at the declaration in `../types.ts`. - * - * This file used to read the vocabulary from the spec's `ThemeModeSchema` and - * carried a designed tripwire: "if the spec retires `Theme`, this stops - * compiling and the rename is up for re-triage". The spec DID retire its - * theme module (objectstack#10485), the re-triage happened, and the - * objectui#5716 ruling made `@object-ui/types` the vocabulary's owner - * (`ThemeMode`, with the `THEME_MODES` runtime witness). So the pins below - * read the new owner — the tripwire fired, was triaged, and is retired. - */ - -import { describe, it, expect } from 'vitest'; -import { THEME_MODES } from '@object-ui/types'; -import type { ThemeMode } from '@object-ui/types'; - -import type { ThemePreference } from '../types'; - -describe("'system' is this package's own legacy spelling, not the vocabulary's", () => { - it('reads a non-empty mode tuple from the owner (the probe is not vacuous)', () => { - expect(Array.isArray(THEME_MODES) && THEME_MODES.length > 0).toBe(true); - }); - - it('the vocabulary never declares `system` — the OS-following mode is spelled `auto`', () => { - expect(THEME_MODES).toContain('auto'); - expect( - (THEME_MODES as readonly string[]).includes('system'), - 'the mode vocabulary now declares `system` too — drop the extra union member in types.ts, it is no longer a local alias', - ).toBe(false); - }); -}); - -/* -------------------------------------------------------------------------- */ -/* Compile-time pins — compiled by tsconfig.test.json, chained off type-check. */ -/* -------------------------------------------------------------------------- */ - -type Assert = T; -type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false; -type IsAny = 0 extends 1 & T ? true : false; - -describe('ThemePreference is the mode vocabulary plus one legacy member', () => { - it('is pinned at compile time', () => { - // The owner's type is real (not `any`), and the runtime witness and the - // type are the same vocabulary — the pair cannot drift apart. - type _OwnerIsReal = Assert, false>>; - type _WitnessCarriesTheType = Assert>; - - // …and this is exactly that vocabulary, plus `system`. Derived, so a mode - // the owner adds appears here (and `ThemeProvider`'s branch must handle it - // — `theme-mode-spec-parity.test.tsx` fails at runtime if it does not). - type _IsTheModeVocabularyPlusLegacy = Assert>; - type _NothingElseIsLocal = Assert, 'system'>>; - - expect(true).toBe(true); - }); -}); diff --git a/packages/providers/src/__tests__/theme-mode-spec-parity.test.tsx b/packages/providers/src/__tests__/theme-mode-spec-parity.test.tsx deleted file mode 100644 index b85a0d196b..0000000000 --- a/packages/providers/src/__tests__/theme-mode-spec-parity.test.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/** - * 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. - */ - -/** - * Theme mode ↔ vocabulary parity + behavior (#2942). - * - * The vocabulary's OS-following mode is `auto`; this provider branched on its - * pre-spec `system` spelling only, so `mode: 'auto'` fell into - * `classList.add('auto')` — a class no Tailwind variant matches. The page - * locked to the light theme, the OS preference was ignored, and nothing - * errored. - * - * The vocabulary read here came from the spec's `ThemeModeSchema` until the - * spec retired its theme module (objectstack#10485) and the objectui#5716 - * ruling moved ownership to `@object-ui/types` — `THEME_MODES` is the owner's - * runtime witness, kept as a tuple precisely so this parity coverage stays - * executable (the `SPEC_GESTURE_TYPES` precedent). - */ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { render } from '@testing-library/react'; -import React from 'react'; -import { THEME_MODES } from '@object-ui/types'; -import { ThemeProvider } from '../ThemeProvider'; - -function mockMatchMedia(prefersDark: boolean) { - Object.defineProperty(window, 'matchMedia', { - writable: true, - value: vi.fn().mockImplementation((query: string) => ({ - matches: prefersDark, - media: query, - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - addListener: vi.fn(), - removeListener: vi.fn(), - dispatchEvent: vi.fn(), - onchange: null, - })), - }); -} - -describe('ThemeProvider covers the theme-mode vocabulary', () => { - beforeEach(() => { - document.documentElement.className = ''; - window.localStorage?.clear?.(); - mockMatchMedia(true); - }); - - it('reads a non-empty mode tuple from the owner', () => { - expect(Array.isArray(THEME_MODES) && THEME_MODES.length > 0, 'could not read THEME_MODES').toBe(true); - }); - - it('every declared mode resolves to a real light/dark class — never a dead literal', () => { - for (const mode of THEME_MODES) { - document.documentElement.className = ''; - const { unmount } = render( - -
- , - ); - const classes = [...document.documentElement.classList]; - expect( - classes.some((c) => c === 'light' || c === 'dark'), - `mode '${mode}' must resolve to light/dark, got [${classes.join(', ')}]`, - ).toBe(true); - expect(classes, `mode '${mode}' leaked a dead class`).not.toContain(mode === 'auto' ? 'auto' : '__never__'); - unmount(); - } - }); - - it("'auto' follows the OS preference (dark here)", () => { - render( - -
- , - ); - expect(document.documentElement.classList.contains('dark')).toBe(true); - }); - - it("legacy 'system' keeps following the OS preference", () => { - render( - -
- , - ); - expect(document.documentElement.classList.contains('dark')).toBe(true); - }); -}); diff --git a/packages/providers/src/index.ts b/packages/providers/src/index.ts index 46ec9b4b3f..eeab3e9c2a 100644 --- a/packages/providers/src/index.ts +++ b/packages/providers/src/index.ts @@ -4,9 +4,10 @@ * Reusable context providers for ObjectUI applications */ -export { DataSourceProvider, useDataSource } from './DataSourceProvider.js'; -export { MetadataProvider, useMetadata } from './MetadataProvider.js'; -export { ThemeProvider, useTheme } from './ThemeProvider.js'; +// `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, @@ -16,13 +17,6 @@ export { createObjectStackUploadAdapter, } from './UploadProvider.js'; -export type { - DataSourceProviderProps, - MetadataProviderProps, - ThemePreferenceProviderProps, - ThemePreference, -} from './types.js'; - export type { UploadAdapter, UploadOptions, diff --git a/packages/providers/src/types.ts b/packages/providers/src/types.ts deleted file mode 100644 index d8fc9eb49e..0000000000 --- a/packages/providers/src/types.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { ReactNode } from 'react'; -import type { ThemeMode } from '@object-ui/types'; - -export interface DataSourceProviderProps { - dataSource: any; - children: ReactNode; -} - -export interface MetadataProviderProps { - metadata?: any; - children: ReactNode; -} - -/** - * The theme MODE this provider stores and applies: the `ThemeMode` vocabulary - * (`auto | light | dark`) plus the one legacy spelling this package still - * accepts. - * - * Named `ThemePreference`, not `Theme` (objectui#3161, objectstack#4115 ledger - * batch 7). `Theme` names a whole theme DOCUMENT — `{ name, label, mode, - * colors: { primary, background, … }, typography, … }` — so the old local - * declaration was not a drifted copy of that document type at all; it was - * `Theme['mode']` under `Theme`'s name, which is the more misleading of the - * two failures: a session reading `Theme` here would conclude an ObjectStack - * theme is a string. `ThemeMode`, the vocabulary's own name for what this - * really is, could not be taken either (the "run the new name past the guard - * first" rule from objectui#3169 — it would have traded one collision for - * another); and it would be wrong anyway, because of `'system'`. - * - * Derivation history (objectui#5716): this union was born reading the spec's - * `ThemeModeSchema` through its `_zod` input carrier. The spec then retired - * its whole theme module (objectstack `35ad101bc`), objectui assumed ownership of - * the theme document types, and the mode vocabulary's owner is now - * `@object-ui/types` (`ThemeMode`, with the `THEME_MODES` runtime witness) — - * so the derivation reads from there. A mode the owner adds still arrives - * here and `ThemeProvider`'s branch must handle it. `'system'` stays as an - * explicit local union member: it is the pre-spec spelling of `auto`, still - * sitting in users' `localStorage`, and the provider treats the two - * identically (#2942, pinned in `theme-mode-spec-parity`). - */ -export type ThemePreference = ThemeMode | 'system'; - -/** - * Props of this package's `ThemeProvider`, which stores one - * {@link ThemePreference} and applies it as the `light` / `dark` class. - * - * Named `ThemePreferenceProviderProps`, not `ThemeProviderProps` - * (objectui#6349, batch 10). `@object-ui/react` publishes a - * `ThemeProviderProps` with a different meaning: the props of its theme-system - * `ThemeProvider`, which registers whole `Theme` documents and turns the - * active one into CSS variables. There `defaultTheme` is a theme NAME and the - * mode is `defaultMode`; here `defaultTheme` is the mode itself. One exported - * name stood for both, so this, the narrower of the two, takes the specific - * name and react keeps the general one. - */ -export interface ThemePreferenceProviderProps { - defaultTheme?: ThemePreference; - storageKey?: string; - children: ReactNode; -} diff --git a/packages/types/src/__tests__/p2-spec-exports.test.ts b/packages/types/src/__tests__/p2-spec-exports.test.ts index b2f6d20060..77cbec3680 100644 --- a/packages/types/src/__tests__/p2-spec-exports.test.ts +++ b/packages/types/src/__tests__/p2-spec-exports.test.ts @@ -252,8 +252,8 @@ describe('v3.0.10 Spec Protocol New Types', () => { // that the spec publishes a live `ThemeModeSchema` validator, and // objectstack#10485 (PR objectstack#10695) retired the spec's entire theme // module — there is no upstream referent left to assert against - // (objectui#5710). The provider's own mode handling stays covered by - // `packages/providers/src/__tests__/theme-mode-spec-parity.test.tsx`. + // (objectui#5710). The mode-only provider that test pointed at retired with + // the `@object-ui/providers` theme trio (objectui#13453). }); describe('Type re-exports from @object-ui/types index', () => { it('should re-export P2.3 types (compile-time verification)', async () => { diff --git a/packages/types/src/theme.ts b/packages/types/src/theme.ts index 007f6d5b2e..4f4c043043 100644 --- a/packages/types/src/theme.ts +++ b/packages/types/src/theme.ts @@ -61,12 +61,11 @@ * The theme mode vocabulary, as a runtime tuple. * * A runtime `as const` tuple rather than a bare union, deliberately — the - * `SPEC_GESTURE_TYPES` precedent in `./mobile.ts`: the parity pins in - * `@object-ui/providers` (`theme-mode-spec-parity.test.tsx`, - * `spec-symbol-batch7.test.ts`) read the mode vocabulary at runtime, and this - * tuple keeps those pins executable against the vocabulary's owner. A - * type-only union would have left them nothing to read, which is how a - * retirement quietly takes working coverage with it. + * `SPEC_GESTURE_TYPES` precedent in `./mobile.ts`: a pin can read the mode + * vocabulary at runtime against its owner, where a type-only union would + * leave it nothing to read, which is how a retirement quietly takes working + * coverage with it. (Its first readers, the parity pins of the + * `@object-ui/providers` theme provider, retired with it in objectui#13453.) */ export const THEME_MODES = ['auto', 'light', 'dark'] as const; @@ -74,9 +73,9 @@ export const THEME_MODES = ['auto', 'light', 'dark'] as const; * Theme display mode. `auto` follows the OS preference. * * Owned here since the upstream theme retirement (objectui#5716). Note the - * pre-spec `'system'` spelling is NOT part of this vocabulary — it is - * `@object-ui/providers`' own legacy alias of `auto`, declared there on - * `ThemePreference` (objectui#2942). + * pre-spec `'system'` spelling is NOT part of this vocabulary — it was the + * legacy alias of `auto` on `@object-ui/providers`' `ThemePreference` + * (objectui#2942), which retired in objectui#13453. */ export type ThemeMode = (typeof THEME_MODES)[number]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 904fc88232..3d07c753f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -492,9 +492,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 diff --git a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts index 6fbf07fe1b..88c22418bb 100644 --- a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts +++ b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts @@ -622,8 +622,11 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ // RENAME branch: providers' declaration now spells // `ThemePreferenceProviderProps`, and the providers root publishes that name // instead; react keeps the general one. The two `ThemeProvider` components - // keep their names — value names are outside this gate's bound - // (objectui#6349, batch 10). + // kept their names — value names are outside this gate's bound + // (objectui#6349, batch 10). objectui#13453 then retired the providers + // copy outright (`ThemeProvider`, `ThemePreferenceProviderProps`, and the + // `DataSourceProvider` / `MetadataProvider` beside it), so react's is the + // only `ThemeProvider` left. // `TranslateFn` sat here — i18next's `t` narrowed to `(key, options?) => string`, // declared BYTE-IDENTICALLY in three files (one sha256 across the three). // objectui#8165 re-pointed `packages/app-shell/src/providers/saveAdvisoryToast.ts` diff --git a/skills/objectui/README.md b/skills/objectui/README.md index 05c91c0bb6..735c9c4c5a 100644 --- a/skills/objectui/README.md +++ b/skills/objectui/README.md @@ -53,7 +53,7 @@ The published packages this skill covers. Nothing checks this list against the - **Core renderer:** `@object-ui/types`, `core`, `components`, `fields`, `layout`, `react` - **Parsing & runtime:** `@object-ui/sdui-parser`, `react-runtime` -- **Integration:** `@object-ui/app-shell`, `providers`, `runner`, `data-objectstack` +- **Integration:** `@object-ui/app-shell`, `providers`, `data-objectstack` - **Platform features:** `@object-ui/auth`, `permissions`, `i18n`, `mobile`, `collaboration` - **Plugins (19):** `plugin-{grid, list, detail, form, kanban, calendar, timeline, gantt, dashboard, report, charts, map, editor, markdown, view, tree, designer, ai, chatbot}` - **Tooling:** `@object-ui/cli`, `create-plugin`, `vscode-extension` diff --git a/skills/objectui/guides/architecture.md b/skills/objectui/guides/architecture.md index d7c5cbd3fd..41cd002aa7 100644 --- a/skills/objectui/guides/architecture.md +++ b/skills/objectui/guides/architecture.md @@ -9,8 +9,8 @@ integration package to install. Day-to-day schema authoring is ## Choosing an integration package -- **Embedding ObjectUI into a third-party React app with your own router/shell?** → use `@object-ui/app-shell` + `@object-ui/providers`. -- **Running a schema as a standalone app?** → use `@object-ui/runner` or the `objectui` CLI. +- **Embedding ObjectUI into a third-party React app with your own router/shell?** → use `@object-ui/app-shell` + `@object-ui/react` (`SchemaRendererProvider`, `ThemeProvider`). +- **Running a schema as a standalone app?** → use the `objectui` CLI (`objectui dev`). - **Custom rendering only (no shell)?** → use `@object-ui/react` (`SchemaRenderer`) directly. ## The JSON Protocol (The "DNA") diff --git a/skills/objectui/guides/project-setup.md b/skills/objectui/guides/project-setup.md index 157185816d..648280c009 100644 --- a/skills/objectui/guides/project-setup.md +++ b/skills/objectui/guides/project-setup.md @@ -89,12 +89,13 @@ Opt-in platform packages (auth, i18n, mobile, realtime): } ``` -Embedding shell + provider stack into a third-party app: +Embedding the shell into a third-party app (the theme and data-source providers come +from `@object-ui/react`): ```json { "@object-ui/app-shell": "latest", - "@object-ui/providers": "latest" + "@object-ui/react": "latest" } ``` @@ -236,23 +237,26 @@ import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react'; ``` -### `@object-ui/providers` — reusable context stack +### Context providers -Framework-agnostic providers without console-specific dependencies: +The provider stack for a third-party app comes from the packages that read it: -- `DataSourceProvider` / `useDataSource` — generic data source binding. -- `MetadataProvider` / `useMetadata` — apps / objects / dashboards / pages metadata. -- `ThemeProvider` / `useTheme` — light/dark/system theming wired to Shadcn variables. +- `SchemaRendererProvider` (`@object-ui/react`) — the ambient data source; pass the + adapter as its `dataSource` prop. +- `ThemeProvider` / `useTheme` (`@object-ui/react`) — theme documents and the + `auto` / `light` / `dark` mode. +- `MetadataProvider` / `useMetadata` (`@object-ui/app-shell`) — apps / objects / + dashboards / pages metadata from the API. +- `UploadProvider` / `useUpload` (`@object-ui/providers`) — the upload adapter the + file and image fields upload through. ```tsx -import { DataSourceProvider, MetadataProvider, ThemeProvider } from '@object-ui/providers'; - - - - - - - +import { SchemaRendererProvider, ThemeProvider } from '@object-ui/react'; + + + + + ``` @@ -290,23 +294,21 @@ takes no `objectName` prop. Its props are the exported `ConsoleObjectViewProps` mount from the "ObjectView" section of `packages/app-shell/README.md`: that block is the one `pnpm check:doc-snippets` compiles, and this guide does not restate it. -### `@object-ui/runner` — universal runtime +### Running a schema as a standalone app -Standalone runtime + dev server with `plugin-charts` and `plugin-kanban` -pre-registered, for running a schema as a one-off app with no project -scaffolding. Usually reached through the `objectui dev` CLI, which wraps the -same renderer; no app in this repo's `apps/` or `examples/` imports it directly. +Use the `objectui dev` CLI (`@object-ui/cli`). The repository's `packages/runner` is +a private workspace demo app, not a published package. ### Decision matrix | Need | Use | |------|-----| | Bare renderer in existing app | `@object-ui/react` | -| Need shared providers (data/metadata/theme) | + `@object-ui/providers` | +| Need the data-source / theme providers | `SchemaRendererProvider`, `ThemeProvider` from `@object-ui/react` | +| Need file / image uploads | + `@object-ui/providers` (`UploadProvider`) | | Need shell + object/dashboard/page/record views | + `@object-ui/app-shell` | | Need full admin console (sidebar, system hub, navigation) | study `apps/console` patterns | | Need a CLI-driven dev server | `@object-ui/cli` (`objectui dev`) | -| Need a standalone runtime bundle | `@object-ui/runner` | ## Deployment