Repository navigation
feat(spec): object-grid declares description, emptyState (the list view's own shape) and keyboardNavigation - #20882
Conversation
…Navigation The list view's inline empty-state shape is extracted into the exported EmptyStateSchema and taken by reference on both doors; its action/button guidance is reworded to read true on the object-grid door as well. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
…igation The three members parse byte-identical, the row still refuses an unknown key and a wrong value in each member (code and path), and both the grid's and the list view's emptyState hold EmptyStateSchema itself. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
The reference docs gate refuses a documented schema without its type alias. The schema has no default or transform, so it is pinned isomorphic (ADR-0122) rather than given an EmptyStateParsed synonym. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
Regenerated by the build and `check:generated --fix`: authorable-surface, json-schema manifest, api-surface, export-origins, declaration-map and the reference docs. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
…ject-grid-desc-emptystate-keynav
authorable-surface/ui.json and the reference docs were merged by the os-regen driver without a text merge; regenerated from the merged tree so both sides' entries are present (PageHeaderProps.breadcrumb [RETIRED] from main, the object-grid and EmptyState rows from this branch). Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f94385ab56cd01d1f71ff7a9f34bc38ea5ed9a4e && git checkout f94385ab56cd01d1f71ff7a9f34bc38ea5ed9a4e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9905e61ca2fddb43d266c23cdb12ced5019c1a74 98dad84e210101e764efd40d32446187ffed7c92 && git checkout -B drift-repro 9905e61ca2fddb43d266c23cdb12ced5019c1a74 && git merge --no-ff 98dad84e210101e764efd40d32446187ffed7c92
node scripts/docs-audit/affected-docs.mjs --json 9905e61ca2fddb43d266c23cdb12ced5019c1a74 |
Contract reviewServed-tier: Inputs, and nothing else: card #20694 (body and all 7 comments: triage ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each named right or wrong.
No governed-surface path in the file list; head repo equals base repo; 387 changed lines. ② Semver level
Clause-②: yes (widening) — ③ Boundary flagsEvery dev flag and every
Nothing escalated. Implemented-by: VERDICT: PASS |
…ame with the list view's `userActions.filter` named (objectui#11068) (objectstack-ai#11306) Part of objectstack-ai#11068 Clause-②: yes The `showFilters` half of the card, as triage's retriage answer (comment 5896596687) ruled it: **RETIRE, with a named tombstone that points at the list view's `userActions.filter`**. `keyboardNavigation` is not in this PR. It waits until objectstack-ai/objectstack#20694's row is installable (PR objectstack-ai/objectstack#20882 is not in `@objectstack/spec` 17.5.0), so the card stays open. ## What changed - **`@object-ui/types`, zod twin** (`ObjectGridSchema` in `zod/objectql.zod.ts`): `showFilters: z.boolean().optional()` is now `retirementTombstone(OBJECT_GRID_SHOW_FILTERS_RETIRED)`, next to the `rowSpecActions` / `bulkSpecActions` / `name` / `placeholder` tombstones. The one string goes into both `.describe()` and the parse message. It names the card, says the grid has no filter UI, sends the author to a `list-view` and its `userActions.filter`, and names `filter` for narrowing the grid's own rows. - **`@object-ui/types`, TypeScript twin** (`ObjectGridSchema` in `objectql.ts`): `showFilters?: boolean` (the deprecated "legacy filters toggle") is now `showFilters?: never` with a RETIRED docblock. It is not deleted, because the key would then fall back to `BaseSchema`'s index signature and type-check again. That is the same convention `rowSpecActions` uses. - **Untouched, on purpose:** `ObjectViewSchema.showFilters` (both faces), which `ObjectView` reads. The list view's `showFilters` (zod `ListViewSchema`), which `ListView` folds onto `userActions.filter`. `NamedListView.showFilters`, already `?: never` since objectui#7924. `DashboardConfig.showFilters`. The `object-view` `table` slot's existing by-name refusal. - **Producers that stopped authoring it:** the root `README.md` `object-grid` example, and the objectui#9729 byte-ruler corpus in `ObjectGrid.operatorsInert-9729.test.tsx`. That corpus's "filter surface on" document carried `showFilters: true`, which never drew anything. It now measures with the grid's search box (`searchableFields`), and the test is renamed to match. - **Docs:** `schema-reference.md` gets a retired callout under `ObjectGridSchema` and an updated history sentence. `plugin-grid.mdx` gets a paragraph next to the four earlier retirements. - **Pins:** the new `packages/types/src/__tests__/object-grid-show-filters-retired-11068.test.ts` covers both authoring faces, the metadata channel, member-not-deletion, the upstream half and the TS twin. `ObjectGrid.declaredKeys-11068.test.tsx` adds `showFilters` (true and false) to the byte ruler. Comment-only count updates (four to five) in `object-view-slot-key-lists.test.ts` and the two slot docblocks. - **Changesets:** the new `.changeset/11068-object-grid-show-filters.md` is `@object-ui/types` `minor`, with a BREAKING (authoring) line. The pending `.changeset/11068-grid-declared-keys.md` (from PR objectui#11130, not yet released) said "`showFilters` and `keyboardNavigation` are still declared and still not read". This PR makes that false, so it now names only `keyboardNavigation`. ## Dispatch assumptions, measured 1. **Red first: holds.** At base `1563d3e10b`, the new pin read **5 failed / 13 passed**. `{ type: 'object-grid', objectName, showFilters: true }` parsed green on `safeValidateSchema` and on `StrictAnyComponentSchema` ("showFilters: true parsed green: expected true to be false"). The 13 that passed are the controls. The renderer half is a byte ruler: the same grid drawn with and without `showFilters` (true and false) gives identical bytes, and the lit control `description` moves them. `packages/plugin-grid/src` is not edited, so the ruler reads base behaviour. 2. **Which declarations belong to the grid: holds.** `objectql.ts` declares `showFilters` on three interfaces. These are `ObjectGridSchema` (retired here), `ObjectViewSchema` (its own key, read by `ObjectView` as the fallback behind `userActions.filter`) and `NamedListView` (already a tombstone). The zod side declares it on `ObjectGridSchema` (retired), `ObjectViewSchema`, `ListViewSchema`, `DashboardConfigSchema`, and in the table-slot withheld set. 3. **`object-view` table slot: holds, and nothing changes there.** The slot is `ObjectGridSchema.omit(type, objectName).extend(OBJECT_VIEW_TABLE_WITHHELD)`. The withheld set already refused `showFilters` by name (objectui#10976), and `.extend` overrides the grid's tombstone, so the slot's message is byte-for-byte the same. No live read goes through the slot. `ObjectView` reads only the node-level `schema.showFilters`, and `OBJECT_VIEW_TABLE_RELAY_KEYS` does not carry it. 4. **Spec row: holds, and the premise stands.** On the installed `@objectstack/spec` 17.5.0, `ComponentPropsMap['object-grid']` is `ObjectGridPropsSchema`, which does not declare `showFilters`. It refuses `{ objectName, showFilters: true }` with `unrecognized_keys`, while `{ objectName }` alone parses green. `ListViewSchema.userActions` declares `filter`. The pin re-derives both facts. 5. **Producers and docs: partly falsified.** The `schema-reference.md` table row that names `showFilters` (the `showSearch` / `showFilters` / `showCreate` row) is in the **`ObjectViewSchema`** table, not the grid's, so it stays. The `ObjectGridSchema` table never listed the key. The card said its only producer was the doc example, which PR objectui#11130 removed. Two more in-repo producers turned up: the root `README.md` `object-grid` example and the 9729 ruler corpus. Both now omit the key. The `plugin-view.mdx` and `packages/plugin-view/README.md` mentions are about the object view's own key and the table slot, and stay. ## Tests (head `b87cbca042`, after merging `main` at `1ccb5ba7de`, which brought in PR objectui#11292) - `pnpm --filter @object-ui/types build && pnpm --filter @object-ui/types type-check && pnpm --filter @object-ui/plugin-grid type-check && pnpm --filter @object-ui/plugin-view type-check` gives `VERDICT command-exit 0`. The types type-check runs three legs: `tsc --noEmit`, `tsconfig.examples.json` and `tsconfig.test.json`. - `pnpm exec vitest run packages/types/ examples/schema-catalog/` gives `Test Files 338 passed (338)`, `Tests 9836 passed (9836)`. - `pnpm exec vitest run packages/plugin-grid/` plus the ten other suites that `git grep -l showFilters` finds (app-shell ×3, core ×3, plugin-list ×1, plugin-view ×3) gives `Test Files 187 passed (187)`, `Tests 1959 passed (1959)`. - Before the merge, the build ran over the `@object-ui/plugin-view` dependency closure (`turbo run build --filter='@object-ui/plugin-view^...' --concurrency=2`: 15/15 tasks). **Ablations** ran on committed code (`181b9b615a`) through objectstack's `scripts/ablation-replace.mjs`, in WRAP mode. Each anchor went from x1 to x0, the blob changed, and the restore was proven (blob equals HEAD, `git diff HEAD` empty). The tests read `src`, so there is no `dist` leg. - (A) In `objectql.ts`, `showFilters?: never` changed to `boolean`. `tsc -p tsconfig.test.json` then exits 2 with `TS2578 Unused '@ts-expect-error'` in the new pin, plus `TS2322` in the `zod-mirror-parity` type ratchet. - (B) The zod tombstone changed to `z.boolean().optional()`. The new pin then reads `5 failed / 13 passed`, the same red as at base. **Repo checks** (at `b87cbca042`): - `check-control-bytes`: OK. - `new-cross-file-line-citations`: `VERDICT … 0 new citation(s)`. - `check-changeset-presence`: "6 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)". - `changeset:check`: no `major`. - `check:doc-types`, `check:doc-fences`, `check:doc-example-ids`, `docs:check-links`, `check:doc-example-readers` and `check:test-path-roots`: all OK. - `check:component-surface-parity`: report-only, and the report is identical before and after (no `object-grid` row either way). - `check:changeset-claims`: report-only. The one falsified pending sentence is repaired as described above. - `check-governed-queue-guard --test` on the 11 paths: NOT GOVERNED. **NOT MEASURED:** - `check:doc-snippets` and `check:doc-examples` exit 2 ("THE GATE COULD NOT RUN"): about 20 packages they import are unbuilt. This diff adds or changes no `ts` / `tsx` fence line (counted: 0), and the only fenced edit is one line removed from a `json` fence in `README.md`. These are left to CI. - `pnpm lint` is CI's to run. - Type-check of the other `ObjectGridSchema` importers (core, plugin-calendar, plugin-dashboard, plugin-designer, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-tree, console) is left to CI. A grep finds no typed writer of `showFilters` on an `ObjectGridSchema` value. Every other writer targets an `object-view`, a `list-view` or a dashboard. ## Acceptance notes - The `object-view` table slot refuses `table.showFilters` with "`ObjectGrid` has no read of it". An author who wrote it probably meant the object view's own `showFilters`, which `ObjectView` reads, and the message does not name it. This is polish to the wording of a refusal that is already loud. Carrier: none. Left as is, because the dispatch rules out widening or reworking that slot. - The `operators` tombstone's comment in `objectql.zod.ts` still describes the objectui#9729 reading as "with the filter surface off AND on". That is a historical description of a measurement taken when the corpus carried `showFilters`, and it is left as written. - `keyboardNavigation` is still declared on `ObjectGridSchema` and still unread. It is the card's remaining half. Session: `https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ` --- _Generated by [Claude Code](https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20694
Clause-②: yes
ComponentPropsMap['object-grid'](ObjectGridPropsSchema) gains the three members named in triage's direction (5897622232). This is a widening. Nothing that parsed before is refused, and the row still refuses every key it does not declare.What changes
description:I18nLabelSchema.optional(). At the.objectui-shapindb11afd4967c,plugin-grid/src/ObjectGrid.tsx:5677resolves it withresolveInlineI18nLabel(schema.description, displayLocale)and draws it above the rows at:5784,:6303and:6342. The card calls the functionresolveI18nLabel; at the pin it is namedresolveInlineI18nLabel.emptyState: the list view's own empty-state shape, taken by reference. It used to be an inlinestrictObjectatListViewShapeSchema.emptyState(view.zod.ts). It is now the exportedEmptyStateSchema(author typeEmptyState), andlist-viewandobject-gridboth hold that one schema. At the pin it is read atObjectGrid.tsx:6225and drawn throughDataEmptyStateat:6233.keyboardNavigation:z.boolean().optional(). Its describe is the one triage specified: arrow-key cell navigation on the WAI-ARIA grid pattern, on by default wheneditableis set, and a read-only grid keeps its Tab behaviour unless this istrue. It is prefixed[EXPERIMENTAL — not enforced]because nothing reads it at the pin. Under objectuipackages/andapps/, excluding tests, CHANGELOGs, READMEs andpackages/types(which holds its type and zod twin), it has 0 hits; the controlschema.editablehas 3 hits inObjectGrid.tsx.The extraction, and the one text change
emptyStatevalues on each of the four list-view doors:ListViewSchema,ObjectListViewSchema,ViewSchema.listand the flattened overlay arm ofViewMetadataSchema. That is 112 cells, compared between BASE72f8c38201and head.successand parseddataare identical in 112 of 112 cells.messagefield stripped at every depth).action,buttonand mixed inputs on each of the four doors. In those cells theaction/buttonguidance is reworded so that it also reads true onobject-grid, which has noaddRecordblock. It still points ataddRecord(view-strictness-batch18.test.tsstill passes), and it follows theVIEW_ROW_BOUND_GUIDANCEprecedent for text that two doors share.Liveness record for
keyboardNavigationThe liveness ledger has no row for any
ComponentPropsMapkey. The page ledger leavespage/regionsundrilled (it is listed inundrilled-containers.baseline.json), andPageComponent.propertiesis an open record, so the walk never reachesObjectGridPropsSchema; a row inpage.jsonwould be an orphan. The record is therefore the describe marker[EXPERIMENTAL — not enforced], which is the ledger's own spelling for a declared key that is not enforced (MARKER_REincheck-liveness.mts). The member docblock says to drop the marker in the change that lands the reader.check:livenessis green.Files outside the claimed surface
packages/spec/src/type-alias-convention.pin.test.ts:gen:docsrefuses to document a schema that has no type alias, soEmptyStateis exported. ADR-0122 then requires an isomorphism pin (Iso_ui_view__EmptyStateSchema), which moves the pin count from 779 to 780. The schema's tree has no default and no transform, so a synonymEmptyStateParsedwould be wrong.Evidence (head
98dad84e21, which includes a merge oforigin/mainat07356a6ab0throughos-regen-merge.sh)object-gridcorpus: 3 of 36 cases parsed at BASE and 14 of 36 parse at head. The newly accepted cases are exactly the valid values of the three members, and no case is newly refused. Bothexamples/app-showcaseobject-gridnodes (command-centercc_queue_gand the my-work queue) parse before and after.component-object-grid-declared-keys.pin.test.tspasses 18 of 18. §1 checks that the three members parse byte-identically. §2 checks that an unknown top-level key is refused withunrecognized_keysat the row, and that a wrong value in each member is refused (code and path). §3 checks that the grid and the list view holdEmptyStateSchemaby identity, and that the grid answers exactly what that schema answers.scripts/ablation-replace.mjs. Each restore was proven: the blob equals HEAD andgit diff HEADis empty.keyboardNavigationmember: 4 tests fail and 14 pass.EmptyStateSchemawith a copiedz.strictObject: only the identity pin fails, and 17 pass.pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 581 files, 17154 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0. This includescheck:test-typecheck, and the debt ledger is unchanged.@objectstack/lint. Its closure was built upstream withturbo run build --filter='@objectstack/lint...'(lint plus its dependencies).typecheck: exit 0.validateComponentPropson anobject-gridnode:emptyStatproducescomponent-props-unknown-keyatproperties.emptyStat.emptyState.headingproduces the same rule atproperties.emptyState.heading. That path exists only through the new declaration, which shows the rule read the rebuilt dist.pnpm --filter @objectstack/spec check:generated: all 15 artifacts are up to date.dispatch-gates.mjs --commands, 108 families, run at head): 104 exited 0. Four exited 3 with PREREQUISITE NOT MET, so they are NOT MEASURED:check:skill-examples: the client SDK closure is not built.check:dual-build-cjs-loads,check:lean-entry-closureandcheck:type-check-debt: each needs the whole-repo package build that CI does.--ran: all 108 accounted for, 104 run, 4 NOT MEASURED.Acceptance notes
emptyState.titleor.message. At the pin,ObjectGridpasses both toDataEmptyStateunresolved (:6241-6242), unlikedescription. React throws on an object child, measured on react 19.2.8: "Objects are not valid as a React child".ListViewguards withtypeof === 'string'(plugin-list/src/ListView.tsx:5112and:5114), so a map there silently falls back to the default text. No producer inexamples/**orplatform-objectswrites a map there; all of them use strings. So this is not filed here. Carrier: the objectui follow-up the seat files at ACCEPT, which lists the three keys inGRID_QUERY_INPUTS. Its fix is one resolver,resolveInlineI18nLabel, applied at both renderers. Meanwhile the changeset tells authors to write strings onobject-grid.origin/mainmoved to9905e61ca2after the merge; fix(metadata-protocol,spec)!: a saved view stores the parsed value of every key its body carried, and a ViewItem record's top-level options is refused by name (stage iv of #20051) #20868 editsview.zod.tsandview.mdx. A merge-tree probe from a bare clone with no merge driver shows a clean merge, and the hunks in both files are disjoint from this branch's. CI validates the merge ref.object-gridfollow-up in objectui, per triage: after the release is installable, listdescription,emptyStateandkeyboardNavigationinGRID_QUERY_INPUTS(the reverse direction of the parity gate).Generated by Claude Code