From 456b307e02ed21dd86b04ea28b5e74b95aa4de40 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 04:57:42 +0000 Subject: [PATCH] docs(types): correct the NamedListView member figures and pin the count exactly (objectui#7947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `NamedListView`'s two quoted figures were wrong in four places, and the pin that should have caught them was floored below the truth. Re-derived on this branch's base d4864f3a4, with the pin file's own `namedListViewMemberCount()` regex — the instrument the assertion itself uses: 47 declared top-level members. A looser count that drops the two-space indent anchor and so also matches nested object-literal lines gives 59 on the same declaration; the retired "about 52" was a hand figure between the two instruments, matching neither. The derived "unread" figure is 41, not 45. The renderer reads seven keys off a named view, but only six of them (`label`, `type`, `columns`, `filter`, `sort`, `options`) are declared `NamedListView` members. The seventh, `data`, is not declared on the interface at all — it reaches the renderer through an `as any` cast on the named-view config, `(currentNamedViewConfig as any)?.data` in `packages/plugin-view/src/ObjectView.tsx`. So the arithmetic is 47 - 6 = 41, not 52 - 7 = 45, and every site now says so in its own words. The pin moves with the figures: `toBeGreaterThanOrEqual(40)` was chosen against the wrong count and constrained nothing — at a true 47 it permitted the declaration to shed seven members, including a shrink toward the read set, which is the exact condition that re-opens the `listViews` value-type decision, and a floor cannot catch growth at all. It is now an exact `toBe(47)` whose failure message names the three sibling files whose figures must move with it, plus a comment recording how the count is taken and where the loose 59 comes from, so the next reader does not re-derive it and re-introduce the middle number. Text and figures only: no schema, no type, and no assertion about what is accepted or refused changed. `REGISTRATION_TEXT` in the shared pin file is untouched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s --- .changeset/7947-named-list-view-figures.md | 37 +++++++++++++++++++ .../object-view-unmirrored-keys-7779.md | 24 +++++++----- .../object-view-unmirrored-keys-7779.test.ts | 36 ++++++++++++++++-- .../src/__tests__/zod-mirror-parity.test.ts | 21 +++++++---- packages/types/src/zod/objectql.zod.ts | 16 +++++--- 5 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 .changeset/7947-named-list-view-figures.md diff --git a/.changeset/7947-named-list-view-figures.md b/.changeset/7947-named-list-view-figures.md new file mode 100644 index 000000000..09da676ee --- /dev/null +++ b/.changeset/7947-named-list-view-figures.md @@ -0,0 +1,37 @@ +--- +--- + +Corrective, comment-and-figures only: no published behaviour changes, so this +declares no bump. + +`NamedListView`'s member figures were quoted wrong in four places. The +declaration has **47** top-level members, counted with the +`namedListViewMemberCount()` regex that +`packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts` uses for +its own assertion — not "about 52", which was a hand figure sitting between two +instruments (a looser count that also matches nested object-literal lines gives +59 on the same declaration). The derived "unread" figure is **41**, not 45: the +renderer reads seven keys off a named view, but only six of them (`label`, +`type`, `columns`, `filter`, `sort`, `options`) are declared members. The +seventh, `data`, is not declared on `NamedListView` at all — it reaches the +renderer through an `as any` cast on the named-view config in +`packages/plugin-view/src/ObjectView.tsx` — so the arithmetic is 47 − 6, not +52 − 7. + +Corrected at all four sites: this changeset's sibling +`.changeset/object-view-unmirrored-keys-7779.md` (still unconsumed, corrected in +place so the wrong figure never reaches a published CHANGELOG), the +`ObjectViewSchema` docblock in `packages/types/src/zod/objectql.zod.ts`, the +`UnmirroredDeclared` note in +`packages/types/src/__tests__/zod-mirror-parity.test.ts`, and the header of the +pin test file. + +The pin that should have caught the drift did not bind: it asserted +`toBeGreaterThanOrEqual(40)` against a true 47, which permitted the declaration +to shed seven members — including a shrink toward the read set, the exact +condition that re-opens the `listViews` value-type decision — and could not +catch growth at all. It is now an exact `toBe(47)` whose failure message names +the three sibling files whose figures must move with it, plus a comment +recording how the count is taken and where the retired "about 52" came from. + +No schema, no type, and no assertion about what is accepted or refused changed. diff --git a/.changeset/object-view-unmirrored-keys-7779.md b/.changeset/object-view-unmirrored-keys-7779.md index 767209c30..65ab787d0 100644 --- a/.changeset/object-view-unmirrored-keys-7779.md +++ b/.changeset/object-view-unmirrored-keys-7779.md @@ -45,15 +45,21 @@ three spec-modelled keys are optional slots on `ListViewSchema` and `ObjectListViewSchema`; the six local keys have no spec slot anywhere. **`listViews` stays unmirrored, on the ruling's own fallback clause.** The -declaration's value is the local `NamedListView` (about 52 members, of which the -renderer reads seven — `label`, `type`, `columns`, `filter`, `sort`, `options`, -`data`); the spec's `ViewSchema.listViews` is a record of the STRICT -`ObjectListViewSchema`, which requires `columns` and refuses `options`, ObjectQL -tuple filters and `default` — that is, it refuses the named views this package's -own README and `content/docs/api/schema-reference.md` teach (`{ label: 'All -Users' }` fails at `columns`; `filter: [["owner", "=", "..."]]` fails at -`filter.0`). Mirroring the spec value would lose documented behaviour; mirroring -the local value would enforce roughly 45 unread members into the contract — the +declaration's value is the local `NamedListView` — **47 declared top-level +members**, of which the renderer reads six: `label`, `type`, `columns`, +`filter`, `sort`, `options`. It reads a seventh key off a named view, `data`, +but `data` is **not a declared member of `NamedListView` at all**: it reaches +the renderer through an `as any` cast on the named-view config in +`packages/plugin-view/src/ObjectView.tsx`, so it never was one of the declared +members a mirror would have to carry. The spec's `ViewSchema.listViews` is a +record of the STRICT `ObjectListViewSchema`, which requires `columns` and +refuses `options`, ObjectQL tuple filters and `default` — that is, it refuses +the named views this package's own README and +`content/docs/api/schema-reference.md` teach (`{ label: 'All Users' }` fails at +`columns`; `filter: [["owner", "=", "..."]]` fails at `filter.0`). Mirroring the +spec value would lose documented behaviour; mirroring the local value would +enforce **41 unread members** (47 declared, minus the 6 that are both declared +and read) into the contract — the very thing ruling B refused for the six local keys. The key therefore stays in the parity ledger with that measurement, pinned, until the maintainer decides its value type. It is not papered over with `z.any()`. diff --git a/packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts b/packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts index d4ea5d03d..891d7fffc 100644 --- a/packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts +++ b/packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts @@ -44,8 +44,14 @@ * ## The one that stayed, and why it is pinned too * * `listViews` is NOT mirrored. The ruling's own fallback clause fires on the - * measurement below: the declaration's value is the local `NamedListView` - * (about 52 members, of which the renderer reads seven), the spec's + * measurement below: the declaration's value is the local `NamedListView` — 47 + * declared top-level members, of which the renderer reads six (`label`, `type`, + * `columns`, `filter`, `sort`, `options`), leaving 41 that a key-for-key local + * mirror would enforce unread. The renderer reads a SEVENTH key off a named + * view, `data`, which is NOT a declared member of `NamedListView`: it arrives + * through an `as any` cast on the named-view config in the renderer + * (`(currentNamedViewConfig as any)?.data`), which is why the read set below + * has seven entries while the arithmetic subtracts only six. The spec's * `ViewSchema.listViews` is a record of the STRICT `ObjectListViewSchema`, and * the spec value refuses the named views this package's docs teach. Both facts * are asserted against the SPEC schema here, so the day the spec relaxes (or @@ -529,12 +535,34 @@ describe('objectui#7779 — `listViews` stays unmirrored on the ruling\'s fallba expect(readRepo('content/docs/api/schema-reference.md')).toContain('"filter": [["owner", "=", "${currentUser.id}"]],'); }); - it('the renderer reads exactly seven `NamedListView` members off a named view, of a declaration with far more — the reason a local key-for-key mirror was not the answer either', () => { + it('the renderer reads exactly seven keys off a named view — six of them declared `NamedListView` members, the seventh (`data`) an `as any` cast — of a declaration with far more, the reason a local key-for-key mirror was not the answer either', () => { expect(namedViewReads()).toEqual([...NAMED_VIEW_READS]); // The tab strip reads `label` off the entries too — same member, second site. expect(readRepo(READER)).toContain('{view.label || key}'); + // Six of those seven are declared `NamedListView` members. The seventh, + // `data`, is not declared at all — it reaches the renderer through an + // `as any` cast on the named-view config — so the "unread" arithmetic + // below subtracts six, not seven. Both directions are already pinned + // without a new assertion: were `data` ever declared, the exact count + // moves 47 → 48 and fails here; were the cast read dropped, + // `namedViewReads()` returns six entries and fails above. const declared = namedListViewMemberCount(); - expect(declared, 'NamedListView shrank to (near) its read set — re-take the listViews decision').toBeGreaterThanOrEqual(40); + // HOW THIS NUMBER IS TAKEN: `namedListViewMemberCount()` above — the two-space + // indent in its `/^ {2}[A-Za-z_$][\w$]*\??:/gm` regex is what makes it a + // TOP-LEVEL member count. A looser count that drops that indent anchor also + // matches nested object-literal lines inside the members' inline types and + // gives 59 on the same declaration; a hand figure between the two instruments + // is where the retired "about 52 members" came from. ⛔ Do not re-derive the + // loose number and quote it beside this one — they measure different things. + // Pinned EXACT rather than floored: the retired `>= 40` floor permitted the + // declaration to shed seven members, including a shrink toward the read set, + // which is exactly the condition that re-opens the `listViews` decision + // (objectui#7928); and a floor cannot catch growth at all, so the quoted + // figures could stale silently in either direction. + expect( + declared, + 'NamedListView\'s top-level member count moved (was 47). Re-derive it with this file\'s own namedListViewMemberCount() regex, then update the "47 declared / 41 unread" figures in the three files that carry them together — .changeset/object-view-unmirrored-keys-7779.md, packages/types/src/zod/objectql.zod.ts and packages/types/src/__tests__/zod-mirror-parity.test.ts — plus this file\'s own header. A shrink toward the read set also re-opens the listViews decision (objectui#7928).', + ).toBe(47); expect(declared).toBeGreaterThan(NAMED_VIEW_READS.length); }); diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts index a7469e027..8909d725c 100644 --- a/packages/types/src/__tests__/zod-mirror-parity.test.ts +++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts @@ -1464,14 +1464,19 @@ interface UnmirroredDeclared { * `viewTabBar` — zero reads; the tab-bar config is `ViewTabBar`'s `config` PROP * from the host, never a node key. * ⚠️ `listViews` STAYS, on the ruling's own fallback clause, with the measurement - * that triggered it: the declaration's value is the local `NamedListView` (~52 - * members, of which the renderer reads seven — `label`, `type`, `columns`, `filter`, - * `sort`, `options`, `data`); the spec slot `ViewSchema.listViews` is a record of - * the STRICT `ObjectListViewSchema`, which requires `columns` and refuses `options`, - * ObjectQL tuple filters and `default` — the named views `plugin-view`'s README and - * `content/docs/api/schema-reference.md` teach fail it at `columns` / `filter.0` / - * unrecognized_keys. Mirroring the spec value loses documented behaviour; mirroring - * the local value enforces ~45 unread members into the contract (the reason ruling B + * that triggered it: the declaration's value is the local `NamedListView`, 47 + * declared top-level members, six of which the renderer reads — `label`, `type`, + * `columns`, `filter`, `sort`, `options`. The renderer reads a seventh key off a + * named view, `data`, and it is NOT a declared member: it arrives through an + * `as any` cast on the named-view config in `plugin-view/src/ObjectView.tsx`, so + * it is outside the 47 this ledger counts. The spec slot `ViewSchema.listViews` is + * a record of the STRICT `ObjectListViewSchema`, which requires `columns` and + * refuses `options`, ObjectQL tuple filters and `default` — the named views + * `plugin-view`'s README and `content/docs/api/schema-reference.md` teach fail it + * at `columns` / `filter.0` / unrecognized_keys. Mirroring the spec value loses + * documented behaviour; mirroring the local value enforces 41 unread members + * (47 declared, minus the 6 that are both declared and read) into the contract + * (the reason ruling B * refused option A for the six local keys). Neither is a mirror edit this ledger can * authorise; ⛔ `z.any()` was ruled out by name. The value type is the maintainer's * decision, recorded on objectui#7779's report. diff --git a/packages/types/src/zod/objectql.zod.ts b/packages/types/src/zod/objectql.zod.ts index 44e31169b..3b83805b0 100644 --- a/packages/types/src/zod/objectql.zod.ts +++ b/packages/types/src/zod/objectql.zod.ts @@ -231,15 +231,21 @@ export const ObjectFormSchema = BaseSchema.extend({ * (`docs/audits/2026-07-objectview-detailview-schema.md`) had already * measured it dead since introduction. * - `listViews` — STILL UNMIRRORED, on the ruling's own fallback clause and - * by measurement: the declaration's value is the local `NamedListView` - * (~52 members, of which the renderer reads `label`, `type`, `columns`, - * `filter`, `sort`, `options`, `data`), while the spec slot - * (`ViewSchema.listViews`) is a record of the STRICT `ObjectListViewSchema`, + * by measurement: the declaration's value is the local `NamedListView`, + * 47 declared top-level members, of which the renderer reads six — + * `label`, `type`, `columns`, `filter`, `sort`, `options`. A seventh key, + * `data`, is read off a named view but is NOT declared on + * `NamedListView`: the renderer reaches it through an `as any` cast on the + * named-view config (`ObjectView.tsx`, `(currentNamedViewConfig as any)?.data`), + * so it is not one of the 47 and never was a member a mirror would carry. + * Meanwhile the spec slot (`ViewSchema.listViews`) is a record of the + * STRICT `ObjectListViewSchema`, * which requires `columns` and refuses `options`, ObjectQL tuple filters and * `default` — i.e. it refuses the named views this package's own README and * `content/docs/api/schema-reference.md` teach. Neither value type can be * mirrored without either losing documented behaviour (spec) or enforcing - * ~45 unread members into the contract (local), so the key stays in the + * 41 unread members (47 declared, minus the 6 that are both declared and + * read) into the contract (local), so the key stays in the * parity ledger with that measurement until the maintainer decides its * value type. ⛔ Not `z.any()`: that was ruled out by name. */