Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/6349-batch9-app-shell.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 7 additions & 0 deletions .changeset/6349-batch9-data-objectstack.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 4 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 (
Expand Down Expand Up @@ -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 (
<ThemeProvider>
<AppShell>{children}</AppShell>
Expand Down
4 changes: 2 additions & 2 deletions packages/app-shell/src/components/AppShell.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,7 +13,7 @@ export function AppShell({
footer,
children,
className = '',
}: AppShellProps): ReactNode {
}: MinimalAppShellProps): ReactNode {
return (
<div className={`app-shell flex h-screen flex-col ${className}`}>
{header && <div className="app-shell-header">{header}</div>}
Expand Down
20 changes: 7 additions & 13 deletions packages/app-shell/src/context/UserStateAdapters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
/** Load the persisted list for the current user. Resolve to [] when absent. */
load(): Promise<T[]>;
/** Persist the full list (debounced upstream). Errors are silently ignored. */
save(items: T[]): Promise<void>;
}
// `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';

Expand Down
2 changes: 1 addition & 1 deletion packages/app-shell/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export { useRecentItemLabel } from './hooks/useRecentItemLabel.js';

// Types
export type {
AppShellProps,
MinimalAppShellProps,
} from './types.js';

export type {
Expand Down
10 changes: 6 additions & 4 deletions packages/app-shell/src/providers/MetadataProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ 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 };

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export type MetadataTypeStatus = 'idle' | 'loading' | 'ready' | 'error';

interface TypeCacheEntry {
status: MetadataTypeStatus;
items: any[];
Expand Down
12 changes: 11 additions & 1 deletion packages/app-shell/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) */
Expand Down
13 changes: 13 additions & 0 deletions packages/data-objectstack/src/userState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
/** Load the persisted list for the current user. Resolve to [] when absent. */
load(): Promise<T[]>;
/** Persist the full list (debounced upstream). Errors are silently ignored. */
save(items: T[]): Promise<void>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
10 changes: 6 additions & 4 deletions packages/layout/src/__tests__/guide-layout-app-shell-doc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
16 changes: 9 additions & 7 deletions packages/layout/src/__tests__/readme-app-shell-example.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -264,9 +266,9 @@ describe("the README's AppShell examples pass only props AppShellProps declares
[
'An `<AppShell>` 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);
});
Expand Down
31 changes: 28 additions & 3 deletions scripts/__tests__/one-authority-per-exported-name-6273.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,17 @@ const KNOWN_COLLISIONS: ReadonlyMap<string, readonly string[]> = 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
Expand Down Expand Up @@ -520,7 +530,14 @@ const KNOWN_COLLISIONS: ReadonlyMap<string, readonly string[]> = 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).
Expand Down Expand Up @@ -570,7 +587,15 @@ const KNOWN_COLLISIONS: ReadonlyMap<string, readonly string[]> = 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
Expand Down
Loading