|
| 1 | +--- |
| 2 | +"@objectstack/lint": minor |
| 3 | +"@objectstack/spec": minor |
| 4 | +--- |
| 5 | + |
| 6 | +feat(lint): every field-bearing prop on a React page block resolves against the |
| 7 | +object it names |
| 8 | + |
| 9 | +#4329 closed ONE of them — `<ListView searchableFields>` — by running the |
| 10 | +metadata rule's core from the gate that owns React block props. That prop was an |
| 11 | +instance, not the class: every other prop a `kind:'react'` page binds BY FIELD |
| 12 | +NAME shipped exactly as typed, the same silent drift `page-field-unknown` |
| 13 | +already closes for the page-component `properties` bag one surface over. |
| 14 | + |
| 15 | +`validate-react-page-props` now resolves all of them: |
| 16 | + |
| 17 | +- `<ListView>` `fields` / `columns` / `sort` / `grouping` / `userFilters` / |
| 18 | + `hiddenFields` / `fieldOrder` / `filterableFields` |
| 19 | +- `<ObjectForm>` `fields`, `initialValues` KEYS, `sections[].fields[]` |
| 20 | +- `<RecordHighlights>` / `<RecordDetails>` / `<RecordPath>` / |
| 21 | + `<RecordRelatedList>` — via the SAME `COMPONENT_FIELD_SPECS` table the |
| 22 | + metadata surface uses, keyed by the block's `schemaType`, so the two surfaces |
| 23 | + agree by construction rather than by two lists that happen to match |
| 24 | +- `<Block type="…">` — the escape hatch reaches the same table by the type the |
| 25 | + author writes, so it is checked instead of being a hole |
| 26 | + |
| 27 | +Findings carry the metadata rule's id (`page-field-unknown`) at its advisory |
| 28 | +severity, because the consumer behaves the same way: an unknown name is skipped |
| 29 | +and the rest renders. |
| 30 | + |
| 31 | +**A FILTER position gates instead.** `<ListView filters>` / `<ObjectChart |
| 32 | +filter>` name fields in a QUERY, and an unknown column there is not a skipped |
| 33 | +column: the predicate can never match, `SqlDriver` swallows the driver's |
| 34 | +"no such column" and returns `[]`, and the surface renders an empty list that |
| 35 | +looks exactly like "there is no data" — the silent zero `filter-token-unknown` |
| 36 | +and `validate-flow-template-paths`' filter-position call both gate on. Those |
| 37 | +are reported as `error`. |
| 38 | + |
| 39 | +Filter positions are also resolved INDEPENDENTLY of each other, unlike every |
| 40 | +other value this gate reads. `filters={['status', '=', stage]}` — a static field |
| 41 | +beside a React-state value — is the shape a react page actually writes, and the |
| 42 | +all-or-nothing static reader skipped the whole array, including the one position |
| 43 | +that was knowable. |
| 44 | + |
| 45 | +Everything else is unchanged: a value from a variable, a call, or behind a |
| 46 | +spread is unresolvable rather than wrong and is skipped silently (ADR-0072 D1), |
| 47 | +as are cross-package objects, objects with no authored field map, dotted |
| 48 | +relationship paths, and registry-injected system columns. |
| 49 | + |
| 50 | +### Breaking: `<RecordRelatedList objectName>` is the RELATED object, as the spec always said |
| 51 | + |
| 52 | +`RecordRelatedListProps.objectName` is the related (child) object — that is what |
| 53 | +`record:related_list` means on every metadata surface, what |
| 54 | +`validate-page-field-bindings` resolves its `columns` against, and what the one |
| 55 | +registry component behind both surfaces consumes. The React overlay declared |
| 56 | +`objectName` a SECOND time and glossed it "The parent object", and the generated |
| 57 | +contract publishes the overlay's description in place of the schema's — so the |
| 58 | +react surface both contradicted the spec and lost any way to name the object it |
| 59 | +renders. |
| 60 | + |
| 61 | +FROM → TO for a page authored against the old gloss: |
| 62 | + |
| 63 | +```diff |
| 64 | +- <RecordRelatedList objectName="account" recordId={id} relationshipField="account_id" columns={['name','total']} /> |
| 65 | ++ <RecordRelatedList objectName="invoice" recordId={id} relationshipField="account_id" columns={['name','total']} /> |
| 66 | +``` |
| 67 | + |
| 68 | +`objectName` names the CHILD object being listed; the parent record stays bound |
| 69 | +by `recordId`, and `relationshipField` is the child's field pointing back at it. |
| 70 | +The lint above reports the old spelling (the child's columns and its FK do not |
| 71 | +resolve against the parent). `objectName` is now also published as required, as |
| 72 | +the schema declares it. |
| 73 | + |
| 74 | +The class is closed as well as the instance: `REACT_OVERLAY_SHADOWS` in |
| 75 | +`@objectstack/spec/ui` ledgers every overlay prop that restates a spec-schema |
| 76 | +prop, and a test asserts the ledger equals the real collision set — so the next |
| 77 | +overlay entry that silently redefines a schema prop fails a test instead of |
| 78 | +shipping a second dialect. |
0 commit comments