Filed unassigned as an out-of-scope finding while implementing #5398 (the plugin-list half of the userActions collision, PR #5434). Not fixed there — different defect class, different packages.
The defect
Three of the toolbar toggles ListView honours are unauthorable through the spec, and the runtime fold produces them anyway.
packages/core/src/utils/normalize-list-view.ts folds legacy bare show* flags into the canonical userActions block through SHOW_FLAG_TO_USER_ACTION:
showSearch: 'search', showSort: 'sort', showFilters: 'filter',
showDensity: 'rowHeight',
showGroup: 'group', showHideFields: 'hideFields', showColor: 'rowColor',
The first four land on keys the spec declares. The last three do not exist in UserActionsConfigSchema, whose vocabulary is sort, search, filter, refresh, rowHeight, addRecordForm, editInline, buttons.
packages/plugin-list/src/ListView.tsx (toolbarFlags) reads all three regardless:
showGroup: ua?.group!==false,showHideFields: ua?.hideFields===true,showColor: ua?.rowColor===true,
Measured — @objectstack/spec@17.0.0, on this repo's main
Each fixture is otherwise legal and produces exactly one issue, so this is a KEY verdict, not a malformed document:
| probe | result |
|---|
ListViewSchema.safeParse(… userActions:{group:true}) | success: false, unrecognized_keys: ["group"], 1 issue |
ListViewSchema.safeParse(… userActions:{hideFields:true}) | success: false, unrecognized_keys: ["hideFields"], 1 issue |
ListViewSchema.safeParse(… userActions:{rowColor:true}) | success: false, unrecognized_keys: ["rowColor"], 1 issue |
ListViewSchema.safeParse(… userActions:{sort:true}) — control | success: true |
UserActionsConfigSchema.safeParse({group:true}) | success: false, unrecognized_keys: ["group"] (same for the other two) |
The control is what makes the three refusals mean "this key", not "this schema refuses everything".
Why it matters
The two halves point opposite ways, and each direction is its own problem:
- The fold emits a document its own save gate refuses. A stored legacy view carrying
showGroup: false comes out of normalizeListViewSchema as userActions: { group: false } — which ListViewSchema rejects by name. Anything that normalizes then validates (or normalizes then persists through a validating path) hits a refusal on a key the runtime itself just wrote. app-shell's ObjectView runs exactly this fold on both halves of every list view it renders. - Three real toolbar affordances have no authorable spelling. Grouping, column visibility and row coloring are honoured by
ListView and cannot be declared in a spec-valid view document at all — the only way to reach them is the legacy show* flags, which the fold exists to retire.
Both readings converge on a coordination question rather than a local patch: either the spec's UserActionsConfigSchema gains the three keys (the fold and the renderer are then declared = enforced), or the three toggles are retired from the fold and the renderer under ADR-0049 enforce-or-remove. That is a maintainer decision, not a dev one — hence no pm:queue and no assignee.
Reproduce
node --input-type=module -e "
import { ListViewSchema, UserActionsConfigSchema } from '@objectstack/spec/ui';
const uk = r => (r.error?.issues ?? []).filter(i => i.code === 'unrecognized_keys').flatMap(i => i.keys ?? []);
for (const k of ['group','hideFields','rowColor']) {
const r = ListViewSchema.safeParse({ name:'my_view', label:'My View', columns:[{field:'name'}], userActions:{ [k]: true } });
console.log(k, r.success, uk(r), (r.error?.issues??[]).length);
}
console.log('control sort', ListViewSchema.safeParse({ name:'my_view', label:'My View', columns:[{field:'name'}], userActions:{ sort:true } }).success);
"
Related: #5398 / #5434 (the userActions view/object name collision — a different defect on the same key), #5240 / #5426 (the plugin-grid half), #2231 (@object-ui/types zod vs @objectstack/spec/uiListViewSchema drift — an adjacent pair, not this one), #4337 (the legacy list-view vocabulary the fold accepts has no declared type).
Generated by Claude Code
Filed unassigned as an out-of-scope finding while implementing #5398 (the
plugin-listhalf of theuserActionscollision, PR #5434). Not fixed there — different defect class, different packages.The defect
Three of the toolbar toggles
ListViewhonours are unauthorable through the spec, and the runtime fold produces them anyway.packages/core/src/utils/normalize-list-view.tsfolds legacy bareshow*flags into the canonicaluserActionsblock throughSHOW_FLAG_TO_USER_ACTION:The first four land on keys the spec declares. The last three do not exist in
UserActionsConfigSchema, whose vocabulary issort,search,filter,refresh,rowHeight,addRecordForm,editInline,buttons.packages/plugin-list/src/ListView.tsx(toolbarFlags) reads all three regardless:Measured —
@objectstack/spec@17.0.0, on this repo'smainEach fixture is otherwise legal and produces exactly one issue, so this is a KEY verdict, not a malformed document:
ListViewSchema.safeParse(… userActions:{group:true})success: false,unrecognized_keys: ["group"], 1 issueListViewSchema.safeParse(… userActions:{hideFields:true})success: false,unrecognized_keys: ["hideFields"], 1 issueListViewSchema.safeParse(… userActions:{rowColor:true})success: false,unrecognized_keys: ["rowColor"], 1 issueListViewSchema.safeParse(… userActions:{sort:true})— controlsuccess: trueUserActionsConfigSchema.safeParse({group:true})success: false,unrecognized_keys: ["group"](same for the other two)The control is what makes the three refusals mean "this key", not "this schema refuses everything".
Why it matters
The two halves point opposite ways, and each direction is its own problem:
showGroup: falsecomes out ofnormalizeListViewSchemaasuserActions: { group: false }— whichListViewSchemarejects by name. Anything that normalizes then validates (or normalizes then persists through a validating path) hits a refusal on a key the runtime itself just wrote.app-shell'sObjectViewruns exactly this fold on both halves of every list view it renders.ListViewand cannot be declared in a spec-valid view document at all — the only way to reach them is the legacyshow*flags, which the fold exists to retire.Both readings converge on a coordination question rather than a local patch: either the spec's
UserActionsConfigSchemagains the three keys (the fold and the renderer are then declared = enforced), or the three toggles are retired from the fold and the renderer under ADR-0049 enforce-or-remove. That is a maintainer decision, not a dev one — hence nopm:queueand no assignee.Reproduce
Related: #5398 / #5434 (the
userActionsview/object name collision — a different defect on the same key), #5240 / #5426 (theplugin-gridhalf), #2231 (@object-ui/typeszod vs@objectstack/spec/uiListViewSchemadrift — an adjacent pair, not this one), #4337 (the legacylist-viewvocabulary the fold accepts has no declared type).Generated by Claude Code