diff --git a/.changeset/6349-types-internal-name-collisions-batch-1.md b/.changeset/6349-types-internal-name-collisions-batch-1.md new file mode 100644 index 0000000000..65c27667e9 --- /dev/null +++ b/.changeset/6349-types-internal-name-collisions-batch-1.md @@ -0,0 +1,43 @@ +--- +'@object-ui/types': minor +--- + +Three exported type names inside `@object-ui/types` had two authorities each; each now has +one (objectui#6349, first batch — the three intra-package collisions from the 46-name +census on objectui#6273). + +**`ActionSchema` — renamed, because the two shapes are unrelated.** `crud.ts` and +`ui-action.ts` both declared it. Measured member-by-member they share 9 keys out of 28 each: +`crud.ts` `extends BaseSchema` and pins `type: 'action'` (a UI node — a button in a +component tree), `ui-action.ts` extends nothing and types `type` as `ActionType` (a spec-v2 +action definition, with `name`, `locations`, `params`, `target`). Re-pointing either at the +other would silently hand a consumer a different type, so this took the rename branch +(objectui#5044 is the precedent for choosing the surviving name). `ui-action.ts`'s +declaration is now spelled **`UIActionSchema`** — the name `src/index.ts` has always +PUBLISHED it under, via `export type { ActionSchema as UIActionSchema }`, which is now a +plain re-export. **The package's public surface is unchanged**: `ActionSchema` still means +`crud.ts`'s legacy shape and `UIActionSchema` still means `ui-action.ts`'s, exactly as +before. Nothing outside `ui-action.ts` imported the old spelling — there is no `./ui-action` +subpath in `exports`, so the old name was never reachable from outside the package. + +**`BreadcrumbItem` / `BreadcrumbSchema` — re-pointed, because one copy was stale.** Both +were declared in `data-display.ts` and in `navigation.ts`. The data-display pair was not a +second dialect but a strict SUBSET: no key declared differently on either side, and missing +`BreadcrumbItem.icon` / `onClick` / `siblings` and `BreadcrumbSchema.maxItems`. Everything +that reads a breadcrumb was already on the navigation declaration — `registry.ts` maps the +`'breadcrumb'` component type to it, `src/index.ts` re-exports it under the bare names, +`zod/navigation.zod.ts` mirrors it (`icon`, `onClick`, `siblings`, `maxItems` included), the +`ui:breadcrumb` renderer consumes it, and the component's own documentation page documents +`icon` and `maxItems`. `data-display.ts` now re-exports the one authority. + +**What changes for a consumer.** The `@object-ui/types/data-display` subpath is published, so +its `BreadcrumbItem` / `BreadcrumbSchema` and the `DataDisplaySchema` union's breadcrumb +member widen to the navigation declaration — they gain the four keys above. Nothing narrows +and no key changes type, so every value that type-checked before still does; what the subpath +now declares is what the renderer already honoured and the docs already described. Graded +`minor` because a published `.d.ts` member changes shape, per this repo's version-alignment +rule (never `major`). + +The three `KNOWN_COLLISIONS` lines came down in the same change; that baseline +(`scripts/__tests__/one-authority-per-exported-name-6273.test.ts`) is shrink-only and fails in +both directions, so converging without deleting them would have been red too. 43 entries → 40. diff --git a/packages/types/src/data-display.ts b/packages/types/src/data-display.ts index b648c58bf0..eba6ff2d41 100644 --- a/packages/types/src/data-display.ts +++ b/packages/types/src/data-display.ts @@ -17,6 +17,7 @@ import type { ChartType as SpecChartType } from '@objectstack/spec/ui'; import type { BaseSchema, SchemaNode } from './base.js'; +import type { BreadcrumbSchema } from './navigation.js'; /** * Alert component @@ -1644,34 +1645,27 @@ export interface TimelineSchema extends BaseSchema { } /** - * Breadcrumb item - */ -export interface BreadcrumbItem { - /** - * Item label - */ - label: string; - /** - * Item href/link - */ - href?: string; -} - -/** - * Breadcrumb component + * Breadcrumb — ONE authority, and it lives in `./navigation.ts`. + * + * This module declared its own `BreadcrumbItem` / `BreadcrumbSchema` until + * objectui#6349. They were not a second dialect, they were a stale COPY: a + * strict subset of the navigation declarations, missing `BreadcrumbItem.icon` / + * `onClick` / `siblings` and `BreadcrumbSchema.maxItems`, with no key declared + * differently on either side. Everything that actually reads a breadcrumb was + * already on the navigation declaration — `registry.ts` maps the `'breadcrumb'` + * component type to it, `packages/types/src/index.ts` re-exports it under the + * bare names, `zod/navigation.zod.ts` mirrors it, and + * `content/docs/components/data-display/breadcrumb.mdx` documents `icon` and + * `maxItems` on THIS page. The copy's only reach was the + * `@object-ui/types/data-display` subpath and the {@link DataDisplaySchema} + * union below, both of which under-declared what the renderer honours. + * + * A re-export is one declaration with a second export site, which is exactly + * how this monorepo is meant to fan a type out; the recurrence guard + * (`scripts/__tests__/one-authority-per-exported-name-6273.test.ts`) does not + * count it. 2026-08-25 family ruling, objectui#6172 decision 甲/A1. */ -export interface BreadcrumbSchema extends BaseSchema { - type: 'breadcrumb'; - /** - * Breadcrumb items - */ - items: BreadcrumbItem[]; - /** - * Separator character - * @default '/' - */ - separator?: string; -} +export type { BreadcrumbItem, BreadcrumbSchema } from './navigation.js'; /** * Keyboard key component diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 6ebe1505e1..f1a1578cc5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1034,7 +1034,7 @@ export type { ObjectUiLocalParamFieldType, ResolvableParamFieldType, ActionParam, - ActionSchema as UIActionSchema, + UIActionSchema, ActionGroup, ActionContext, ActionResult, diff --git a/packages/types/src/ui-action.ts b/packages/types/src/ui-action.ts index 3281c5ccb5..edf950b30b 100644 --- a/packages/types/src/ui-action.ts +++ b/packages/types/src/ui-action.ts @@ -367,11 +367,23 @@ export interface ActionParam /** * Enhanced Action Schema (ObjectStack Spec v2.0.1) - * - * This is the primary action schema that should be used for all new implementations. - * The legacy ActionSchema in crud.ts is maintained for backward compatibility. + * + * This is the primary action schema that should be used for all new + * implementations. The legacy `ActionSchema` in `crud.ts` is maintained for + * backward compatibility. + * + * ⚠️ Named `UIActionSchema`, which is the name `packages/types/src/index.ts` + * has always PUBLISHED it under. It was declared as `ActionSchema` until + * objectui#6349 — a second authority for a name `crud.ts` also declares, so an + * IDE auto-import picked between two structurally unrelated types (9 shared + * keys out of 28 each; `type` is the literal `'action'` there and + * {@link ActionType} here) and the wrong pick surfaced as a remote `TS2322`. + * The declaration now spells the published name; nothing outside this file + * imported the old one, so the package's public surface is unchanged. See the + * 2026-08-25 family ruling (objectui#6172, decision 甲/A1) and the recurrence + * guard `scripts/__tests__/one-authority-per-exported-name-6273.test.ts`. */ -export interface ActionSchema { +export interface UIActionSchema { /** Unique action identifier (snake_case) */ name: string; @@ -472,7 +484,7 @@ export interface ActionSchema { * `inline-action-api-params-to-body-extra` conversion (ADR-0087 D2). * * Declared here so the action renderers can forward it off a typed - * `ActionSchema` rather than through an `as any` cast — the renderers are the + * `UIActionSchema` rather than through an `as any` cast — the renderers are the * consumers this field exists for (objectstack#6837). Typed off the spec * rather than restated, so the shape cannot drift from the contract. */ @@ -491,7 +503,7 @@ export interface ActionSchema { * exactly that, so the key has one meaning everywhere it is honoured. * * Declared here for the same reason as {@link bodyExtra}: the action - * renderers forward it off a typed `ActionSchema` instead of an `as any` + * renderers forward it off a typed `UIActionSchema` instead of an `as any` * cast, and dropping it from those whitelists is what made a declared wrap * degrade silently to a flat body (objectstack#6938). Typed by derivation * from the spec so the union cannot drift from the contract. @@ -583,7 +595,7 @@ export interface ActionGroup { icon?: string; /** Actions in this group */ - actions: ActionSchema[]; + actions: UIActionSchema[]; /** Group visibility condition */ visible?: string; @@ -637,7 +649,7 @@ export interface ActionResult { * Action executor function type */ export type ActionExecutor = ( - action: ActionSchema, + action: UIActionSchema, context: ActionContext, params?: Record ) => Promise; @@ -698,7 +710,7 @@ export interface TransactionConfig { /** Timeout in milliseconds */ timeout?: number; /** Actions to execute within the transaction */ - actions: ActionSchema[]; + actions: UIActionSchema[]; /** Rollback action on failure */ rollbackAction?: string; /** Whether to auto-retry on conflict */ diff --git a/scripts/__tests__/check-action-forward-parity.test.ts b/scripts/__tests__/check-action-forward-parity.test.ts index 3da3612d04..b6322af0c4 100644 --- a/scripts/__tests__/check-action-forward-parity.test.ts +++ b/scripts/__tests__/check-action-forward-parity.test.ts @@ -98,7 +98,7 @@ function repoWith( files: { [UI_VIEW]: overrides.view ?? - 'export interface ActionSchema {\n name?: string;\n description?: string;\n}\n', + 'export interface UIActionSchema {\n name?: string;\n description?: string;\n}\n', [KEYS_MODULE]: overrides.keys ?? "export const RETIRED_ACTION_KEYS = {\n execute: 'renamed to target',\n};\n", [CONSUMER]: @@ -388,7 +388,7 @@ describe('extraction failure throws rather than returning a clean verdict', () = }); it('a renderer view with no properties is red', () => { - expect(() => judge(repoWith('{ target }', { view: 'export interface ActionSchema {}\n' }))).toThrow( + expect(() => judge(repoWith('{ target }', { view: 'export interface UIActionSchema {}\n' }))).toThrow( /declares no properties/, ); }); @@ -409,7 +409,7 @@ describe('extraction failure throws rather than returning a clean verdict', () = // Authorable and runtime-read do not intersect at all: nothing would ever be // checked for this surface again. expect(() => - judge(repoWith('{ target }', { view: 'export interface ActionSchema {\n nothingReadsThis?: string;\n}\n' }), { + judge(repoWith('{ target }', { view: 'export interface UIActionSchema {\n nothingReadsThis?: string;\n}\n' }), { spec: { declared: ['norThis'], inline: [] }, }), ).toThrow(/owed set is empty/); 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 542a445903..b0f1027154 100644 --- a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts +++ b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts @@ -349,11 +349,17 @@ const filesOf = (sites: readonly Located[]): string[] => [ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ ['ActionContext', ['packages/core/src/actions/ActionRunner.ts', 'packages/types/src/ui-action.ts']], ['ActionResult', ['packages/core/src/actions/ActionRunner.ts', 'packages/types/src/ui-action.ts']], - ['ActionSchema', ['packages/types/src/crud.ts', 'packages/types/src/ui-action.ts']], ['AggregationConfig', ['packages/plugin-grid/src/useGroupedData.ts', 'packages/types/src/data-protocol.ts']], ['AppShellProps', ['packages/app-shell/src/types.ts', 'packages/layout/src/AppShell.tsx']], - ['BreadcrumbItem', ['packages/types/src/data-display.ts', 'packages/types/src/navigation.ts']], - ['BreadcrumbSchema', ['packages/types/src/data-display.ts', 'packages/types/src/navigation.ts']], + // `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 + // the remedy was the RENAME branch: ui-action's declaration now spells + // `UIActionSchema`, the name `src/index.ts` always published it under + // (objectui#6349). `BreadcrumbItem` / `BreadcrumbSchema` sat here too, colliding + // between `packages/types/src/data-display.ts` and + // `packages/types/src/navigation.ts`; data-display's were a strict SUBSET copy, so + // that file re-points at navigation's one authority. ['CalendarEvent', ['packages/plugin-calendar/src/index.tsx', 'packages/types/src/complex.ts']], // the ruled-on objectui#5044 alias — see the header ['CalendarSchema', ['packages/plugin-calendar/src/ObjectCalendar.tsx', 'packages/types/src/form.ts']], ['ChatMessage', ['packages/plugin-chatbot/src/ChatbotEnhanced.tsx', 'packages/types/src/complex.ts']], diff --git a/scripts/check-action-forward-parity.mjs b/scripts/check-action-forward-parity.mjs index c6d373d5cd..dd3cd9c46b 100644 --- a/scripts/check-action-forward-parity.mjs +++ b/scripts/check-action-forward-parity.mjs @@ -392,7 +392,12 @@ export function loadSpecSchemas(require = createRequire(import.meta.url)) { } /** - * `@object-ui/types`' renderer VIEW of an action. + * `@object-ui/types`' renderer VIEW of an action — `interface UIActionSchema`. + * + * ⚠️ It was `interface ActionSchema` until objectui#6349, a second authority for + * a name `packages/types/src/crud.ts` also declares. The declaration now spells + * the name the package always PUBLISHED it under (`UIActionSchema`); the type + * and the file are unchanged. * * Declared surfaces are typed against this, not against the spec schema * directly — it carries renderer-only fields the spec does not model @@ -408,16 +413,16 @@ export function uiActionViewKeys(root, file = UI_ACTION_VIEW) { } const sf = parse(root, file); for (const stmt of sf.statements) { - if (!ts.isInterfaceDeclaration(stmt) || stmt.name.text !== "ActionSchema") continue; + if (!ts.isInterfaceDeclaration(stmt) || stmt.name.text !== "UIActionSchema") continue; const keys = stmt.members .filter(ts.isPropertySignature) .map((m) => (m.name && (ts.isIdentifier(m.name) || ts.isStringLiteral(m.name)) ? m.name.text : null)) .filter((n) => n !== null); - if (keys.length === 0) fail(`\`ActionSchema\` in ${file} declares no properties — extraction failed.`); + if (keys.length === 0) fail(`\`UIActionSchema\` in ${file} declares no properties — extraction failed.`); return keys; } fail( - `\`interface ActionSchema\` not found in ${file}.\n` + + `\`interface UIActionSchema\` not found in ${file}.\n` + " The renderer view moved or was renamed; re-point this gate at it." ); } diff --git a/scripts/check-spec-symbol-derivation.mjs b/scripts/check-spec-symbol-derivation.mjs index 30d0b3495c..9590ed1072 100644 --- a/scripts/check-spec-symbol-derivation.mjs +++ b/scripts/check-spec-symbol-derivation.mjs @@ -243,11 +243,14 @@ const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const ALLOW = { "@object-ui/types:ActionSchema": { reason: - "Two deliberate objectui-side shapes, both documented at their declaration. " + - "`ui-action.ts` is a renderer VIEW over the spec's action — it carries renderer-only " + - "fields the spec does not model, while importing the spec-owned parts it does share " + - "(`ActionLocation`, `ActionType`). `crud.ts` is the explicitly @deprecated legacy " + - "shape kept for backward compatibility and slated for removal in a future major.", + "`crud.ts`'s explicitly @deprecated legacy action shape, kept for backward " + + "compatibility and slated for removal in a future major. ⚠️ This entry covered TWO " + + "objectui-side shapes until objectui#6349: `ui-action.ts` declared a same-named " + + "renderer VIEW over the spec's action (renderer-only fields the spec does not model, " + + "importing the spec-owned parts it shares — `ActionLocation`, `ActionType`). That one " + + "is now declared as `UIActionSchema`, the name the package always published it under, " + + "so it no longer shadows a spec export and no longer needs excusing here. This entry " + + "stays because `crud.ts` still carries the name.", issue: 4115, }, "@object-ui/types:FormField": {