Repository navigation
lint: field-bearing props on React page blocks are unchecked against the bound object — close the class, not the next instance (#4329 follow-up) #4340
Description
#4329 (closed by #4338) found <ListView searchableFields> unchecked on the react page surface and fixed that one prop by exporting the metadata rule's core (indexObjectSearchTargets + checkSearchableFieldList in validate-searchable-fields.ts) and running it from validate-react-page-props.ts — the gate that owns React block dataProps. But searchableFields was an instance, not the class. This issue is the central worklist for the class: every field-bearing prop in REACT_BLOCKS (packages/spec/src/ui/react-blocks.ts) that a react page can author and nothing resolves.
Today validate-react-page-props.ts checks: required bindings, prop typos, <ObjectChart> bindings (#3701/#3729), and <ListView searchableFields> (#4329). Everything else a block binds by field name ships exactly as typed — the same silent-skip-or-mismatch drift validate-page-field-bindings.ts already closes for the page-component properties bag, one surface over.
Inventory
| Block · prop | Names fields on | Metadata twin (already linted there) | Live corpus instance |
|---|---|---|---|
<ListView fields> (interaction, string[]) |
bound object | interfaceConfig.columns → page-field-unknown |
crm-workbench.page.ts:74, task-desk.page.ts:45 |
<ListView columns> (dataProp; string[] | {field}[]) |
bound object | interfaceConfig.columns |
renewals-pipeline.page.ts:96 |
<ListView sort / grouping / userFilters.fields> (+ legacy filterableFields) |
bound object | interfaceConfig.sort / .userFilters.fields |
— |
<ListView filters> (FilterArray — field positions) |
bound object | flow filter-position tokens gate (validate-flow-template-paths) |
renewals-pipeline.page.ts:95 (['status','=',stage]) |
<ObjectForm fields>, initialValues keys |
bound object | element:form → form-field-unknown family |
— |
<RecordHighlights fields> |
objectName |
record:highlights in COMPONENT_FIELD_SPECS |
renewals-pipeline.page.ts:115 |
<RecordDetails fields / hideFields / sections[].fields> |
objectName |
record:details |
— |
<RecordPath statusField> |
objectName |
record:path |
— |
<RecordRelatedList columns / sort / relationshipField> |
record:related_list branch of validate-page-field-bindings |
renewals-pipeline.page.ts:125 |
|
<Block type="…"> (whole props bag, by registered type) |
per type | COMPONENT_FIELD_SPECS table |
none today |
All live instances above resolve — like #4329, this is a gap to close before it bites, not a live bug.
⚠️ A contract conflict blocks one row
RecordRelatedListProps.objectName in the spec is the related object (component.zod.ts:86: "Related object name"), but the react overlay in react-blocks.ts declares objectName as "The parent object" — and the live page authors it the overlay's way: objectName="showcase_account" with columns={['name','status','total']} naming invoice fields. A naive resolve-columns-against-objectName would false-positive on total. Settle which object the react block's objectName names (and how the related object is bound on the react surface) before linting this block — that ambiguity is arguably an authoring-contract bug in its own right, since the generated contract (build-react-blocks-contract.ts) currently publishes both meanings under one prop name.
Method (established by #4338 — follow it, don't re-derive)
- Share the core with the metadata twin wherever one exists, so the two surfaces agree on what counts as a field by construction — the way
checkSearchableFieldListis now shared. A descriptor table keyed by tag (mirroringCOMPONENT_FIELD_SPECS) keeps the sweep one-table-per-block. - Static literals only: a value from a variable, call, or behind a spread skips silently (ADR-0072 D1 — unresolvable is not wrong). Note the
filtersnuance:['status','=',stage]has a static field position beside a non-static value, andstaticValuecurrently bails on the whole array — per-position resolution is needed to check the common case. - Severity per the established calls: render-degrading props (columns/fields/sort) skip unknowns gracefully →
warning, matchingpage-field-unknown.filtersfield-position misses widen or refuse the query → candidate forerror, matching thevalidate-flow-template-pathsfilter-position call and the REST 读路径:searchFields/groupBy/aggregations指向不存在的字段时被静默降级(#4226 收口后剩下的三条轴) #4254 request-time behavior. - Unify the system-fields set while sweeping:
validate-react-page-props.tsandvalidate-page-field-bindings.tsstill hand-copySYSTEM_FIELDS, whilevalidate-searchable-fields.tsderives it from the spec'sFIELD_GROUP_SYSTEM_FIELDS+SystemFieldName(排查「手抄 spec 清单 + "keep in sync" 注释」模式:一天内确认三例,全部曾静默漂移 #3786's lesson). The copies should migrate to the derived set.
Suggested order
- Settle the
RecordRelatedListobjectNamecontract conflict (its own fix; unblocks that row). - Descriptor-table pass for the unambiguous props (
ListView fields/columns/sort/grouping/userFilters,ObjectForm fields,RecordHighlights/Details/Path) atwarning. filtersper-position static resolution + severity decision.<Block type="…">escape hatch, reusingCOMPONENT_FIELD_SPECSby registered type.SYSTEM_FIELDSderivation cleanup.
Acceptance: every field-bearing prop in REACT_BLOCKS is either resolved against its object or explicitly ledgered as skipped-with-reason; the example corpus stays green.
Activity
feat(lint): <ListView searchableFields> on a react page resolve…
feat(lint): every field-bearing prop on a React page block resolves a…
fix(lint,cli): os lint / os compile 不再放行一个 os validate 会拒绝的 react 页面 (#…
fix(cli): every author-time rule that can gate runs on all three comm…