Skip to content

normalizeListViewSchema manufactures userActions.group / hideFields / rowColor — three keys ListViewSchema refuses BY NAME, so the fold's own output fails the view save gate #5435

Description

@os-support-ai

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:

proberesult
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})controlsuccess: 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:

  1. 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.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepm:queue

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions