feat(types,fields): formula and summary widgets read the spec's returnType and summaryOperations; field metadata retires the snake spellings (objectui#11070, round 3) - #11240
Conversation
…eturnType and summaryOperations (objectui#11070) The form-field face declares `returnType` and `summaryOperations` by reference to the spec's `FieldSchema` members, on both faces, with the form-field-zod-coverage row. `FormulaField` reads `returnType` and `SummaryField` reads `summaryOperations.function`; the snake_case reads are gone. `FormulaFieldMetadata.return_type` and `SummaryFieldMetadata`'s `summary_object` / `summary_field` / `summary_type` / `summary_filter` retire for the spec members, and `PasswordFieldMetadata.min_length` / `max_length` for `minLength` / `maxLength`. Refs objectui#11070 (round 3). Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
…aryOperations / minLength (objectui#11070)
The fields-formula fixtures write the spec's `expression` and `returnType`;
the fields-summary fixtures fold `summary_object` / `summary_field` /
`summary_type` into `summaryOperations { object, field, function }`. The
five packages/fields tests that wrote `return_type: 'date'` write
`returnType`, now as annotated literals. A new pin covers both widgets'
spec reads, each against a control, and the retirement of the snake reads.
formula.mdx, summary.mdx and password.mdx teach the spec members. One
changeset (types + fields minor, breaking stated), and a dated note on the
three pending changesets whose `return_type` wording this makes false.
Refs objectui#11070 (round 3).
Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
…azy slot (objectui#11070) `summaryOperations` is the spec's `FieldSchema` member by reference, and its optional `filter` is the spec's recursive filter condition, a `z.lazy` inside an imported schema that no local const names. The blind-region ledger of the wider direction records it, as the leg asks when a new lazy source joins. Refs objectui#11070 (round 3). Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
…larations bring (objectui#11070) Measured on the built face at base and head: a `returnType` of `datetime` and a malformed `summaryOperations` were stripped unjudged by the tolerant face before, and are refused now. Refs objectui#11070 (round 3). Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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
|
Contract reviewServed-tier: Inputs read: card objectui#11070 (body and all 15 comments, rulings and earlier records included); PR #11240 (body, the 28-file list, the net diff against ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged:
Nothing is judged wrong. ② Semver level
③ Boundary flagsEvery dev flag (PR body) and every
No flag is left unanswered, and no answer needs the maintainer before this head lands. Implemented-by: VERDICT: PASS |
Refs #11070
Clause-②: yes (narrowing). The form-field face declares the spec's
returnTypeandsummaryOperations(the strict accept set widens by those two keys), while a malformed value for either is now refused by the tolerant face too, andFormulaFieldMetadata/SummaryFieldMetadata/PasswordFieldMetadataretire their snake_case members (the published TypeScript types narrow), under the startup no-gradualism rule. Both directions were measured on the built face at base and head (below), and the changeset prices the break as minor.Refs, not a closing keyword, on purpose: the card keeps its remainder after this lands (the grid field'scolumns, theobject-chartdeclaration, the dashboard pairs per objectui#11228, and thereference_tostop below). This is round 3 of the card, executing the seat's answers Q2 A and Q3 A (5914385802) under claim5915152317. Draft; the dev does not mark it ready.What changed
@object-ui/types).FormFielddeclaresreturnType?: SpecField['returnType']andsummaryOperations?: SpecField['summaryOperations']; the zod mirrorFormFieldSchemadeclaresstripImportedDefaults(SpecFieldSchema).shape.returnType/.summaryOperations. Both are the spec'sFieldSchemamembers by reference, never a restated copy.form-field-zod-coveragegains the two keys.@object-ui/fields).FormulaFieldreadsreturnTypeonly (numberto two decimals,booleanas Yes/No,datethrough the shared date face, else text).SummaryFieldreadssummaryOperations.functiononly (countas it arrives;sum/avg/min/maxto two decimals). The snake_case reads are gone, with no alias and no dual read.FormulaField'scurrencybranch is gone as well: the spec'sreturnTypedoes not listcurrency.@object-ui/types,field-types.ts).FormulaFieldMetadata.return_typebecomesreturnType;SummaryFieldMetadata.summary_object/summary_field/summary_typebecomesummaryOperations(object/field/function);PasswordFieldMetadata.min_length/max_lengthbecomeminLength/maxLength. Each new member isSpecField['KEY'].SummaryFieldMetadata.summary_filteralso becomessummaryOperations.filter, under the bounded in-place exemption (same defect class and same interface; a mechanical fold whose target shape the spec pins; the file is this claim's; no new verification surface). Nothing readssummary_filter, and leaving it would have kept a second spelling offilterbesidesummaryOperations.fields-formula/*write the spec'sexpressionandreturnType;fields-summary/*foldsummary_object/summary_field/summary_typeintosummaryOperations { object, field, function }. Thefields-lookupandfields-passwordfixtures already wrotereference/minLength(round 1).packages/fieldstests that renderedFormulaFieldwithreturn_type: 'date'writereturnType: 'date', now as annotated literals (theas anycast is gone, sotsc -p tsconfig.test.jsonjudges the key). A new pin,widgets/__tests__/formula-summary-spec-reads-11070.test.tsx, covers both widgets' spec reads, each against a control that changes only that key, plus the retirement (a snake spelling alone formats nothing).strict-face-read-keys-11070.test.tsaccepts the two keys, refuses bad values on both faces, movesreturn_type/summary_typefrom PENDING to RETIRED (withsummary_object/summary_field), and adds type-level pins: the members equal the spec's exactly, and the retired names are gone from the three metadata types.zod-mirror-parity'sUNNAMED_LAZY_SLOTSrecordsform.zod.ts#FormFieldSchema.summaryOperations: itsfilteris the spec's recursive filter condition, az.lazyinside an imported schema.imported-defaults-8317's comment no longer states a member count. No parity key set or total moved.fields/formula.mdx,fields/summary.mdxandfields/password.mdxteach the spec members in their typed fences, their plaintext fences and their prose (including the summary page's backend sketch)..changeset/11070-field-metadata-spec-spellings.md:@object-ui/typesand@object-ui/fieldsminor, with the break stated (AGENTS.md: the fixed group never declares major). Three pending entries this change makes false get a dated note, appended in the maintainer's shape:6138-fields-schema-block-parity-pr2.md(the shippedreturn_typeunion),8194-fields-date-widget-convention.mdand8809-date-carrier-unparsable.md(return_type: 'date').Stopped:
reference_to(the ruling's live-producer case)The brief's stop rule says not to retire a spelling a live producer still writes, and to stop at that key.
reference_tohas live producers inside this repository, soLookupFieldMetadata.reference_to,MasterDetailFieldMetadata.reference_to, the lookup readers'reference_tolimb,lookup.mdxand its fixtures are untouched. H2 holds:MetadataProvider's comment is a read (d.reference ?? d.reference_to), and the writers are these.reference_toontonormalizeFieldReferenceKeys(the ingestion stamp)MetadataProvider's list and by-name reads,ObjectStackAdapter.getObjectSchema)paramToFieldRecordDetailView(toField)FlowReferenceFieldfieldprop ofLookupField, a typedLookupFieldMetadataliteralRecordDetailPanelDetailViewField, whose contract declaresreference_toRecordMetaFooterRelatedListFieldMetadataliteralfieldEnrichment,buildDefaultPageSchemasectionFieldsbulkParamToFieldObjectGalleryThe three readers that would lose the limb are
LookupField(reference_to || reference),UserField(reference || reference_to) andLookupCellRenderer(reference_to || reference). The pending.changeset/6837-reference-to-arm-deletion.mdrecords leaving exactly those three alone for this reason. objectstack's example apps (objectstackorigin/main00a92e18d) writereference_to0 times. Retiring it means retiring ObjectUI's own internal carrier spelling across six packages; that is the maintainer question in the report, not this PR.Runtime probe: an object-bound formula and summary field, before and after
A scratch test (never committed) rendered
ObjectFormin edit mode with a stub data source.getObjectSchemareturned field definitions exactly as objectstack's example apps write them: app-todo'sdays_overdue(returnType: 'number'), app-crm'sline_total(summaryOperations: { object, field, function: 'sum' }), plus a boolean formula, a date formula and acountroll-up.findOnereturned the record. The base leg swapped in the two widget files' base blobs (hash equal to base verified) and restored them (blob equal to HEAD,git diff HEADempty).0c95d3dreturnType: 'number'333.00returnType: 'boolean'truetrueYesreturnType: 'date'2026-07-042026-07-04Jul 4function: 'sum'15750.515750.515750.50function: 'count'(control)444The type formatting did not apply on an object-bound form before, and it does now. This is the user-visible half of the round.
Census
Type-checker census of the retired members. A TypeScript LanguageService
findReferencesfrom each member's declaration, run at base0c95d3d. The program was every tracked TS/TSX file that names one of the member names (223 files), under the roottsconfig(itspathsmap@object-ui/typesto source), with the other packages' types resolved from builtdist.FormulaFieldMetadata.return_typeSummaryFieldMetadata.summary_object/summary_field/summary_type/summary_filterPasswordFieldMetadata.min_length/max_lengthLookupFieldMetadata.reference_to(the live control, and the stop case)FlowReferenceField,RelatedList, 6 test literalsMasterDetailFieldMetadata.reference_toRelatedListA first run without built dists missed
FlowReferenceField(itsLookupFieldimport resolved to nothing), so the reading above is the run with them. The untyped reads, found by grep becauseanyhides them from the type checker, are these.return_typeis read byFormulaFieldand written by the fivefieldstests throughas any.summary_typeis read bySummaryField.min_length/max_lengthare read only by the genericminLength ?? min_lengthreaders:buildValidationRules,ObjectForm,EmbeddableForm, the form renderer's input / textarea / fallback branches,RichTextFieldandTextAreaField. Those serve every text-family type, and none is password-specific;PasswordFieldreads neither. So the package set is@object-ui/typesand@object-ui/fields. Every package that names the changed or newly typed members was type-checked as well:core,components,plugin-designer,plugin-form,app-shellandexample-schema-catalog.M3, before and after. Round 2's method: a port of PR objectui#11069's
findUndeclaredKeysplusmeasure-strict-authoring-face.mjs's loaders and node walk, run over the SHIPPED face from a freshly builtpackages/types/distin each tree.0c95d3d2a83d35Every pair that moved is listed.
form · fields[].return_type3 → 0 andsummary_type3 → 0 leave class (ii).summary_object3 → 0 andsummary_field3 → 0 leave class (i).form · fields[].formula3 → 0 becomesform · fields[].expression0 → 3, a lateral move inside class (i): the spec formula key, which nothing on the form path reads andFormFielddoes not declare (see Acceptance notes). The 10 pairs left are the 9 dashboard pairs and the grid field'scolumns. H3 holds for its first half. Its second half, the doc-taughtreference_to/min_length, had nothing to lose: M3 reads only JSON documents and JSON fences, and neither key occurs there at base. Thepassword.mdxTypeScript fence is judged bycheck:doc-snippetsinstead (below).Accept sets, measured on the built face at base and head.
formfield)returnType: 'number'summaryOperations: { object, field, function: 'sum' }returnType: 'datetime'summaryOperationswith nofunction, withfunction: 'first', or with an unknown memberreturn_type: 'number',summary_type: 'sum'Tests and gates
Final head
998fb63. It differs from2a83d35by one changeset sentence, and from52a2d42only inpackages/typestests and that changeset. Heavy runs went throughos-verify-lock, and exit codes were captured before any pipe.pnpm exec vitest run packages/types/2a83d35Test Files 291 passed (291)·Tests 7079 passed (7079)pnpm exec vitest run packages/fields/52a2d42(fields unchanged since)Test Files 225 passed / 1 skipped (226)·Tests 3505 passed / 7 skippedexamples/schema-catalog/plus the tests that walk the whole catalog (app-shell widget-dom-leak sweep, cli check-validity-recogniser, core rowPredicateCanon and component-deprecation, sixscripts/__tests__censuses)2a83d35Test Files 46 passed (46)·Tests 2605 passed (2605)type-check:types(tsc + examples +tsconfig.test.json),fields(tsc +tsconfig.test.json)2a83d35Donetype-check:core,components,plugin-designer,plugin-form,example-schema-catalog,app-shell2a83d35Donecheck-doc-snippet-types.mjs --build-filter, turbo)2a83d3535 successful, 35 totalpnpm check:doc-snippets2a83d35696 of 696 block(s) judged, 0 failedcheck:doc-fences·docs:check-links·check:control-bytes·check:doc-example-ids·check:test-path-roots998fb63check:changeset-claims·check:pending-changeset-literals·changeset:check·check-changeset-presence.mjs·check:new-line-citations998fb630 new citation(s); presence declares this change's changeset)check:doc-examples·check:doc-types·check:spec-symbols·check:doc-example-readers2a83d352a83d35--stdin). Type-aware linting is not enabled ineslint.config.js(tseslint.configs.recommended, no parser project), so the diff cannot move a verdict on an untouched filecheck-governed-queue-guard.mjs --testover the 28 paths998fb63NOT GOVERNEDThe pending changesets
check:changeset-claimsnames for files this change edits (30 of them) were read. None states anything about the members this change moves, apart from the three with a dated note.Reverse verification. Each ablation was committed first, predicted before the run, and applied through objectstack's
scripts/ablation-replace.mjs: the anchor hit once, the blob moved, and the restore left the blob equal to HEAD withgit diff HEADempty.FormulaFieldreadsreturn_typeagainFormulaFieldrow of the five date files red; summary greenFormulaFieldrows, no other rowSummaryFieldreadssummary_typeagainFormFieldSchema.returnTypedeleteddatetimerefused on both faces, coverage exact set) plustscredtsc -p tsconfig.test.jsonexit 2 (TS2322 in the parity ledger)FormulaFieldMetadatakeeps areturn_typemembertscred at the retired-members pin onlyassertionRetiredMembersAreGoneformula.mdx's typed fence writesreturn_typeagaincheck:doc-snippets1 failed, proving the gate judged the rebuiltdist696 of 696 block(s) judged, 1 failed· TS2561 namingreturn_type("Did you mean to write 'returnType'?")NOT MEASURED locally, left to CI: the repository-wide lint and test shards beyond the named sets; the Spec Main Shape Gate;
type-checkof the downstream packages that name none of the changed members.Acceptance notes
expression. The ruling moved them to the spec formula key.FormFielddoes not declareexpression, and nothing on the hand-authored form path reads it (the value is computed by the backend), so it is class (i), the same asformulabefore. The three summary fixtures now parse on the strict face. Round 1 dropped the auto-number fixtures'formatfor the same reason. Whether to dropexpressionfrom these three fixtures is in the report.FormulaFieldMetadata.formulais the spec'sexpressionunder another name. The spec refusesformulaby name, with a rename hint toexpression. The type still declaresformula, soformula.mdx's typed fence still teaches it.formulaalso has readers outside this claim (app-shell's metadata admin readsdef.expression ?? def.formula, and plugin-form'ssanitizereadsfieldDef.formula). This is the same snake-versus-spec family, left for the card's remainder.min_length/max_length(Text,Textarea,Markdown,Html,Richtext,EmailandUrl), and their pages teach them. They have live dual readers. Same family, outside Q3's ruling, which namedPasswordFieldMetadata.auto_compute/auto_updatestay declared. Nothing reads them, but neither is a snake twin of a spec member, so they are noted here only.Generated by Claude Code