You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(list,grid,detail,tree,core): every column resolver reads one key (#3104 PR2) (#3122)
PR1 (#3119) put a canonicalizing fold at ListView's ingestion boundary. This
converges the 22 read sites themselves onto `columnIdentity()`, so a surface
that is NOT downstream of that fold resolves the same identity anyway.
That distinction is the user-visible part. A standalone `object-grid` node —
authored directly on a page, with no `list-view` above it — never passed
through `normalizeListViewSchema`. Its `getSelectFields` read `c.field` alone
while the `ensureId` probe one line above read `f?.name || f?.field`, so a
legacy `{name:'account'}` column reached `$select` as a literal `undefined`
hole: the server never returned the field and every cell in that column came
back empty. Same for ObjectTree, RelatedList and the record:details /
record:related_list renderers.
Converged:
ListView ×9 + its 2 request builders -> columnIdentity()
RelatedList ×8 -> accessorKey || columnIdentity()
ObjectGrid (probe + projection) -> columnIdentity()
ObjectTree -> columnIdentity() || key
buildExpandFields -> columnIdentity()
record-details / record-related-list -> columnIdentity() (|| key)
`accessorKey` keeps its precedence in RelatedList — it is TanStack Table's
column key, not ObjectStack metadata identity, and only the `field || name`
tail was converged. `key` stays a tail fallback in ObjectTree and
record-related-list for the same reason: it is a generic entry key.
Two incidental fixes TypeScript surfaced once the resolver stopped returning
`any`: ListView's filter-field options and its hide-fields popover both built
entries keyed `undefined` for a column with no resolvable identity. Those
entries could never match a column; they are now dropped.
Inventory re-triage: PR1 recorded 24 family members. Two were mis-classified
and are reclassified rather than converged — reading what they actually feed
shows they are not column reads at all. ViewPreview adapts a ViewItem FORM
section to what object-form selects by (#3090's two-layer join); SchemaForm
renders an arbitrary metadata ARRAY into a popover summary and guesses at a
display key. So the family was 22, and it is now 0.
The ratchet asserts that, asserts each converged surface actually routes
through the shared reader (a surface that dropped identity resolution instead
of converging it goes red), and pins accessorKey's precedence in RelatedList.
Refs: objectstack#4115, #3090 (playbook), #3119 (PR1)
Claude-Session: https://claude.ai/code/session_01C2pdPmf2yZSd4wFDs1NHY5
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: packages/core/src/utils/__tests__/column-identity.ratchet.test.ts
+80-83Lines changed: 80 additions & 83 deletions
Original file line number
Diff line number
Diff line change
@@ -4,11 +4,17 @@
4
4
*
5
5
* Ratchet — the column-identity dual read (`field` ?? `name`) may only shrink.
6
6
*
7
-
* objectui#3104. A column's identity is read in two incompatible precedences
8
-
* across the repo, and the two halves land on different fields for the same
9
-
* column (see `ListView.columnIdentity.test.tsx` for the live repro). This file
10
-
* freezes the inventory so the family cannot grow while it is being retired:
11
-
* a new dual read in a new file fails, and an extra one in a listed file fails.
7
+
* objectui#3104. A column's identity used to be read in two incompatible
8
+
* precedences across the repo, and the two halves landed on different fields
9
+
* for the same column (see `ListView.columnIdentity.test.tsx` for the repro).
10
+
*
11
+
* **The family is now at zero** (PR2): every column-identity read goes through
12
+
* `columnIdentity()`. What remains in the inventory below is the residue the
13
+
* scanner still matches — reads that share the key names but not the concept.
14
+
* They are listed, not converged, and each says why.
15
+
*
16
+
* This file freezes that state: a new dual read in a new file fails, and an
17
+
* extra one in a listed file fails.
12
18
*
13
19
* Shrinking also fails, deliberately — lower the number here in the same commit
14
20
* that removes the read, so the count in the tree and the count on record never
@@ -78,71 +84,25 @@ interface Entry {
78
84
}
79
85
80
86
/**
81
-
* The inventory, as of objectui#3104 PR1.
87
+
* The inventory, as of objectui#3104 PR2.
88
+
*
89
+
* The column-identity family is EMPTY: all 22 reads now go through
90
+
* `columnIdentity()`. What is left below matched the scanner but was never the
91
+
* same concept — two-layer joins where both precedences are correct, the form
92
+
* cluster that #3090 settled the other way, and generic display fallbacks that
93
+
* merely share the key names. They stay listed so the scanner cannot quietly
94
+
* grow a real dual read inside a file we already decided about.
82
95
*
83
-
* The headline is not that two precedences exist — it is that ListView and
84
-
* ObjectGrid each disagree with THEMSELVES. Both build their `$expand` /
85
-
* `$select` from `f?.field` alone (a single-key read the scanner cannot see,
86
-
* because it is not an alternation) while every render-side read in the same
87
-
* file is name-first. That is the mechanism behind "relation column shows a
88
-
* bare id": the request asks for one field, the renderer shows another.
96
+
* PR1 recorded 24 family members; two of those were mis-triaged and are
97
+
* reclassified here after reading what they actually feed:
98
+
* - `ViewPreview.tsx` converts a ViewItem FORM section into `object-form`'s
99
+
* runtime shape (`field` -> `name`) — the #3090 two-layer join, not a column.
100
+
* - `SchemaForm.tsx` renders an arbitrary metadata ARRAY into a summary string
101
+
* and guesses at a display key; the entries are validations/actions/whatever
102
+
* the JSON schema declares, so there is no column vocabulary to converge.
89
103
*/
90
104
constINVENTORY: Record<string,Entry>={
91
-
// ── the column-identity family (24) ──────────────────────────────────────
92
-
'plugin-list/src/ListView.tsx': {
93
-
count: 9,
94
-
order: 'name-first',
95
-
verdict: 'column-identity',
96
-
why: 'FLS gate, hidden-field filter, fieldOrder, export ×2, hide-fields list — all name-first, while $expand/$select in the same file read `f?.field` only.',
97
-
},
98
-
'plugin-detail/src/RelatedList.tsx': {
99
-
count: 8,
100
-
order: 'adapter-first',
101
-
verdict: 'column-identity',
102
-
why: '`accessorKey || field || name` — merges TanStack\'s column key with metadata identity. The adapter key is out of scope; the `field || name` tail is not.',
103
-
},
104
-
'core/src/utils/expand-fields.ts': {
105
-
count: 1,
106
-
order: 'field-first',
107
-
verdict: 'column-identity',
108
-
why: 'The request path. Canonical-first, so it is the side the renderers must converge ONTO.',
109
-
},
110
-
'plugin-grid/src/ObjectGrid.tsx': {
111
-
count: 1,
112
-
order: 'name-first',
113
-
verdict: 'column-identity',
114
-
why: 'The `ensureId` probe is name-first while `getSelectFields` right below it reads `c.field` only — the same self-disagreement as ListView.',
why: 'Re-triaged in PR2: `toFormFieldEntry` adapts a ViewItem FORM section to what `object-form` selects by (`name`), which is the #3090 join — not a list column. Converging it onto `columnIdentity` would be a category error.',
144
+
},
180
145
181
-
// ── not identity reads (2) ───────────────────────────────────────────────
146
+
// ── not identity reads (3) ───────────────────────────────────────────────
why: 'Re-triaged in PR2: `summariseComposite` renders ANY composite/array metadata value into a popover summary and guesses at a display key. The array items are whatever the JSON schema declares, so this is a display fallback over arbitrary objects, not a column read.',
0 commit comments