Repository navigation
Commit f750119
fix(spec): type object-grid and object-kanban grouping by GroupingConfigSchema (#20856)
Fixes #20831
Clause-②: yes
## What this changes
`ComponentPropsMap['object-grid'].grouping` and
`ComponentPropsMap['object-kanban'].grouping` were
`z.unknown().optional()`. Both now take the list view's own
`GroupingConfigSchema` **by reference**, imported from `./view.zod`
beside the six schemas `component.zod.ts` already takes from there.
There is one schema, not a copy of its shape, and each row keeps its
describe. A padded grouping field name, a number, a bare string, an
empty `fields` list or an undeclared key inside the config is now
refused on these two doors exactly as `list-view` refuses it.
The prop is typed, not retired. Both renderers are live readers of this
exact shape. Readings are at objectui `db11afd49`, the pin in
`.objectui-sha`:
- **Grid**: `ObjectGridSchema.grouping` is typed as the spec's
`GroupingConfig` (`packages/types/src/objectql.ts:1123`).
`ObjectGrid.tsx` reads `schema.grouping?.fields` (`:2050`, `:2839`), and
`useGroupedData.ts` reads `.field`, `.order` and `.collapsed` per entry
(`:281`, `:443-547`).
- **Kanban**: `effectiveSwimlaneField = schema.swimlaneField ||
schema.grouping?.fields?.[0]?.field` (`ObjectKanban.tsx:1212`).
- A read-point grep of every `grouping.` member across objectui
`packages/*/src` (tests excluded) at the pin finds `fields` and nothing
else.
## Files (7, +338 / -7 against `origin/main` `688ddef3c`)
| File | Why |
| --- | --- |
| `packages/spec/src/ui/component.zod.ts` | the import, the two members,
and a comment on each |
| `packages/spec/src/ui/component-object-block-grouping.pin.test.ts` |
the pins (below) |
|
`packages/spec/src/migrations/entries/semantic/18.ui-object-block-grouping-config-typed.ts`
+ regenerated `registry.ts` | the ADR-0087 disposition (below) |
| `content/docs/references/ui/component.mdx` | regenerated by
`gen:docs`: `grouping` now shows its shape on both rows |
| `packages/spec/dropped-refinements.baseline.json` | **outside the
claim's file surface; see below** |
| `.changeset/20831-object-block-grouping-typed.md` | `minor`, BREAKING
banner, migration table, `Clause-②: yes (narrowing)` and the registered
ADR-0087 marker |
**File-surface breach, declared.** The spec build refuses the schema
change until `dropped-refinements.baseline.json` records two new sites,
`grouping.fields.element.field` on `ui/ObjectGridProps` and on
`ui/ObjectKanbanProps`, and moves `droppedRefinementSites` 610 to 612.
The build prints the corrected entry itself. The cause is the non-padded
field-name `superRefine` that `GroupingFieldSchema` carries. JSON Schema
cannot express it, and the ledger already records it at every list-view
grouping site (`ui/GroupingConfig`, `ui/GroupingField`, `ui/ListView`,
the manifest views). The ledger is hand-edited by design, so a new site
is a reviewed line. The alternatives are both out: deleting the
refinement is forbidden by the ledger's own rule, and teaching the
projection list a new named pattern is a public-contract decision of its
own. The ledger's `refinementSitesThatDidProject` (369) is left alone.
This build prints 421, but none of the grouping refinements is one of
the five projected patterns, so this diff adds zero to it.
## Pins
The pins are in `component-object-block-grouping.pin.test.ts`: 22 cases,
each asserting issue `code` and `path`, on both rows.
- **Refused:**
- padded name: `custom` at `grouping.fields.0.field`, and the message
names the received value and the unpadded spelling;
- `grouping: 42`: `invalid_type` at `grouping`. There is no `fields[0]`
for a number to fail at, so the triage's `grouping.fields[0].field` path
applies to the padded case only;
- bare string: `invalid_type` at `grouping`;
- `{ fields: [] }`: `too_small` at `grouping.fields`;
- an undeclared key: `unrecognized_keys` at `grouping`.
- **Controls:**
- a fully spelled grouping parses byte-identical (`toStrictEqual` on the
input);
- the short form `{ field }` parses clean and gains the list view's
defaults (`order: 'asc'`, `collapsed: false`) on the parse output. The
component-props gate discards that output and the envelope audit walks
raw keys only, so nothing downstream sees them;
- an absent `grouping` stays absent.
- **One schema:** the member unwraps to `GroupingConfigSchema._zod.def`
by identity, and answers what `GroupingConfigSchema` answers on the
whole corpus, one path level down.
**Ablation**, run at `69a1be23f` (the `component.zod.ts` blob `39065a62`
is unchanged at HEAD): both members were reverted to `z.unknown()`
through `scripts/ablation-replace.mjs` (anchor 2 to 0, `--expect 2`,
trap restore). Result: **18 failed / 4 passed**. The 4 that stay green
are the byte-identical and absent controls, which `z.unknown()` also
passes. The restore was proven by blob equals HEAD and an empty `git
diff HEAD`. No `dist/` was involved: the test imports `./component.zod`
from source.
## ADR-0087 disposition: `registered
ui-object-block-grouping-config-typed`
A new semantic entry for protocol 18 carries the rewrite table:
- padded name: write it unpadded;
- bare string: wrap it as `{ fields: [{ field }] }`;
- empty list, number or other value: delete the key;
- on `object-kanban`, `swimlaneField` still wins over `grouping`.
It is deliberately not a D2 conversion: a padded name is refused with
guidance, never trimmed. The entry states what happens at rest, and it
was measured:
- `PageComponentSchema` does not parse `properties` against
`ComponentPropsMap`.
- The component-props rule is `tier: 'advisory'`, `surfaces: CLI_ONLY`
(`packages/lint/src/authoring-rules.ts:1022`).
- So a stored page keeps loading and rendering. The refusal reaches
authors as `component-props-invalid` on `os validate` / `os build` / `os
lint`.
- Measured through `validateComponentProps` on the rebuilt spec: a
padded kanban grouping reports at `…properties.grouping.fields.0.field`,
and `42` on a grid at `…properties.grouping`. The valid control reports
nothing.
`node scripts/check-adr-0087-registration.mjs --base origin/main`
answers exit 0, reporting `[BREAKING+clause-②-narrowing] registered
ui-object-block-grouping-config-typed (new here …)`.
## Dispatch measurements
1. Both members read `z.unknown()` at base `1741c5dcb` (`:3596`,
`:3993`).
2. No import cycle: `view.zod.ts` imports nothing from
`component.zod.ts`, and `component.zod.ts` already takes six lazy
schemas from it. The identity pin is green under the lazy proxy.
3. The grid reader consumes the `GroupingConfigSchema` shape (readings
above), so both rows are typed. There is no `needs_decision` fork.
4. The ADR-0087 marker is `registered`, judged by the gate (above).
5. Consumer radius, all measured at the merge base `688ddef3c`:
- `examples/**` has 2 `object-grid` nodes (`app-showcase`
`command-center.page.ts:172`, `my-work.page.ts:62`). Neither authors
`grouping`, and there are 0 `object-kanban` nodes.
- No fixture under `packages/**` authors `grouping` on either row.
- The only non-spec package that parses these rows is
`@objectstack/lint` (`validate-component-props.ts`,
`page-envelope-audit.ts`), downstream of spec (the
`...@objectstack/spec` direction). Its suite is green (below).
- Full-repo pin sweep: no test anywhere asserts the old `z.unknown()`
acceptance on these rows, and the padded-name refusal text is unchanged,
so no pin flips.
## Verification, at HEAD `47c76064d` (after merging `origin/main`
through `os-regen-merge.sh`; no conflicts)
- `pnpm --filter @objectstack/spec build`: exit 0; 612 dropped sites
across 212 schemas, all declared.
- `pnpm --filter @objectstack/spec check:generated --fix`: "All 15
generated artifacts are up to date", measured against a dist built by
that run. authorable-surface, authorable-defaults and api-surface are
unchanged.
- `pnpm --filter @objectstack/spec typecheck`: exit 0. The test layer
compiles with debt unchanged (53 files / 251 errors / 138 signatures).
- Spec suite (`vitest run --project local --maxWorkers=2`): **579 files,
17088 passed**, 1 todo.
- Consumer `@objectstack/lint` suite: **117 files, 5436 passed**,
against a spec dist rebuilt from this tree.
- `node scripts/pm/dispatch-gates.mjs --commands` (no paths): 112
commands. `--ran` reconciles **112 derived, 110 run (all exit 0), 2 NOT
MEASURED, 0 UNRUN**.
- Five gates first exited 3 on missing builds:
`check:doc-formula-expressions`, `check:doc-security-posture`,
`check:skill-examples`, `check:docs-transcript-drift`,
`check:lean-entry-closure`. Each was rerun green after building lint,
client, client-react and objectql.
- `check-changeset-no-major` with this body as the event: LEVEL AXIS
reads `Clause-②: yes`, and no package is graded `patch`.
- **Lint narrowing** (the repo-wide `pnpm lint` is CI's):
- eslint `--no-inline-config --format json` over the 4 changed TS files
reports 4 files, 0 errors, 0 warnings;
- `eslint.config.mjs` puts every `packages/**/*.ts` in the population,
and the other 3 changed files are md, mdx and json, outside its globs;
- the config never enables type-aware linting (no
`parserOptions.project`), so this diff cannot move a verdict on an
untouched file.
**NOT MEASURED locally:**
- `check:dual-build-cjs-loads` and `check:type-check-debt`, reason: both
need every workspace package built (84 have no `dist/`).
- The CLI `integration` tier, reason: declared for CI; this diff touches
no CLI file. Its `migrate-meta-engine-guidance.test.ts` prints every
semantic entry. I checked the new entry against its tracker-id regex (no
hit), but not its verbatim print.
- The four CI type-check lanes and the six path-scheduled CI jobs,
reason: CI's own runs.
## Acceptance notes
These were noted, not filed. Carrier: none.
- The protocol-18 entry
`ui-object-grid-page-size-positive-integer-refused` says a stored page
"is refused on its next authoring-path save". Its sibling
`object-grid-default-filters-rule-array`, and the rule's `surfaces:
CLI_ONLY`, say the refusal is an advisory CLI finding only. This is a
read-only reading: I did not exercise a save.
- The `component.zod.ts` section header still lists "filter shapes …
grouping configs" as the `z.unknown()` value posture. Both have since
been typed. This is historical text, left as it stands.
- `dropped-refinements.baseline.json`
`measured.refinementSitesThatDidProject` reads 369 while the build
prints 421. The gap predates this diff and is ungated.
- For the objectui mirror card (the seat files it): the `object-kanban`
registration description (`plugin-kanban/src/index.tsx:684`) and
`structuredKeysAreDeclaredAndHonoured-8313.test.ts` both describe the
spec row as `z.unknown()`. Its row-4 fixture `{ fields: [{ field:
'owner' }] }` stays valid under the typed row.
Concurrent work: #20785 edits `component.zod.ts` only at
`PageHeaderProps` and one `component.mdx` line in that section, disjoint
from these regions; this diff touches neither
`authorable-surface/ui.json` nor `authorable-defaults/ui.json`. #20694
is not addressed here; it runs after this lands.
---
_Generated by [Claude
Code](https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 76bd58f commit f750119
7 files changed
Lines changed: 338 additions & 7 deletions
File tree
- .changeset
- content/docs/references/ui
- packages/spec
- src
- migrations
- entries/semantic
- ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
705 | 705 | | |
706 | 706 | | |
707 | 707 | | |
708 | | - | |
| 708 | + | |
709 | 709 | | |
710 | 710 | | |
711 | 711 | | |
| |||
757 | 757 | | |
758 | 758 | | |
759 | 759 | | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
760 | 766 | | |
761 | 767 | | |
762 | 768 | | |
| |||
806 | 812 | | |
807 | 813 | | |
808 | 814 | | |
809 | | - | |
| 815 | + | |
810 | 816 | | |
811 | 817 | | |
812 | 818 | | |
| |||
822 | 828 | | |
823 | 829 | | |
824 | 830 | | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
825 | 837 | | |
826 | 838 | | |
827 | 839 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
1326 | 1326 | | |
1327 | 1327 | | |
1328 | 1328 | | |
1329 | | - | |
| 1329 | + | |
| 1330 | + | |
1330 | 1331 | | |
1331 | 1332 | | |
1332 | 1333 | | |
1333 | 1334 | | |
1334 | | - | |
| 1335 | + | |
| 1336 | + | |
1335 | 1337 | | |
1336 | 1338 | | |
1337 | 1339 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17882 | 17882 | | |
17883 | 17883 | | |
17884 | 17884 | | |
| 17885 | + | |
| 17886 | + | |
| 17887 | + | |
| 17888 | + | |
| 17889 | + | |
| 17890 | + | |
| 17891 | + | |
| 17892 | + | |
| 17893 | + | |
| 17894 | + | |
| 17895 | + | |
| 17896 | + | |
| 17897 | + | |
| 17898 | + | |
| 17899 | + | |
| 17900 | + | |
| 17901 | + | |
| 17902 | + | |
| 17903 | + | |
| 17904 | + | |
| 17905 | + | |
| 17906 | + | |
| 17907 | + | |
| 17908 | + | |
| 17909 | + | |
| 17910 | + | |
| 17911 | + | |
| 17912 | + | |
| 17913 | + | |
| 17914 | + | |
| 17915 | + | |
| 17916 | + | |
| 17917 | + | |
| 17918 | + | |
| 17919 | + | |
| 17920 | + | |
| 17921 | + | |
| 17922 | + | |
| 17923 | + | |
| 17924 | + | |
| 17925 | + | |
| 17926 | + | |
| 17927 | + | |
| 17928 | + | |
| 17929 | + | |
| 17930 | + | |
| 17931 | + | |
| 17932 | + | |
| 17933 | + | |
| 17934 | + | |
| 17935 | + | |
| 17936 | + | |
| 17937 | + | |
| 17938 | + | |
| 17939 | + | |
17885 | 17940 | | |
17886 | 17941 | | |
17887 | 17942 | | |
| |||
0 commit comments