diff --git a/.changeset/6349-batch9-app-shell.md b/.changeset/6349-batch9-app-shell.md new file mode 100644 index 0000000000..2fa68861f5 --- /dev/null +++ b/.changeset/6349-batch9-app-shell.md @@ -0,0 +1,11 @@ +--- +'@object-ui/app-shell': minor +--- + +The props of this package's minimal `AppShell` container are declared and exported as `MinimalAppShellProps` instead of `AppShellProps` (objectui#6349, batch 9). `@object-ui/layout` publishes a different `AppShellProps`: the props of its sidebar `AppShell`, the shell the console composes, which takes `navbar`, `defaultOpen`, `branding` and `rightRail` where this container takes `header` and `footer`. One exported name stood for two props types across the two packages, and layout keeps it. + +**Type change, breaking for some consumers.** `AppShellProps` is no longer exported from `@object-ui/app-shell`. Replace `import type { AppShellProps } from '@object-ui/app-shell'` with `MinimalAppShellProps`; the members (`sidebar`, `header`, `footer`, `children`, `className`) are unchanged. The old import fails to compile with TS2305 ("has no exported member 'AppShellProps'"). The `AppShell` component keeps its name. + +`MetadataTypeStatus` and `UserDataAdapter` are no longer declared a second time in this package. `MetadataTypeStatus` is re-exported from `@object-ui/react`, which declares the same four statuses (`'idle' | 'loading' | 'ready' | 'error'`) for `MetadataContextValue.getTypeStatus`. `UserDataAdapter` is re-exported from `@object-ui/data-objectstack`, whose `createObjectStackUserStateAdapter` builds exactly the adapter `UserStateAdaptersProvider` injects. Both declarations had the same members as this package's copies, so this package's entry still exports both names with the same shapes, and those imports do not change. + +No runtime behaviour changes. diff --git a/.changeset/6349-batch9-data-objectstack.md b/.changeset/6349-batch9-data-objectstack.md new file mode 100644 index 0000000000..4aec8d9ffc --- /dev/null +++ b/.changeset/6349-batch9-data-objectstack.md @@ -0,0 +1,7 @@ +--- +'@object-ui/data-objectstack': patch +--- + +Doc comments only. `UserDataAdapter` now carries the contract notes `@object-ui/app-shell` kept on its own copy of the interface: implementations must be safe to call concurrently and should never throw, so the hosting provider can degrade to localStorage when the backend fails. `@object-ui/app-shell` now re-exports this declaration instead of declaring a second one (objectui#6349, batch 9). The type and its members are unchanged. + +No runtime behaviour changes. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 673cfcd69e..cfc4a04e2d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -160,7 +160,7 @@ Third-Party App ```tsx import { AppShell } from '@object-ui/app-shell'; -import type { AppShellProps } 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'; @@ -172,7 +172,7 @@ import type { DataSourceProviderProps } from '@object-ui/providers'; // 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']; -declare function MySidebar(): AppShellProps['sidebar']; +declare function MySidebar(): MinimalAppShellProps['sidebar']; function MyConsole() { return ( @@ -207,10 +207,10 @@ asked to run on the server, where neither exists. 'use client'; import { AppShell } from '@object-ui/app-shell'; -import type { AppShellProps } from '@object-ui/app-shell'; +import type { MinimalAppShellProps } from '@object-ui/app-shell'; import { ThemeProvider } from '@object-ui/providers'; -export default function RootLayout({ children }: { children: AppShellProps['children'] }) { +export default function RootLayout({ children }: { children: MinimalAppShellProps['children'] }) { return ( {children} diff --git a/packages/app-shell/src/components/AppShell.tsx b/packages/app-shell/src/components/AppShell.tsx index 33fef1a02d..b39cd01952 100644 --- a/packages/app-shell/src/components/AppShell.tsx +++ b/packages/app-shell/src/components/AppShell.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from 'react'; -import type { AppShellProps } from '../types.js'; +import type { MinimalAppShellProps } from '../types.js'; /** * AppShell - Minimal layout container @@ -13,7 +13,7 @@ export function AppShell({ footer, children, className = '', -}: AppShellProps): ReactNode { +}: MinimalAppShellProps): ReactNode { return (
{header &&
{header}
} diff --git a/packages/app-shell/src/context/UserStateAdapters.tsx b/packages/app-shell/src/context/UserStateAdapters.tsx index 42c0270568..cc18e5be2d 100644 --- a/packages/app-shell/src/context/UserStateAdapters.tsx +++ b/packages/app-shell/src/context/UserStateAdapters.tsx @@ -23,24 +23,18 @@ import { useState, type ReactNode, } from 'react'; +import type { UserDataAdapter } from '@object-ui/data-objectstack'; // --------------------------------------------------------------------------- // Adapter contract // --------------------------------------------------------------------------- -/** - * Generic persistence adapter for a user-scoped list. - * - * Implementations must be safe to call concurrently and should *never* - * throw — failures should be swallowed so the hosting provider can degrade - * to localStorage-only mode without crashing the UI. - */ -export interface UserDataAdapter { - /** Load the persisted list for the current user. Resolve to [] when absent. */ - load(): Promise; - /** Persist the full list (debounced upstream). Errors are silently ignored. */ - save(items: T[]): Promise; -} +// `UserDataAdapter` is `@object-ui/data-objectstack`'s, the one authority: +// `createObjectStackUserStateAdapter` returns exactly the contract this +// registry injects, so this module re-exports it rather than declaring a +// second copy. Its contract notes (never throw; degrade to localStorage) live +// on that declaration (objectui#6349, batch 9). +export type { UserDataAdapter }; export type UserStateKind = 'favorites' | 'recent' | 'flowPaletteRecents'; diff --git a/packages/app-shell/src/index.ts b/packages/app-shell/src/index.ts index 2046ec1788..8a6f3be1bf 100644 --- a/packages/app-shell/src/index.ts +++ b/packages/app-shell/src/index.ts @@ -52,7 +52,7 @@ export { useRecentItemLabel } from './hooks/useRecentItemLabel.js'; // Types export type { - AppShellProps, + MinimalAppShellProps, } from './types.js'; export type { diff --git a/packages/app-shell/src/providers/MetadataProvider.tsx b/packages/app-shell/src/providers/MetadataProvider.tsx index 40143ceda4..370553ac34 100644 --- a/packages/app-shell/src/providers/MetadataProvider.tsx +++ b/packages/app-shell/src/providers/MetadataProvider.tsx @@ -11,12 +11,16 @@ import { ActiveOrganizationStorage, TokenStorage, useAuth } from '@object-ui/aut import { type ObjectStackAdapter } from '@object-ui/data-objectstack'; import { normalizeSchemaReferenceKeys } from '@object-ui/core'; import { resolveInlineMode } from '@object-ui/plugin-form'; -import { MetadataCtx, useMetadata, type MetadataContextValue, type MetadataCacheState } from '@object-ui/react'; +import { MetadataCtx, useMetadata, type MetadataContextValue, type MetadataCacheState, type MetadataTypeStatus } from '@object-ui/react'; import { usePreviewDrafts } from '../preview/PreviewModeContext.js'; import { createConsoleMetadataClient } from '../views/metadata-admin/metadataClientFactory.js'; import { subscribeCanvasInvalidate, subscribeMetadataRefresh } from '../assistant/assistantBus.js'; -export type { MetadataCacheState, MetadataContextValue }; +// `MetadataTypeStatus` is `@object-ui/react`'s, the one authority: the status +// `MetadataContextValue.getTypeStatus` returns is the same four words this +// provider's cache entries carry, so this module re-exports it rather than +// declaring a second copy (objectui#6349, batch 9). +export type { MetadataCacheState, MetadataContextValue, MetadataTypeStatus }; export { useMetadataItem } from '@object-ui/react'; export { useMetadata }; @@ -24,8 +28,6 @@ export { useMetadata }; // Types // --------------------------------------------------------------------------- -export type MetadataTypeStatus = 'idle' | 'loading' | 'ready' | 'error'; - interface TypeCacheEntry { status: MetadataTypeStatus; items: any[]; diff --git a/packages/app-shell/src/types.ts b/packages/app-shell/src/types.ts index a0d356f1b4..84a01514f9 100644 --- a/packages/app-shell/src/types.ts +++ b/packages/app-shell/src/types.ts @@ -14,7 +14,17 @@ export interface DataSource { [key: string]: any; // Allow additional methods } -export interface AppShellProps { +/** + * Props of this package's minimal `AppShell` container: header, body and + * footer slots, with no routing. + * + * Not `AppShellProps`: `@object-ui/layout` publishes that name for the props of + * its own sidebar `AppShell`, the shell the console's `ConsoleLayout` composes, + * which takes `navbar`, `defaultOpen`, `branding` and `rightRail` where this + * one takes `header` and `footer`. One exported name stood for both until this + * declaration was renamed (objectui#6349, batch 9). + */ +export interface MinimalAppShellProps { /** Sidebar component (optional) */ sidebar?: ReactNode; /** Header component (optional) */ diff --git a/packages/data-objectstack/src/userState.ts b/packages/data-objectstack/src/userState.ts index a5697c52c6..fbdffaaf18 100644 --- a/packages/data-objectstack/src/userState.ts +++ b/packages/data-objectstack/src/userState.ts @@ -68,8 +68,21 @@ export interface ObjectStackUserStateAdapterOptions { onError?: (where: 'load' | 'save', error: unknown) => void; } +/** + * Generic persistence adapter for a user-scoped list. + * + * Implementations must be safe to call concurrently and should *never* + * throw — failures should be swallowed so the hosting provider can degrade + * to localStorage-only mode without crashing the UI. + * + * This is the one declaration of the name: `@object-ui/app-shell`'s + * `UserStateAdaptersProvider` injects adapters of this type and re-exports it + * (objectui#6349, batch 9). + */ export interface UserDataAdapter { + /** Load the persisted list for the current user. Resolve to [] when absent. */ load(): Promise; + /** Persist the full list (debounced upstream). Errors are silently ignored. */ save(items: T[]): Promise; } diff --git a/packages/layout/src/__tests__/app-shell-docs-nav-example.test.ts b/packages/layout/src/__tests__/app-shell-docs-nav-example.test.ts index a4319cbedf..57baa492af 100644 --- a/packages/layout/src/__tests__/app-shell-docs-nav-example.test.ts +++ b/packages/layout/src/__tests__/app-shell-docs-nav-example.test.ts @@ -93,8 +93,8 @@ const SIDEBAR_NAV_SRC = readFileSync( /** * The page documents `@object-ui/layout`'s AppShell — every example on it * imports from `@object-ui/layout` — so this is the source of truth for its - * props. `packages/app-shell` exports a same-named `AppShellProps` for a - * different component; that one is not what this page teaches. + * props. `packages/app-shell` exports `MinimalAppShellProps` for a different, + * same-named `AppShell` component; that one is not what this page teaches. */ const APP_SHELL_SRC = readFileSync( join(REPO_ROOT, 'packages', 'layout', 'src', 'AppShell.tsx'), diff --git a/packages/layout/src/__tests__/guide-layout-app-shell-doc.test.ts b/packages/layout/src/__tests__/guide-layout-app-shell-doc.test.ts index 6484e7476c..0ddef3c415 100644 --- a/packages/layout/src/__tests__/guide-layout-app-shell-doc.test.ts +++ b/packages/layout/src/__tests__/guide-layout-app-shell-doc.test.ts @@ -100,10 +100,12 @@ * (objectui#4793 / #4808); pinning the whole docs tree to source is * objectui#3786's problem, not this one. * - * Note also that `@object-ui/app-shell` exports a DIFFERENT `AppShellProps` - * (`packages/app-shell/src/types.ts`) which really does declare `header` — that - * is a separate component, and reading this one's key list off this one's source - * is what keeps the two from being conflated again. + * Note also that `@object-ui/app-shell` exports a DIFFERENT props type for its + * own `AppShell`, `MinimalAppShellProps` (`packages/app-shell/src/types.ts`), + * which really does declare `header` — that is a separate component, and + * reading this one's key list off this one's source is what keeps the two from + * being conflated again. Until objectui#6349 (batch 9) it was also spelled + * `AppShellProps`. */ import { describe, it, expect } from 'vitest'; diff --git a/packages/layout/src/__tests__/readme-app-shell-example.test.ts b/packages/layout/src/__tests__/readme-app-shell-example.test.ts index a69c53affc..da26ea9654 100644 --- a/packages/layout/src/__tests__/readme-app-shell-example.test.ts +++ b/packages/layout/src/__tests__/readme-app-shell-example.test.ts @@ -66,10 +66,12 @@ * `app-shell-docs-nav-example.test.ts`'s surface (objectui#4793), and pinning * the whole docs tree to source is objectui#3786's problem, not this one. * - * Note also that `@object-ui/app-shell` exports a DIFFERENT `AppShellProps` - * (`packages/app-shell/src/types.ts`) which really does declare `header` — that - * is a separate component, and reading this one's key list off this one's source - * is what keeps the two from being conflated again. + * Note also that `@object-ui/app-shell` exports a DIFFERENT props type for its + * own `AppShell`, `MinimalAppShellProps` (`packages/app-shell/src/types.ts`), + * which really does declare `header` — that is a separate component, and + * reading this one's key list off this one's source is what keeps the two from + * being conflated again. Until objectui#6349 (batch 9) it was also spelled + * `AppShellProps`. */ import { describe, it, expect } from 'vitest'; @@ -264,9 +266,9 @@ describe("the README's AppShell examples pass only props AppShellProps declares [ 'An `` example in packages/layout/README.md is passing `header` again.', 'That prop does not exist on this component (objectui#4817). Note that', - '`@object-ui/app-shell` exports a different `AppShellProps` which DOES declare', - '`header` — this README documents `@object-ui/layout`\'s, whose top-bar slot is', - '`navbar`.', + '`@object-ui/app-shell`\'s own `AppShell` takes `MinimalAppShellProps`, which DOES', + 'declare `header` — this README documents `@object-ui/layout`\'s, whose top-bar', + 'slot is `navbar`.', ].join('\n'), ).toBe(false); }); 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 fc43aeb340..77ed18b839 100644 --- a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts +++ b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts @@ -376,7 +376,17 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ // (`field`, `type`). RENAME branch: the general name stays on // `@object-ui/types`, and the grid's declaration now spells // `GroupAggregationConfig` (objectui#6349, batch 7). - ['AppShellProps', ['packages/app-shell/src/types.ts', 'packages/layout/src/AppShell.tsx']], + // `AppShellProps` sat here, colliding between `packages/app-shell/src/types.ts` + // and `packages/layout/src/AppShell.tsx`. Two components, not one drifted + // shape: app-shell's is the props of its minimal `AppShell` container + // (`header` and `footer` slots, no routing) and layout's the props of the + // sidebar shell the console's `ConsoleLayout` composes (`navbar`, + // `defaultOpen`, `branding`, `rightRail`). app-shell depends on layout, but + // re-pointing its name at layout's does not compile. RENAME branch: app-shell's + // declaration now spells `MinimalAppShellProps`, and the app-shell root + // publishes that name instead; layout is the one authority. The two `AppShell` + // components keep their names — value names are outside this gate's bound + // (objectui#6349, batch 9). // `ActionSchema` sat here, colliding between `packages/types/src/crud.ts` and // `packages/types/src/ui-action.ts`. Structurally unrelated types — 28 members // each, 9 shared, `type` the literal `'action'` there and `ActionType` here — so @@ -520,7 +530,14 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ // `type: 'separator'` legal on one side and a refusal on the other. That is // the RENAME branch: app's declaration now spells `AppMenuItem`, the name // `src/index.ts` always published it under (objectui#6349). - ['MetadataTypeStatus', ['packages/app-shell/src/providers/MetadataProvider.tsx', 'packages/react/src/context/AppShellContext.tsx']], + // `MetadataTypeStatus` sat here, colliding between + // `packages/app-shell/src/providers/MetadataProvider.tsx` and + // `packages/react/src/context/AppShellContext.tsx`. One status, declared + // twice with the same four words (`'idle' | 'loading' | 'ready' | 'error'`): + // the provider's per-type cache entries carry it and the context's + // `getTypeStatus` returns it. app-shell depends on `@object-ui/react`, so + // react's is the one authority and `MetadataProvider.tsx` RE-EXPORTS it; the + // app-shell root still publishes the name (objectui#6349, batch 9). // `NamedActionDef` sat here — two IDENTICAL declarations inside plugin-grid. // `resolveLegacyRowActions.ts` is the one authority; `resolveBulkActions.ts` // re-exports it (objectui#6349). @@ -570,7 +587,15 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ // `@objectstack/spec` counterpart, so it was RETIRED with its two companions // (`UndoRedoEntry`, `UndoRedoConfig`) rather than renamed. The designer's // declaration is the one authority (objectui#6349, batch 5). - ['UserDataAdapter', ['packages/app-shell/src/context/UserStateAdapters.tsx', 'packages/data-objectstack/src/userState.ts']], + // `UserDataAdapter` sat here, colliding between + // `packages/app-shell/src/context/UserStateAdapters.tsx` and + // `packages/data-objectstack/src/userState.ts`. One contract with identical + // members (`load()` and `save(items)`, generic over the item): the adapter + // `createObjectStackUserStateAdapter` builds is the adapter app-shell's + // registry injects. app-shell depends on `@object-ui/data-objectstack`, so + // that declaration is the one authority (it took app-shell's contract notes) + // and `UserStateAdapters.tsx` RE-EXPORTS it; the app-shell root still + // publishes the name (objectui#6349, batch 9). // `ValidationFunction` sat here, colliding between // `packages/types/src/data-protocol.ts` and `packages/types/src/field-types.ts`. // The two signatures disagree at both ends of the arrow — data-protocol's takes