Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-grid): retire the three inert fieldMeta copies the cell never reads - #7186
Conversation
…never reads
`applyRelationalMeta` no longer copies `descriptionField`, `lookupColumns` or
`lookupFilters` onto a relational column's `fieldMeta`. No behaviour change:
all three still reach the inline lookup picker, by a different seam.
`fieldMeta` is handed to `<CellRenderer>` as the `field` prop and nowhere else
— six JSX passes across the three column-building paths. A relational column
resolves to `LookupCellRenderer`, which reads exactly `reference_to`,
`reference`, `display_field`, `displayField`, `reference_field` and `options`;
a `user` column resolves to `UserCellRenderer`, which destructures `{ value }`
and reads no field meta at all. Measured by receiver: `fields/src/index.tsx`,
the file holding every cell renderer, contains zero occurrences of the three
retired keys, against a control of 22 for the spellings the cell does read.
Their only readers off a field meta are `LookupField` and `UserField` — the two
editor widgets — which `renderCellEditor` feeds from the schema def directly,
so every key a def carries reaches them whether or not it is copied.
Two of the three were added by objectui#6875, classified from a read-set
derivation alone; its third key `displayField` is genuinely delivered and stays
copied. A derivation establishes that a consumer reads a key, not that a given
bag is how the consumer gets it — recorded in both docblocks.
- New `relationalMetaCopySet-7166.test.tsx` renders both halves: the cell (four
columns differing only in the key under test resolve the same text, with a
`displayField` control that differs) and the editor (each retired key still
takes effect in the picker, each against a control column declaring nothing).
- The three snake_case `legacy-alias` spellings are deliberately kept: their
retention rests on an open producer-side question this reader-side
measurement does not touch. Their verdict is now recorded on the table.
- The derivation gate cannot enforce this retirement — its read set is a union
over three consumers, two of which are not fed this bag — and now says so.
Absence is pinned by an explicit assertion plus the rendering test.
- Gate strengthened: every `deferred` verdict is now mechanically proved
spec-declared, and the sibling pins turn the retired `lookupFilters` into a
live negative assertion on a fixture that still declares it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-warren
commented
Sep 1, 2026
os-dev-report {
"issue": 7166,
"status": "done",
"branch": "claude/issue-7166-inert-fieldmeta-copies",
"pr": "https://github.com/objectstack-ai/objectui/pull/7186",
"premise_still_valid": true,
"summary": "RULING ITEM 3 FIRST, and it came back INTERNAL, so I proceeded: `RELATIONAL_META_KEYS` is not re-exported from the plugin-grid barrel (fully enumerated export list, no `export *`), the built `dist/index.d.ts` holds zero occurrences of any of the module's symbols, and it is absent from the built bundle's export list — each with `useRowColor` as a live control that hits in all three probes. Clause 2 not engaged. A2.1 re-derived on 47035ce79 and it still holds: the copy set was 10, six had no reader on the bag. Retired the 3 modern spellings (`descriptionField`, `lookupColumns`, `lookupFilters`) through the derivation table by flipping their verdict to `deferred`, the verdict this repo already uses for the same class; copy set now derives to 7, verified at runtime. Kept the 3 snake_case legacy aliases with their reader-side verdict recorded on the table and the unanswered producer question stated beside it. A2.3, A2.4 and A2.5 all confirmed on head by re-measurement, not inherited: 6 `field={fieldMeta}` JSX passes across the three column-building paths and no other whole-object use except `applyRelationalMeta`; `UserCellRenderer` has zero occurrences of `field` against a control of 8 in `LookupCellRenderer`; and `packages/fields/src/index.tsx`, which holds every cell renderer, has zero occurrences of all six keys against a control of 22 for the spellings the cell does read. The gate was strengthened, not weakened. Note for the PM: the platform appended a second copy of the signature footer to the PR body; I left it, because a PATCH edit would downgrade both footers to the bare form and lose the session URL.",
"tests": "All runs used the canonical repo-root vitest invocation (this repo's guard refuses a package-cwd run as a false green) and went through the shared verify lock. UNION on the final commit f4f299e23: `pnpm exec vitest run packages/plugin-grid/src packages/fields/src/__tests__/lookupCellDisplayField.test.tsx` gave `Test Files 106 passed (106)` / `Tests 956 passed (956)`, VERDICT command-exit 0. Baseline before any edit was 5 files / 27 tests; the relational suites after the change are 6 files / 34 tests. `pnpm --filter @object-ui/plugin-grid run type-check` clean — the FIRST attempt returned TS2307 'Cannot find module @object-ui/*' across files I never touched, which is an unbuilt dependency closure and NOT MEASURED rather than a red; re-ran after `pnpm --filter '@object-ui/plugin-grid^...' build` (13 packages) and it passed with the script name echoed, so it was not a zero-match no-op. Confirmed the new test file is actually IN the tsconfig.test.json program with `--listFiles` (1 hit, control `lookupPickerKeys-7154` also 1, 1770 files). LINT: ran `eslint . --no-inline-config` REPO-WIDE rather than narrowing — 4100 files by eslint's own count, 75 files with errors, every one pre-existing and none in this diff; my files are E0 (one unused import it caught was fixed). Control-character self-scan clean over all 6 changed files, with the pattern itself verified against 0x01 and 0x0b after my first control (a tab) proved nothing because 0x09 is deliberately outside the class. ABLATIONS — both targets resolve from SOURCE (the root config aliases every @object-ui/* to src/, and both are relative imports), so no rebuild leg applies and none was needed; each mutation was proven on disk by marker count AND blob hash versus the HEAD blob, never by an editor's exit code, and each restore proven by STATE with `git diff HEAD`, `git diff --cached` and `git status --short` all empty plus the blob back to the HEAD blob and the marker gone. Both ran against the COMMITTED implementation. (1) Break `renderCellEditor`'s schema spread — direction predicted before running as 'the editor half goes red, the cell half stays green' — result `Tests 3 failed | 2 passed`: exactly B1/B2/B3 red, test A and the premise test green. (2) Re-add `lookupFilters` to the copy set — direction predicted as 'the derived gate assertions stay green, only the hand-written pins go red' — result `Tests 8 failed | 19 passed`, and the ONLY derivation-gate failure was my hand-written pin while every derived assertion stayed green. One flaw recorded rather than hidden: in ablation 2 my `deferred`-count grep was malformed by nested quotes and printed a bogus 0 — the exact 'empty output read as a zero' trap; the mutation was still proven by the injected-marker count and the blob change, and I re-counted cleanly afterwards (7 deferred, control `legacy-alias` 4).",
"mcp_calls": "9 — REST was 403 on every repo-scoped read and `gh` is absent, so the duplicate check for the filed finding used ONE targeted MCP search_issues (3 hits, so a non-empty reading needing no control query). Channel switch declared.",
"open_questions": [
{
"question": "The 3 snake_case legacy aliases (`description_field`, `lookup_filters`, `id_field`) have the SAME reader-side verdict as the three I retired — no reader on this bag — and are kept only on a producer-side argument that no evidence gathered in these two repos can confirm or refute: does any host DataSource outside objectui and objectstack actually hand-feed these spellings onto a field def? Their verdict is now recorded on the table, so this is not blocking, but it is the question that decides whether a future card retires them.",
"options": [
"A. Leave as is. The reader-side verdict is now recorded with the open producer question stated beside it, so the next pass inherits a measurement instead of a silence.",
"B. Commission a producer survey outside these two repos (host DataSource implementations, any published adapter) and retire whichever aliases come back with zero producers.",
"C. Retire all three now on the reader-side measurement alone, treating the legacy-alias verdict as unbacked."
],
"recommendation": "A, and B only if someone independently wants the producer inventory for its own sake. On the four axes: REAL BUSINESS NEED — there is no measured pull for retiring them; they cost three conditional writes per relational column build and nothing else, while the failure mode of being wrong is a silently broken host integration we cannot see from here. LONG-TERM SOUNDNESS — the honest state is now encoded rather than implied; retiring on reader-side evidence would set the precedent that a back-compat verdict can be overturned by a measurement that does not address it. AI-ERROR-RESISTANCE — this is the axis that argues hardest against C: these are runtime-only spellings no spec-compliant producer can emit, so the strict FieldSchema already stops an AI-authored app from declaring them, and removing the runtime tolerance buys no authoring-time safety while risking a live host. STARTUP SCOPE — C spends real risk on tidiness with zero pull, and B spends survey effort on the same. C is explicitly out of scope per the ruling and I did not take it."
}
],
"out_of_scope_findings": [
"filed as #7187: the relational copy-set derivation cannot judge a COPY — its read set is a union over three consumers, two of which are never fed that bag, so read-set membership proves only that some consumer reads a key and not that this bag is how it arrives. Pinned by my ablation 2: re-adding a retired key leaves every DERIVED gate assertion green and turns red only the hand-written pins. Re-scoping it around the cell alone is a design change to #6875's mechanism, which #7166 explicitly declined to make, so it is filed rather than ridden. Unassigned, labels finding + domain:ui.",
"not filed, recorded here: #7165 (the dependsOn uneditable-column bug) was named out of scope by the dispatch and I did not touch it; the `dependsOn` gating behaviour it concerns is still pinned as current behaviour by #7154's suite, which stayed green throughout."
]
}Generated by Claude Code |
os-warren
commented
Sep 1, 2026
PM review — |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7166
ObjectGridno longer copiesdescriptionField,lookupColumnsorlookupFiltersonto a relational column'sfieldMeta. No behaviour change — all three still reach the inline lookup picker, by a different seam.Copy set: 10 keys to 7. Re-measured on
47035ce79(the card's own numbers were taken on51449a043, before PR #7167 landed into these files).Ruling item 3 — the gating measurement:
RELATIONAL_META_KEYSis INTERNALClause 2 is not engaged, measured three ways, each with a live control:
useRowColor)src/index.tsxbarrelexport *either)dist/index.d.tsdist/index.jsexport listEvery import of
./relationalMetaKeysin the repo lives insidepackages/plugin-grid/src/— the implementation plus two of its own tests. The cross-package hits are prose in docblocks;plugin-dashboardkeeps its own independentCELL_RELATIONAL_META_KEYSliteral rather than importing this one. Narrowing the copy set therefore moves no published surface.What was measured
applyRelationalMetawrites onto thefieldMetathatgenerateColumnshands toCellRendereras thefieldprop — 6 JSX passes across the three column-building paths, and nowhere else (as a whole object it otherwise only reachesapplyRelationalMeta, which mutates it).LookupCellRendererreads exactlyreference_to,reference,display_field,displayField,reference_field, plusoptions.UserCellRendererhas zero occurrences offield— control:LookupCellRendererhas 8.Stated by receiver rather than by count:
packages/fields/src/index.tsx, the file holding every cell renderer, contains zero occurrences of the three retired keys, against a control of 22 occurrences of thedisplay_field/displayField/reference_tospellings the cell does read.Their only readers off a field meta are
LookupFieldandUserField— the two editor widgets — and the editor is not fed this bag.renderCellEditorlooks the field up in the object schema and spreads the whole def into the widget ({ name: ctx.column.accessorKey, ...fieldDef }), so every key a def carries reachesLookupFieldwhether or not it is copied. The copies were dead writes: the #6711 / #6874 class arriving from the opposite direction — those keys had no declaration, these have no reader on this path.The evidence bar this card exists to raise
Two of the three,
descriptionFieldandlookupColumns, were added by #6875 as its fix, classified from a read-set derivation alone. Its third keydisplayFieldis genuinely delivered and stays copied — and it is the one that arrived with a rendering test.⭐ A derivation establishes that a consumer READS a key. It does not establish that a given BAG is how the consumer gets it. Recorded in both docblocks.
So this is pinned by rendering, in both directions — new
__tests__/relationalMetaCopySet-7166.test.tsx:displayFieldcontrol column resolves something else. The control is what makes the three zeros readings rather than a fixture that never reached the lookup path.descriptionFielddrives the secondary line,lookupColumnsshapes the picker columns,lookupFiltersscopes the candidates — each against a sibling control column that declares nothing. The referenced schema is deliberately two fields, soderiveLookupColumnsderives nothing and the controls stay load-bearing.Scope: 3 keys, not 6
The card measures six keys with no reader on this bag. They are two populations. The three snake_case
legacy-aliasspellings —description_field,lookup_filters,id_field— are deliberately kept. They are recorded as legacy aliases precisely because a hostDataSourceoutside these repos may hand-feed them; that is a producer-side argument and this measurement is entirely reader-side. Their reader-side verdict is now recorded on the table with the open producer question stated, so the next pass inherits a measurement instead of a silence.⛔ Reported, not fixed: the derivation gate cannot see this class
The gate's read set is a union over three consumers, two of which are not fed this bag. Membership means "some consumer reads this key", never "this bag is how that consumer gets it" — and only the second claim justifies a copy. All three retired keys remain in the extracted read set, so every derived assertion passes whichever verdict they carry.
Measured, not asserted (ablation 2 below): re-adding
lookupFiltersto the copy set leaves every derived assertion in the gate green and turns red only the hand-written pins. Re-scoping the derivation around the cell alone, with the editor widgets classified separately, is a design change to #6875's mechanism and is filed rather than made here.The gate is strengthened, not weakened: every
deferredverdict is now mechanically proved spec-declared against the installedFieldSchema, and the sibling pins turn the retiredlookupFiltersinto a live negative assertion on a fixture that still declares it.Tests
All runs are the canonical repo-root invocation (the repo's vitest guard refuses a package-cwd run as a false green). Union run on the final commit
f4f299e23:pnpm --filter @object-ui/plugin-grid run type-check— clean (first attempt reportedTS2307: Cannot find module '@object-ui/*', which is an unbuilt dependency closure and NOT a result; re-run afterpnpm --filter '@object-ui/plugin-grid^...' build). The new test file is confirmed present in thetsconfig.test.jsonprogram via--listFiles, so that green does cover it.eslint . --no-inline-configwas run repo-wide, not narrowed: 4100 files, 75 files with errors, all pre-existing and none in this diff; the changed files are E0.Ablations
Both targets resolve from source (the root config aliases every
@object-ui/*tosrc/, and both are relative imports), so no rebuild leg applies. Each mutation was proven on disk by marker count and blob hash — never an editor's exit code — and each restore by state:git diff HEAD,git diff --cachedandgit status --shortall empty, blob back to the HEAD blob, marker gone.Tests 3 failed | 2 passed. Exactly B1/B2/B3 red; test A and the premise test green. The retirement's safety claim is load-bearing.lookupFiltersto the copy set (predicted: derived gate assertions stay green, hand-written pins go red) —Tests 8 failed | 19 passed. The only derivation-gate failure is the hand-written finding(plugin-grid): 6 of the 10 keys ObjectGrid copies onto a column'sfieldMetahave no reader on that bag — the picker keys reach the editor by a different seam #7166 pin; every derived assertion stayed green. The cell and editor tests also stayed green, which is itself the finding: the copy makes no observable difference.Generated by Claude Code
Generated by Claude Code