Repository navigation
The legacy list-view vocabulary normalizeListViewSchema exists to accept has no declared type — every fixture exercising the fold must cast #4337
Description
Observation-class finding, surfaced while wiring @object-ui/plugin-list's tests into tsc for #4040 (tranche 3). Nothing a user meets today; it costs type safety in exactly the tests that cover the fold.
What
normalizeListViewSchema (packages/core/src/utils/normalize-list-view.ts) is the vocabulary bridge #2890 introduced: it folds objectui's historical list-view spellings onto @objectstack/spec's canonical ones — fields → columns, densityMode → rowHeight, filters → filter, the show* flags → userActions, aria: { label, describedBy } → { ariaLabel, ariaDescribedBy }, sharing: { visibility, enabled } → { type }, and the per-column identity keys.
Its signature is:
export function normalizeListViewSchema< T >(schema: T): TFully generic. There is no type for what it ACCEPTS. ListViewSchema describes the canonical surface — the fold's OUTPUT, which is all ListView reads, because ListView.tsx:581 is normalizeListViewSchema(propSchema) before anything else. The legacy INPUT vocabulary is documented only in that function's prose comment.
That is fine for production code, which passes stored metadata straight through. It bites the tests that exist to prove the fold still happens, because those must author the legacy spelling in a position typed as the canonical one:
src/__tests__/ListView.test.tsx(561,9): error TS2353: Object literal may only specify known
properties, and 'label' does not exist in type '{ ariaLabel?: ...; ariaDescribedBy?: ...;
role?: ...; live?: ... }'.
src/__tests__/ListView.test.tsx(579,9): error TS2353: Object literal may only specify known
properties, and 'enabled' does not exist in type '{ type?: "personal" | "collaborative";
lockedBy?: string }'.
Both compiler verdicts are correct, and neither fixture is wrong — they are pre-fold input, and the two "obvious" fixes are both worse than the cast:
- Re-spell the fixtures canonically — deletes the only coverage that the fold still runs. The
sharingcase in particular asserts the legacy four-value audience (team) collapses onto the spec's ownership type, which is the fold's whole content. - Widen
ListViewSchemato accept both — re-forks the vocabulary Migrate the remaining ListView legacy vocabulary to spec-canonical keys, and audit ObjectView/DetailView (#2231 phases 4–5) #2890 unified, and puts the legacy keys back on the canonical surface every consumer reads.
PR (this tranche) therefore casts through a named local helper (authored(...)) with the reasoning written next to it. Three test files now carry that helper or an equivalent.
Worth knowing: the filter key is a third case, and a sharper one
The normalizer's own contract note already records it:
filters→filter(#2890 scope A step 4). A key rename only: BOTH keys carry an ObjectQL FilterNode array ([['stage','=','won']]) everywhere in objectui — every consumer passes the value straight to$filter. The spec typesfilterasViewFilterRule[]({field, operator, value}objects), so objectui's field is typed from the spec but used as something else. That mismatch is real and out of scope here; converting formats inside a vocabulary fold would change what reaches the data source.
So ListViewSchema.filter is declared as one shape and carries another in production, not only in tests — the declaration and every real value disagree. ListView.emptyStateFilteredCopy.test.tsx authors the ObjectQL form because that is the form the renderer receives, and needs the same cast for it.
Why it is finding and not queued
The fold is permanent by design — the normalizer's own comment says stored view metadata in user databases still carries the legacy keys, so the renderer cannot stop accepting them. That means this is not absorbed by #2890 finishing; it outlives it.
Resolving it is one of two shapes, and picking between them is a contract call:
- Declare the legacy input surface next to the vocabulary it bridges — a
ListViewSchemaInput(or similar) in@object-ui/corebesidenormalizeListViewSchema, withnormalizeListViewSchema(schema: ListViewSchemaInput): ListViewSchemaas the signature. Fixtures then say what they are with no cast, and the fold's accepted vocabulary becomes checkable rather than prose. This is the shape [P2] objectui: every package type-checks its tests or declares why not — and guards must prove they can fail (measured: 355 errors across 19 packages) #4040 itself prescribes for the dialect problem ("declare the dialect next to the vocabulary it extends"). - Leave it undeclared and accept the casts as the marker of pre-fold input, on the grounds that the legacy vocabulary is frozen and should not gain a maintained type.
The filter mismatch above is a separate, larger question (it is a real production divergence, not a test-only one) and may want its own card whichever way this one goes.
Filed standalone rather than as a sub-issue of #2890 for the reason given above — #2890 completing does not close this.
Generated by Claude Code
Activity
os-zhuang commentedon Aug 11, 2026
Findings-triage route B: held —
findingstays.normalizeListViewSchema— is Migrate the remaining ListView legacy vocabulary to spec-canonical keys, and audit ObjectView/DetailView (#2231 phases 4–5) #2890's territory: that card (pm:queue, phases 4–5 of the vocabulary migration) is the thing that decides which legacy spellings remain accepted at all, and authoring the input type before that ruling would freeze today's accidental set. Restart condition: when Migrate the remaining ListView legacy vocabulary to spec-canonical keys, and audit ObjectView/DetailView (#2231 phases 4–5) #2890 is taken up, its dispatch should name this card — the accepts-type is a natural deliverable of that audit (recording that here and in the round brief so it has a reader).本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
test(plugin-list): type-check its tests and clear its TEST_DEBT entry (…
os-steve commentedon Aug 17, 2026
Concentrated finding round (unified triage seat, 2026-08-17): closed not planned. The card itself establishes that both "obvious" fixes are worse than the status quo (re-spelling fixtures deletes the fold's only coverage; widening
ListViewSchemare-forks the vocabulary #2890 unified), and the landed resolution — a namedauthored(...)cast helper with its reasoning written beside it — is the accepted answer. Declaring a legacy-input type would mean maintaining a second public vocabulary surface forever, for test ergonomics only.Reopen trigger: the legacy fold gains new spellings or the cast helper spreads past test files into production code — either changes the cost/benefit. Maintainer veto: reopen is free.
Generated by Claude Code