From bdb519c55784b13441c220eab1832c2816434fb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 11:14:01 +0000 Subject: [PATCH 1/3] feat(types): export `ObjectTreeSchema` from the root barrel (objectui#9550) `ObjectTreeSchema` was the one arm of `ObjectQLComponentSchema` that the root barrel of `@object-ui/types` did not name, so no TypeScript consumer could import it: the declaration lives in `objectql.ts`, the union applies it, and the zod barrel re-exports it, while the package publishes no `./objectql` subpath to reach around the barrel. The repair is one name added to the existing explicit named re-export list. The declaration is not moved or edited - it is held by another open pull request and only published from here. `object-tree-root-barrel-9550.test.ts` pins the repair on both halves: the type-level legs (enforced by `tsc -p tsconfig.test.json`) assert the name resolves off the root barrel and is the SAME declaration as the `Extract` spelling a consumer was forced into, and the source legs (enforced under vitest) assert the list stays explicit rather than becoming a wildcard and that the declaration stays in `objectql.ts`. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L --- .changeset/9550-object-tree-root-barrel.md | 33 +++ .../object-tree-root-barrel-9550.test.ts | 222 ++++++++++++++++++ packages/types/src/index.ts | 1 + 3 files changed, 256 insertions(+) create mode 100644 .changeset/9550-object-tree-root-barrel.md create mode 100644 packages/types/src/__tests__/object-tree-root-barrel-9550.test.ts diff --git a/.changeset/9550-object-tree-root-barrel.md b/.changeset/9550-object-tree-root-barrel.md new file mode 100644 index 0000000000..c52436e40c --- /dev/null +++ b/.changeset/9550-object-tree-root-barrel.md @@ -0,0 +1,33 @@ +--- +'@object-ui/types': minor +--- + +Export `ObjectTreeSchema` from the `@object-ui/types` root barrel + +`ObjectQLComponentSchema` declares the node types an ObjectQL block may be. +Every one of its arms was a named export of this package's root barrel except +`ObjectTreeSchema`, which was declared in `objectql.ts`, applied by the union, +and re-exported by the `./zod` barrel (objectui#7917) — while no TypeScript +consumer could name it. There is no `./objectql` subpath to reach around the +barrel: the package's `exports` map is pinned by +`packages/types/src/__tests__/package-exports-manifest.test.ts`, and the root +barrel was the only route to this type. + +The omission was not inert. The seat that stopped `ObjectTreeProps.schema` +being `any` in `@object-ui/plugin-tree` (objectui#8655) could not import the +name, so it had to spell the node as +`Extract` — an idiom that +works, and that every later reader of that file has to decode. A type nobody +can import mints a fresh hand-written copy of itself for each consumer that +needs it, which is the second-authority shape objectui#6349 is burning down. + +The change is one name added to an existing explicit named re-export list. +Nothing is removed, retyped or narrowed: `Extract` off the union still +resolves, and `packages/types/src/__tests__/object-tree-root-barrel-9550.test.ts` +pins that the imported name and that `Extract` are the same declaration, that +the list stays explicit rather than becoming a wildcard, and that the +declaration itself stays in `objectql.ts`. + +⛔ Not done here: a gate over barrel completeness for every declared node +schema. That was the card's own third option and it is a wider design with its +own review; objectui#9526 is the sibling card in the same family. diff --git a/packages/types/src/__tests__/object-tree-root-barrel-9550.test.ts b/packages/types/src/__tests__/object-tree-root-barrel-9550.test.ts new file mode 100644 index 0000000000..9324b9da4c --- /dev/null +++ b/packages/types/src/__tests__/object-tree-root-barrel-9550.test.ts @@ -0,0 +1,222 @@ +/** + * 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. + * + * objectui#9550 - `ObjectTreeSchema` is nameable on the ROOT barrel of + * `@object-ui/types`. + * + * ## The defect + * + * `ObjectTreeSchema` is a declared node schema that an author could not import + * from `@object-ui/types`. Its eleven sibling arms of + * `ObjectQLComponentSchema` could be. Historical reading, taken once on + * `origin/main` `72f55c9ec1` before this repair and deliberately not restated + * as a live figure - the assertions below are what re-derive the claim: + * + * | name | hits in the root barrel source | + * | --------------------------- | -----------------------------: | + * | `ObjectTreeSchema` | 0 | + * | `ObjectGridSchema` (control)| 2 | + * | `ListViewSchema` (control)| 3 | + * | `BreadcrumbSchema` (control)| 1 | + * + * Three controls read non-zero under the same query, so the zero was a reading + * and not a dark instrument. `ObjectTreeSchema` was and is declared in + * `objectql.ts` and re-exported by the zod barrel (objectui#7917 repaired that + * half, objectui#8784 keeps it repaired); only the TypeScript barrel omitted + * it, and this package publishes NO `./objectql` subpath to reach around the + * barrel - its `exports` map declares `.`, `./base`, `./complex`, `./data`, + * `./data-display`, `./feedback`, `./form`, + * `./internal/retired-field-keys`, `./layout`, `./navigation`, `./overlay` and + * `./zod`. The root barrel was the only route, and it did not carry the name. + * + * ## The measured consequence, which is the natural "before" of leg (a2) + * + * The seat delivering objectui#8655 needed this type to stop + * `ObjectTreeProps.schema` being `any`. Because the name could not be + * imported, it had to spell the node as + * `Extract`. That idiom is + * correct and it still is; what it is not is discoverable. So leg (a2) asserts + * the imported name and that Extract are the SAME type - a barrel line that + * published a fork of the name would satisfy leg (a1) and still leave the + * reader with two things to reconcile. + * + * ## Why a SOURCE scan sits next to the type-level pins + * + * The type-level pins are erased by the compiler, so they say nothing during + * `pnpm test`; their enforcement is `tsc -p tsconfig.test.json`, the third leg + * of this package's `type-check` script, which CI runs as its own job. The + * source scan is the half that runs under `vitest`, and it is deliberately NOT + * a `dist/` read: this repo's per-PR `test` job runs `pnpm test` with no build + * step ahead of it (turbo's `test` task depends on `^build`, the DEPENDENCY + * closure, never the package's own build), so a test needing a fresh `dist/` + * would be vacuously absent-or-red on a cold cache. + * `package-exports-manifest.test.ts` and + * `combobox-option-root-barrel-7697.test.ts` record that same constraint for + * the same package; this file follows them rather than re-litigating it. + * + * ## What this file is NOT + * + * It is a PER-NAME pin, not a barrel-completeness gate. The card that ordered + * this repair offered such a gate as its option 3 and it was ruled out for + * this change: a gate over "every declared node schema is reachable from the + * barrel that publishes its family" is a wider design with its own review, and + * it would arrive here unrequested. objectui#9526 is the sibling card in the + * same family. If that gate is ever built, this file becomes one of its + * regression cases rather than its substitute. + */ +import { readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { describe, it, expect } from 'vitest'; + +import type { ObjectTreeSchema as FromRootBarrel } from '../index'; +import type { ObjectQLComponentSchema } from '../index'; +// The two controls: sibling arms that were ALREADY on the root barrel's +// `./objectql.js` list before this change. If either fails to resolve, every +// reading in this file is dark. +import type { ObjectMapSchema as MapFromRootBarrel } from '../index'; +import type { ObjectGanttSchema as GanttFromRootBarrel } from '../index'; + +const require = createRequire(import.meta.url); + +const readSource = (relative: string): string => + readFileSync(require.resolve(relative), 'utf8'); + +const INDEX_SRC = readSource('../index.ts'); +const OBJECTQL_SRC = readSource('../objectql.ts'); + +/** + * Invariant type equality - the house spelling + * (`combobox-option-root-barrel-7697`, `chart-series-keys-7546`, and others). + * Assignability alone would call a widened or `any`-resolved type a match; + * this does not. + */ +type Eq = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false; + +/** The spelling objectui#8655 was forced into, kept here as leg (a2)'s probe. */ +type ViaExtract = Extract; + +/** + * The bodies of every `export type { ... } from './objectql.js';` clause in + * the root barrel. There is exactly one, and the assertion below says so. + * + * The brace class is load-bearing, not tidiness: a lazy `[\s\S]*?` body would + * be free to open at some earlier `export type {` and close on the file's + * other `./objectql.js` line - the `import type { ObjectQLComponentSchema, + * ListViewSchema }` near the node-union declaration - swallowing every export + * clause in between. The count assertion is what would catch that. + */ +const objectqlReExportBodies = (): string[] => + [...INDEX_SRC.matchAll(/export type \{([^{}]*?)\} from '\.\/objectql\.js';/gu)].map( + (m) => m[1] ?? '', + ); + +/** Every name on the root barrel's `./objectql.js` named re-export list. */ +const objectqlReExportNames = (): string[] => { + const bodies = objectqlReExportBodies(); + // One clause, or the extraction below is reading a shape this file was not + // written against and every membership answer under it is unreliable. + expect(bodies).toHaveLength(1); + return (bodies[0] ?? '') + .split('\n') + .map((line) => line.trim()) + // Drop the prose. The list carries `//` commentary, so a substring read of + // the raw block would count comments as exports. + .filter((line) => line.length > 0 && !line.startsWith('//')) + .map((line) => /^([A-Za-z_$][\w$]*)\s*,?$/u.exec(line)?.[1] ?? '') + .filter((name) => name.length > 0); +}; + +/* -- (a) the type level: the name resolves, to the union's own arm --------- */ + +describe('objectui#9550 - `ObjectTreeSchema` resolves from the root barrel', () => { + it('a1 - resolves, with its declared members', () => { + // RED on the untouched base: `../index` has no exported member + // `ObjectTreeSchema`, so the import above fails to resolve under + // `tsc -p tsconfig.test.json` (TS2305) and each alias below is an error. + // + // Members are pinned one at a time rather than as a whole-shape equality: + // a key ADDED to the declaration is a decision for its own card, not a + // reason for this one to red, while a member RETYPED here would be exactly + // the "nothing retyped or narrowed" claim breaking. + const tag: Eq = true; + const objectName: Eq = true; + const parentField: Eq = true; + const labelField: Eq = true; + const fields: Eq = true; + const depth: Eq = true; + expect([tag, objectName, parentField, labelField, fields, depth]).toEqual([ + true, + true, + true, + true, + true, + true, + ]); + }); + + it('a2 - the barrel name IS the union arm, not a fork of it', () => { + // The `Extract` spelling objectui#8655 had to write keeps working, and it + // names the same declaration the barrel now publishes. Both halves matter: + // a fork would pass a1 and still leave two meanings behind one word, the + // trap `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` + // guards for the declaration case. + const same: Eq = true; + expect(same).toBe(true); + }); + + it('a3 - CONTROL: two sibling arms already on the list still resolve', () => { + const map: Eq = true; + const gantt: Eq = true; + expect([map, gantt]).toEqual([true, true]); + }); +}); + +/* -- (b) the source: an explicit named list, the declaration left in place - */ + +describe('objectui#9550 - the root barrel lists the name, explicitly', () => { + it('b1 - `ObjectTreeSchema` is on the `./objectql.js` named re-export list', () => { + // RED on the untouched base: the list closed without this name. + expect(objectqlReExportNames()).toContain('ObjectTreeSchema'); + }); + + it('b2 - CONTROL: the sibling arms are on the same list', () => { + // If these fail, the extraction is dark and the reading above says + // nothing. They are the arms the card measured as already present. + const names = objectqlReExportNames(); + expect(names).toEqual( + expect.arrayContaining([ + 'ObjectGridSchema', + 'ObjectFormSchema', + 'ObjectViewSchema', + 'ObjectMapSchema', + 'ObjectGanttSchema', + 'ObjectCalendarSchema', + 'ObjectKanbanSchema', + 'ObjectChartSchema', + 'ObjectGallerySchema', + 'ObjectDataTableSchema', + 'ListViewSchema', + ]), + ); + }); + + it('b3 - the list is still an EXPLICIT named list - no wildcard', () => { + // A wildcard would make b1 vacuous and would publish every other name in + // `objectql.ts` as a side effect - a far wider surface change than the one + // this card authorises. + expect(INDEX_SRC).not.toMatch(/export (?:type )?\* (?:as \w+ )?from '\.\/objectql\.js';/u); + }); + + it('b4 - the declaration did NOT move - `objectql.ts` still owns it', () => { + // The fix is a barrel line, not a relocation: `index.ts` re-exports, it + // never declares. Held under a file fence on this card as well - the + // declaration file was owned by another open pull request while this + // repair was made. + expect(OBJECTQL_SRC).toMatch(/^export interface ObjectTreeSchema\b/mu); + expect(INDEX_SRC).not.toMatch(/\b(?:interface|type)\s+ObjectTreeSchema\b/u); + }); +}); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index f540b80e6b..7ff8987152 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -442,6 +442,7 @@ export type { // Component schemas ObjectMapSchema, ObjectMapConfig, + ObjectTreeSchema, ObjectGanttSchema, ObjectCalendarSchema, ObjectKanbanSchema, From 5a82fd9e9f282364fe22c4adb9321e0b662ec0a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 12:04:19 +0000 Subject: [PATCH 2/3] refactor(plugin-tree): import `ObjectTreeSchema` instead of re-deriving it (objectui#9550) `ObjectTree` typed its node by narrowing the published union on the `object-tree` tag and binding that to a module-local alias. The spelling was forced, not chosen: the name it wanted was declared in `@object-ui/types` and carried by the zod barrel, but the root barrel did not name it, so there was nothing to import. The preceding commit put the name on that barrel; this one consumes it at both read sites, `ObjectTreeProps.schema` and `getTreeConfig`. The type does not move. `ObjectTree.schemaTyped-8655.test.ts` asserts `ObjectTreeProps['schema']` invariantly equal to the narrowing, independently of this branch, and it stays green. What goes away is a derived restatement of a published type - the per-consumer second authority an unimportable type mints, which is the class objectui#6349 is burning down. The docblock's justification paragraph goes with it: it gave as its MEASURED reason for the narrowing that the barrel omits the name so it "cannot be imported today", and that sentence is false now. The objectui#8651 shadowing warning beside it is KEPT and re-pointed at the import - a module-local type wearing a published type's name is the two-layers-one-word trap, and that is more load-bearing once the published name is importable, not less. The block now annotates the import rather than a declaration that no longer exists. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L --- .changeset/9550-object-tree-root-barrel.md | 4 +- ...0-plugin-tree-import-object-tree-schema.md | 26 +++++++++ packages/plugin-tree/src/ObjectTree.tsx | 58 ++++++++----------- 3 files changed, 53 insertions(+), 35 deletions(-) create mode 100644 .changeset/9550-plugin-tree-import-object-tree-schema.md diff --git a/.changeset/9550-object-tree-root-barrel.md b/.changeset/9550-object-tree-root-barrel.md index c52436e40c..a69b54d5a8 100644 --- a/.changeset/9550-object-tree-root-barrel.md +++ b/.changeset/9550-object-tree-root-barrel.md @@ -2,7 +2,7 @@ '@object-ui/types': minor --- -Export `ObjectTreeSchema` from the `@object-ui/types` root barrel +Export `ObjectTreeSchema` from the `@object-ui/types` root barrel (objectui#9550) `ObjectQLComponentSchema` declares the node types an ObjectQL block may be. Every one of its arms was a named export of this package's root barrel except @@ -28,6 +28,8 @@ pins that the imported name and that `Extract` are the same declaration, that the list stays explicit rather than becoming a wildcard, and that the declaration itself stays in `objectql.ts`. +The one hand-written copy this gap had already minted is retired in the same change: `@object-ui/plugin-tree` now imports the name instead of re-deriving it. See that package's own entry. + ⛔ Not done here: a gate over barrel completeness for every declared node schema. That was the card's own third option and it is a wider design with its own review; objectui#9526 is the sibling card in the same family. diff --git a/.changeset/9550-plugin-tree-import-object-tree-schema.md b/.changeset/9550-plugin-tree-import-object-tree-schema.md new file mode 100644 index 0000000000..c80979f900 --- /dev/null +++ b/.changeset/9550-plugin-tree-import-object-tree-schema.md @@ -0,0 +1,26 @@ +--- +'@object-ui/plugin-tree': patch +--- + +Import `ObjectTreeSchema` instead of re-deriving it (objectui#9550) + +`ObjectTree` typed its node by narrowing the published union on the `object-tree` +tag and binding that to a module-local alias. That spelling was correct and it was +forced: the name it wanted was declared in `@object-ui/types` and re-exported by +the `./zod` barrel, but the root barrel did not carry it, so there was nothing to +import. objectui#9550 put the name on that barrel, and this package now imports it. + +Nothing about the type changes — the imported name and the narrowing resolve to the +same declaration, which `ObjectTree.schemaTyped-8655.test.ts` asserts invariantly and +independently through `ObjectTreeProps['schema']`. What changes is that the renderer +no longer carries a derived restatement of a published type. A type nobody can import +mints one of those per consumer, and each one is correct on the day it is written — +that is the second-authority shape objectui#6349 is burning down, and this was one of +its instances rather than a tidy-up. + +The docblock's justification paragraph went with it: it stated, as its measured reason +for choosing the narrowing, that the barrel omits the name so it "cannot be imported +today". That sentence is false as of objectui#9550 and nothing re-derived it. The +objectui#8651 warning beside it is KEPT and re-pointed at the import: a module-local +type wearing a published type's name is the two-layers-one-word trap, and it is more +load-bearing now that the published name is importable, not less. diff --git a/packages/plugin-tree/src/ObjectTree.tsx b/packages/plugin-tree/src/ObjectTree.tsx index 14170e33b9..903b7829f7 100644 --- a/packages/plugin-tree/src/ObjectTree.tsx +++ b/packages/plugin-tree/src/ObjectTree.tsx @@ -20,7 +20,28 @@ */ import React, { useEffect, useMemo, useState } from 'react'; -import type { DataSource, ObjectQLComponentSchema, TreeViewConfig } from '@object-ui/types'; +/** + * The `object-tree` NODE this renderer draws, imported BY NAME from the + * published barrel (objectui#8655 step ①; objectui#9550 is what put the name + * on that barrel, which is what makes a named import possible at all). + * + * ⛔ Not a second declaration of the node's shape. `ObjectTreeSchema` in + * `@object-ui/types` is the one declaration — the interface whose own docblock + * calls itself the "Object Tree (tree-grid) Component Schema", and which + * `views.ts` names as "the node an author writes". Every key and every key TYPE + * arrives from there, so a key added, renamed or retyped on it reaches this + * renderer without an edit here: the property a hand-copied interface cannot + * have, and the same derivation `ResolvedTreeConfig` below already uses against + * `TreeViewConfig`. + * + * ⛔ Never re-declared LOCALLY under this name, and not aliased on the way in: + * a module-local type under a published type's name is the two-layers-one-word + * trap objectui#8651 recorded when a local `CalendarSchema` shadowed the + * published one. That is what makes the line below a plain named import, and + * the warning is MORE load-bearing now that the published name is importable, + * not less. + */ +import type { DataSource, ObjectTreeSchema, TreeViewConfig } from '@object-ui/types'; import { useNavigationOverlay, useSafeFieldLabel, @@ -78,37 +99,6 @@ const useTreeTranslation = createSafeTranslation( 'detail.recordDetail', ); -/** - * The `object-tree` NODE, taken off the PUBLISHED union rather than written - * out here (objectui#8655 step ①). - * - * ⛔ Not a second declaration of the node's shape. `ObjectTreeSchema` in - * `@object-ui/types` is the one declaration — the interface whose own docblock - * calls itself the "Object Tree (tree-grid) Component Schema", and which - * `views.ts` names as "the node an author writes". Every key and every key TYPE - * arrives from there, so a key added, renamed or retyped on it reaches this - * renderer without an edit here: the property a hand-copied interface cannot - * have, and the same derivation `ResolvedTreeConfig` below already uses against - * `TreeViewConfig`. - * - * ⚠️ `Extract` off `ObjectQLComponentSchema`, and NOT a named import, for a - * measured reason: `ObjectTreeSchema` is declared in `objectql.ts` and re-exported - * from the ZOD barrel (objectui#8784 / PR #8777 repaired that half), but the TS - * barrel's `export type { … } from './objectql.js'` block omits it, while its nine - * siblings — `ObjectMapSchema`, `ObjectGanttSchema`, `ObjectCalendarSchema`, - * `ObjectKanbanSchema`, `ObjectChartSchema`, `ObjectGallerySchema`, - * `ObjectDataTableSchema`, `ObjectGridSchema`, `ObjectFormSchema` — are all on it. - * So the name cannot be imported today. ⛔ That omission is NOT repaired here: it - * is a published-surface addition on another package and belongs to whoever files - * it. `Extract` is the spelling `ObjectQLComponentSchema`'s own docblock teaches, - * and it needs nothing added to any published face. - * - * ⛔ Deliberately NOT spelled `ObjectTreeSchema` locally: a module-local type under - * a published type's name is the two-layers-one-word trap objectui#8651 recorded - * when a local `CalendarSchema` shadowed the published one. - */ -type ObjectTreeNodeSchema = Extract; - export interface ObjectTreeProps { /** * The `object-tree` node this renderer draws. @@ -136,7 +126,7 @@ export interface ObjectTreeProps { * question becomes ANSWERABLE by the checker, ⛔ not that an undeclared read is * refused — the same ceiling objectui#5155 / objectui#7927 record for the mirror. */ - schema: ObjectTreeNodeSchema; + schema: ObjectTreeSchema; dataSource?: DataSource; className?: string; /** @@ -242,7 +232,7 @@ function fieldKey(f: any): string | undefined { * `parentField` alone) — else delete the read. This is that deletion, executed * on objectui#8841. */ -function getTreeConfig(schema: ObjectTreeNodeSchema): ResolvedTreeConfig { +function getTreeConfig(schema: ObjectTreeSchema): ResolvedTreeConfig { const nested = (schema.tree || schema.filter?.tree || {}) as TreeViewConfig; const rawFields = Array.isArray(schema.fields) ? schema.fields From a8cfe1ced22ab05db29f49392f338ffd9fd03f5f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 12:32:49 +0000 Subject: [PATCH 3/3] docs(changeset): point the pending 8655 prose at the state being released (objectui#9550) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three prose sites asserted the pre-merge tree. One class, one layer out from the one the previous commit closed - and the dangerous half is that two of them publish VERBATIM into a CHANGELOG at the next release, so they would have shipped as a description of code that never existed that way. `.changeset/8655-object-tree-props-schema-typed.md` is UNRELEASED and lands in the same `@object-ui/plugin-tree` release as this branch's own entry. Two of its sentences are falsified by this head: that the type is DERIVED by union-narrowing, and that this is so BECAUSE the TS barrel does not export `ObjectTreeSchema`. Both are now past tense and point forward at the entry that took the repair. Its "nine siblings" was born wrong - the union declares twelve arms, so eleven are siblings - and is corrected in the same pass. Frontmatter is byte-identical; `check-changeset-overwrite.mjs` prints the base and current declarations side by side and they match, which is that gate's case 2. `ObjectTree.schemaTyped-8655.test.ts` kept two comments describing the retired declaration. ⛔ The pin itself is UNCHANGED - proven, not asserted: masking comments with this repo's own `js-comment-mask.mjs` leaves 202 code lines hashing identically before and after, with a lit control showing the comparison does move when one token of code changes. That module-local narrowing is now the VALUABLE half: an independently written second spelling whose agreement with the renderer's named import is what makes the swap provably safe, so both comments now say it is a deliberate probe kept on purpose. This branch's own entry carried the same defect in miniature and is corrected with them: one present-tense clause about readers of a file that no longer reads that way, and a generic written in the bare angle-bracket form that GitHub's body sanitizer deletes - respelled in the spaced form the sibling changeset already uses, since this text publishes to a rendered page. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L --- .changeset/8655-object-tree-props-schema-typed.md | 15 ++++++++++----- .changeset/9550-object-tree-root-barrel.md | 4 ++-- .../src/ObjectTree.schemaTyped-8655.test.ts | 12 ++++++++++-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.changeset/8655-object-tree-props-schema-typed.md b/.changeset/8655-object-tree-props-schema-typed.md index ddc7aca299..9afc01e06d 100644 --- a/.changeset/8655-object-tree-props-schema-typed.md +++ b/.changeset/8655-object-tree-props-schema-typed.md @@ -5,14 +5,19 @@ `ObjectTreeProps.schema` is the published `object-tree` node instead of `any`, and `getTreeConfig`'s parameter with it (objectui#8655). -The type is DERIVED, not written out: `Extract< ObjectQLComponentSchema, { type: +The type WAS DERIVED rather than written out: `Extract< ObjectQLComponentSchema, { type: 'object-tree' } >`, the spelling that union's own docblock teaches. So every key name and key type still has exactly one declaration, `ObjectTreeSchema` in `@object-ui/types`, and a key added or retyped there arrives here without an -edit. `Extract` rather than a named import because the TS barrel does not export -`ObjectTreeSchema` — the zod barrel does, its nine siblings are all on the TS -one, and repairing that omission is a published-surface addition on another -package, so it is reported rather than smuggled in here. +edit. `Extract` rather than a named import because, when this landed, the TS barrel did +not export `ObjectTreeSchema` — the zod barrel did, and its eleven sibling arms +were all on the TS one. Repairing that omission was a published-surface addition +on another package, so it was reported rather than smuggled in here, and +objectui#9550 took it: the name is on that barrel in THIS SAME RELEASE and this +package now imports it — see that entry. ⚠️ So the derivation described above is the +state this card left behind, ⛔ not the state being released; the shipped code reads +as a named import. The claim that survives both spellings is the one that mattered: +one declaration, `ObjectTreeSchema`, and no copy of it here. Accept-set change on the published props type, stated plainly: diff --git a/.changeset/9550-object-tree-root-barrel.md b/.changeset/9550-object-tree-root-barrel.md index a69b54d5a8..6a8c928b83 100644 --- a/.changeset/9550-object-tree-root-barrel.md +++ b/.changeset/9550-object-tree-root-barrel.md @@ -16,8 +16,8 @@ barrel was the only route to this type. The omission was not inert. The seat that stopped `ObjectTreeProps.schema` being `any` in `@object-ui/plugin-tree` (objectui#8655) could not import the name, so it had to spell the node as -`Extract` — an idiom that -works, and that every later reader of that file has to decode. A type nobody +`Extract< ObjectQLComponentSchema, { type: 'object-tree' } >` — an idiom that +worked, and that every reader of that file had to decode. A type nobody can import mints a fresh hand-written copy of itself for each consumer that needs it, which is the second-authority shape objectui#6349 is burning down. diff --git a/packages/plugin-tree/src/ObjectTree.schemaTyped-8655.test.ts b/packages/plugin-tree/src/ObjectTree.schemaTyped-8655.test.ts index f969358076..03ef4811f7 100644 --- a/packages/plugin-tree/src/ObjectTree.schemaTyped-8655.test.ts +++ b/packages/plugin-tree/src/ObjectTree.schemaTyped-8655.test.ts @@ -122,7 +122,15 @@ const HERE = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = join(HERE, '..', '..', '..'); const TREE_READER = 'packages/plugin-tree/src/ObjectTree.tsx'; -/** The node the checker now sees at every read, spelled the way the renderer spells it. */ +/** + * The node spelled INDEPENDENTLY of the renderer: narrowed off the published + * union, here and only here. ⭐ A deliberate SECOND spelling, kept on purpose — + * `ObjectTree.tsx` imports `ObjectTreeSchema` by name as of objectui#9550, so the + * ① row below is a CONSUMER-SIDE proof that the named import and the union arm + * are one declaration. ⛔ Do not "tidy" this into the same import the renderer + * uses: two independently written spellings that must agree IS the assertion, and + * one spelling agreeing with itself asserts nothing. + */ type ObjectTreeNode = Extract; /** A key nothing reads and nothing declares — the both-ways control. */ @@ -205,7 +213,7 @@ type DeclaredKeys = keyof { }; type Declares = K extends DeclaredKeys ? true : false; -/** ① — the prop is the published node, derived off the union, and it is not `any`. */ +/** ① — the prop is the published node, equal to the union arm, and it is not `any`. */ export type _PropIsThePublishedNode = Expect>; export type _PropIsNotAnyAnyMore = Expect, false>>; /** The `Equal` helper can FAIL — synthetic control, so the two rows above count. */