From b4a9f4a3cdcf1fd8fd1f7d445bc4b97bc35858ac Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 05:06:09 +0000 Subject: [PATCH 1/6] docs(plugin-view): teach the spec-shaped named list view Every `listViews` entry in the plugin-view README, plugin-view.mdx and schema-reference.md now parses under @objectstack/spec's ObjectListViewSchema: each named view carries `columns` (the one key the spec requires), and schema-reference's `my-deals` view spells its filter as a spec rule object instead of the ObjectQL tuple. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC --- content/docs/api/schema-reference.md | 6 ++++-- content/docs/plugins/plugin-view.mdx | 4 +++- packages/plugin-view/README.md | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/content/docs/api/schema-reference.md b/content/docs/api/schema-reference.md index b2b95ae127..67508396a7 100644 --- a/content/docs/api/schema-reference.md +++ b/content/docs/api/schema-reference.md @@ -872,12 +872,14 @@ A complete object management interface combining grid, form, search, filters, an "listViews": { "all": { "label": "All Deals", + "columns": ["name", "stage", "value", "owner", "closeDate"], "filter": [], "sort": [{ "field": "value", "order": "desc" }] }, "my-deals": { - "filter": [["owner", "=", "${currentUser.id}"]], - "label": "My Deals" + "label": "My Deals", + "columns": ["name", "stage", "value", "owner", "closeDate"], + "filter": [{ "field": "owner", "operator": "equals", "value": "${currentUser.id}" }] } }, "defaultListView": "my-deals", diff --git a/content/docs/plugins/plugin-view.mdx b/content/docs/plugins/plugin-view.mdx index 8441514a71..f0d92e8efe 100644 --- a/content/docs/plugins/plugin-view.mdx +++ b/content/docs/plugins/plugin-view.mdx @@ -69,7 +69,7 @@ const shape: ObjectViewSchema = { // --- List surface --- defaultViewType: 'grid', // grid | kanban | gallery | calendar | timeline | gantt | map - listViews: { all: { label: 'All Users' } }, // named views; each needs a `label` + listViews: { all: { label: 'All Users', columns: ['name', 'email'] } }, // named views; each needs `label` and `columns` defaultListView: 'all', table: { columns: ['name', 'email'] }, // grid configuration (see below) @@ -401,6 +401,7 @@ const productViews: ObjectViewSchema = { cheap: { label: 'Under 100', type: 'grid', + columns: ['name', 'price'], filter: [{ field: 'price', operator: 'less_than', value: 100 }], }, }, @@ -525,6 +526,7 @@ const userTabs: ObjectViewSchema = { admins: { label: 'Admins', type: 'grid', + columns: ['name', 'email'], filter: [{ field: 'role', operator: 'equals', value: 'admin' }], }, }, diff --git a/packages/plugin-view/README.md b/packages/plugin-view/README.md index 76a244922f..e7f0e491f2 100644 --- a/packages/plugin-view/README.md +++ b/packages/plugin-view/README.md @@ -151,7 +151,7 @@ const shape: ObjectViewSchema = { // --- List surface --- defaultViewType: 'grid', // grid | kanban | gallery | calendar | timeline | gantt | map - listViews: { all: { label: 'All Users' } }, // named views; each needs a `label` + listViews: { all: { label: 'All Users', columns: ['name', 'email'] } }, // named views; each needs `label` and `columns` defaultListView: 'all', table: { columns: ['name', 'email'] }, // grid configuration (see below) @@ -466,6 +466,7 @@ const schema: ObjectViewSchema = { cheap: { label: 'Under 100', type: 'grid', + columns: ['name', 'price'], filter: [{ field: 'price', operator: 'less_than', value: 100 }], }, }, @@ -624,6 +625,7 @@ const schema: ObjectViewSchema = { admins: { label: 'Admins', type: 'grid', + columns: ['name', 'email'], filter: [{ field: 'role', operator: 'equals', value: 'admin' }], }, }, From ff723dc5432ef06212bb20a40bb72a9863874d86 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 05:08:21 +0000 Subject: [PATCH 2/6] test(types): the listViews pin parses every named view the docs author The pin that held the three refused named-view forms as "still what the docs teach" now asserts their successor: every `listViews` entry in the plugin-view README, plugin-view.mdx and schema-reference.md, read off disk with the TypeScript parser, parses under the spec's ObjectListViewSchema. A positive control runs the same predicate over the retired forms (label-only, tuple filter) and requires both refused. The spec-side refusal assertions are unchanged. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC --- .../object-view-unmirrored-keys-7779.test.ts | 98 ++++++++++++++++--- 1 file changed, 83 insertions(+), 15 deletions(-) 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 b6c7369c54..a3cb115c03 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 @@ -60,10 +60,13 @@ * ⛔ Do not quote 47 / 6 / 41 from anywhere: they are this file's history, not * its reading. 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 - * the renderer's read set moves) the measurement — and the stop — is re-taken - * rather than remembered. + * the spec value refuses the local named-view dialect this package's docs + * taught until objectui#8255 (a view with no `columns`, an ObjectQL tuple + * filter). Both facts are asserted against the SPEC schema here, so the day the + * spec relaxes (or the renderer's read set moves) the measurement — and the + * stop — is re-taken rather than remembered. objectui#8255 rewrote those docs in + * the spec shape, and the `listViews` block below parses every named view they + * author through that same schema, so the docs cannot drift back unseen. * * ## objectui#7924 — the same measurement, now PER MEMBER * @@ -1037,6 +1040,55 @@ describe('objectui#7779 — the zod mirror REFUSES `viewTabBar` by name', () => /* ── `listViews`: the measurement that keeps it in the ledger ─────────────── */ +/** The pages that author `object-view` named views (objectui#8255). */ +const NAMED_VIEW_DOCS = [README, DOC, 'content/docs/api/schema-reference.md'] as const; + +/** A literal expression's value. Anything else THROWS, so a reading is never silently partial. */ +function literalValue(node: ts.Expression, where: string): unknown { + const e = unwrapExpr(node); + if (ts.isObjectLiteralExpression(e)) { + const out: Record = {}; + for (const p of e.properties) { + if (!ts.isPropertyAssignment(p) || !(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))) { + throw new Error(`${where}: \`${p.getText()}\` is not a literal property, so this reading cannot judge it`); + } + out[p.name.text] = literalValue(p.initializer, where); + } + return out; + } + if (ts.isArrayLiteralExpression(e)) return e.elements.map((x) => literalValue(x, where)); + if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e)) return e.text; + if (ts.isNumericLiteral(e)) return Number(e.text); + if (e.kind === ts.SyntaxKind.TrueKeyword) return true; + if (e.kind === ts.SyntaxKind.FalseKeyword) return false; + if (e.kind === ts.SyntaxKind.NullKeyword) return null; + throw new Error(`${where}: \`${e.getText()}\` is not a literal, so this reading cannot judge it`); +} + +/** + * Every named view a page authors, off disk: each fenced block that mentions + * `listViews` is parsed with the TypeScript parser (a `json` body inside an + * initializer), and the entries of every `listViews` property come back by name. + */ +function docNamedViews(rel: string): Array<{ where: string; view: unknown }> { + const text = readRepo(rel); + const out: Array<{ where: string; view: unknown }> = []; + for (const m of text.matchAll(/^```(\w*)\n([\s\S]*?)^```$/gm)) { + const lang = m[1] ?? ''; + const body = m[2] ?? ''; + if (!body.includes('listViews')) continue; + const at = `${rel}:${text.slice(0, m.index ?? 0).split('\n').length}`; + const code = /^jsonc?$/.test(lang) ? `const doc = ${body};` : body; + const sf = ts.createSourceFile(`${at}.tsx`, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); + for (const n of allNodes(sf)) { + if (!ts.isPropertyAssignment(n) || !(ts.isIdentifier(n.name) || ts.isStringLiteral(n.name)) || n.name.text !== 'listViews') continue; + const views = literalValue(n.initializer, at) as Record; + for (const [name, view] of Object.entries(views)) out.push({ where: `${at} listViews.${name}`, view }); + } + } + return out; +} + describe('objectui#7779 — `listViews` stays unmirrored on the ruling\'s fallback clause; the measurement is pinned against the SPEC', () => { it('the spec slot `ViewSchema.listViews` is a record whose value is the strict `ObjectListViewSchema`', () => { const slot = shapeMember(SpecViewSchema, 'listViews') as { unwrap(): { def?: { type?: string; valueType?: unknown }; _def?: { type?: string; valueType?: unknown } } }; @@ -1051,19 +1103,19 @@ describe('objectui#7779 — `listViews` stays unmirrored on the ruling\'s fallba if (!r.success) expect((r.error.issues as readonly Issue[]).some((i) => i.code === 'unrecognized_keys')).toBe(true); }); - it('the spec value REFUSES the named views this package\'s docs teach — the behaviour a by-reference mirror would lose', () => { - // README / plugin-view.mdx: `listViews: { all: { label: 'All Users' } }` — - // "each needs a `label`", nothing else. The spec requires `columns`. + it('the spec value REFUSES the named-view dialect this package\'s docs taught until objectui#8255 — the behaviour a by-reference mirror would have lost', () => { + // README / plugin-view.mdx taught `listViews: { all: { label: 'All Users' } }` + // — "each needs a `label`", nothing else. The spec requires `columns`. const labelOnly = SpecObjectListViewSchema.safeParse({ label: 'All Users' }); expect(labelOnly.success).toBe(false); if (!labelOnly.success) expect(refusedAt(labelOnly.error.issues as readonly Issue[], 'columns')).toBe(true); - // README: a `filter`-only view with `type: 'grid'` and no `columns`. + // README taught a `filter`-only view with `type: 'grid'` and no `columns`. const filtered = SpecObjectListViewSchema.safeParse({ label: 'Under 100', type: 'grid', filter: [{ field: 'price', operator: 'less_than', value: 100 }] }); expect(filtered.success).toBe(false); if (!filtered.success) expect(refusedAt(filtered.error.issues as readonly Issue[], 'columns')).toBe(true); - // schema-reference.md: an ObjectQL tuple filter. Only `tuple` still has a - // doc side, and that side is HELD — the "still what the docs teach" test - // below pins this exact filter string off disk. + // schema-reference.md taught an ObjectQL tuple filter. None of these three + // forms has a doc side any more: objectui#8255 rewrote every one in the spec + // shape, and the next test parses what those pages author now. const tuple = SpecObjectListViewSchema.safeParse({ label: 'My Deals', columns: ['name'], filter: [['owner', '=', '${currentUser.id}']] }); expect(tuple.success).toBe(false); if (!tuple.success) expect(refusedAt(tuple.error.issues as readonly Issue[], 'filter')).toBe(true); @@ -1083,10 +1135,26 @@ describe('objectui#7779 — `listViews` stays unmirrored on the ruling\'s fallba expect(SpecObjectListViewSchema.safeParse({ label: 'Directory', columns: ['name', 'email'] }).success).toBe(true); }); - it('the documented shapes the spec refuses are still what the docs teach (the measurement\'s inputs, off disk)', () => { - expect(readRepo(README)).toContain("listViews: { all: { label: 'All Users' } }"); - expect(readRepo(DOC)).toContain("listViews: { all: { label: 'All Users' } }"); - expect(readRepo('content/docs/api/schema-reference.md')).toContain('"filter": [["owner", "=", "${currentUser.id}"]],'); + it('the docs now teach the spec shape — every named view the three pages author parses under `ObjectListViewSchema` (objectui#8255, off disk)', () => { + // The predicate, and the SAME function the positive control below runs. + const refused = (entries: ReadonlyArray<{ where: string; view: unknown }>): string[] => + entries.filter((e) => !SpecObjectListViewSchema.safeParse(e.view).success).map((e) => e.where); + for (const doc of NAMED_VIEW_DOCS) { + const entries = docNamedViews(doc); + expect(entries.length, `${doc} authors no \`listViews\` entry any more — this reading would pass vacuously`).toBeGreaterThan(0); + expect( + refused(entries), + `${doc} teaches a named view the spec's ObjectListViewSchema refuses. Every entry needs \`columns\`, and \`filter\` is \`{ field, operator, value }\` rule objects, never an ObjectQL tuple.`, + ).toEqual([]); + } + // POSITIVE CONTROL: the predicate goes red on the dialect these pages taught + // before objectui#8255, and stays quiet on the spec shape, so the empty + // readings above are readings. + expect(refused([ + { where: 'label-only', view: { label: 'All Users' } }, + { where: 'tuple', view: { label: 'My Deals', columns: ['name'], filter: [['owner', '=', '${currentUser.id}']] } }, + { where: 'spec', view: { label: 'Directory', columns: ['name', 'email'] } }, + ])).toEqual(['label-only', 'tuple']); }); it('the renderer reads twenty-one keys off a named view — every one of them a declared `NamedListView` member since objectui#8980 — of a declaration with 64, the reason a local key-for-key mirror is still not the answer', () => { From 9313d82c06760f38c9028be1cdffc0dfa1fda943 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 05:14:17 +0000 Subject: [PATCH 3/6] chore(changeset): declare the listViews docs change as releasing nothing Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC --- .changeset/8255-docs-spec-shaped-list-view.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/8255-docs-spec-shaped-list-view.md diff --git a/.changeset/8255-docs-spec-shaped-list-view.md b/.changeset/8255-docs-spec-shaped-list-view.md new file mode 100644 index 0000000000..4533236f46 --- /dev/null +++ b/.changeset/8255-docs-spec-shaped-list-view.md @@ -0,0 +1,13 @@ +--- +--- + +Docs and a test only, no package released. The plugin-view README, `content/docs/plugins/plugin-view.mdx` +and `content/docs/api/schema-reference.md` now teach the spec-shaped named list view: every `listViews` +entry carries `columns`, the one key `@objectstack/spec`'s `ObjectListViewSchema` requires, and the +schema reference's `my-deals` view spells its filter as a `{ field, operator, value }` rule object +instead of the ObjectQL tuple (objectui#8255, the docs half of the objectui#7928 ruling). + +The `@object-ui/types` pin that held the refused forms as "still what the docs teach" now asserts the +successor: it reads every named view the three pages author off disk with the TypeScript parser and +requires each to parse under `ObjectListViewSchema`, with a positive control that the retired forms +are refused by the same predicate. The spec-side refusal assertions in that pin are unchanged. From 176be646b0f40533d8a962fc4f1da07d07812ee2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 05:39:39 +0000 Subject: [PATCH 4/6] docs: spec context token in my-deals, rule-object filter in the CRUD guide schema-reference's my-deals view now filters on the spec's own context token {current_user_id} (CONTEXT_TOKENS in @objectstack/spec/data) instead of an invented ${currentUser.id}, with one sentence saying the host that renders the list resolves it and that the object-view node path is tracked in objectui#10506. The CRUD guide's TaskSchema listViews.active filter becomes a { field, operator, value } rule object, which the spec's ObjectSchema.listViews accepts. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC --- content/docs/api/schema-reference.md | 4 +++- content/docs/guide/building-crud-app.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/content/docs/api/schema-reference.md b/content/docs/api/schema-reference.md index 67508396a7..caf261b9f2 100644 --- a/content/docs/api/schema-reference.md +++ b/content/docs/api/schema-reference.md @@ -879,7 +879,7 @@ A complete object management interface combining grid, form, search, filters, an "my-deals": { "label": "My Deals", "columns": ["name", "stage", "value", "owner", "closeDate"], - "filter": [{ "field": "owner", "operator": "equals", "value": "${currentUser.id}" }] + "filter": [{ "field": "owner", "operator": "equals", "value": "{current_user_id}" }] } }, "defaultListView": "my-deals", @@ -895,6 +895,8 @@ A complete object management interface combining grid, form, search, filters, an } ``` +`{current_user_id}` in the `my-deals` filter is the spec's context token (`CONTEXT_TOKENS` in `@objectstack/spec/data`): the host that renders the list resolves it to the signed-in user's id before the query runs, and resolving it on the `object-view` node path itself is tracked in objectui#10506. + | Property | Type | Description | |----------|------|-------------| | `objectName` | `string` | **Required.** ObjectQL object API name. | diff --git a/content/docs/guide/building-crud-app.md b/content/docs/guide/building-crud-app.md index 534fc67b5d..f390aef0ef 100644 --- a/content/docs/guide/building-crud-app.md +++ b/content/docs/guide/building-crud-app.md @@ -135,7 +135,7 @@ export const TaskSchema = { active: { label: 'Active', columns: ['title', 'status', 'priority', 'assignee', 'due_date'], - filter: [['status', '!=', 'Done']], + filter: [{ field: 'status', operator: 'not_equals', value: 'Done' }], sort: [{ field: 'priority', order: 'asc' }], }, }, From 4aa49b1aae7e9ba15e359bb8703f3f4e345fdf4f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 05:39:39 +0000 Subject: [PATCH 5/6] test(types): the listViews pin also reads the CRUD guide's named views The guide page joins NAMED_VIEW_DOCS, keeping the per-page non-vacuity bound, and the markdown-test-inputs ledger row for the pin now lists it as a read so a markdown-only change to the guide still runs the pin. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC --- .../__tests__/object-view-unmirrored-keys-7779.test.ts | 10 +++++++--- scripts/markdown-test-inputs.mjs | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) 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 a3cb115c03..a0b1182232 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 @@ -1040,8 +1040,12 @@ describe('objectui#7779 — the zod mirror REFUSES `viewTabBar` by name', () => /* ── `listViews`: the measurement that keeps it in the ledger ─────────────── */ -/** The pages that author `object-view` named views (objectui#8255). */ -const NAMED_VIEW_DOCS = [README, DOC, 'content/docs/api/schema-reference.md'] as const; +/** + * The pages that author named list views (objectui#8255): three on an + * `object-view` node, plus the CRUD guide's object definition, whose + * `listViews` the spec types with the same `ObjectListViewSchema` value. + */ +const NAMED_VIEW_DOCS = [README, DOC, 'content/docs/api/schema-reference.md', 'content/docs/guide/building-crud-app.md'] as const; /** A literal expression's value. Anything else THROWS, so a reading is never silently partial. */ function literalValue(node: ts.Expression, where: string): unknown { @@ -1135,7 +1139,7 @@ describe('objectui#7779 — `listViews` stays unmirrored on the ruling\'s fallba expect(SpecObjectListViewSchema.safeParse({ label: 'Directory', columns: ['name', 'email'] }).success).toBe(true); }); - it('the docs now teach the spec shape — every named view the three pages author parses under `ObjectListViewSchema` (objectui#8255, off disk)', () => { + it('the docs now teach the spec shape — every named view the four pages author parses under `ObjectListViewSchema` (objectui#8255, off disk)', () => { // The predicate, and the SAME function the positive control below runs. const refused = (entries: ReadonlyArray<{ where: string; view: unknown }>): string[] => entries.filter((e) => !SpecObjectListViewSchema.safeParse(e.view).success).map((e) => e.where); diff --git a/scripts/markdown-test-inputs.mjs b/scripts/markdown-test-inputs.mjs index 4a422e4ce5..91cde502cf 100644 --- a/scripts/markdown-test-inputs.mjs +++ b/scripts/markdown-test-inputs.mjs @@ -456,6 +456,7 @@ export const ADJUDICATED = new Map([ { reads: [ 'content/docs/api/schema-reference.md', + 'content/docs/guide/building-crud-app.md', 'content/docs/plugins/plugin-view.mdx', 'packages/plugin-view/README.md', ], From 9c7f893d3bf0498677039a9bd3dd98892f14635d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 05:39:39 +0000 Subject: [PATCH 6/6] docs(changeset): past-tense the listViews docs claim this change falsified The pending 7779 entry said the spec refuses the named views the docs teach; the docs no longer teach them, so that sentence now says what the docs taught when the entry was written and that objectui#8255 has since rewritten them. Front matter unchanged. This change's own empty-frontmatter changeset now also names the CRUD guide and the context token. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC --- .changeset/8255-docs-spec-shaped-list-view.md | 6 ++++-- .changeset/object-view-unmirrored-keys-7779.md | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.changeset/8255-docs-spec-shaped-list-view.md b/.changeset/8255-docs-spec-shaped-list-view.md index 4533236f46..6d439af548 100644 --- a/.changeset/8255-docs-spec-shaped-list-view.md +++ b/.changeset/8255-docs-spec-shaped-list-view.md @@ -5,9 +5,11 @@ Docs and a test only, no package released. The plugin-view README, `content/docs and `content/docs/api/schema-reference.md` now teach the spec-shaped named list view: every `listViews` entry carries `columns`, the one key `@objectstack/spec`'s `ObjectListViewSchema` requires, and the schema reference's `my-deals` view spells its filter as a `{ field, operator, value }` rule object -instead of the ObjectQL tuple (objectui#8255, the docs half of the objectui#7928 ruling). +instead of the ObjectQL tuple, with the spec's own context token `{current_user_id}` as its value +(objectui#8255, the docs half of the objectui#7928 ruling). `content/docs/guide/building-crud-app.md`'s +`TaskSchema.listViews.active` gets the same filter rewrite. The `@object-ui/types` pin that held the refused forms as "still what the docs teach" now asserts the -successor: it reads every named view the three pages author off disk with the TypeScript parser and +successor: it reads every named view those four pages author off disk with the TypeScript parser and requires each to parse under `ObjectListViewSchema`, with a positive control that the retired forms are refused by the same predicate. The spec-side refusal assertions in that pin are unchanged. diff --git a/.changeset/object-view-unmirrored-keys-7779.md b/.changeset/object-view-unmirrored-keys-7779.md index b3d50af093..44221551ed 100644 --- a/.changeset/object-view-unmirrored-keys-7779.md +++ b/.changeset/object-view-unmirrored-keys-7779.md @@ -54,11 +54,13 @@ reach the renderer through an `as any` cast on the named-view config in objectui#8980 ruling declared it by name — objectui#7928's open half, answered. 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 +refuses `options`, ObjectQL tuple filters and `default` — that is, it refused 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 +`content/docs/api/schema-reference.md` taught when this entry was written +(`{ label: 'All Users' }` fails at `columns`; `filter: [["owner", "=", "..."]]` +fails at `filter.0`), and objectui#8255 has since rewritten them in the spec +shape. Mirroring the +spec value would have lost documented behaviour; mirroring the local value would enforce **43 unread members** (64 declared, minus the 21 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