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 b0bf413
Browse filesBrowse the repository at this point in the historyBrowse files
fix(plugin-grid): group-header labels read the object field's options only; the column options read is retired (objectui#11544) (#11563)
Fixes#11544
Clause-②: no
Retire, not widen (triage ruling `5966937083`): a grouped `object-grid`
labels its group headers from the object field's `options` only.
`ListColumnSchema` declares no `options` and is strict, so the
column-level read honoured a key every publish refuses. Session
`session_01FjqrwXPfSMkSfkKYDSRkN2`, branch
`claude/issue-11544-group-header-field-options`.
## What changed
- `packages/plugin-grid/src/ObjectGrid.tsx`, `groupValueFormatter`: the
label source `colOverride?.options || objectDefField?.options` is now
`objectDefField?.options`. The column lookup stays, because it also
supplies `type`, a declared `ListColumn` member (a `boolean` column
respells `"true"` / `"false"` as Yes / No). The lookup is now typed as
`ListColumn`, with the same `c is ListColumn` guard
`maskedGroupingSignature` already uses, instead of `as any[]`. As a
result, an undeclared column-member read at this site no longer compiles
(proved below).
-
`packages/plugin-grid/src/__tests__/gridGroupingMembers-8071.test.tsx`:
the two cases that pinned `columns[].options` as behaviour are turned to
pin the field's labels:
- "the object field's `options` decide the header label, and a column
`options` beside them is not read". It has three legs. The field's
labels reach the header. A column carrying `options` beside the field's
still shows the FIELD's labels. The same column with no field options
shows the stored values.
- "`order` sorts the RENDERED labels, so the field's options move the
groups". This is `order: 'asc'` over the field's labels, with a
no-field-labels control.
- The header note ("pinned below AS BEHAVIOUR and reported as a
finding") is rewritten to say the read is retired. `renderGrouped` now
takes the host `objectFields` runtime prop, which is how field options
reach an inline-rows grid without a schema read.
- `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`: the
`object-grid.grouping` `pins` descriptor follows the fixture; it had
said the read was pinned as behaviour and not repaired. This is the move
PR #11540 made to its own descriptor. It is text only: nothing asserts
on `pins` content.
- `.changeset/11544-group-header-field-options.md`:
`@object-ui/plugin-grid` patch.
## Premise, measured on `origin/main` `d0c0c7fe9` (after PR #11554)
- `@objectstack/spec` 17.6.0 `ListColumnSchema` has fourteen keys
(field, label, width, align, hidden, sortable, resizable, wrap, type,
pinned, summary, prefix, link, action). `safeParse({ field, options })`
answers `unrecognized_keys` naming `options`, and the control `{ field,
type: 'select' }` parses.
- objectui `ObjectGridSchema.safeParse` with a column `options` answers
`invalid_union` at `columns`, and the same grid with a column `type`
parses.
- One read site. A grep for `.options` in `ObjectGrid.tsx` finds only
the `groupValueFormatter` line reading it off a column; every other hit
reads a field. A repo grep for the column-override lookup finds only
this memo. Other `col.options` hits (`GridField`, `RecordPickerDialog`,
the flow inspector) read different column schemas, not `ListColumn`, and
none of them labels a group.
- In-repo producers of column `options`: none. The `options` spreads in
app-shell build detail-form fields and import targets, not grid columns.
- PM hypothesis 1 ("the now-unused column lookup is removed if nothing
else in the memo needs it") is half-falsified: `type` needs the lookup,
so it stays, typed.
## Tests (HEAD `92bddae`)
- Fixture plus parity ledger: `pnpm exec vitest run --maxWorkers=2
packages/plugin-grid/src/__tests__/gridGroupingMembers-8071.test.tsx
apps/console/src/__tests__/registry-inputs-spec-parity.test.ts` gives
`Test Files 2 passed (2)` and `Tests 265 passed (265)`.
- Whole package: `pnpm exec vitest run --maxWorkers=2
packages/plugin-grid/` gives `Test Files 182 passed (182)` and `Tests
1691 passed (1691)`.
- `pnpm --filter @object-ui/plugin-grid type-check` (`tsc --noEmit &&
tsc -p tsconfig.test.json`) exits 0. `--listFiles` on
`tsconfig.test.json` lists the fixture once and `ObjectGrid.tsx` once.
- Dependency closure: `pnpm --workspace-concurrency=2 --filter
'@object-ui/plugin-grid^...' build` exits 0.
## Ablation (one-shot, run from the committed fix through objectstack's
`scripts/ablation-replace.mjs` in wrap mode)
- **Behaviour.** Anchor `const rawOptions = objectDefField?.options;`,
replaced with `const rawOptions = (colOverride as any)?.options ||
objectDefField?.options;` (anchor count 1 to 0, blob `004444984f55` to
`316153fc4622`). Predicted: the label case goes red and everything else
stays green. Observed: `Tests 1 failed | 9 passed (10)`, `expected [
'Column Lost', 'Column Won' ] to deeply equal [ 'Closed Lost', 'Closed
Won' ]`. Restore: blob equals HEAD, and `git diff HEAD` is empty.
- **Type.** The same anchor, replaced with `const rawOptions =
colOverride?.options || objectDefField?.options;`. `tsc --noEmit` gives
`error TS2339: Property 'options' does not exist on type` (the
`ListColumn` object shape). Restore: blob equals HEAD.
- The `order` case stays green under the behaviour ablation by design.
It carries no column `options`; it pins that `order` sorts the field's
labels, which both trees do.
## Gates (HEAD `92bddae`, each exit 0)
- `pnpm check:control-bytes`: `check-control-bytes: OK`.
- `pnpm check:new-line-citations`: `VERDICT
new-cross-file-line-citations: 0 new citation(s)`.
- `node scripts/check-changeset-presence.mjs`: `3 source file(s) of 2
released package(s) changed, and this change declares 1 changeset(s)`.
- `node scripts/check-changeset-no-major.mjs`,
`check-changeset-fixed.mjs` and `check-changeset-overwrite.mjs`: all
report OK.
- `pnpm check:changeset-claims` is report-only. It names six pending
changesets that mention `ObjectGrid.tsx` or the parity ledger. I re-read
each paragraph, and none makes a claim about group-header labels or
column `options`.
- `pnpm check:pending-changeset-literals`: `No test source names a
pending changeset`.
- `pnpm check:test-path-roots`, `node
scripts/check-vi-mock-specifiers.mjs`, `node
scripts/check-vi-mock-inherit.mjs`, `pnpm check:spec-symbols` and `node
scripts/check-type-check-coverage.mjs`: all report OK.
## Narrowings (declared)
- **Lint (CI-owned).** A narrowed run over the three changed source
files. The population is from eslint's own config: `eslint
--print-config` resolves all three, so none is ignored. `--format json`
counts 3 files. `eslint.config.js` sets no `parserOptions.project` /
`projectService`, so type-aware linting is off, and this diff cannot
move an untouched file's verdict. In package form (inline config
honoured, as `pnpm lint` runs it): 0 errors. In root
`--no-inline-config` form: 1 error, `react-hooks/static-components`, at
the annotated `AuthoredEmptyIcon` site. It is identical at BASE (BASE 1
error / 210 warnings, head 1 / 209).
- Build-reading gates (`check:published-dist`, `check:node-esm-load`,
`check:phantom-deps` and the rest) and the full sharded `pnpm test`
belong to CI. This diff adds no import, export or dependency.
## Acceptance notes
- The parity ledger's slice-17 history paragraph, which says the
group-label memo reads `columns[].options`, is left as written. It
records what that slice did, the way a changelog entry does. Only the
live `pins` descriptor changed.
- No doc page or README described the column-options read (grep of
`content/docs` and `packages/plugin-grid/README.md`), so there is no doc
edit.
- The parity ledger is a shared file. This PR edits one string in its
`object-grid.grouping` entry.
- objectstack#21464 types `columns` by reference once its
`.objectui-sha` covers this landing. That stays on objectstack#21464,
per triage.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2)_
Co-authored-by: Claude <noreply@anthropic.com>
pins: 'The one member `GroupingConfigSchema` declares (`fields`) and the three its strict entries declare (`field`, `order`, `collapsed`), at the group headers the grid paints AND at the projection it asks the server for. `order` sorts the headers by their RENDERED LABEL rather than by the stored value, `collapsed` is a DEFAULT that the first click inverts rather than a state, `fields` is ORDERED so the second entry nests inside the first and each level sorts on its own entry, and an unusable entry (a `null` hole, the bare-string shorthand the strict schema refuses, a blank name) is DROPPED — never coerced, never fatal — with the usable one still grouping. The query half is what keeps the screen honest: a grouped field the columns never mention is UNIONED into `$select`, because without it the server never returns it and ONE `(empty)` group holds every record (objectui#7179); the entries it groups by and the entries it projects are asserted as the same set. ⭐ THE SHARED MEMO with `columns` is pinned here, and it is what makes these two keys non-disjoint: `groupValueFormatter` derives the header labels from `schema.grouping` AND `schema.columns`, so `columns[].type: \'boolean\'` respells a grouped `"true"`/`"false"` as Yes/No, and because `order` sorts the rendered labels a column override MOVES the groups. ⚠️ That memo also reads `columns[].options`, which `ListColumnSchema` does not declare and, being strict, REFUSES at publish — the `declared != enforced` split, same family objectui#6458 retired from the cell branch, outside the region `columnReadBoundary-6458` bounds. Pinned as behaviour with its own absent-member control and handed back as a finding, ⛔ not repaired here. New file (objectui#8071 slice 17).',
2977
+
pins: 'The one member `GroupingConfigSchema` declares (`fields`) and the three its strict entries declare (`field`, `order`, `collapsed`), at the group headers the grid paints AND at the projection it asks the server for. `order` sorts the headers by their RENDERED LABEL rather than by the stored value, `collapsed` is a DEFAULT that the first click inverts rather than a state, `fields` is ORDERED so the second entry nests inside the first and each level sorts on its own entry, and an unusable entry (a `null` hole, the bare-string shorthand the strict schema refuses, a blank name) is DROPPED — never coerced, never fatal — with the usable one still grouping. The query half is what keeps the screen honest: a grouped field the columns never mention is UNIONED into `$select`, because without it the server never returns it and ONE `(empty)` group holds every record (objectui#7179); the entries it groups by and the entries it projects are asserted as the same set. ⭐ THE SHARED MEMO with `columns` is pinned here, and it is what makes these two keys non-disjoint: `groupValueFormatter` derives the header labels from `schema.grouping` AND `schema.columns`, so `columns[].type: \'boolean\'` respells a grouped `"true"`/`"false"` as Yes/No. A select value\'s header LABEL comes from the object field\'s `options` only, and because `order` sorts the rendered labels the field\'s options MOVE the groups. The memo\'s former read of `columns[].options`, which `ListColumnSchema` does not declare and, being strict, REFUSES at publish, is retired (objectui#11544): a column carrying `options` beside the field\'s is pinned as not read, with a no-field-options control. New file (objectui#8071 slice 17).',
0 commit comments