Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/7947-named-list-view-figures.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 15 additions & 9 deletions .changeset/object-view-unmirrored-keys-7779.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
});

Expand Down
21 changes: 13 additions & 8 deletions packages/types/src/__tests__/zod-mirror-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 11 additions & 5 deletions packages/types/src/zod/objectql.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Loading