You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit f5c7b2c
Browse filesBrowse the repository at this point in the historyBrowse files
feat(spec): object-grid declares description, emptyState (the list view's own shape) and keyboardNavigation (#20882)
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-sha` pin `db11afd4967c`,
`plugin-grid/src/ObjectGrid.tsx:5677` resolves it with
`resolveInlineI18nLabel(schema.description, displayLocale)` and draws it
above the rows at `:5784`, `:6303` and `:6342`. The card calls the
function `resolveI18nLabel`; at the pin it is named
`resolveInlineI18nLabel`.
- **`emptyState`**: the list view's own empty-state shape, taken **by
reference**. It used to be an inline `strictObject` at
`ListViewShapeSchema.emptyState` (`view.zod.ts`). It is now the exported
`EmptyStateSchema` (author type `EmptyState`), and `list-view` and
`object-grid` both hold that one schema. At the pin it is read at
`ObjectGrid.tsx:6225` and drawn through `DataEmptyState` at `: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 when `editable` is set, and a read-only grid
keeps its Tab behaviour unless this is `true`. It is prefixed
`[EXPERIMENTAL — not enforced]` because nothing reads it at the pin.
Under objectui `packages/` and `apps/`, excluding tests, CHANGELOGs,
READMEs and `packages/types` (which holds its type and zod twin), it has
0 hits; the control `schema.editable` has 3 hits in `ObjectGrid.tsx`.
- The block docblock records each key's read point at the pin, following
its convention, and each member repeats its own.
### The extraction, and the one text change
- A parse corpus, run as a scratch script and not committed, covers 28
`emptyState` values on each of the four list-view doors:
`ListViewSchema`, `ObjectListViewSchema`, `ViewSchema.list` and the
flattened overlay arm of `ViewMetadataSchema`. That is 112 cells,
compared between BASE `72f8c38201` and head.
- `success` and parsed `data` are identical in 112 of 112 cells.
- Issue codes, paths and keys are identical in 112 of 112 cells (every
`message` field stripped at every depth).
- After substituting only the guidance sentence, the full JSON of each
result is byte-identical in 112 of 112 cells.
- The 12 cells that differ at all are the `action`, `button` and mixed
inputs on each of the four doors. In those cells the `action`/`button`
guidance is reworded so that it also reads true on `object-grid`, which
has no `addRecord` block. It still points at `addRecord`
(`view-strictness-batch18.test.ts` still passes), and it follows the
`VIEW_ROW_BOUND_GUIDANCE` precedent for text that two doors share.
- The members gain short describes. The list view's reference docs lose
the inherited sentence claiming the inline locale map is "resolved at
render time"; see Acceptance note 1 for why that claim did not hold.
### Liveness record for `keyboardNavigation`
The liveness ledger has no row for any `ComponentPropsMap` key. The page
ledger leaves `page/regions` undrilled (it is listed in
`undrilled-containers.baseline.json`), and `PageComponent.properties` is
an open record, so the walk never reaches `ObjectGridPropsSchema`; a row
in `page.json` would 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_RE` in
`check-liveness.mts`). The member docblock says to drop the marker in
the change that lands the reader. `check:liveness` is green.
### Files outside the claimed surface
- `packages/spec/src/type-alias-convention.pin.test.ts`: `gen:docs`
refuses to document a schema that has no type alias, so `EmptyState` is
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 synonym
`EmptyStateParsed` would be wrong.
## Evidence (head `98dad84e21`, which includes a merge of `origin/main`
at `07356a6ab0` through `os-regen-merge.sh`)
- `object-grid` corpus: 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. Both
`examples/app-showcase` `object-grid` nodes (command-center `cc_queue_g`
and the my-work queue) parse before and after.
- **Pins:** `component-object-grid-declared-keys.pin.test.ts` passes 18
of 18. §1 checks that the three members parse byte-identically. §2
checks that an unknown top-level key is refused with `unrecognized_keys`
at the row, and that a wrong value in each member is refused (code and
path). §3 checks that the grid and the list view hold `EmptyStateSchema`
by identity, and that the grid answers exactly what that schema answers.
- **Ablations**, run on the committed state through
`scripts/ablation-replace.mjs`. Each restore was proven: the blob equals
HEAD and `git diff HEAD` is empty.
- Renaming the `keyboardNavigation` member: 4 tests fail and 14 pass.
- Replacing the grid's `EmptyStateSchema` with a copied
`z.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 includes
`check:test-typecheck`, and the debt ledger is unchanged.
- **Consumer `@objectstack/lint`.** Its closure was built upstream with
`turbo run build --filter='@objectstack/lint...'` (lint plus its
dependencies).
- Tests: 117 files, 5437 passed. `typecheck`: exit 0.
- Component-props reading through `validateComponentProps` on an
`object-grid` node:
- The three keys produce 0 findings.
- The control `emptyStat` produces `component-props-unknown-key` at
`properties.emptyStat`.
- The control `emptyState.heading` produces the same rule at
`properties.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.
- **Derived gates** (`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-closure` and
`check:type-check-debt`: each needs the whole-repo package build that CI
does.
- Reconciled with `--ran`: all 108 accounted for, 104 run, 4 NOT
MEASURED.
## Acceptance notes
1. **objectui renderer gap: an inline locale map in `emptyState.title`
or `.message`.** At the pin, `ObjectGrid` passes both to
`DataEmptyState` unresolved (`:6241-6242`), unlike `description`. React
throws on an object child, measured on react 19.2.8: "Objects are not
valid as a React child". `ListView` guards with `typeof === 'string'`
(`plugin-list/src/ListView.tsx:5112` and `:5114`), so a map there
silently falls back to the default text. No producer in `examples/**` or
`platform-objects` writes 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 in `GRID_QUERY_INPUTS`. Its fix is
one resolver, `resolveInlineI18nLabel`, applied at both renderers.
Meanwhile the changeset tells authors to write strings on `object-grid`.
2. `origin/main` moved to `9905e61ca2` after the merge; #20868 edits
`view.zod.ts` and `view.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.
3. The `object-grid` follow-up in objectui, per triage: after the
release is installable, list `description`, `emptyState` and
`keyboardNavigation` in `GRID_QUERY_INPUTS` (the reverse direction of
the parity gate).
---
_Generated by [Claude
Code](https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The `object-grid` page block now declares `description`, `emptyState` and `keyboardNavigation`, so a page that authors them validates clean instead of having each reported as a prop the block does not declare (#20694).
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
-**`description`** — an `I18nLabel` (a string, or an inline locale map): one line of help text the grid draws above its rows, resolved against the display locale.
10
+
-**`emptyState`** — `{ title?, message?, icon? }`, what the grid draws instead of an empty table. It is the list view's own empty-state shape, now exported as `EmptyStateSchema` (author type `EmptyState`) and taken by reference on both `list-view` and `object-grid`, so the two cannot drift apart.
11
+
-**`keyboardNavigation`** — a boolean, marked `[EXPERIMENTAL — not enforced]`: arrow-key cell navigation on the WAI-ARIA grid pattern, on by default when `editable` is set. No renderer reads it yet, so authoring it changes nothing today.
12
+
13
+
Nothing that parsed before is refused now. The `object-grid` row still refuses every key it does not declare, and the list view's `emptyState` accepts exactly the values it accepted before the shape was extracted. One refusal message is reworded: an `action` or `button` key inside an empty state is still refused and still points at the list view's `addRecord` block, now phrased so that it also reads true on `object-grid`, which has no add-record block.
14
+
15
+
On `object-grid`, write `emptyState.title` and `emptyState.message` as plain strings for now: the grid renderer draws both as they are and does not yet resolve an inline locale map there the way it resolves `description`, so a map in either member fails to render.
16
+
17
+
Where it surfaces: the component-props gate (`os validate`, `os build`, `os lint`) no longer reports these three keys on an `object-grid` node, and the published JSON Schema for `ObjectGridProps` describes them.
Copy file name to clipboardExpand all lines: content/docs/references/ui/component.mdx
+11Lines changed: 11 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -693,6 +693,8 @@ Sort field and direction pair
693
693
|**objectName**|`string`| optional | Object this grid binds to. Optional because the component-level `dataSource` binding can supply the object instead |
694
694
|**label**|`string \| Record<string, string>`| optional | Grid label — used as the table caption and export file title |
695
695
|**title**|`string \| Record<string, string>`| optional | Fallback for `label` (the renderer reads `label \|\| title`) |
696
+
|**description**|`string \| Record<string, string>`| optional | One line of help text drawn above the grid's rows — a string, or an inline locale map resolved against the display locale |
697
+
|**emptyState**|`{ title?: string \| Record<string, string>; message?: string \| Record<string, string>; icon?: string }`| optional | What the grid draws instead of an empty table: `{ title, message, icon }` — the list view's own empty-state shape |
696
698
|**columns**|`any[]`| optional | Columns: field names or column definition objects |
697
699
|**fields**|`any[]`| optional | Field list fallback used when `columns` is absent |
698
700
|**filter**|`{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]`| optional | Base query filter — the ViewFilterRule array form `[{ field, operator, value }, ...]`, the one filter orthography every `filter` door in this map shares; lowered to the wire `$filter`. THE key, singular — not the plural misspelling. The MongoDB-style record form is refused — see migration `element-data-source-and-object-block-filter-rule-array`|
@@ -718,6 +720,7 @@ Sort field and direction pair
718
720
|**navigation**|`any`| optional | Row-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| new_window \| none }`) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema |
|**singleClickEdit**|`boolean`| optional | Enter cell edit on single click (default true when editable) |
723
+
|**keyboardNavigation**|`boolean`| optional |[EXPERIMENTAL — not enforced] Arrow-key cell navigation on the WAI-ARIA grid pattern. Defaults to on when `editable` is set; a read-only grid keeps its Tab behaviour unless this is `true`. No renderer reads it yet: it is declared ahead of the grid's keyboard-navigation build, so authoring it changes nothing today |
721
724
|**resizable**|`boolean`| optional | Allow column resize (read before `resizableColumns`) |
722
725
|**resizableColumns**|`boolean`| optional | Alternate spelling of `resizable` (the renderer reads `resizable ?? resizableColumns`) |
0 commit comments