From b4e86e412e7c840dbde42b9df8e6a35d6f2b08f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 17:41:07 +0000 Subject: [PATCH 1/2] fix(types): collapse the redundant `UserActionsSchema` extension, correct its docblock (objectui#8992) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `objectql.zod.ts`'s `UserActionsSchema` carried `stripImportedDefaults(Spec).extend({ group, hideFields, rowColor })`, an extension that existed only because `@objectstack/spec` did not declare those three keys while `normalizeListViewSchema` folded objectui's legacy `showGroup` / `showHideFields` / `showColor` onto them. The protocol adopted all three in 17.3.0, so the extension is now a second local copy of a protocol declaration and it collapses into the plain by-reference re-export its own note said it would become. The urgent half is the docblock, which told its reader that `UserActionsConfigSchema` "is NOT `.strict()`, so ... an author writing `userActions: { group: false }` had it silently stripped — valid on parse, no effect at render". Measured against the published artifacts of 17.0.0, 17.2.0, 17.3.0 and the resolved 17.4.0, every one of them refuses an undeclared key and names it (`unrecognized_keys`, one issue). A comment promising silent tolerance in front of a loud-rejection runtime points an author — human or AI — at a config that will fail the save gate while telling them that outcome is impossible. The accept set does not move: extended and collapsed were parsed side by side over a 33-document corpus with an identical result — same success, same parsed output, same refusal codes, keys and messages — with a sentinel proving the comparison can see a difference when one exists. The one published byte that moves is the loss of three local `.describe()` strings the extension carried, which the protocol declares those keys without. `__tests__/user-actions-mirror-8992.test.ts` pins the refusal and the key-set identity, with firing controls in both directions, so the corrected paragraph cannot rot back. The `zod-mirror-parity.test.ts` exclusion survives — the TS name is re-exported from the spec, not restated here — but its reason no longer names an `.extend(…)` that is gone. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w --- ...8992-user-actions-collapse-and-docblock.md | 60 +++++++ .../user-actions-mirror-8992.test.ts | 151 ++++++++++++++++++ .../src/__tests__/zod-mirror-parity.test.ts | 2 +- packages/types/src/zod/objectql.zod.ts | 50 +++--- 4 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 .changeset/8992-user-actions-collapse-and-docblock.md create mode 100644 packages/types/src/__tests__/user-actions-mirror-8992.test.ts diff --git a/.changeset/8992-user-actions-collapse-and-docblock.md b/.changeset/8992-user-actions-collapse-and-docblock.md new file mode 100644 index 0000000000..63ba968b4a --- /dev/null +++ b/.changeset/8992-user-actions-collapse-and-docblock.md @@ -0,0 +1,60 @@ +--- +'@object-ui/types': patch +--- + +Collapse the now-redundant `UserActionsSchema` extension, and correct a docblock that +told authors an undeclared `userActions` key is silently dropped when it is refused by +name (objectui#8992). + +`objectql.zod.ts`'s `UserActionsSchema` read +`stripImportedDefaults(Spec).extend({ group, hideFields, rowColor })`, an extension that +existed only because `@objectstack/spec` did not declare those three keys while +`normalizeListViewSchema` folded objectui's legacy `showGroup` / `showHideFields` / +`showColor` onto them. The protocol adopted all three in 17.3.0 (objectui#5435's +ruling), so the extension is now a second local copy of a protocol declaration — the +shape two faces start drifting from — and it collapses into the plain by-reference +re-export its own note always said it would become. + +⭐ The urgent half is the docblock. It stated that `UserActionsConfigSchema` "is NOT +`.strict()`, so ... an author writing `userActions: { group: false }` had it silently +stripped — valid on parse, no effect at render". Measured against the published +artifacts of 17.0.0, 17.2.0, 17.3.0 and the resolved 17.4.0, every one of them REFUSES +an undeclared key and NAMES it (`unrecognized_keys`, one issue). A comment promising +silent tolerance in front of a loud-rejection runtime points an author — human or AI — +at a config that will fail the save gate while telling them that outcome is impossible. +`__tests__/user-actions-mirror-8992.test.ts` now pins the refusal, with firing controls +in both directions, so the sentence cannot rot back. + +The accept set does not move: extended and collapsed were parsed side by side over a +33-document corpus (every declared key in both polarities, the full block, undeclared +keys, wrong types, non-objects) with an identical result — same success, same parsed +output, same refusal codes, keys and messages — and a sentinel proving the comparison +can see a difference when one exists. + +⚠️ TWO THINGS ON THE PUBLISHED SURFACE DO MOVE, both confined to those three keys, and +both measured by rebuilding `packages/types/dist` on each side of the change: + +1. They lose the three local `.describe()` strings the extension carried, because the + protocol declares those keys without descriptions of its own. Nothing in this + repository reads them, and a mirror that authors prose for a protocol key is the same + class of local invention the import boundary (objectui#8317) removed for defaults. +2. In the emitted `objectql.zod.d.ts` the three move from `z.ZodOptional` + to `z.ZodDefault` — the spec's own declaration, as the compiler sees it + before the runtime strip. `z.input` is unchanged (`boolean | undefined` either way); + `z.output` for these three goes from `boolean | undefined` to `boolean`. This is the + import boundary's DELIBERATE and ruled property — `stripImportedDefaults` is typed + `T` in, `T` out, because stripping is "a property of the PARSE, not of the + declaration" (decision batch #90) — and it is what the OTHER EIGHT keys on this same + object have declared all along. The extension was making three keys the odd ones out + of an object whose eleven members behave identically at runtime; the collapse makes + the declaration uniform. ⛔ It does not change what parses: an omitted key is still + absent from the parsed output, measured, on all eleven. + +The emitted declaration also carries `z.core.$strict` on BOTH sides of this change, +which is the compiler restating in objectui's own published artifact what the corrected +docblock now says in prose. + +⚠️ The three keys are version-borne from here on. `@object-ui/types` declares +`@objectstack/spec: ^17.3.0` and that floor is now load-bearing for them — 17.2.0 +declares 8 keys on this object, 17.3.0 declares 11. The extension used to carry the +three locally whatever version resolved; it no longer does. diff --git a/packages/types/src/__tests__/user-actions-mirror-8992.test.ts b/packages/types/src/__tests__/user-actions-mirror-8992.test.ts new file mode 100644 index 0000000000..c29d891734 --- /dev/null +++ b/packages/types/src/__tests__/user-actions-mirror-8992.test.ts @@ -0,0 +1,151 @@ +/** + * 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#8992 — `userActions` refuses an undeclared key BY NAME, and the + * mirror carries exactly the protocol's key set. + * + * Two facts, one file, because they are the two halves of the same repair. + * + * 1. ⭐ THE REFUSAL. `objectql.zod.ts`'s `UserActionsSchema` docblock used to + * tell its reader that `UserActionsConfigSchema` "is NOT `.strict()`, so ... + * an author writing `userActions: { group: false }` had it silently stripped + * — valid on parse, no effect at render". Measured against the published + * artifacts of 17.0.0, 17.2.0, 17.3.0 and the resolved 17.4.0, every one of + * them REFUSES an undeclared key and NAMES it. A comment promising silent + * tolerance in front of a loud-rejection runtime is the worst direction for + * a comment to be wrong in: an author — human or AI — who trusts it writes a + * config that fails the save gate, having been told that outcome is + * impossible. Prose cannot hold that fact down; this file does. + * + * 2. THE COLLAPSE. The same card removed the local + * `.extend({ group, hideFields, rowColor })`, which existed only because the + * protocol did not declare those three. It does since 17.3.0 + * (objectui#5435's ruling, adopted upstream), so what is asserted here is + * that the mirror's key set IS the spec's — DERIVED from + * `UserActionsConfigSchema` at assert time, never restated. A restated list + * would pass for exactly as long as it happened to agree, which is the drift + * the collapse exists to prevent. + * + * ⚠️ NOT VACUOUS BY CONSTRUCTION. "Refused" is worthless as an assertion unless + * the harness is shown to also report ACCEPTED, and "the key sets are equal" is + * worthless unless the comparison is shown to be able to see a difference. Both + * firing controls are below, and every population is asserted non-empty before + * it is used. + * + * ⚠️ These three keys are VERSION-BORNE since the collapse: `@object-ui/types` + * declares `@objectstack/spec: ^17.3.0` and that floor is what carries them + * (17.2.0 declares 8 keys, 17.3.0 declares 11). If this file ever reddens on + * `THE_THREE`, the reading is that the resolved spec is below the declared + * floor — ⛔ not that the mirror regressed. + */ +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; +import { UserActionsConfigSchema as SpecUserActionsConfigSchema } from '@objectstack/spec/ui'; +import { UserActionsSchema, ListViewSchema } from '../zod/objectql.zod.js'; + +/** The three toggles objectui's legacy `show*` fold emits (objectui#5435). */ +const THE_THREE = ['group', 'hideFields', 'rowColor'] as const; + +/** A key no version of the protocol has ever declared on this object. */ +const NEVER_DECLARED = 'zzNeverDeclaredByAnySpec'; + +type Parsed = { success: boolean; error?: { issues?: readonly unknown[] } }; + +/** Unrecognized-key names a zod result refuses, flattened. */ +const refusedKeys = (r: Parsed): string[] => + ((r.error?.issues ?? []) as { code?: string; keys?: string[] }[]) + .filter((i) => i.code === 'unrecognized_keys') + .flatMap((i) => i.keys ?? []); + +const issueCount = (r: Parsed): number => (r.error?.issues ?? []).length; + +const keysOf = (schema: { shape: Record }): string[] => Object.keys(schema.shape).sort(); + +describe('objectui#8992 — the mirror carries the protocol key set, by reference', () => { + it('the key sets are equal — derived from the spec, not restated here', () => { + const spec = keysOf(SpecUserActionsConfigSchema as unknown as { shape: Record }); + expect(spec.length, 'a census over an empty key set passes for the wrong reason').toBeGreaterThan(0); + expect(keysOf(UserActionsSchema as unknown as { shape: Record })).toEqual(spec); + }); + + it('FIRING CONTROL — the key-set comparison can see a difference', () => { + const narrowed = UserActionsSchema.omit({ group: true }); + expect(keysOf(narrowed as unknown as { shape: Record })).not.toEqual( + keysOf(UserActionsSchema as unknown as { shape: Record }), + ); + }); + + it('carries the three the fold emits — the whole premise of the collapse', () => { + const keys = keysOf(UserActionsSchema as unknown as { shape: Record }); + for (const key of THE_THREE) { + expect(keys, `${key} is missing — read the resolved @objectstack/spec, not this mirror`).toContain(key); + } + }); + + it('authors no default — an empty block stays empty (the objectui#8317 boundary)', () => { + const r = UserActionsSchema.safeParse({}); + expect(r.success).toBe(true); + expect(r.success && r.data).toEqual({}); + }); + + it('accepts each of the three, both polarities', () => { + for (const key of THE_THREE) { + for (const value of [true, false]) { + const r = UserActionsSchema.safeParse({ [key]: value }); + expect(r.success, `${key}: ${value} was refused`).toBe(true); + expect(r.success && r.data).toEqual({ [key]: value }); + } + } + }); +}); + +describe('objectui#8992 — an undeclared key is REFUSED BY NAME, never dropped', () => { + it('the refusal names the key, and there is exactly one issue', () => { + const r = UserActionsSchema.safeParse({ [NEVER_DECLARED]: true }); + expect(r.success, 'silently accepted — the corrected docblock is wrong again').toBe(false); + expect(refusedKeys(r)).toEqual([NEVER_DECLARED]); + expect(issueCount(r)).toBe(1); + }); + + it('⛔ it is NOT stripped — the refused key never comes back as parsed output', () => { + const r = UserActionsSchema.safeParse({ group: false, [NEVER_DECLARED]: true }); + expect(r.success).toBe(false); + expect(refusedKeys(r)).toEqual([NEVER_DECLARED]); + }); + + it("objectui's own legacy toolbar spelling is refused here too — `showGroup` is not `group`", () => { + const r = UserActionsSchema.safeParse({ showGroup: true }); + expect(r.success).toBe(false); + expect(refusedKeys(r)).toEqual(['showGroup']); + }); + + it('FIRING CONTROL — the same harness reports ACCEPTED when the key IS declared', () => { + const widened = UserActionsSchema.extend({ [NEVER_DECLARED]: z.boolean().optional() }); + const r = widened.safeParse({ [NEVER_DECLARED]: true }); + expect(r.success, 'the harness refuses everything — the refusal above proves nothing').toBe(true); + expect(refusedKeys(r)).toEqual([]); + }); + + it('the refusal survives to the published face — `ListViewSchema.userActions`', () => { + const good = ListViewSchema.safeParse({ + type: 'list-view', + objectName: 'accounts', + userActions: { group: false, hideFields: true, rowColor: true }, + }); + expect(good.success, 'the three are not authorable through the published list view').toBe(true); + + const bad = ListViewSchema.safeParse({ + type: 'list-view', + objectName: 'accounts', + userActions: { [NEVER_DECLARED]: true }, + }); + expect(bad.success).toBe(false); + expect(refusedKeys(bad)).toEqual([NEVER_DECLARED]); + }); +}); diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts index 60d238eb4f..a10f6f80e1 100644 --- a/packages/types/src/__tests__/zod-mirror-parity.test.ts +++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts @@ -3021,7 +3021,7 @@ const EXCLUSIONS: Readonly> = { 'objectql.zod.ts#PaginationConfigSchema': "spec-owned BY REFERENCE — the local `.extend(…)` adds renderer props that no TS declaration in this package restates", 'objectql.zod.ts#UserActionsSchema': - "spec-owned BY REFERENCE — the local `.extend(…)` adds renderer props that no TS declaration in this package restates", + "spec-owned BY REFERENCE — a plain `stripImportedDefaults(Spec…)` re-export since objectui#8992 collapsed its `.extend(…)` (the protocol declares `group` / `hideFields` / `rowColor` itself from 17.3.0), and the TS name for it, `UserActionsConfig`, is re-exported FROM `@objectstack/spec/ui` by `../index.ts` rather than restated here — so there is no second definition to drift from", 'objectql.zod.ts#ListViewSchema': "the DECLARATION is derived FROM this mirror — `ListViewSchema = ListViewInferred & ListViewRuntimeProps`, and `ListViewInferred = z.input` (`../objectql.ts`). Asserting parity here would be true no matter what either side said: a phantom assertion, not a check.", 'objectql.zod.ts#ObjectQLComponentSchema': diff --git a/packages/types/src/zod/objectql.zod.ts b/packages/types/src/zod/objectql.zod.ts index 2ae2b6947d..67f2626302 100644 --- a/packages/types/src/zod/objectql.zod.ts +++ b/packages/types/src/zod/objectql.zod.ts @@ -590,29 +590,43 @@ const TimelineConfig = stripImportedDefaults(SpecTimelineConfigSchema).partial() const ViewKindEnum = SpecListViewSchema.shape.type.removeDefault(); /** - * User Actions — the spec's `UserActionsConfigSchema` plus the three toolbar - * affordances it does not model yet (#2890 scope A step 3). + * User Actions — `@objectstack/spec/ui`'s `UserActionsConfigSchema`, mirrored + * BY REFERENCE (objectui#8992). * * The spec documents this object as "which interactive actions are available to * users in the view toolbar — each boolean toggles the corresponding toolbar - * element on/off", and already carries `rowHeight` (objectui's old - * `showDensity`). Grouping, column visibility and row coloring are the same kind - * of toggle — the spec models all three as CONFIGURATION (`grouping`, - * `hiddenFields`, `rowColor`) but has no "may the user change it" switch for - * any of them, so an author cannot express a complete toolbar policy. These - * three are named after the config key they gate, following the precedent - * `rowHeight` set. + * element on/off". Grouping, column visibility and row coloring are the same + * kind of toggle as `rowHeight` (objectui's old `showDensity`), each named + * after the config key it gates (`grouping`, `hiddenFields`, `rowColor`). * - * This `.extend()` is temporary: it collapses into a plain re-export once the - * keys land upstream. Note `UserActionsConfigSchema` is NOT `.strict()`, so - * before this extension an author writing `userActions: { group: false }` had - * it silently stripped — valid on parse, no effect at render. + * This read `stripImportedDefaults(Spec).extend({ group, hideFields, rowColor })` + * for as long as the protocol declared none of the three while + * `normalizeListViewSchema` folded objectui's legacy `showGroup` / + * `showHideFields` / `showColor` onto them. The protocol declares all three + * now — the maintainer ruled option A on objectui#5435 (2026-08-22) and the + * spec adopted them in 17.3.0 — so the extension collapses, exactly as its own + * note said it would. A redundant local extension is how two faces start to + * drift. + * + * ⛔ AN UNDECLARED KEY IS REFUSED HERE, BY NAME (`unrecognized_keys`, one + * issue, the key named) — it is NOT dropped. The note this replaces claimed + * the opposite: "`UserActionsConfigSchema` is NOT `.strict()`, so ... an author + * writing `userActions: { group: false }` had it silently stripped — valid on + * parse, no effect at render". That was false at every published 17.x — + * measured by parsing a one-undeclared-key document against the published + * artifacts of 17.0.0, 17.2.0, 17.3.0 and the resolved 17.4.0, each of which + * refuses and names the key. Silent-tolerance prose in front of a + * loud-rejection runtime is the worst direction for a comment to be wrong in: + * it tells an author — human or AI — that a config which will FAIL the save + * gate is harmless. `__tests__/user-actions-mirror-8992.test.ts` pins the + * refusal so this paragraph cannot rot back into the one it replaced. + * + * ⚠️ The three keys are VERSION-BORNE from here on. `@object-ui/types` declares + * `@objectstack/spec: ^17.3.0`, and that floor is load-bearing for them: 17.2.0 + * declares 8 keys, 17.3.0 declares 11. The extension used to carry the three + * locally whatever version resolved; it no longer does. */ -export const UserActionsSchema = stripImportedDefaults(SpecUserActionsConfigSchema).extend({ - group: z.boolean().optional().describe('Allow users to group records'), - hideFields: z.boolean().optional().describe('Allow users to show/hide columns'), - rowColor: z.boolean().optional().describe('Allow users to color rows by a field value'), -}); +export const UserActionsSchema = stripImportedDefaults(SpecUserActionsConfigSchema); export const ListViewSchema = BaseSchema // Spec-owned fields by reference. `specFieldsExcept` reads the spec object's From 2f430a3152f1a32a77a8b4ad8291f1405bf14586 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 19:30:51 +0000 Subject: [PATCH 2/2] fix(types): grade the changeset `minor`, and correct two false claims in it (objectui#8992) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contract review returned REWORK on the changeset prose and the declared bump. The code — the docblock, the `.extend()` collapse, the new pin and the parity row — was re-measured by the reviewer and found correct, and is untouched here. Only `.changeset/8992-user-actions-collapse-and-docblock.md` moves. 1. `patch` -> `minor`. Re-derived rather than taken on the reviewer's word, and every anchor found by content: `packages/types/CHANGELOG.md` records the IDENTICAL operation on this same file — `ListColumnSchema`'s local `.extend()` collapsing into a plain by-reference re-export — under `## 17.1.0` / `### Minor Changes`; `.changeset/8317-strip-imported-defaults.md`, the boundary ruling this change enrolls three more keys into, is `minor`; the sibling PR of this batch (objectui#8990) declares `minor`; and AGENTS.md states objectui's own breaking changes ship as `minor` with the break spelled out in the body. My own `tsc` probe, built from the two declarations side by side, confirms the break is real and producer-side: a value typed as the OLD output is not assignable to the NEW one (the three keys read as missing), while the reverse direction compiles. The probe is not vacuous — injecting a `@ts-expect-error` where nothing is wrong made the same run fail with TS2578, so the file was genuinely in the program. 2. ⛔ CORRECTING THIS COMMIT'S PARENT. The previous commit message, the changeset and the PR body all said the three keys lose their descriptions "which the protocol declares those keys without". THAT IS FALSE, and it is corrected here rather than by rewriting the parent (a force-push is not available to this branch). The protocol describes all three — `group` reads "Allow users to change record grouping from the toolbar. …" in the 17.3.0 and 17.4.0 tarballs. The real mechanism is objectui's own import boundary: `stripImportedDefaults` unwraps each `ZodDefault` with `.removeDefault()` and re-optionalises the inner node, and the description sits on the outer node it discards. Measured on this object: all ten defaulted keys read `description = undefined` after the strip, on both sides of the change, while `buttons` — the only member that never carried a default — keeps its description through it, which is the positive control. The OUTCOME sentence was right; the cause was invented. 3. "the OTHER EIGHT keys have declared that all along" -> SEVEN of the other eight. `buttons` is `z.ZodOptional[z.ZodArray[z.ZodString]]` and never declared a default. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w --- ...8992-user-actions-collapse-and-docblock.md | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/.changeset/8992-user-actions-collapse-and-docblock.md b/.changeset/8992-user-actions-collapse-and-docblock.md index 63ba968b4a..2eca453727 100644 --- a/.changeset/8992-user-actions-collapse-and-docblock.md +++ b/.changeset/8992-user-actions-collapse-and-docblock.md @@ -1,11 +1,25 @@ --- -'@object-ui/types': patch +'@object-ui/types': minor --- Collapse the now-redundant `UserActionsSchema` extension, and correct a docblock that told authors an undeclared `userActions` key is silently dropped when it is refused by name (objectui#8992). +⚠️ **Breaking, in the producer direction, which is why this is `minor` and not `patch`** +(objectui's own breaking changes ship as `minor` with the break spelled out — AGENTS.md, +"changeset 里不要声明 `major`"). On the published `@object-ui/types` face, +`userActions.group` / `.hideFields` / `.rowColor` move from `z.ZodOptional[z.ZodBoolean]` +to `z.ZodDefault[z.ZodBoolean]` in the emitted `.d.ts`, so `z.output` for those three +goes from `boolean | undefined` to `boolean` — the key becomes REQUIRED on the output +type. Measured with `tsc`: a value typed as the old output is **not** assignable to the +new one (the three read as missing); the reverse direction is fine. **Readers of parsed +output are unaffected; code that CONSTRUCTS an output-typed `userActions` value must add +the three keys or widen its annotation.** ⛔ Nothing about what parses changes — see the +equivalence measurement below. Precedent for the same operation on the same file: +`ListColumnSchema`'s local `.extend()` collapsed into a plain by-reference re-export and +shipped under 17.1.0 **Minor Changes**. + `objectql.zod.ts`'s `UserActionsSchema` read `stripImportedDefaults(Spec).extend({ group, hideFields, rowColor })`, an extension that existed only because `@objectstack/spec` did not declare those three keys while @@ -31,30 +45,34 @@ keys, wrong types, non-objects) with an identical result — same success, same output, same refusal codes, keys and messages — and a sentinel proving the comparison can see a difference when one exists. -⚠️ TWO THINGS ON THE PUBLISHED SURFACE DO MOVE, both confined to those three keys, and +⚠️ TWO THINGS ON THE PUBLISHED SURFACE MOVE, both confined to those three keys, and both measured by rebuilding `packages/types/dist` on each side of the change: -1. They lose the three local `.describe()` strings the extension carried, because the - protocol declares those keys without descriptions of its own. Nothing in this - repository reads them, and a mirror that authors prose for a protocol key is the same - class of local invention the import boundary (objectui#8317) removed for defaults. -2. In the emitted `objectql.zod.d.ts` the three move from `z.ZodOptional` - to `z.ZodDefault` — the spec's own declaration, as the compiler sees it - before the runtime strip. `z.input` is unchanged (`boolean | undefined` either way); - `z.output` for these three goes from `boolean | undefined` to `boolean`. This is the - import boundary's DELIBERATE and ruled property — `stripImportedDefaults` is typed - `T` in, `T` out, because stripping is "a property of the PARSE, not of the - declaration" (decision batch #90) — and it is what the OTHER EIGHT keys on this same - object have declared all along. The extension was making three keys the odd ones out - of an object whose eleven members behave identically at runtime; the collapse makes - the declaration uniform. ⛔ It does not change what parses: an omitted key is still - absent from the parsed output, measured, on all eleven. +1. They end up carrying no `.describe()` metadata. ⛔ Not because the protocol leaves + them undescribed — it describes all three (`group`: "Allow users to change record + grouping from the toolbar. …", and likewise `hideFields` / `rowColor` in the 17.3.0 + and 17.4.0 tarballs). The cause is objectui's own import boundary: + `stripImportedDefaults` unwraps each `ZodDefault` with `.removeDefault()` and + re-optionalises the inner node, and the description sits on the OUTER node it + discards. Measured on this object: all ten defaulted keys read + `description = undefined` after the strip, on both sides of this change, while + `buttons` — the one member that never carried a default — keeps its description + through it. So the three simply stop being an exception: before, the local extension + supplied descriptions the other ten defaulted keys did not have. Nothing in this + repository reads them. +2. In the emitted `objectql.zod.d.ts` the three move from `z.ZodOptional[z.ZodBoolean]` + to `z.ZodDefault[z.ZodBoolean]` — the spec's own declaration, as the compiler sees it + before the runtime strip. This is the import boundary's DELIBERATE and ruled property + — `stripImportedDefaults` is typed `T` in, `T` out, because stripping is "a property + of the PARSE, not of the declaration" (decision batch #90) — and it is what SEVEN of + the other eight keys on this same object have declared all along (`sort`, `search`, + `filter`, `refresh`, `rowHeight`, `addRecordForm`, `editInline`; `buttons` is + `z.ZodOptional[z.ZodArray[z.ZodString]]` and never declared a default). The extension + was making three keys the odd ones out of an object whose eleven members behave + identically at runtime; the collapse makes the declaration uniform. ⛔ It does not + change what parses: an omitted key is still absent from the parsed output, measured, + on all eleven. The emitted declaration also carries `z.core.$strict` on BOTH sides of this change, which is the compiler restating in objectui's own published artifact what the corrected docblock now says in prose. - -⚠️ The three keys are version-borne from here on. `@object-ui/types` declares -`@objectstack/spec: ^17.3.0` and that floor is now load-bearing for them — 17.2.0 -declares 8 keys on this object, 17.3.0 declares 11. The extension used to carry the -three locally whatever version resolved; it no longer does.