From 6f6c82f1e6d19238b1a467296d90c28ec8a5d20b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 23:48:42 +0000 Subject: [PATCH 1/5] refactor(types): name the class-name/style zod object for what it is MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `StylePropsSchema` (2 keys: `className`, `style`) and the TypeScript `StyleProps` (the Tailwind-scale vocabulary: `padding`, `margin`, `gap`, …) shared a name and zero keys, in a package where the `…Schema` suffix otherwise means "runtime mirror of the like-named declaration" (154 registered pairs, counted from the AST of `MIRRORS` in `zod-mirror-parity.test.ts`). The false pair is what a name-derived pairing produced when objectui#5684's registry was built. - rename the zod const to `ClassNameStylePropsSchema`; keep `StylePropsSchema` as a deprecated alias of the SAME object for one release, exported from the published `@object-ui/types/zod` barrel, so the rename does not narrow the surface; - teach the name-pairing to skip explicit non-pairs WITH A STATED REASON (`NAME_NON_PAIRS`), and re-measure the claim each reason rests on every run: the named declaration must exist and the two sides must share no key, so a collision that becomes PARTIAL turns red instead of comparing like a mirror. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC --- .../5928-classname-style-props-rename.md | 13 ++ .../__tests__/style-props-alias-5928.test.ts | 63 ++++++++ .../src/__tests__/zod-mirror-parity.test.ts | 150 +++++++++++++++++- packages/types/src/zod/base.zod.ts | 21 ++- packages/types/src/zod/index.zod.ts | 2 + 5 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 .changeset/5928-classname-style-props-rename.md create mode 100644 packages/types/src/__tests__/style-props-alias-5928.test.ts diff --git a/.changeset/5928-classname-style-props-rename.md b/.changeset/5928-classname-style-props-rename.md new file mode 100644 index 0000000000..09b59b5b93 --- /dev/null +++ b/.changeset/5928-classname-style-props-rename.md @@ -0,0 +1,13 @@ +--- +"@object-ui/types": minor +--- + +`@object-ui/types/zod`: `StylePropsSchema` is renamed to `ClassNameStylePropsSchema`, with the old name kept as a deprecated alias for one release (objectui#5928) + +The zod const `StylePropsSchema` (`zod/base.zod.ts`) declares exactly two keys — `className` and `style`, the CSS passthrough attributes. The TypeScript `StyleProps` (`base.ts`) is the Tailwind-SCALE vocabulary: `padding`, `margin`, `gap`, `backgroundColor`, `textColor`, `borderWidth`, `borderColor`, `borderRadius`. Measured on this base with an AST read of both files: 2 keys against 8, sharing ZERO keys. + +In this package the `…Schema` suffix otherwise means "runtime mirror of the like-named declaration" — 154 registered pairs on this tree (`Object.keys(MIRRORS).length` in `zod-mirror-parity.test.ts`, counted from the AST of that object literal). So the shared name asserted a mirror relationship that does not exist, and building objectui#5684's parity registry by name pairing duly put the two together and reported drift on a pair that has no counterpart at all. + +**Published surface.** `ClassNameStylePropsSchema` is added; `StylePropsSchema` continues to be exported from `@object-ui/types/zod` as a deprecated alias of the same object (same reference, same accept set — nothing that parsed before parses differently), and is removed one release out. Import the new name. + +**The guard half.** `zod-mirror-parity.test.ts` now carries `NAME_NON_PAIRS`: the consts a name-derived pairing must SKIP, each with the reason it is skipped and the declaration it is skipped against. The reason is not prose — the suite re-measures the claim it rests on on every run: the named declaration must still exist, and the two sides must still share no key. A collision that starts to overlap PARTIALLY (the case `assertionEveryPairOverlaps` cannot see, because it only rejects a TOTALLY empty overlap) turns the suite red and has to be decided, instead of quietly comparing like a mirror and reporting phantom drift. diff --git a/packages/types/src/__tests__/style-props-alias-5928.test.ts b/packages/types/src/__tests__/style-props-alias-5928.test.ts new file mode 100644 index 0000000000..83a9b8fdbd --- /dev/null +++ b/packages/types/src/__tests__/style-props-alias-5928.test.ts @@ -0,0 +1,63 @@ +/** + * 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. + */ + +/** + * `StylePropsSchema` is renamed to `ClassNameStylePropsSchema`, and the old name + * stays LIVE as a deprecated alias for one release (objectui#5928). + * + * ## What this file exists to prove + * + * The rename moves a name on a PUBLISHED surface (`@object-ui/types/zod`). The + * alias is the mechanism that keeps the rename from narrowing that surface, so the + * alias has to be checked the way a published export is checked — by IMPORTING it + * through the published barrel and using it — not by the type-checker's silence. A + * `tsc` run stays green on an alias that was dropped from the barrel, because + * nothing in this package imports it; only a runtime import of the published path + * can report that the name is gone. + * + * Both faces are pinned here: + * - the alias resolves to the SAME object as the new name (`toBe`), so the two + * spellings cannot drift into two schemas, and + * - it VALIDATES, so the export is a live zod schema and not an inert re-export + * of something that lost its identity through the rename. + * + * The other half of the card — that the old name never was a mirror of the + * like-named TS `StyleProps` — is measured in `zod-mirror-parity.test.ts` + * (`NAME_NON_PAIRS`). + */ + +import { describe, it, expect } from 'vitest'; + +// The PUBLISHED path (`@object-ui/types/zod` resolves to this barrel), deliberately +// not `../zod/base.zod.js`: an alias that survives in the source file but is missing +// from the barrel is exactly the regression this file must catch. +import { ClassNameStylePropsSchema, StylePropsSchema } from '../zod/index.zod.js'; + +describe('ClassNameStylePropsSchema (objectui#5928)', () => { + it('the deprecated `StylePropsSchema` alias is the SAME object, not a copy', () => { + expect(StylePropsSchema).toBe(ClassNameStylePropsSchema); + }); + + it('the alias is a live schema — importing the old name still validates', () => { + const ok = StylePropsSchema.safeParse({ className: 'p-4 text-sm', style: { color: 'red', zIndex: 10 } }); + expect(ok.success).toBe(true); + + // A refusal through the old name, addressed to the key that is wrong — the + // accept set the alias carries is the schema's, not a passthrough of anything. + const bad = StylePropsSchema.safeParse({ className: 42 }); + expect(bad.success).toBe(false); + if (!bad.success) expect(bad.error.issues[0]?.path).toEqual(['className']); + }); + + it('the renamed const carries exactly the two keys the name claims', () => { + // The rename was justified by a measurement (2 keys, both CSS passthrough + // attributes). Pinned so the name cannot outlive what it describes: a third key + // arriving here makes `ClassNameStyleProps…` a lie and must be a decision. + expect(Object.keys(ClassNameStylePropsSchema.shape).sort()).toEqual(['className', 'style']); + }); +}); diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts index 28092c3e99..a107ca933a 100644 --- a/packages/types/src/__tests__/zod-mirror-parity.test.ts +++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts @@ -144,7 +144,7 @@ import ts from 'typescript'; import type { z } from 'zod'; import { AppActionSchema, AppComponentSchema, NavigationAreaSchema } from '../zod/app.zod.js'; -import { BaseSchema, ComponentConfigSchema, ComponentInputSchema, ComponentMetaSchema, KeyedI18nLabelSchema } from '../zod/base.zod.js'; +import { BaseSchema, ClassNameStylePropsSchema, ComponentConfigSchema, ComponentInputSchema, ComponentMetaSchema, KeyedI18nLabelSchema, StylePropsSchema } from '../zod/base.zod.js'; import { CalendarEventSchema, CalendarViewSchema, CarouselItemSchema, CarouselSchema, ChatbotSchema, ChatMessageSchema, ChatMessageSourceSchema, ChatToolInvocationSchema, DashboardComponentSchema, DashboardConfigSchema, DashboardWidgetConfigSchema, DashboardWidgetLayoutSchema, DashboardWidgetSchema, FilterBuilderSchema, FilterFieldSchema, KanbanCardSchema, KanbanColumnSchema, KanbanSchema } from '../zod/complex.zod.js'; import { ActionCallbackSchema, CRUDDialogSchema, DetailSchema } from '../zod/crud.zod.js'; import { AlertSchema, AvatarSchema, BadgeSchema, BarChartSchema, ChartDataSeriesSchema, ChartSchema, DataTableSchema, HtmlSchema, KbdSchema, ListItemSchema, ListSchema, MarkdownSchema, StaticTableColumnSchema, StatisticSchema, TableColumnSchema, TableSchema, TimelineEventSchema, TimelineSchema, TreeViewSchema } from '../zod/data-display.zod.js'; @@ -1504,6 +1504,44 @@ export type assertionBaseSchemaKeysResolve = Expect< > >; +/* ── Declared non-pairs: the pairing SKIPS these, and says why ──────────────── */ + +/** + * Names that LOOK like a pair and are not (objectui#5928). + * + * ## Why a skip has to be written down, and re-measured + * + * `MIRRORS` was built by pairing a `…Schema` const with the like-named TS + * declaration, because in this package that suffix almost always means "runtime + * mirror of the like-named declaration" — `Object.keys(MIRRORS).length` registered + * pairs, a figure the census below prints rather than restates. `StylePropsSchema` + * was not one of them: the like-named `StyleProps` (`../base.ts`) is the + * Tailwind-SCALE vocabulary, so the name-derived pairing compared two unrelated key + * sets and reported drift on a mirror relationship that does not exist. + * + * `assertionEveryPairOverlaps` caught that one, but only because the overlap is + * TOTALLY empty. A collision that overlaps PARTIALLY compares like a mirror and + * reports phantom drift with nothing to catch it — the hole objectui#5928 was filed + * against. So the skip is recorded here WITH ITS REASON, and the claim the reason + * rests on is re-measured on every run: the named declaration must still exist, and + * the two sides must still share NO key. The day either side moves so that they DO + * overlap, this reddens and the collision is decided again — instead of quietly + * becoming a mirror. + * + * ⚠️ Scope. This is for consts the pairing SKIPS. A const that IS paired, but + * against a differently-named declaration, is a different case and is deliberately + * not recorded here: `FieldConstraintsSchema` mirrors `FieldValidationRules`, not + * the like-named legacy `FieldConstraints`, and its own docstring is what says so. + */ +const NAME_NON_PAIRS = { + 'base.zod.ts#StylePropsSchema': { + mirror: StylePropsSchema, + declaration: 'StyleProps', + reason: + 'the deprecated alias of `ClassNameStylePropsSchema` (objectui#5928), exported for one release. `StyleProps` (../base.ts) is the Tailwind-scale layout vocabulary (`padding`, `margin`, `gap`, `backgroundColor`, …); this object carries the two CSS passthrough attributes (`className`, `style`). They only ever shared a name.', + }, +} as const; + /* ── Exclusions ─────────────────────────────────────────────────────────────── */ /** @@ -1512,12 +1550,14 @@ export type assertionBaseSchemaKeysResolve = Expect< * in neither map. */ const EXCLUSIONS: Readonly> = { - // A NAME COLLISION, not a mirror. The like-named `StyleProps` in `../base.ts` is a - // Tailwind-scale vocabulary (`padding`, `margin`, `gap`, `backgroundColor`, …) and - // shares ZERO keys with this `{ className, style }` object. A name-derived pairing - // put them together; `assertionEveryPairOverlaps` rejected it. + // The `{ className, style }` passthrough object. Nothing in this package restates + // it — and the name that LOOKED like its declaration is the collision objectui#5928 + // renamed away; `NAME_NON_PAIRS` above keeps measuring that collision for as long as + // the deprecated alias is exported. + 'base.zod.ts#ClassNameStylePropsSchema': + 'no TS declaration in this package restates it — the `{ className, style }` passthrough attributes are declared inline on each schema, never as one shared interface', 'base.zod.ts#StylePropsSchema': - 'no TS declaration in this package restates it — `StyleProps` (../base.ts) is an unrelated Tailwind style vocabulary that shares no key with it', + 'the deprecated alias of `ClassNameStylePropsSchema` (objectui#5928), removed one release out — it restates nothing, and the like-named `StyleProps` (../base.ts) is the unrelated Tailwind-scale vocabulary recorded in `NAME_NON_PAIRS`', 'app.zod.ts#NavigationItemTypeSchema': "a bare vocabulary with no `.shape`; it is checked where a mirrored KEY declares it", 'app.zod.ts#NavigationItemSchema': @@ -1680,6 +1720,37 @@ function exportedConsts(): string[] { return out; } +/* ── Which keys a TS declaration in this package declares (AST) ──────────────── */ + +const SRC_DIR = join(dirname(fileURLToPath(import.meta.url)), '..'); + +/** + * The member names of ONE TS declaration in this package, or `null` if no + * declaration by that name exists. + * + * Read from the AST of the package's own sources rather than from a type-level + * probe, because `NAME_NON_PAIRS` has to answer a question the type level cannot: + * "does a declaration by this NAME still exist at all?". A type import of a name + * that has been deleted is a compile error, not a measurement — the entry would + * have to be edited before the check could report on it, which is the opposite of + * a ratchet. + */ +export function declaredMemberNames(name: string): string[] | null { + for (const file of readdirSync(SRC_DIR).sort()) { + if (!file.endsWith('.ts')) continue; + const sf = ts.createSourceFile(file, readFileSync(join(SRC_DIR, file), 'utf8'), ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS); + for (const stmt of sf.statements) { + if (ts.isInterfaceDeclaration(stmt) && stmt.name.text === name) { + return stmt.members.filter((m) => m.name && ts.isIdentifier(m.name)).map((m) => (m.name as ts.Identifier).text); + } + if (ts.isTypeAliasDeclaration(stmt) && stmt.name.text === name && ts.isTypeLiteralNode(stmt.type)) { + return stmt.type.members.filter((m) => m.name && ts.isIdentifier(m.name)).map((m) => (m.name as ts.Identifier).text); + } + } + } + return null; +} + /* ── Which exports reference a spec symbol (AST, not raw text) ───────────────── */ /** @@ -1809,7 +1880,7 @@ exactly how objectui#4605 and #5186 stayed latent.`).toEqual([]); it('no map entry names a const that no longer exists', () => { const onDisk = new Set(exportedConsts()); - const stale = [...Object.keys(MIRRORS), ...Object.keys(EXCLUSIONS)].filter((k) => !onDisk.has(k)); + const stale = [...Object.keys(MIRRORS), ...Object.keys(EXCLUSIONS), ...Object.keys(NAME_NON_PAIRS)].filter((k) => !onDisk.has(k)); expect(stale, 'stale entries — the const was renamed or removed').toEqual([]); }); @@ -1846,6 +1917,71 @@ exactly how objectui#4605 and #5186 stayed latent.`).toEqual([]); }); }); +describe('the name-derived pairing skips declared non-pairs (objectui#5928)', () => { + const entries = Object.entries(NAME_NON_PAIRS); + + it('the skip list is not empty — this suite has something to measure', () => { + // Every assertion below is written over `entries`. An empty map makes all of + // them pass while measuring nothing, which is the failure mode the whole file + // is built against. + expect(entries.length).toBeGreaterThan(0); + }); + + it('every skip names a TS declaration that still exists', () => { + const missing = entries.filter(([, e]) => declaredMemberNames(e.declaration) === null); + expect(missing.map(([k, e]) => `${k} -> ${e.declaration}`), ` +A skip names a declaration this package no longer has. The collision it records is +gone (or the name is misspelled), so the entry states something unmeasurable — drop +the entry, or correct the name it points at.`).toEqual([]); + }); + + it('every skip carries a stated reason', () => { + const empty = entries.filter(([, e]) => e.reason.trim().length < 20); + expect(empty.map(([k]) => k), + 'a skip without a reason is how the next false pair gets added silently').toEqual([]); + }); + + it('a skipped const is never also a registered pair', () => { + const both = entries.map(([k]) => k).filter((k) => k in MIRRORS); + expect(both, 'a const cannot be both skipped and paired — one of the two is wrong').toEqual([]); + }); + + it('the skip measures the object the canonical name exports', () => { + // The alias is the subject of the entry, so the entry must hold the SAME object + // the canonical export holds. A copy would let the two drift apart and the + // measurement below would be of something nobody publishes. + expect(NAME_NON_PAIRS['base.zod.ts#StylePropsSchema'].mirror).toBe(ClassNameStylePropsSchema); + }); + + it('THE MEASUREMENT — the two sides of every skip still share no key', () => { + const overlapping = entries + .map(([k, e]) => { + const mirrored = Object.keys(e.mirror.shape); + const declared = declaredMemberNames(e.declaration) ?? []; + return [k, mirrored.filter((key) => declared.includes(key))] as const; + }) + .filter(([, shared]) => shared.length > 0); + + expect(overlapping.map(([k, shared]) => `${k}: ${shared.join(', ')}`), ` +A skipped non-pair now OVERLAPS the declaration it was skipped against. That is the +partial-overlap collision objectui#5928 was filed about: from here a name-derived +pairing compares like a mirror and reports phantom drift with nothing to catch it. +Decide the collision — rename one of the two, or register the pair deliberately — +rather than editing this expectation.`).toEqual([]); + }); + + it('non-vacuity: the same two readers MEASURE the overlap of a real pair', () => { + // Both readers returning nothing would make the assertion above green forever. + // Run them, unchanged, over a registered pair that demonstrably overlaps. + const mirrored = Object.keys(ListItemSchema.shape); + const declared = declaredMemberNames('ListItem'); + expect(declared, '`ListItem` is declared in ../data-display.ts — a null here is a broken reader').not.toBeNull(); + const shared = mirrored.filter((key) => (declared ?? []).includes(key)); + expect(shared).toContain('label'); + expect(shared.length).toBeGreaterThan(3); + }); +}); + describe('the spec-reference scan reads code, not prose (objectui#6705)', () => { const scan = (src: string): string[] => [...specReferencingExports('fixture.zod.ts', src)].sort(); diff --git a/packages/types/src/zod/base.zod.ts b/packages/types/src/zod/base.zod.ts index 3cc82e00bb..84f699b3dc 100644 --- a/packages/types/src/zod/base.zod.ts +++ b/packages/types/src/zod/base.zod.ts @@ -407,9 +407,26 @@ export const HTMLAttributesSchema = z.record(z.string(), z.any()).describe('HTML export const EventHandlersSchema = z.record(z.string(), z.function()).describe('Event handlers'); /** - * Style Props + * The two CSS passthrough attributes a node exposes: a Tailwind class string and + * an inline style record. + * + * ⚠️ NOT a mirror of `StyleProps` in `../base.ts` (objectui#5928). That + * declaration is the Tailwind-SCALE vocabulary (`padding`, `margin`, `gap`, + * `backgroundColor`, …) and shares ZERO keys with this object — the two only ever + * shared a name, and pairing them by that name reported drift on a mirror + * relationship that does not exist. The non-pair is recorded, with the overlap + * re-measured on every run, in `../__tests__/zod-mirror-parity.test.ts`. */ -export const StylePropsSchema = z.object({ +export const ClassNameStylePropsSchema = z.object({ className: z.string().optional(), style: z.record(z.string(), z.union([z.string(), z.number()])).optional(), }).describe('Style properties'); + +/** + * @deprecated Renamed to `ClassNameStylePropsSchema` (objectui#5928) — the old + * name asserted a mirror relationship with `StyleProps` (`../base.ts`) that does + * not exist. This alias IS that object (same reference, same accept set), kept for + * one release so the rename does not narrow the published surface; it is removed + * after that. Import `ClassNameStylePropsSchema`. + */ +export const StylePropsSchema = ClassNameStylePropsSchema; diff --git a/packages/types/src/zod/index.zod.ts b/packages/types/src/zod/index.zod.ts index 1cfdaefb62..11422c950f 100644 --- a/packages/types/src/zod/index.zod.ts +++ b/packages/types/src/zod/index.zod.ts @@ -58,6 +58,8 @@ export { ComponentConfigSchema, HTMLAttributesSchema, EventHandlersSchema, + ClassNameStylePropsSchema, + /** @deprecated objectui#5928 — use `ClassNameStylePropsSchema`. Same object; removed one release out. */ StylePropsSchema, } from './base.zod.js'; From 681053908d7e7a267091357c6d745b64980c54a1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 23:59:00 +0000 Subject: [PATCH 2/5] docs(types): do not restate the pair count in the new skip-list docstring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A written count in this header is prose that rots — objectui#6141 measured exactly that on the three counts already there. The skip list names the population as `Object.keys(MIRRORS)` instead of quoting a number. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC --- .../types/src/__tests__/zod-mirror-parity.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts index a107ca933a..0215eb252a 100644 --- a/packages/types/src/__tests__/zod-mirror-parity.test.ts +++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts @@ -1513,11 +1513,13 @@ export type assertionBaseSchemaKeysResolve = Expect< * * `MIRRORS` was built by pairing a `…Schema` const with the like-named TS * declaration, because in this package that suffix almost always means "runtime - * mirror of the like-named declaration" — `Object.keys(MIRRORS).length` registered - * pairs, a figure the census below prints rather than restates. `StylePropsSchema` - * was not one of them: the like-named `StyleProps` (`../base.ts`) is the - * Tailwind-SCALE vocabulary, so the name-derived pairing compared two unrelated key - * sets and reported drift on a mirror relationship that does not exist. + * mirror of the like-named declaration" — the population is `Object.keys(MIRRORS)`, + * deliberately not restated here as a written number (objectui#6141 measured what + * happens to counts in this header: they rot, and get quoted as fact). + * `StylePropsSchema` was not one of them: the like-named `StyleProps` (`../base.ts`) + * is the Tailwind-SCALE vocabulary, so the name-derived pairing compared two + * unrelated key sets and reported drift on a mirror relationship that does not + * exist. * * `assertionEveryPairOverlaps` caught that one, but only because the overlap is * TOTALLY empty. A collision that overlaps PARTIALLY compares like a mirror and From 247c90a6c2723d38c6c71509de4a104913b5a753 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 04:09:44 +0000 Subject: [PATCH 3/5] refactor(types)!: rename the class-name/style zod object outright, no alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deprecated `StylePropsSchema` alias and its barrel line are removed: `ClassNameStylePropsSchema` is the only spelling on `@object-ui/types/zod`. Maintainer ruling, 2026-08-27: a deprecated alias needs named external-consumer evidence, and this rename has none — no window, no second spelling. `NAME_NON_PAIRS` existed only to skip that alias, so it goes with it. With no like-named pair left in the tree the const is accounted for through the mechanism `zod-mirror-parity.test.ts` already has for a const with no TypeScript twin: an `EXCLUSIONS` entry with a stated reason. Both pins stay, renamed for what they now prove: the published barrel exports the new name as a live schema and no longer carries the retired one, and the object carries exactly the two keys its name claims. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC --- .../5928-classname-style-props-rename.md | 12 +- .../classname-style-props-rename-5928.test.ts | 85 ++++++++++ .../__tests__/style-props-alias-5928.test.ts | 63 -------- .../src/__tests__/zod-mirror-parity.test.ts | 151 +----------------- packages/types/src/zod/base.zod.ts | 15 +- packages/types/src/zod/index.zod.ts | 2 - 6 files changed, 102 insertions(+), 226 deletions(-) create mode 100644 packages/types/src/__tests__/classname-style-props-rename-5928.test.ts delete mode 100644 packages/types/src/__tests__/style-props-alias-5928.test.ts diff --git a/.changeset/5928-classname-style-props-rename.md b/.changeset/5928-classname-style-props-rename.md index 09b59b5b93..903fdb33e3 100644 --- a/.changeset/5928-classname-style-props-rename.md +++ b/.changeset/5928-classname-style-props-rename.md @@ -1,13 +1,13 @@ --- -"@object-ui/types": minor +'@object-ui/types': minor --- -`@object-ui/types/zod`: `StylePropsSchema` is renamed to `ClassNameStylePropsSchema`, with the old name kept as a deprecated alias for one release (objectui#5928) +`@object-ui/types/zod`: the zod const `StylePropsSchema` is renamed to `ClassNameStylePropsSchema` (objectui#5928). **The old name is gone** — there is no deprecated alias and no second spelling. Import `ClassNameStylePropsSchema`. -The zod const `StylePropsSchema` (`zod/base.zod.ts`) declares exactly two keys — `className` and `style`, the CSS passthrough attributes. The TypeScript `StyleProps` (`base.ts`) is the Tailwind-SCALE vocabulary: `padding`, `margin`, `gap`, `backgroundColor`, `textColor`, `borderWidth`, `borderColor`, `borderRadius`. Measured on this base with an AST read of both files: 2 keys against 8, sharing ZERO keys. +**What moves on the published surface.** `StylePropsSchema` is removed from `@object-ui/types/zod`; the same object is exported under the new name with the same accept set, so nothing that parsed before parses differently and nothing refused before is accepted now. The break is the name alone: an import of `StylePropsSchema` no longer resolves. -In this package the `…Schema` suffix otherwise means "runtime mirror of the like-named declaration" — 154 registered pairs on this tree (`Object.keys(MIRRORS).length` in `zod-mirror-parity.test.ts`, counted from the AST of that object literal). So the shared name asserted a mirror relationship that does not exist, and building objectui#5684's parity registry by name pairing duly put the two together and reported drift on a pair that has no counterpart at all. +**Why the name had to move.** The const declares exactly two keys — `className` and `style`, the CSS passthrough attributes a node exposes. The TypeScript `StyleProps` (`base.ts`) is the Tailwind-SCALE vocabulary: `padding`, `margin`, `gap`, `backgroundColor`, `textColor`, `borderWidth`, `borderColor`, `borderRadius`. Measured on this branch's base with an AST read of both files: 2 keys against 8, sharing ZERO keys. In this package the `…Schema` suffix otherwise means "runtime mirror of the like-named declaration", so the shared name asserted a mirror relationship that does not exist — and building objectui#5684's parity registry by name pairing duly put the two together and reported drift on a pair that has no counterpart at all. -**Published surface.** `ClassNameStylePropsSchema` is added; `StylePropsSchema` continues to be exported from `@object-ui/types/zod` as a deprecated alias of the same object (same reference, same accept set — nothing that parsed before parses differently), and is removed one release out. Import the new name. +**Where the non-pair is recorded now.** `zod-mirror-parity.test.ts` keys its existing `EXCLUSIONS` entry — the mechanism that accounts for every exported const with no TypeScript declaration to mirror, each with its stated reason — to `ClassNameStylePropsSchema`. Named for its own two keys, the const leaves no like-named declaration for a name-derived pairing to reach for. -**The guard half.** `zod-mirror-parity.test.ts` now carries `NAME_NON_PAIRS`: the consts a name-derived pairing must SKIP, each with the reason it is skipped and the declaration it is skipped against. The reason is not prose — the suite re-measures the claim it rests on on every run: the named declaration must still exist, and the two sides must still share no key. A collision that starts to overlap PARTIALLY (the case `assertionEveryPairOverlaps` cannot see, because it only rejects a TOTALLY empty overlap) turns the suite red and has to be decided, instead of quietly comparing like a mirror and reporting phantom drift. +**No deprecation window, deliberately.** No consumer of the old name was found: its only three references in this repository were the definition, the barrel line and the guard's own key (lit control on the same query shape: `BaseSchema` returns hits in 199 files). A staged retirement here would need named external-consumer evidence, and there is none. diff --git a/packages/types/src/__tests__/classname-style-props-rename-5928.test.ts b/packages/types/src/__tests__/classname-style-props-rename-5928.test.ts new file mode 100644 index 0000000000..829f7ce3a0 --- /dev/null +++ b/packages/types/src/__tests__/classname-style-props-rename-5928.test.ts @@ -0,0 +1,85 @@ +/** + * 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. + */ + +/** + * `StylePropsSchema` is RENAMED to `ClassNameStylePropsSchema` on the published + * `@object-ui/types/zod` surface — outright, with no deprecated alias standing in + * for the old name (objectui#5928). + * + * ## What this file exists to prove + * + * The rename moves a name on a PUBLISHED surface, so the new name has to be + * checked the way a published export is checked — by IMPORTING it through the + * published barrel and using it — not by the type-checker's silence. A `tsc` run + * stays green on a const that never reached the barrel, because nothing inside + * this package imports it; only a runtime import of the published path can report + * that the name is not there. + * + * Both faces of the rename are pinned here: + * - the barrel publishes the new name and it VALIDATES — a live zod schema that + * also REFUSES, with the issue addressed to the offending key, so the export is + * the schema and not an inert re-export of something that lost its identity in + * the rename — and the retired name is no longer among the barrel's exports; + * - the object carries exactly the two keys the new name claims, so the name + * cannot outlive what it describes. + * + * ## What is NOT pinned here, and where it lives instead + * + * That the retired name cannot come back as a DEFINITION is already a ratchet in + * `zod-mirror-parity.test.ts`: its census reads every `export const` in `../zod/` + * and fails on one that is neither a registered pair nor an excluded one, so + * re-declaring `StylePropsSchema` there reddens that suite with no help from this + * file. What that census cannot see — it matches `export const` declarations — is + * the same name returning as a re-export alias (`export { X as Y }`), which is why + * the absence below is read off the barrel's own export list rather than restated + * against the source. + * + * The other half of the card — that the old name never was a mirror of the + * like-named TS `StyleProps`, the Tailwind-scale vocabulary it shares no key with — + * is recorded with its reason in that same file's `EXCLUSIONS`, now keyed to the + * name this const actually carries. + */ + +import { describe, it, expect } from 'vitest'; + +// The PUBLISHED path (`@object-ui/types/zod` resolves to this barrel), deliberately +// not `../zod/base.zod.js`: a const that survives in the source file but never +// reaches the barrel is exactly the regression this import must catch, and a +// missing named export fails this module at link time. +import { ClassNameStylePropsSchema } from '../zod/index.zod.js'; + +describe('ClassNameStylePropsSchema (objectui#5928)', () => { + it('the published barrel exports it as a live schema — and no longer carries the retired name', async () => { + const ok = ClassNameStylePropsSchema.safeParse({ className: 'p-4 text-sm', style: { color: 'red', zIndex: 10 } }); + expect(ok.success).toBe(true); + + // A refusal addressed to the key that is wrong — the accept set is this + // schema's, not a passthrough of anything. + const bad = ClassNameStylePropsSchema.safeParse({ className: 42 }); + expect(bad.success).toBe(false); + if (!bad.success) expect(bad.error.issues[0]?.path).toEqual(['className']); + + // The rename is a removal too: `StylePropsSchema` left the published surface + // with it, under no spelling — no alias, no re-export. + const zodBarrel = await import('../zod/index.zod.js'); + expect( + 'StylePropsSchema' in zodBarrel, + '`StylePropsSchema` is back on the published ./zod surface — the rename was outright, no alias', + ).toBe(false); + // Positive control on the same barrel object, same run: the surviving name IS + // exported, so the refusal above measures the removal and not a broken import. + expect('ClassNameStylePropsSchema' in zodBarrel).toBe(true); + }); + + it('it carries exactly the two keys its name claims', () => { + // The rename was justified by a measurement (2 keys, both CSS passthrough + // attributes). Pinned so the name cannot outlive what it describes: a third key + // arriving here makes `ClassNameStyleProps…` a lie and must be a decision. + expect(Object.keys(ClassNameStylePropsSchema.shape).sort()).toEqual(['className', 'style']); + }); +}); diff --git a/packages/types/src/__tests__/style-props-alias-5928.test.ts b/packages/types/src/__tests__/style-props-alias-5928.test.ts deleted file mode 100644 index 83a9b8fdbd..0000000000 --- a/packages/types/src/__tests__/style-props-alias-5928.test.ts +++ /dev/null @@ -1,63 +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. - */ - -/** - * `StylePropsSchema` is renamed to `ClassNameStylePropsSchema`, and the old name - * stays LIVE as a deprecated alias for one release (objectui#5928). - * - * ## What this file exists to prove - * - * The rename moves a name on a PUBLISHED surface (`@object-ui/types/zod`). The - * alias is the mechanism that keeps the rename from narrowing that surface, so the - * alias has to be checked the way a published export is checked — by IMPORTING it - * through the published barrel and using it — not by the type-checker's silence. A - * `tsc` run stays green on an alias that was dropped from the barrel, because - * nothing in this package imports it; only a runtime import of the published path - * can report that the name is gone. - * - * Both faces are pinned here: - * - the alias resolves to the SAME object as the new name (`toBe`), so the two - * spellings cannot drift into two schemas, and - * - it VALIDATES, so the export is a live zod schema and not an inert re-export - * of something that lost its identity through the rename. - * - * The other half of the card — that the old name never was a mirror of the - * like-named TS `StyleProps` — is measured in `zod-mirror-parity.test.ts` - * (`NAME_NON_PAIRS`). - */ - -import { describe, it, expect } from 'vitest'; - -// The PUBLISHED path (`@object-ui/types/zod` resolves to this barrel), deliberately -// not `../zod/base.zod.js`: an alias that survives in the source file but is missing -// from the barrel is exactly the regression this file must catch. -import { ClassNameStylePropsSchema, StylePropsSchema } from '../zod/index.zod.js'; - -describe('ClassNameStylePropsSchema (objectui#5928)', () => { - it('the deprecated `StylePropsSchema` alias is the SAME object, not a copy', () => { - expect(StylePropsSchema).toBe(ClassNameStylePropsSchema); - }); - - it('the alias is a live schema — importing the old name still validates', () => { - const ok = StylePropsSchema.safeParse({ className: 'p-4 text-sm', style: { color: 'red', zIndex: 10 } }); - expect(ok.success).toBe(true); - - // A refusal through the old name, addressed to the key that is wrong — the - // accept set the alias carries is the schema's, not a passthrough of anything. - const bad = StylePropsSchema.safeParse({ className: 42 }); - expect(bad.success).toBe(false); - if (!bad.success) expect(bad.error.issues[0]?.path).toEqual(['className']); - }); - - it('the renamed const carries exactly the two keys the name claims', () => { - // The rename was justified by a measurement (2 keys, both CSS passthrough - // attributes). Pinned so the name cannot outlive what it describes: a third key - // arriving here makes `ClassNameStyleProps…` a lie and must be a decision. - expect(Object.keys(ClassNameStylePropsSchema.shape).sort()).toEqual(['className', 'style']); - }); -}); diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts index 0215eb252a..e83bffffd5 100644 --- a/packages/types/src/__tests__/zod-mirror-parity.test.ts +++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts @@ -144,7 +144,7 @@ import ts from 'typescript'; import type { z } from 'zod'; import { AppActionSchema, AppComponentSchema, NavigationAreaSchema } from '../zod/app.zod.js'; -import { BaseSchema, ClassNameStylePropsSchema, ComponentConfigSchema, ComponentInputSchema, ComponentMetaSchema, KeyedI18nLabelSchema, StylePropsSchema } from '../zod/base.zod.js'; +import { BaseSchema, ComponentConfigSchema, ComponentInputSchema, ComponentMetaSchema, KeyedI18nLabelSchema } from '../zod/base.zod.js'; import { CalendarEventSchema, CalendarViewSchema, CarouselItemSchema, CarouselSchema, ChatbotSchema, ChatMessageSchema, ChatMessageSourceSchema, ChatToolInvocationSchema, DashboardComponentSchema, DashboardConfigSchema, DashboardWidgetConfigSchema, DashboardWidgetLayoutSchema, DashboardWidgetSchema, FilterBuilderSchema, FilterFieldSchema, KanbanCardSchema, KanbanColumnSchema, KanbanSchema } from '../zod/complex.zod.js'; import { ActionCallbackSchema, CRUDDialogSchema, DetailSchema } from '../zod/crud.zod.js'; import { AlertSchema, AvatarSchema, BadgeSchema, BarChartSchema, ChartDataSeriesSchema, ChartSchema, DataTableSchema, HtmlSchema, KbdSchema, ListItemSchema, ListSchema, MarkdownSchema, StaticTableColumnSchema, StatisticSchema, TableColumnSchema, TableSchema, TimelineEventSchema, TimelineSchema, TreeViewSchema } from '../zod/data-display.zod.js'; @@ -1504,46 +1504,6 @@ export type assertionBaseSchemaKeysResolve = Expect< > >; -/* ── Declared non-pairs: the pairing SKIPS these, and says why ──────────────── */ - -/** - * Names that LOOK like a pair and are not (objectui#5928). - * - * ## Why a skip has to be written down, and re-measured - * - * `MIRRORS` was built by pairing a `…Schema` const with the like-named TS - * declaration, because in this package that suffix almost always means "runtime - * mirror of the like-named declaration" — the population is `Object.keys(MIRRORS)`, - * deliberately not restated here as a written number (objectui#6141 measured what - * happens to counts in this header: they rot, and get quoted as fact). - * `StylePropsSchema` was not one of them: the like-named `StyleProps` (`../base.ts`) - * is the Tailwind-SCALE vocabulary, so the name-derived pairing compared two - * unrelated key sets and reported drift on a mirror relationship that does not - * exist. - * - * `assertionEveryPairOverlaps` caught that one, but only because the overlap is - * TOTALLY empty. A collision that overlaps PARTIALLY compares like a mirror and - * reports phantom drift with nothing to catch it — the hole objectui#5928 was filed - * against. So the skip is recorded here WITH ITS REASON, and the claim the reason - * rests on is re-measured on every run: the named declaration must still exist, and - * the two sides must still share NO key. The day either side moves so that they DO - * overlap, this reddens and the collision is decided again — instead of quietly - * becoming a mirror. - * - * ⚠️ Scope. This is for consts the pairing SKIPS. A const that IS paired, but - * against a differently-named declaration, is a different case and is deliberately - * not recorded here: `FieldConstraintsSchema` mirrors `FieldValidationRules`, not - * the like-named legacy `FieldConstraints`, and its own docstring is what says so. - */ -const NAME_NON_PAIRS = { - 'base.zod.ts#StylePropsSchema': { - mirror: StylePropsSchema, - declaration: 'StyleProps', - reason: - 'the deprecated alias of `ClassNameStylePropsSchema` (objectui#5928), exported for one release. `StyleProps` (../base.ts) is the Tailwind-scale layout vocabulary (`padding`, `margin`, `gap`, `backgroundColor`, …); this object carries the two CSS passthrough attributes (`className`, `style`). They only ever shared a name.', - }, -} as const; - /* ── Exclusions ─────────────────────────────────────────────────────────────── */ /** @@ -1552,14 +1512,13 @@ const NAME_NON_PAIRS = { * in neither map. */ const EXCLUSIONS: Readonly> = { - // The `{ className, style }` passthrough object. Nothing in this package restates - // it — and the name that LOOKED like its declaration is the collision objectui#5928 - // renamed away; `NAME_NON_PAIRS` above keeps measuring that collision for as long as - // the deprecated alias is exported. + // Renamed from `StylePropsSchema` by objectui#5928. Under the old name the + // like-named `StyleProps` (../base.ts) — the Tailwind-scale vocabulary, sharing + // ZERO keys with this `{ className, style }` object — read as its declaration, and + // a name-derived pairing duly compared two unrelated key sets. Named for its own + // two keys, it has no like-named declaration left to be paired with. 'base.zod.ts#ClassNameStylePropsSchema': 'no TS declaration in this package restates it — the `{ className, style }` passthrough attributes are declared inline on each schema, never as one shared interface', - 'base.zod.ts#StylePropsSchema': - 'the deprecated alias of `ClassNameStylePropsSchema` (objectui#5928), removed one release out — it restates nothing, and the like-named `StyleProps` (../base.ts) is the unrelated Tailwind-scale vocabulary recorded in `NAME_NON_PAIRS`', 'app.zod.ts#NavigationItemTypeSchema': "a bare vocabulary with no `.shape`; it is checked where a mirrored KEY declares it", 'app.zod.ts#NavigationItemSchema': @@ -1722,37 +1681,6 @@ function exportedConsts(): string[] { return out; } -/* ── Which keys a TS declaration in this package declares (AST) ──────────────── */ - -const SRC_DIR = join(dirname(fileURLToPath(import.meta.url)), '..'); - -/** - * The member names of ONE TS declaration in this package, or `null` if no - * declaration by that name exists. - * - * Read from the AST of the package's own sources rather than from a type-level - * probe, because `NAME_NON_PAIRS` has to answer a question the type level cannot: - * "does a declaration by this NAME still exist at all?". A type import of a name - * that has been deleted is a compile error, not a measurement — the entry would - * have to be edited before the check could report on it, which is the opposite of - * a ratchet. - */ -export function declaredMemberNames(name: string): string[] | null { - for (const file of readdirSync(SRC_DIR).sort()) { - if (!file.endsWith('.ts')) continue; - const sf = ts.createSourceFile(file, readFileSync(join(SRC_DIR, file), 'utf8'), ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS); - for (const stmt of sf.statements) { - if (ts.isInterfaceDeclaration(stmt) && stmt.name.text === name) { - return stmt.members.filter((m) => m.name && ts.isIdentifier(m.name)).map((m) => (m.name as ts.Identifier).text); - } - if (ts.isTypeAliasDeclaration(stmt) && stmt.name.text === name && ts.isTypeLiteralNode(stmt.type)) { - return stmt.type.members.filter((m) => m.name && ts.isIdentifier(m.name)).map((m) => (m.name as ts.Identifier).text); - } - } - } - return null; -} - /* ── Which exports reference a spec symbol (AST, not raw text) ───────────────── */ /** @@ -1882,7 +1810,7 @@ exactly how objectui#4605 and #5186 stayed latent.`).toEqual([]); it('no map entry names a const that no longer exists', () => { const onDisk = new Set(exportedConsts()); - const stale = [...Object.keys(MIRRORS), ...Object.keys(EXCLUSIONS), ...Object.keys(NAME_NON_PAIRS)].filter((k) => !onDisk.has(k)); + const stale = [...Object.keys(MIRRORS), ...Object.keys(EXCLUSIONS)].filter((k) => !onDisk.has(k)); expect(stale, 'stale entries — the const was renamed or removed').toEqual([]); }); @@ -1919,71 +1847,6 @@ exactly how objectui#4605 and #5186 stayed latent.`).toEqual([]); }); }); -describe('the name-derived pairing skips declared non-pairs (objectui#5928)', () => { - const entries = Object.entries(NAME_NON_PAIRS); - - it('the skip list is not empty — this suite has something to measure', () => { - // Every assertion below is written over `entries`. An empty map makes all of - // them pass while measuring nothing, which is the failure mode the whole file - // is built against. - expect(entries.length).toBeGreaterThan(0); - }); - - it('every skip names a TS declaration that still exists', () => { - const missing = entries.filter(([, e]) => declaredMemberNames(e.declaration) === null); - expect(missing.map(([k, e]) => `${k} -> ${e.declaration}`), ` -A skip names a declaration this package no longer has. The collision it records is -gone (or the name is misspelled), so the entry states something unmeasurable — drop -the entry, or correct the name it points at.`).toEqual([]); - }); - - it('every skip carries a stated reason', () => { - const empty = entries.filter(([, e]) => e.reason.trim().length < 20); - expect(empty.map(([k]) => k), - 'a skip without a reason is how the next false pair gets added silently').toEqual([]); - }); - - it('a skipped const is never also a registered pair', () => { - const both = entries.map(([k]) => k).filter((k) => k in MIRRORS); - expect(both, 'a const cannot be both skipped and paired — one of the two is wrong').toEqual([]); - }); - - it('the skip measures the object the canonical name exports', () => { - // The alias is the subject of the entry, so the entry must hold the SAME object - // the canonical export holds. A copy would let the two drift apart and the - // measurement below would be of something nobody publishes. - expect(NAME_NON_PAIRS['base.zod.ts#StylePropsSchema'].mirror).toBe(ClassNameStylePropsSchema); - }); - - it('THE MEASUREMENT — the two sides of every skip still share no key', () => { - const overlapping = entries - .map(([k, e]) => { - const mirrored = Object.keys(e.mirror.shape); - const declared = declaredMemberNames(e.declaration) ?? []; - return [k, mirrored.filter((key) => declared.includes(key))] as const; - }) - .filter(([, shared]) => shared.length > 0); - - expect(overlapping.map(([k, shared]) => `${k}: ${shared.join(', ')}`), ` -A skipped non-pair now OVERLAPS the declaration it was skipped against. That is the -partial-overlap collision objectui#5928 was filed about: from here a name-derived -pairing compares like a mirror and reports phantom drift with nothing to catch it. -Decide the collision — rename one of the two, or register the pair deliberately — -rather than editing this expectation.`).toEqual([]); - }); - - it('non-vacuity: the same two readers MEASURE the overlap of a real pair', () => { - // Both readers returning nothing would make the assertion above green forever. - // Run them, unchanged, over a registered pair that demonstrably overlaps. - const mirrored = Object.keys(ListItemSchema.shape); - const declared = declaredMemberNames('ListItem'); - expect(declared, '`ListItem` is declared in ../data-display.ts — a null here is a broken reader').not.toBeNull(); - const shared = mirrored.filter((key) => (declared ?? []).includes(key)); - expect(shared).toContain('label'); - expect(shared.length).toBeGreaterThan(3); - }); -}); - describe('the spec-reference scan reads code, not prose (objectui#6705)', () => { const scan = (src: string): string[] => [...specReferencingExports('fixture.zod.ts', src)].sort(); diff --git a/packages/types/src/zod/base.zod.ts b/packages/types/src/zod/base.zod.ts index 84f699b3dc..3e65ece460 100644 --- a/packages/types/src/zod/base.zod.ts +++ b/packages/types/src/zod/base.zod.ts @@ -414,19 +414,12 @@ export const EventHandlersSchema = z.record(z.string(), z.function()).describe(' * declaration is the Tailwind-SCALE vocabulary (`padding`, `margin`, `gap`, * `backgroundColor`, …) and shares ZERO keys with this object — the two only ever * shared a name, and pairing them by that name reported drift on a mirror - * relationship that does not exist. The non-pair is recorded, with the overlap - * re-measured on every run, in `../__tests__/zod-mirror-parity.test.ts`. + * relationship that does not exist. The old name is gone: with this const named + * for its own keys there is no like-named declaration left to pair it with, and + * the reason it mirrors nothing is recorded against this name in + * `../__tests__/zod-mirror-parity.test.ts`'s `EXCLUSIONS`. */ export const ClassNameStylePropsSchema = z.object({ className: z.string().optional(), style: z.record(z.string(), z.union([z.string(), z.number()])).optional(), }).describe('Style properties'); - -/** - * @deprecated Renamed to `ClassNameStylePropsSchema` (objectui#5928) — the old - * name asserted a mirror relationship with `StyleProps` (`../base.ts`) that does - * not exist. This alias IS that object (same reference, same accept set), kept for - * one release so the rename does not narrow the published surface; it is removed - * after that. Import `ClassNameStylePropsSchema`. - */ -export const StylePropsSchema = ClassNameStylePropsSchema; diff --git a/packages/types/src/zod/index.zod.ts b/packages/types/src/zod/index.zod.ts index 11422c950f..945e9a7b8a 100644 --- a/packages/types/src/zod/index.zod.ts +++ b/packages/types/src/zod/index.zod.ts @@ -59,8 +59,6 @@ export { HTMLAttributesSchema, EventHandlersSchema, ClassNameStylePropsSchema, - /** @deprecated objectui#5928 — use `ClassNameStylePropsSchema`. Same object; removed one release out. */ - StylePropsSchema, } from './base.zod.js'; // ============================================================================ From f4158082c386eb5d500a5e598162eae8fdf98b8e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 04:12:13 +0000 Subject: [PATCH 4/5] docs(types): say what the barrel pin actually measures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inherited sentence claimed a `tsc` run stays green when the name never reaches the barrel. Measured on this branch by deleting the barrel line: the suite fails at module load AND `tsc -p tsconfig.test.json` fails with TS2305 — both because this file names the export. The honest claim is narrower and is the one that justifies the file: nothing else in the package imports the barrel's copy, so without this import the barrel line is load-bearing for nobody. The value-import half is stated separately, since that is what a type import could not do. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC --- .../classname-style-props-rename-5928.test.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/types/src/__tests__/classname-style-props-rename-5928.test.ts b/packages/types/src/__tests__/classname-style-props-rename-5928.test.ts index 829f7ce3a0..7d2af06f48 100644 --- a/packages/types/src/__tests__/classname-style-props-rename-5928.test.ts +++ b/packages/types/src/__tests__/classname-style-props-rename-5928.test.ts @@ -15,10 +15,17 @@ * * The rename moves a name on a PUBLISHED surface, so the new name has to be * checked the way a published export is checked — by IMPORTING it through the - * published barrel and using it — not by the type-checker's silence. A `tsc` run - * stays green on a const that never reached the barrel, because nothing inside - * this package imports it; only a runtime import of the published path can report - * that the name is not there. + * published barrel and USING it. Nothing else in this package imports the barrel's + * copy of this name, so without this file the barrel line is load-bearing for + * nobody and can be dropped in silence. Measured on this branch by deleting + * `ClassNameStylePropsSchema` from `../zod/index.zod.ts`: this suite fails at + * module load, and `tsc -p tsconfig.test.json` fails with TS2305 at the import + * below — both of them only because this file names the export. + * + * The import is a VALUE import rather than a type-only one, because the type level + * cannot see the half that matters: a type import erases, while the `safeParse` + * pair below proves the published name still resolves to a LIVE zod schema at + * runtime and not to something that lost its identity in the rename. * * Both faces of the rename are pinned here: * - the barrel publishes the new name and it VALIDATES — a live zod schema that From 306924df4f69aea4d694091ae8336ec634d7511f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 04:14:34 +0000 Subject: [PATCH 5/5] docs(changeset): quote the sweep numbers this round measured The 199-file lit control was inherited from the previous round and does not reproduce under either query shape run here (251 files word-boundary, 252 substring). Numbers that get quoted onward are the failure this package keeps paying for, so the changeset now carries the reading taken on this tree, with the method beside it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC --- .changeset/5928-classname-style-props-rename.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/5928-classname-style-props-rename.md b/.changeset/5928-classname-style-props-rename.md index 903fdb33e3..4099c4e346 100644 --- a/.changeset/5928-classname-style-props-rename.md +++ b/.changeset/5928-classname-style-props-rename.md @@ -10,4 +10,4 @@ **Where the non-pair is recorded now.** `zod-mirror-parity.test.ts` keys its existing `EXCLUSIONS` entry — the mechanism that accounts for every exported const with no TypeScript declaration to mirror, each with its stated reason — to `ClassNameStylePropsSchema`. Named for its own two keys, the const leaves no like-named declaration for a name-derived pairing to reach for. -**No deprecation window, deliberately.** No consumer of the old name was found: its only three references in this repository were the definition, the barrel line and the guard's own key (lit control on the same query shape: `BaseSchema` returns hits in 199 files). A staged retirement here would need named external-consumer evidence, and there is none. +**No deprecation window, deliberately.** No consumer of the old name exists in this repository. Measured on this branch's base: `StylePropsSchema` had exactly three references — the definition, the barrel line, and the guard's own exclusion key — all three inside `packages/types` (lit control on the same query shape: `BaseSchema` matches 251 tracked files). A staged retirement would need named external-consumer evidence, and there is none.