Repository navigation
refactor(types,fields,app-shell): field metadata stops declaring host-written and field-level regex keys; geolocation leaves the union (objectui#12242) - #12293
Conversation
…nd field-level regex keys Ruling A on objectui#12242, round two. The authoring face of a field describes only what a published object can carry. - mobile_fullscreen, idField, picker and subtitle leave the field metadata types. mobile_fullscreen is typed on LongTextCarrierHandoff in @object-ui/fields; the three picker keys are declared on FormField, which LookupField reads them through. - pattern, pattern_message and validate retire as never tombstones, and buildValidationRules stops reading them. text.mdx teaches the object's format validation rule. - GeolocationFieldMetadata leaves the FieldMetadata union; the field:geolocation widget stays registered. - dueLike is held: @objectstack/spec main declares it on FieldSchema. Claude-Session: https://claude.ai/code/session_015YrgVw7e65cs1ndbebt46G Co-authored-by: Claude <noreply@anthropic.com>
…er on the hand-off, plus changesets FlowReferenceField builds LookupField's carrier with idField and the people-picker keys; it now types that literal as LookupFieldMetadata plus FormField's picker keys, since the field metadata types no longer declare them. buildValidationRules gains a pin for the retired field-level regex keys. Changesets for @object-ui/types and @object-ui/fields. Claude-Session: https://claude.ai/code/session_015YrgVw7e65cs1ndbebt46G Co-authored-by: Claude <noreply@anthropic.com>
…l two read-site quotes The text page's format-rule example moves out of a code fence: the doc-types gate reads fenced `type` values as node types, and a validation rule type is not one. The 6140 read-site pin quotes the two long-text casts as they read now, with the carrier hand-off intersected. Claude-Session: https://claude.ai/code/session_015YrgVw7e65cs1ndbebt46G Co-authored-by: Claude <noreply@anthropic.com>
The FormField picker keys no longer reference UserFieldMetadata, so the
import served only a {@link}, which object-ui/no-unused-imports refuses.
Claude-Session: https://claude.ai/code/session_015YrgVw7e65cs1ndbebt46G
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
|
…pt-positions Claude-Session: https://claude.ai/code/session_015YrgVw7e65cs1ndbebt46G Co-authored-by: Claude <noreply@anthropic.com>
…er keys are declared now objectui#12242 moved idField / picker / subtitle off the field metadata types, so FormField declares them itself. The pending 12130 bullet said "by reference to the field metadata types"; it now says what is true. The 12242 types changeset drops its "supersedes" sentence, which the corrected note no longer needs. Claude-Session: https://claude.ai/code/session_015YrgVw7e65cs1ndbebt46G Co-authored-by: Claude <noreply@anthropic.com>
✅ 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: Read at 2026-10-13T02:44Z · PR objectui#12293 (draft, base ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged:
Shape: none of the 29 paths is a governed surface ( ② Semver level
③ Boundary flagsDev deviations (report 6136825207), each answered or escalated:
Out-of-scope findings (carrier: none), each answered:
Reviewer's own observations:
Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #12242
Clause-②: yes
Round two of the field metadata family close-out. It executes maintainer ruling A on the card (comment 6135661503: Q1 A, Q2 A, Q3 B), from
main74964ad6. The authoring face of a field now describes only what a published object can carry. One position is held, not executed:dueLike, because the ruling's fork clause fired on it (see "The fork" below). That is why the first line isPart of, notFixes: the card stays open for that one position.What changed
@object-ui/types(field-types.ts,form.ts):BaseFieldMetadata.mobile_fullscreen,LookupFieldMetadata.idField, andUserFieldMetadata.picker/subtitleare no longer declared. Each spot carries a "there is deliberately no X here" note that names where the key lives now.FormFieldkeepsidField/picker/subtitleat the same types. They were declared by reference to the removed members; the entry now declares them itself (string,'search' | 'default',string[]). The ruling keeps the faces that already typed them.nevertombstones.TextFieldMetadata.patternandpattern_message, andBaseFieldMetadata.validate, are nownevertombstones. Each docblock names the spec's object-levelformatvalidation rule,{ type: 'format', name, field, regex, message }.FieldValidationFunctionandFieldConstraintsare marked@deprecatedbecause no member declares them now, the same treatment round one gaveVisibilityCondition.GeolocationFieldMetadataleavesFieldMetadata. The interface stays exported, marked@deprecated, and thefield:geolocationwidget registration is untouched.FieldValidationRules.pattern's docblock no longer saysbuildValidationRulescompiles a string pattern.@object-ui/fields:buildValidationRulesno longer readspattern/pattern_message/validate. Alocationfield's rule is now the stored-value check alone. No client pre-check of the object-level rule is added (the ruling did not take B).LongTextCarrierHandoff(widgets/longTextCarrierHandoff.ts, not exported from the index, following therenderHandoff.ts/chartRenderHandoff.tspattern).TextAreaFieldandRichTextFieldreadmobile_fullscreenthrough it, intersected into their existing declared casts. Its producer is stillObjectForm, unchanged.LookupFieldreadsidField/picker/subtitlethrough an annotated alias, typed asPickofFormFieldover those three keys.FormFieldis the existing face that declares them. The reads were untyped (any) before; behaviour is unchanged.@object-ui/app-shell,FlowReferenceField(the flow designer's record-lookup cell). It buildsLookupField's carrier withidFieldand the people-picker keys, taken from its own package-localRecordLookupBinding. The literal was typed asLookupFieldMetadatathrough the widget prop, so removingidFieldturnedapp-shelltype-checkred (TS2353). The literal is now typed asLookupFieldMetadataintersected withPickofFormFieldover the three keys. This is the producer side of the hand-off, fixed on the producer.@object-ui/plugin-grid(relationalMetaKeys.ts): thewidget-contractverdict's docblock and theidFieldrow's note now nameFormFieldinstead ofLookupFieldMetadata. This is internal data, not exported, with no behaviour change. The derivation test's alias control now expectsLookupField's second alias (pickerConfig).text.mdxteaches the object-levelformatrule, written as a prose list (see Acceptance notes).lookup.mdxanduser.mdxdrop the moved keys from their field metadata snippets and say whereidField/picker/subtitlelive now.user.mdxshows them on a typedFormField.blocks/forms.mdx,plugins/plugin-form.mdxand the plugin-form README no longer say a field-level string pattern is compiled.The fork:
dueLikeis heldThe ruling's premise for Q1 is that the spec refuses each key and no published object can carry it. For
dueLikethat is no longer true on@objectstack/spec's ownmain:215e66204, pending changeset22227-field-deadline-due-like.md) declaresdueLikeandsettledWhenonFieldSchema, fordate/datetimeonly. Its changeset names objectui's date cells as the reader.dueLike(unrecognized_keys), so the adoption arrives with the next spec release.dueLike: trueon published object definitions:app-crm'sactivity,app-showcase'staskandapp-todo'stask.resolveDueLikein@object-ui/fields, which the date and datetime cells call, and the record-detail enrichment copy. That is an authored key, which a runtime hand-off type cannot honestly carry.So
dueLikeis a reader no hand-off type can carry, and the key is moving toward the authoring face, not away from it. Per the fork clause, this run reports it and does not decide it.DateFieldMetadata.dueLike,DateTimeFieldMetadata.dueLike,date.mdx,datetime.mdx,DetailViewFieldSchema.dueLikeandschema-reference.mdare all unchanged. No silent C.Census of the kept rows (starting from PR objectui#12257's table)
FieldSchema(17.7.0)mainmobile_fullscreenunrecognized_keysObjectForm; read byTextAreaField,RichTextField, and the form renderer's built-intextareabranch (through its own props type)LongTextCarrierHandoffvalidateunrecognized_keysbuildValidationRulesnever; read removedpattern,pattern_messageunrecognized_keysformatrule onlybuildValidationRulesnever; reads removeddueLikeunrecognized_keysresolveDueLike(date and datetime cells),DetailViewFieldSchema.dueLike, the detail enrichment copyidFieldunrecognized_keysFieldSchema(only onInlineGridColumnSchema)LookupField; produced byFormFieldentries, action parameters,FlowReferenceFieldandGridFieldinline columnsFormFielddeclares itpicker,subtitleunrecognized_keysLookupField, andUserField(defaults); produced byFormFieldentries andFlowReferenceFieldFormFielddeclares themgeolocationinvalid_valueattypeFieldTypefield:geolocationwidgetfield-metadata-family-retired-12242.test.tsre-derives the installed-spec column on every run, each row behind an accepting control. The other columns are a reading taken once.Fork-clause measurements
type: 'geolocation'producers. The query was the regextype['"]?\s*:\s*['"]geolocation['"], excluding tests, specs,content/**,docs/**, markdown and changelogs:74964ad6: 1 hit,GeolocationFieldMetadata's owntypemember. The control, the same query fortext: 221 files.origin/mainc9b5b24b0: 0 hits; control: 80 files.0f84c9c: 0 hits; control: 16 files.packages/**,examples/**,apps/**) and hotcrm. Every hit was read:mobile_fullscreen: 0 in both.picker/subtitle: none on a field definition. The hits are related-listadd.picker, page-headersubtitleand chartsubtitle.idField: onlyInlineGridColumnSchemaand unrelated identifiers.dueLike: the fork above.Export closure (Clause-②)
Measured on the built
.d.ts: base74964ad6from a scratch worktree, head from this branch. The comparison is per top-level declaration, as a token multiset with comments excluded, so the type printer's union-member order cannot read as a change.@deprecatedis compared separately.exportsentry of@object-ui/types,@object-ui/fieldsand@object-ui/plugin-grid. No name is added or removed.@object-ui/typesdeclarations changed:BaseFieldMetadata,TextFieldMetadata,LookupFieldMetadataandUserFieldMetadata(the members above).FieldMetadata(one union member fewer).FormField: the same three member types, now spelled out.form-field-channel-keys-12130.test.tspins themEqualtostring/'search' | 'default'/string[], each withundefined.@deprecatedadded onFieldValidationFunction,FieldConstraintsandGeolocationFieldMetadata.@object-ui/fields: one added file,widgets/longTextCarrierHandoff.d.ts. No emitted declaration file references it, and no export entry reaches it. Every other declaration is unchanged.@object-ui/plugin-grid: no declaration changed.Tests and gates (at
31da2084, from the repo root; heavy runs under the shared verify lock)type-check, with the script name echoed and after the dependency-closure build (turbo, 28 of 28 tasks), exit 0 for each of@object-ui/types,fields,core,components,plugin-form,plugin-detail,plugin-gridandapp-shell.app-shellwas red once (theFlowReferenceFieldliteral above) and green after that fix.typeswas re-run at31da2084: exit 0.vitest run:packages/types/, plus the two newfieldspins: 375 files, 10239 tests passed (at31da2084).packages/fields/: 252 files passed (1 skipped), 4031 tests passed (atbf418ddc; nofieldssource changed after it).bf418ddc):plugin-gridrelationalMetaCopySet.derivationandlookupPickerKeys-7154; theplugin-formlocation suites andObjectForm.mobileFullscreen;app-shellFlowReferenceField*andparamToField; thecomponentsfullscreen-textarea suites;schema-catalogform-field-channel-census-12130,form-field-channel-effects-12130andform-builtin-branch-dom-allowlist-12155;plugin-detailfieldEnrichment.scripts/__tests__/(atbf418ddc): 186 files passed, 1 red:check-doc-component-types, "this repository is green", on this branch's firsttext.mdx. That page now teaches the rule in prose, and the re-run of that file is green (at155bd4e1, with thetypessuite).31da2084, with its own verdict line:check:doc-types: "Every documented component type is registered".check:doc-snippets: "Every covered documentation snippet compiles against the built types" (after the scoped build it names, 37 of 37 tasks).check:doc-examples: "Every covered@examplecompiles, or fails exactly as its ledger row declares".check:doc-json,check:doc-fences,check:doc-example-ids, andcheck-doc-links("Links are valid across 17 scan roots").check:readme-exports: OK, 0 fabricated.check:spec-symbols,check:self-import,check:phantom-deps,check:unreferenced-sources,check:handler-key-reads,check:esm-specifiers(specifier leg), andcheck:designer-field-key-parity("OK").check:new-line-citations: "0 new citation(s)".check:control-bytes: OK.check:changeset-claims: report-only. It names round one's types changeset, whose paragraph about the family pin stays true: this PR adds rows and changes none.check:pending-changeset-literals.check-changeset-presence: "20 source file(s) of 5 released package(s) changed, and this change declares 2 changeset(s)". Alsocheck-changeset-no-majorandcheck-changeset-fixed.check:eager-closure: 3131.3 KB of 3204.6 KB.check-governed-queue-guard --testover the 28 changed paths: NOT GOVERNED..ts/.tsxfiles: 0 errors and 233 warnings. The 16 files that exist at base carry the identical per-file counts there, and the 3 new files carry 0 of each.eslint.config.js's TS block (**/*.{ts,tsx}).--format json.project/projectService), and no rule ineslint-rules/reads another file. So this diff cannot move any untouched file's verdict.Ablations (each from committed state, through objectstack's
ablation-replace.mjs, wrap mode, under the lock)Each mutation hit its anchor, and the blob changed. Each restore proved blob equal to
HEADand an emptygit diff HEAD.pattern?: nevertostring | RegExp:tsc -p tsconfig.test.jsonred, TS2344 on the tombstone assertion and TS2578 (unused@ts-expect-error) on_pattern.idField?: stringput back onLookupFieldMetadata: red, TS2578 on_idFieldand TS2344 on the moved-off assertion.GeolocationFieldMetadataput back in the union: red, TS2578 on_geolocation.buildValidationRules' field-levelpatternread put back: 2 rows of the newfieldspin red ("expected { pattern: … } to be undefined", "expected [ 'maxLength', 'pattern' ] to deeply equal [ 'maxLength' ]").validateread put back: 1 row red ("expected { validate: [Function validate] } to be undefined"). Thelocationcontrol row stayed green, as expected: that branch replacesrules.validatewholesale.The first attempt at A2 to A5 was a no-op: each replacement contained its own anchor, so the tool refused it, and the command never ran. Those readings were discarded and re-run with replacements that consume the anchor.
Changesets
12242-types-kept-positions.md:@object-ui/typesminor, BREAKING, FROM → TO for every member and the union. Patch round 1 corrects the pending objectui#12130 changeset's bullet about these keys in place.12242-fields-validation-rules.md:@object-ui/fieldsminor, BREAKING for hosts that hand-writepattern/validatein code. Those hosts lose the client-side check; the server enforces the object'sformatrule on save.plugin-grid(internal note text) andapp-shell(the same carrier object, now built in a typed const before the render branch) carry no behaviour change and get no release entry; the presence gate counts their files under these two declarations.Acceptance notes (observed, not filed)
text.mdxteaches theformatrule as a prose list, not a code fence.check:doc-typesreads every fencedtypevalue as a node type. Its per-file ledger (where round one exemptedobject.mdx'sjson_schema) is outside this claim's file surface, and prose is outside that gate's question.ENRICHED_FIELD_METADATA_KEYS(plugin-detail) still copiesidField,picker,subtitleanddueLikeoff the served object definition. The installed spec refuses all four on a field, so for spec-compliant data those copies reach nothing. The ruling says behaviour is unchanged, so they stay. Carrier: none.validation.patterndefault message any more;buildValidationRuleswas its only producer.check:i18n-dead-keys(report-only, exit 0) already listed the pack key as needs-review before this change. Carrier: none.buildValidationRulesstill readsrequired_message,min_length_message,max_length_message,min_messageandmax_message, which no type declares. The spec's answer on them was not measured here. Carrier: none.GeolocationFieldMetadatastays exported, marked@deprecated, rather than removed. That follows theVisibilityConditionprecedent, and the barrel (index.ts) is outside this claim's file surface. Removing it would be a separate decision.Generated by Claude Code