This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit 5d3f437
refactor(types,fields,app-shell): field metadata stops declaring host-written and field-level regex keys; geolocation leaves the union (objectui#12242) (#12293)
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 `main`
`74964ad6`. 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 is `Part of`, not `Fixes`: the card
stays open for that one position.
## What changed
- **`@object-ui/types`** (`field-types.ts`, `form.ts`):
- **Q1, moved off the authoring face.**
`BaseFieldMetadata.mobile_fullscreen`, `LookupFieldMetadata.idField`,
and `UserFieldMetadata.picker` / `subtitle` are no longer declared. Each
spot carries a "there is deliberately no X here" note that names where
the key lives now.
- `FormField` keeps `idField` / `picker` / `subtitle` at 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.
- **Q2, `never` tombstones.** `TextFieldMetadata.pattern` and
`pattern_message`, and `BaseFieldMetadata.validate`, are now `never`
tombstones. Each docblock names the spec's object-level `format`
validation rule, `{ type: 'format', name, field, regex, message }`.
`FieldValidationFunction` and `FieldConstraints` are marked
`@deprecated` because no member declares them now, the same treatment
round one gave `VisibilityCondition`.
- **Q3, out of the union.** `GeolocationFieldMetadata` leaves
`FieldMetadata`. The interface stays exported, marked `@deprecated`, and
the `field:geolocation` widget registration is untouched.
- `FieldValidationRules.pattern`'s docblock no longer says
`buildValidationRules` compiles a string pattern.
- **`@object-ui/fields`**:
- `buildValidationRules` no longer reads `pattern` / `pattern_message` /
`validate`. A `location` field'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).
- New package-local hand-off: `LongTextCarrierHandoff`
(`widgets/longTextCarrierHandoff.ts`, not exported from the index,
following the `renderHandoff.ts` / `chartRenderHandoff.ts` pattern).
`TextAreaField` and `RichTextField` read `mobile_fullscreen` through it,
intersected into their existing declared casts. Its producer is still
`ObjectForm`, unchanged.
- `LookupField` reads `idField` / `picker` / `subtitle` through an
annotated alias, typed as `Pick` of `FormField` over those three keys.
`FormField` is 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 builds `LookupField`'s carrier with `idField`
and the people-picker keys, taken from its own package-local
`RecordLookupBinding`. The literal was typed as `LookupFieldMetadata`
through the widget prop, so removing `idField` turned `app-shell`
`type-check` red (TS2353). The literal is now typed as
`LookupFieldMetadata` intersected with `Pick` of `FormField` over the
three keys. This is the producer side of the hand-off, fixed on the
producer.
- **`@object-ui/plugin-grid`** (`relationalMetaKeys.ts`): the
`widget-contract` verdict's docblock and the `idField` row's note now
name `FormField` instead of `LookupFieldMetadata`. This is internal
data, not exported, with no behaviour change. The derivation test's
alias control now expects `LookupField`'s second alias (`pickerConfig`).
- **Docs**:
- `text.mdx` teaches the object-level `format` rule, written as a prose
list (see Acceptance notes).
- `lookup.mdx` and `user.mdx` drop the moved keys from their field
metadata snippets and say where `idField` / `picker` / `subtitle` live
now. `user.mdx` shows them on a typed `FormField`.
- `blocks/forms.mdx`, `plugins/plugin-form.mdx` and the plugin-form
README no longer say a field-level string pattern is compiled.
## The fork: `dueLike` is held
The ruling's premise for Q1 is that the spec refuses each key and no
published object can carry it. For `dueLike` that is no longer true on
`@objectstack/spec`'s own `main`:
- objectstack-ai/objectstack#22503 (merged 2026-10-09, commit
`215e66204`, pending changeset `22227-field-deadline-due-like.md`)
declares `dueLike` and `settledWhen` on `FieldSchema`, for `date` /
`datetime` only. Its changeset names objectui's date cells as the
reader.
- The spec installed here (17.7.0) still refuses `dueLike`
(`unrecognized_keys`), so the adoption arrives with the next spec
release.
- Three example apps in objectstack write `dueLike: true` on published
object definitions: `app-crm`'s `activity`, `app-showcase`'s `task` and
`app-todo`'s `task`.
- Its readers take it off the object's field definition:
`resolveDueLike` in `@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 `dueLike` is 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.dueLike` and `schema-reference.md` are all
unchanged. No silent C.
## Census of the kept rows (starting from PR objectui#12257's table)
| Interface | Member / value | Installed `FieldSchema` (17.7.0) | Spec
`main` | Non-test readers and producers | This PR |
|---|---|---|---|---|---|
| Base | `mobile_fullscreen` | `unrecognized_keys` | not declared |
produced by `ObjectForm`; read by `TextAreaField`, `RichTextField`, and
the form renderer's built-in `textarea` branch (through its own props
type) | moved to `LongTextCarrierHandoff` |
| Base | `validate` | `unrecognized_keys` | not declared |
`buildValidationRules` | `never`; read removed |
| Text | `pattern`, `pattern_message` | `unrecognized_keys` |
object-level `format` rule only | `buildValidationRules` | `never`;
reads removed |
| Date, DateTime | `dueLike` | `unrecognized_keys` | **declared**
(objectstack#22503) | `resolveDueLike` (date and datetime cells),
`DetailViewFieldSchema.dueLike`, the detail enrichment copy | **held**
(fork) |
| Lookup | `idField` | `unrecognized_keys` | not on `FieldSchema` (only
on `InlineGridColumnSchema`) | `LookupField`; produced by `FormField`
entries, action parameters, `FlowReferenceField` and `GridField` inline
columns | moved; `FormField` declares it |
| User | `picker`, `subtitle` | `unrecognized_keys` | not declared |
`LookupField`, and `UserField` (defaults); produced by `FormField`
entries and `FlowReferenceField` | moved; `FormField` declares them |
| Geolocation | type `geolocation` | `invalid_value` at `type` | not a
`FieldType` | the `field:geolocation` widget | out of the union |
`field-metadata-family-retired-12242.test.ts` re-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 regex
`type['"]?\s*:\s*['"]geolocation['"]`, excluding tests, specs,
`content/**`, `docs/**`, markdown and changelogs:
- objectui `74964ad6`: 1 hit, `GeolocationFieldMetadata`'s own `type`
member. The control, the same query for `text`: 221 files.
- objectstack `origin/main` `c9b5b24b0`: 0 hits; control: 80 files.
- hotcrm `0f84c9c`: 0 hits; control: 16 files.
- **Writers of the other Q1 keys** in objectstack (`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-list `add.picker`, page-header `subtitle` and chart `subtitle`.
- `idField`: only `InlineGridColumnSchema` and unrelated identifiers.
- `dueLike`: the fork above.
## Export closure (Clause-②)
Measured on the built `.d.ts`: base `74964ad6` from 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. `@deprecated` is compared
separately.
- **Export names:** identical for every `exports` entry of
`@object-ui/types`, `@object-ui/fields` and `@object-ui/plugin-grid`. No
name is added or removed.
- **`@object-ui/types` declarations changed:**
- `BaseFieldMetadata`, `TextFieldMetadata`, `LookupFieldMetadata` and
`UserFieldMetadata` (the members above).
- `FieldMetadata` (one union member fewer).
- `FormField`: the same three member types, now spelled out.
`form-field-channel-keys-12130.test.ts` pins them `Equal` to `string` /
`'search' | 'default'` / `string[]`, each with `undefined`.
- `@deprecated` added on `FieldValidationFunction`, `FieldConstraints`
and `GeolocationFieldMetadata`.
- **`@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-grid` and `app-shell`. `app-shell` was red once
(the `FlowReferenceField` literal above) and green after that fix.
`types` was re-run at `31da2084`: exit 0.
- `vitest run`:
- `packages/types/`, plus the two new `fields` pins: 375 files, 10239
tests passed (at `31da2084`).
- `packages/fields/`: 252 files passed (1 skipped), 4031 tests passed
(at `bf418ddc`; no `fields` source changed after it).
- A targeted set of 28 files, 354 tests passed (at `bf418ddc`):
`plugin-grid` `relationalMetaCopySet.derivation` and
`lookupPickerKeys-7154`; the `plugin-form` location suites and
`ObjectForm.mobileFullscreen`; `app-shell` `FlowReferenceField*` and
`paramToField`; the `components` fullscreen-textarea suites;
`schema-catalog` `form-field-channel-census-12130`,
`form-field-channel-effects-12130` and
`form-builtin-branch-dom-allowlist-12155`; `plugin-detail`
`fieldEnrichment`.
- `scripts/__tests__/` (at `bf418ddc`): 186 files passed, 1 red:
`check-doc-component-types`, "this repository is green", on this
branch's first `text.mdx`. That page now teaches the rule in prose, and
the re-run of that file is green (at `155bd4e1`, with the `types`
suite).
- **Gates, each exit 0 at `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 `@example` compiles, or fails
exactly as its ledger row declares".
- `check:doc-json`, `check:doc-fences`, `check:doc-example-ids`, and
`check-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), and
`check: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)". Also
`check-changeset-no-major` and `check-changeset-fixed`.
- `check:eager-closure`: 3131.3 KB of 3204.6 KB.
- `check-governed-queue-guard --test` over the 28 changed paths: NOT
GOVERNED.
- **Targeted eslint** on the 19 changed `.ts` / `.tsx` files: 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.
- Population: `eslint.config.js`'s TS block (`**/*.{ts,tsx}`).
- The count is read from `--format json`.
- Invariance: the config enables no type-aware linting (no `project` /
`projectService`), and no rule in `eslint-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 `HEAD` and an empty `git diff HEAD`.
- **A1**, `pattern?: never` to `string | RegExp`: `tsc -p
tsconfig.test.json` red, TS2344 on the tombstone assertion and TS2578
(unused `@ts-expect-error`) on `_pattern`.
- **A2**, `idField?: string` put back on `LookupFieldMetadata`: red,
TS2578 on `_idField` and TS2344 on the moved-off assertion.
- **A3**, `GeolocationFieldMetadata` put back in the union: red, TS2578
on `_geolocation`.
- **A4**, `buildValidationRules`' field-level `pattern` read put back: 2
rows of the new `fields` pin red ("expected { pattern: … } to be
undefined", "expected [ 'maxLength', 'pattern' ] to deeply equal [
'maxLength' ]").
- **A5**, the field-level `validate` read put back: 1 row red ("expected
{ validate: [Function validate] } to be undefined"). The `location`
control row stayed green, as expected: that branch replaces
`rules.validate` wholesale.
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/types` minor, 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/fields` minor,
BREAKING for hosts that hand-write `pattern` / `validate` in code. Those
hosts lose the client-side check; the server enforces the object's
`format` rule on save.
- `plugin-grid` (internal note text) and `app-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.mdx` teaches the `format` rule as a prose list, not a code
fence. `check:doc-types` reads every fenced `type` value as a node type.
Its per-file ledger (where round one exempted `object.mdx`'s
`json_schema`) is outside this claim's file surface, and prose is
outside that gate's question.
- `ENRICHED_FIELD_METADATA_KEYS` (`plugin-detail`) still copies
`idField`, `picker`, `subtitle` and `dueLike` off 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.
- No in-repo path emits the form renderer's `validation.pattern` default
message any more; `buildValidationRules` was its only producer.
`check:i18n-dead-keys` (report-only, exit 0) already listed the pack key
as needs-review before this change. Carrier: none.
- `buildValidationRules` still reads `required_message`,
`min_length_message`, `max_length_message`, `min_message` and
`max_message`, which no type declares. The spec's answer on them was not
measured here. Carrier: none.
- `GeolocationFieldMetadata` stays exported, marked `@deprecated`,
rather than removed. That follows the `VisibilityCondition` precedent,
and the barrel (`index.ts`) is outside this claim's file surface.
Removing it would be a separate decision.
---
_Generated by [Claude
Code](https://claude.ai/code/session_015YrgVw7e65cs1ndbebt46G)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9d313eb commit 5d3f437
29 files changed
Lines changed: 646 additions & 283 deletions
File tree
- .changeset
- content/docs
- blocks
- fields
- plugins
- packages
- app-shell/src/views/metadata-admin/inspectors
- fields/src
- __tests__
- widgets
- __tests__
- plugin-form
- src
- plugin-grid/src
- __tests__
- types/src
- __tests__
- zod
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
21 | | - | |
| 20 | + | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
37 | 36 | | |
38 | 37 | | |
39 | 38 | | |
| |||
62 | 61 | | |
63 | 62 | | |
64 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
65 | 72 | | |
66 | 73 | | |
67 | 74 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
46 | | - | |
47 | 45 | | |
48 | 46 | | |
49 | 47 | | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
50 | 69 | | |
51 | 70 | | |
52 | 71 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
28 | | - | |
29 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | | - | |
41 | | - | |
42 | 40 | | |
43 | 41 | | |
44 | 42 | | |
| |||
47 | 45 | | |
48 | 46 | | |
49 | 47 | | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
50 | 66 | | |
51 | 67 | | |
52 | 68 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
130 | | - | |
| 130 | + | |
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| 54 | + | |
54 | 55 | | |
55 | 56 | | |
56 | 57 | | |
| |||
569 | 570 | | |
570 | 571 | | |
571 | 572 | | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
572 | 595 | | |
573 | 596 | | |
574 | 597 | | |
| |||
610 | 633 | | |
611 | 634 | | |
612 | 635 | | |
613 | | - | |
614 | | - | |
615 | | - | |
616 | | - | |
617 | | - | |
618 | | - | |
619 | | - | |
620 | | - | |
621 | | - | |
622 | | - | |
623 | | - | |
624 | | - | |
625 | | - | |
626 | | - | |
627 | | - | |
628 | | - | |
629 | | - | |
630 | | - | |
631 | | - | |
632 | | - | |
| 636 | + | |
633 | 637 | | |
634 | 638 | | |
635 | 639 | | |
| |||
0 commit comments