feat(types, fields, plugin-grid)!: converge the lookup dialect on the spec's camelCase - #7641
Merged
Merged
Conversation
… spec's camelCase Two published contracts declared OPPOSITE dialects for the same four lookup keys, and `@object-ui/fields`' read chains served both with the snake leg FIRST — so the dialect the object contract refuses outranked the one it declares: @objectstack/spec FieldSchema camelCase declared / snake refused @object-ui/types LookupFieldMetadata snake declared / camelCase TS2561 `LookupFieldMetadata` and `UserFieldMetadata` now declare `displayField`, `descriptionField`, `lookupFilters` and `idField`; the snake members are removed, not deprecated. The two doc pages, all seven in-repo producers and the inline-edit enrichment allow-list move with them, and the read chains read the spec spelling only. `relationalMetaKeys.ts` drops the four `legacy-alias` verdicts and retires that verdict class; its gate is restated to assert the class no longer exists rather than passing vacuously. objectui#6875's both-spellings parity pin is inverted into a refusal pin. `idField` is kept as a widget-contract key: it carries objectstack#3508's machine-name hydration, which has no FieldSchema twin and needs none. `reference_to` stays — the adapter choke point genuinely stamps it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Contributor
✅ 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
|
This was referenced Sep 4, 2026
This was referenced Sep 4, 2026
os-warren
marked this pull request as ready for review
September 4, 2026 14:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7155
Implements the maintainer ruling A′ recorded in comment 5523072764 (director seat, summon #11, decision batch #19, 2026-09-03, maintainer verbatim 「同意」), re-confirmed in comment 5535917075. A′ supersedes the 2026-09-01 deletion ruling on that ruling's falsified premise, and explicitly does not take option D (delete the reader alone).
The defect: two published contracts, opposite dialects
@objectstack/specFieldSchema(object metadata)@object-ui/typesLookupFieldMetadata(widget metadata)TS2561)unrecognized_keys)@object-ui/fields' read chains served both bags, and they read the snake leg first — so the dialect the object contract refuses outranked the one it declares.content/docs/fields/lookup.mdx:18taught the snake dialect as normative ("the source of truth for the key set"), and that page is not incheck-doc-snippet-types.mjs'sUNGATED_DOCS, so CI compiled those snake snippets on every run.What this PR does — one concept, one spelling, one payment
packages/types/src/field-types.ts—LookupFieldMetadata/UserFieldMetadatadeclaredisplayField,descriptionField,lookupFilters,idField. The snake members are removed, not deprecated (maintainer's standing principle: 项目在创业阶段,不渐进).fields/lookup.mdx,fields/user.mdx).GridField×3,UserField, app-shellparamToField, app-shellFlowReferenceField, plugin-gridbulkParamToField.LookupField.tsx,UserField.tsx,fields/src/index.tsx).relationalMetaKeys.tsdrops the fourlegacy-aliasverdicts and retires that verdict class; the gate is restated to assert the class no longer exists (finding(views): the gantt and timeline branches still fabricate date-axis field names on all three faces — and app-shell's own #3129 note certifies them as already fixed #7070 discipline — never deleted into a vacuum).idFieldis kept under a newwidget-contractverdict: it carries objectstack#3508's machine-name hydration, which has noFieldSchematwin and needs none.displayField,reference_field,descriptionField,lookup_columnsnever arrive) #6875's both-spellings parity pin is INVERTED, not deleted, into a refusal pin.reference_tostays — the adapter'snormalizeSchemaReferenceKeyschoke point genuinely stamps it onto every def.@object-ui/plugin-detail's inline-edit enrichment allow-list, which forwarded all four onto the picker's field meta.Premise re-measured on this base (
0b24d7f85), with lit controls1.
FieldSchemais strict and refuses the four.strictObject(options, shape)isz.object(shape).strict()(packages/spec/src/shared/strict-object.ts:327). Declared key set read from objectstack's own generatedpackages/spec/authorable-surface.base.json(machine-readable, no hand parsing):name/type/labelall False, 0 properties). I discarded it rather than reporting it — the number above comes from the generated baseline.2. Nothing inbound manufactures a snake spelling. Zero occurrences of all four across
packages/data-objectstack/src, against a lit control (reference_topresent:index.ts1,getObjectSchema.test.ts7).3. The
FieldSchema-absence gate. It did not simply "keep passing" — under the deletion it would have passed vacuously (for (…) { if (e.verdict !== 'legacy-alias') continue; … }over an empty class). It is therefore retired with the keys and replaced, per A′ and the #7070 discipline, by a gate that asserts the retired dialect reaches no consumer and no verdict — with the surviving camel spellings as its lit control.Verification
Heavy runs through
scripts/pm/os-verify-lock.sh(slotissue-7155). All results are fromb7c45e83, the final commit.vitest packages/fields/ packages/app-shell/src/utils/ …Test Files 3 failed → after triage: 245 passed; final:Test Files 356 passed (356)/Tests 3690 passed | 9 skippedvitest packages/plugin-grid/ packages/plugin-detail/(targeted)Test Files 4 passed (4)/Tests 19 passed (19)turbo run type-check --concurrency=2Tasks: 81 successful, 81 totalcheck:doc-snippetsSemantic phase: 452 of 452 block(s) judged, 0 failed— "Every covered documentation snippet compiles against the built types" (own sentinel controls lit: sentinel 1 diagnostic, positive 0)check-changeset-presence.mjs✅ 27 source file(s) of 5 released package(s) changed, and this change declares 1 changeset(s)check:control-bytes·check:doc-types·check:doc-fences·check:designer-field-key-parity·check:docs-route-closure·check:spec-symbols·check:spec-floors·check:readme-exportseslintover the 27 changed lintable filesno-explicit-anycounts, none introduced)check:readme-exportsfirst exited 1 withtype entry ./dist/index.d.ts is not on diskfor@object-ui/plugin-ai— an unbuilt-package precondition, not a defect in this diff (that package is untouched). Green after building it.Lint narrowing, declared. I ran eslint over the 27 changed files rather than repo-wide
eslint .. The narrowing is a measurement, not a gap: file count27read from--format jsonoutput length, and the config is not type-aware (tseslint.configs.recommended, noproject/projectServiceinlanguageOptions), so every rule judges each file in isolation and this diff cannot move the verdict of a file it did not touch. CI runs the full farm regardless.Ablation — predicted vs observed, including a miss
Both legs: mutation proved on disk by anchored
grepcounts and blob-hash movement (never by the editor's exit code); restore viagit checkout HEAD -- <absolute path>under anEXIT INT TERMtrap, verified by emptygit diff HEADand blob-hash equality. No build leg applies and none is claimed —vitest.config.mts:417aliases@object-ui/fieldsto./packages/fields/src, so tests run against source.Leg A — restore the cell's snake read leg only. Predicted: the #6875 refusal pin and the derivation gate go red.
Observed: derivation gate RED as predicted (2 tests, including
fields/src/index.tsx#LookupCellRenderer reads 'display_field' again). The #6875 pin stayed GREEN — prediction missed, and the miss is informative: the copy set is a second, independent barrier. Withdisplay_fieldoffRELATIONAL_META_KEYS, the key never reaches the cell's bag at all, so restoring the reader alone changes nothing observable. The retirement is defended in depth.Leg B — restore the reader leg and re-add
display_fieldto the copy set. Predicted: #6875 test 2 red (snake column resolves again →ACME-42count 2 not 1,Wrong Nameabsent); test 1 (camel control) stays green.Observed: exactly that —
Tests 1 failed | 1 passed, failing atexpect(screen.getByText('Wrong Name')).toBeInTheDocument(). The camel control passed, so the render path was live and the red is a reading.Housekeeping
⭐ The two unmeasurable populations — declared as NOT MEASURED, not argued away.
This is a behaviour change, not a compile-only one: the chains used to read snake first, so removing that leg changes which value wins for any bag carrying both spellings.
objectstack+objectui. A host may hand-feed aDataSourcewhosegetObjectSchemareturns snake_case defs. Unmeasurable in principle from these two repos: nothing here can enumerate third-partyDataSourceimplementations. For such a host, a lookup's display value degrades to the referenced record's generic name heuristic — visible, and recoverable by renaming the four keys.FieldSchemabecame strict. If any is persisted it would still be served. Unmeasurable from a source repo: it is a property of deployed databases, not of this tree.⛔ The
objectstackcensus does not cover either population, and nothing in this PR should be read as claiming it does. What the census does establish is the in-repo half: every snake occurrence acrossobjectstackis prose against lit camelCase controls.Out of scope — a second, separate population found while measuring
Six sites read the four snake spellings off an object-schema field def (
FieldSchema), not off the widget bag this PR converges. Those are dead legs already —FieldSchemarefuses the spellings — but they are a different finding with a different fix, and A′ does not name them. Left untouched, filed separately:packages/plugin-charts/src/ObjectChart.tsx:197,208·packages/plugin-form/src/deriveMasterDetail.ts:249,303·packages/plugin-list/src/ListView.tsx:2714,2736·packages/plugin-list/src/UserFilters.tsx:300·packages/app-shell/src/utils/resolveActionParams.ts:304-310(which documents its own source as "an object schema field def, i.e. the protocol") ·packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx:1145.Also out of scope, and deliberately not renamed: the wider snake dialect on the same widget bag that A′'s four keys do not cover —
reference_to,title_format,lookup_columns,lookup_page_size,depends_on,allow_create,avatar_field.paramToFieldnow emits a deliberately mixed bag, andparamToField.test.tsasserts that mixed shape so the two halves stay visibly separate.Fences
Respected. Three fenced files carry stale prose mentioning
display_fieldand were left alone —packages/plugin-grid/src/ObjectGrid.tsx:2455,2658,2816andpackages/core/src/utils/expand-fields.ts:49(both dispatch #7627), pluspackages/plugin-dashboard/src/ObjectDataTable.tsx:359. All are comments; no behavioural collision.packages/types/src/complex.tsandpackages/types/src/__tests__/zod-mirror-parity.test.ts(parked PR #7633) are untouched, and I verified the parity test references none of these keys, so there is no collision with that PR.Clause-②: yes
A published type's member set and two published doc pages change, and the renderer's accept behaviour narrows.
needs:contract-reviewis on this PR and on the card. Kept a draft, not enqueued — it parks for the director seat atCONTRACT_REVIEW_TIER.🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code