Skip to content

fix(types,plugin-form,plugin-detail): field metadata members FieldSchema refuses retire as never, and their readers follow the spec keys (objectui#12242) - #12257

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12242-field-metadata-family
Oct 12, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12242-field-metadata-family

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Part of #12242
Clause-②: yes

The family close-out for "an exported *FieldMetadata type declares a member @objectstack/spec's FieldSchema refuses". I measured every member of every exported field metadata interface against the installed spec, then gave each refused member one verdict. Each member that can retire now retires on field-types.ts's own ?: never convention, the one GridFieldMetadata uses for min_rows. Two type values become the spec's FieldType spellings. Readers move to the spec keys, and the docs pages teach the spec's spellings. Members that a live widget reads, that have no field-level spec twin, and whose removal would drop a working capability are kept and reported for a decision, as the dispatch's stop condition requires.

What changed

  • @object-ui/types (field-types.ts):
    • BaseFieldMetadata.visible_on becomes a never tombstone. visibleWhen is declared by reference to FieldSchema.visibleWhen.
    • CurrencyFieldMetadata.currency becomes a never tombstone. currencyConfig is declared by reference.
    • These retire as never with no replacement key, because the spec has no twin and nothing reads them: filterable, min_date / max_date (on both date types), default_zoom, starting_number, schema and normalize.
    • AutoNumberFieldMetadata.type changes 'auto_number' → 'autonumber'. ObjectFieldMetadata.type changes 'object' → 'json'.
    • VisibilityCondition is marked @deprecated, since no member declares it any more.
  • @object-ui/plugin-form: ObjectForm's object-field builder and the sectioned forms' name-string entries stop reading visible_on. Both already carried visibleWhen.
  • @object-ui/plugin-detail (p2 position, outside the claim's listed file surface, named here per the dispatch): RecordDetailPanel's field build and RelatedList's cell bag forwarded the retiring currency and dropped currencyConfig. On those two surfaces, a spec field declaring currencyConfig: { currencyMode: 'fixed', defaultCurrency: 'EUR' } read as a bare number. Measured red before the change: RelatedList cells ["Tower","1,234.50","6,789.50"], with no €. Both sites now forward currencyConfig. They are readers of the retiring member, which is why they move in this change (E3 (b) / E6).
  • Docs: date, datetime, location, vector, currency, auto-number and object under content/docs/fields/ teach the spec's spellings. object.mdx now teaches the object-level json_schema validation rule in place of the field-level schema. It also no longer claims that the authored schema "is carried on the field metadata untouched" to the server: the spec refuses that key at publish.
  • scripts/check-doc-component-types.mjs (fixture triage, outside the listed file surface): the two pages' entries in that gate's per-file type ledger follow the new values (autonumber, json, json_schema). The old auto_number, string and boolean entries had gone stale.
  • Fixture triage: in summaryChip.displayLocale-9453.test.tsx, the EUR field cast to FieldMetadata is re-spelled as currencyConfig, which is what failed plugin-detail type-check. In sectionFields.blankGate-11262.test.ts, the visible_on chain row pinned the branch this change deletes. It is replaced by a load-bearing block: a predicate under visible_on is not carried and is not reported, and the same predicate under visibleOn still is (control).

E2: what reads GRID_FIELD_RETIRED_KEYS

Two runtime refusals read it, and no alias does: GridField draws a named refusal instead of the grid, and the FormFieldSchema zod mirror carries an aliasKeyRefusal arm per entry. No reader accepts a snake key through it.

⚠️ This change follows the ?: never member shape and its docblock mapping: each tombstone's @deprecated line names the key to write. It adds no new exported map constant. The grid map exists for those two runtime consumers, and this family has none. Only two of its retired members (visible_on, currency) even have a twin to map to. A constant with zero readers would be a new unpulled public export. The seat decides whether that matches the triage ruling's "mapped to its camelCase key". It is listed under open questions in the report.

Census (installed @objectstack/spec 17.7.0, objectui main 923504f)

Method: FieldSchema.safeParse on a minimal field of the interface's own type, carrying the member. A type that is not a spec FieldType was probed on the nearest spec type: autonumber, json, location, master_detail. Readers come from a non-test git grep of each spelling in packages/*/src. This table is a reading taken once. The retired rows are re-derived on every run by field-metadata-family-retired-12242.test.ts, and nothing re-derives the kept rows.

Interface Member / value FieldSchema Spec twin Non-test readers Verdict
Base visible_on unrecognized_keys, hint → visibleWhen visibleWhen (CEL) ObjectForm, sectionFields. Both already read visibleWhen too (a) retired → visibleWhen, declared by reference; both snake reads removed
Base filterable unrecognized_keys none none (the filterable hits are RelatedList / ActivityTimeline component props) (c) retired, no target
Base mobile_fullscreen unrecognized_keys none TextAreaField, RichTextField, the form renderer; produced by ObjectForm kept: stop condition
Base validate unrecognized_keys none at field level buildValidationRules kept: stop condition
Text pattern, pattern_message unrecognized_keys object level only: validations[] { type: 'format', field, regex, message } buildValidationRules (field level only) kept: needs decision (cross-level twin; no live client reads the object rule)
Currency currency unrecognized_keys currencyConfig (fixed + defaultCurrency) resolveFieldCurrency reads both; RecordDetailPanel / RelatedList forwarded only currency (p2) (a) retired → currencyConfig, declared by reference; the two forwarders now carry it
Date, DateTime min_date, max_date unrecognized_keys none (min / max are numbers) none (c) retired, no target
Date, DateTime dueLike unrecognized_keys none the date/datetime cell renderers; also DetailViewFieldSchema.dueLike kept: stop condition
Location default_zoom unrecognized_keys none none (c) retired, no target
Lookup idField unrecognized_keys none LookupField, GridField lookup cells kept: stop condition
Lookup reference_field unrecognized_keys, hint → referenceVia n/a n/a out of scope (objectui#12157)
AutoNumber type auto_number invalid_value at type autonumber widget key field:auto_number; mapFieldTypeToFormType maps both spellings type value → autonumber
AutoNumber starting_number unrecognized_keys none none (c) retired, no target
User picker, subtitle unrecognized_keys none LookupField, UserField kept: stop condition
User avatar_field unrecognized_keys n/a n/a out of scope (objectui#12157)
Object type object invalid_value at type json json and object both resolve to field:object type value → json
Object schema unrecognized_keys object level: validations[] { type: 'json_schema', field, schema } none (ObjectField checks syntax only) (c) retired; docs teach the object rule
Vector normalize unrecognized_keys none none (VectorField reads dimensions only) (c) retired, no target
Geolocation type geolocation invalid_value at type none (location is a different widget) GeolocationField kept: stop condition
Grid type grid and its members invalid_value at type; members refused on master_detail n/a GridField out of scope: objectui's own field type, keys ruled by objectui#11610 / objectui#11070

Accepted by the spec, no action:

  • Base: name, label, type, inlineHelpText, description, required, readonly, system, placeholder, defaultValue, sortable, dependsOn, unique.
  • Text-family minLength / maxLength.
  • rows on textarea / markdown / html / richtext.
  • Number min, max, precision, scale, step, useGrouping.
  • Currency precision, min, max.
  • Percent precision, scale, min, max.
  • format on date / datetime / time / phone / autonumber.
  • Select options, multiple, searchable.
  • File / image multiple, accept, maxSize.
  • Lookup reference, multiple, searchable, options, displayField, allowCreate, descriptionField, lookupColumns, lookupPageSize, lookupFilters.
  • Formula expression, returnType. Summary summaryOperations.
  • User multiple, lookupFilters.
  • Vector dimensions.
  • Slider min, max, step. Rating max. Master-detail reference.
  • Boolean, color, code, avatar, signature, qrcode and address: type value only, and each is a spec FieldType.

Not field documents, so not censused: SelectOptionMetadata (the option face, ruled under objectui#6153 / objectui#7014), LookupColumnDef / LookupFilterDef (nested in accepted keys), and UploadedFileMetadata (a field VALUE payload).

Tests (run on 00f58c4 unless noted)

  • field-metadata-family-retired-12242.test.ts (@object-ui/types). One pin per member: a fresh annotated literal carrying the retired key is refused (@ts-expect-error), the two spec twins are declared by reference and compile, both type values go both ways, and the installed spec's own answer is checked for each, behind an accepting control.
  • ObjectForm.visibleWhen-12242.test.tsx (@object-ui/plugin-form). Through the real ObjectForm: visibleWhen hides the field until the record satisfies the predicate (the live control), and the same predicate under visible_on gates nothing.
  • currencyConfigRelay-12242.test.tsx (@object-ui/plugin-detail). The fixed-EUR amount reaches DetailSection, RecordDetailPanel and RelatedList, beside a no-currency control. It was red before the fix on exactly the panel and the related list.
  • Ablations, each committed first, mutated via ablation-replace.mjs (anchor hit and blob change verified on disk), and restored to blob == HEAD with an empty git diff HEAD:
    • Putting ObjectForm's visible_on read back turns its pin red (visible_on is no gate: the field is drawn: expected null not to be null).
    • Putting sectionFields' visible_on ?? back turns 5 rows red.
    • Un-retiring currency (?: never → ?: string) turns tsc -p tsconfig.test.json red (TS2578: Unused '@ts-expect-error' directive at the _currency row).
    • One ablation stayed green, and this change was dropped because of it. A first draft also moved ObjectForm's computed-type gate from 'auto_number' to 'autonumber'. Restoring 'auto_number' left its pin green: the auto-number widget is display-only, and sanitize.ts strips both spellings, so the disabled flag changes nothing a person sees. That edit and its vacuous pin were removed. The position is under Acceptance notes.

Results at 00f58c4, all from the repo root under the verify lock:

  • pnpm exec vitest run per package, each exit 0:
    • packages/types/: 373 files, 10217 tests passed.
    • packages/plugin-form/: 180 files; 2200 passed, 1 skipped.
    • packages/fields/: 250 files (249 passed, 1 skipped); 4019 tests passed, 7 skipped.
    • scripts/__tests__/: 183 files (181 passed, 2 skipped); 5541 tests passed, 2 skipped.
    • The nine tests outside scripts/ that name check-doc-component-types (which this change edits): 9 files, 708 tests passed.
  • packages/plugin-detail/: exit 1, with one test timing out: record-details.hideFieldsUncast-9965 ("resolved the mirror"), 34s against its 15s budget, mid-suite on a shared box. Run alone on the same HEAD it passes 10/10 in 7s; the other 260 files passed. This reads as load, not a verdict.
  • type-check, after the dependency-closure build: @object-ui/types, fields, plugin-form, plugin-detail and plugin-grid, each exit 0, with the script name echoed. plugin-detail was red once, on the summaryChip fixture, before that fixture was re-spelled.
  • Green gates:
    • check:doc-types: "Every documented component type is registered".
    • check:doc-json.
    • check:designer-field-key-parity: OK.
    • check:spec-symbols.
    • check:new-line-citations: 0 new.
    • check:control-bytes: OK.
    • changeset:check, check-changeset-presence, check-changeset-no-major, check:changeset-claims.
    • check-doc-links, check-doc-example-ids, check-doc-fence-languages.
    • Governed-surface --test: NOT GOVERNED.
  • NOT MEASURED locally:
    • check:doc-snippets and check:doc-examples exit 2 (could not run): they need the whole package set built, and CI builds it. A narrowed measurement stands in. Every ts block of the seven touched pages (14 snippets) compiles --strict against the freshly built @object-ui/types / @object-ui/fields dist/. A planted control carrying min_date on a DateFieldMetadata fails with TS2322, so the harness can fail.
    • check:readme-exports could not judge 407 self-imports on the unbuilt tree. This change touches no README and no export.

The seat's disposition

The census found members the stop condition keeps, so they are not retired here: mobile_fullscreen, validate, pattern / pattern_message, dueLike, idField, picker / subtitle, and the geolocation type value. The seat switched the first line from Fixes to Part of, so objectui#12242 stays open as the anchor for those positions. Their disposition is recorded on the card. The merge of this PR decides none of them.

Acceptance notes (observed, not filed)

  • sectionFields' name-string chain still reads the object schema's visibleOn. The spec refuses that key too ("did you mean visibleOn → visibleWhen?"), so it is a read of a key no published object can carry. It is not a census member and is left as it is.
  • resolveFieldCurrency (@object-ui/i18n) reads a field-level currency before currencyConfig. That is a second spelling for field metadata, but the resolver is shared with presentation surfaces where currency is a spec key. It is reported, not extended, and not changed here.
  • app-shell RecordDetailView's section build forwards currency without currencyConfig. I did not measure whether DetailSection's enrichment from the object schema puts it back on the record page.
  • ObjectForm's computed-type gate still compares auto_number. It is inert for the display-only widget, as the ablation above shows.
  • auto-number.mdx teaches a {Q} (quarter) token. The spec's parseAutonumberFormat reads {Q} as a FIELD interpolation, so the page's own example {YY}Q{Q}-{000} renders 24Q-001 (measured with the spec's renderAutonumber), not 24Q1-001. That is a different defect class from this card, and it goes to the report for the seat.

Generated by Claude Code

…tire as never; form builders read visibleWhen and autonumber (objectui#12242)

- BaseFieldMetadata: visible_on and filterable retire as never tombstones;
  visibleWhen is declared by reference to FieldSchema.visibleWhen.
- CurrencyFieldMetadata: currency retires as never; currencyConfig is
  declared by reference.
- Date/DateTime min_date and max_date, Location default_zoom, AutoNumber
  starting_number, Object schema and Vector normalize retire as never with
  no replacement key: no twin in the spec, and nothing reads them.
- AutoNumberFieldMetadata.type is autonumber; ObjectFieldMetadata.type is json.
- ObjectForm and sectionFields stop reading visible_on; ObjectForm's
  computed-type gate reads autonumber, the spelling a spec object carries.

Claude-Session: https://claude.ai/code/session_0149MdowTfyuWZuSSESLUYnE
Co-authored-by: Claude <noreply@anthropic.com>
…rop the inert autonumber gate change (objectui#12242)

The computed-type gate's `auto_number` arm is left as it was: an ablation
showed the flag changes nothing a person sees (the auto-number widget is
display-only and the payload strips both spellings), so changing it bought
no behaviour and no pin could fail on it.

Claude-Session: https://claude.ai/code/session_0149MdowTfyuWZuSSESLUYnE
Co-authored-by: Claude <noreply@anthropic.com>
… (objectui#12242)

Claude-Session: https://claude.ai/code/session_0149MdowTfyuWZuSSESLUYnE
Co-authored-by: Claude <noreply@anthropic.com>
…red members and type values (objectui#12242)

- date/datetime: no range key; location: no zoom key; vector: no
  normalize key; currency: currencyConfig.
- auto-number: type autonumber, no starting value key.
- object: type json; a JSON value's shape is an object-level json_schema
  validation rule, not a field-level schema key.
- check-doc-component-types: the two pages' exemption entries follow the
  new type values (autonumber, json, json_schema); string and boolean were
  stale.
- changesets for @object-ui/types (minor, breaking) and
  @object-ui/plugin-form (patch).

Claude-Session: https://claude.ai/code/session_0149MdowTfyuWZuSSESLUYnE
Co-authored-by: Claude <noreply@anthropic.com>
…eld's currencyConfig (objectui#12242)

RecordDetailPanel's field build and RelatedList's cell bag copied the
field-level `currency` (which FieldSchema refuses) and dropped
`currencyConfig` (the spec's fixed-currency declaration), so a fixed-EUR
amount read in the tenant's currency there. The record page's sections
already carried it. The summary-chip fixture re-spells its EUR field as
currencyConfig.

Claude-Session: https://claude.ai/code/session_0149MdowTfyuWZuSSESLUYnE
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 71 chunks) 3129.1 KB 3204.6 KB
Main entry chunk (gzip) 73.1 KB 350 KB
Entry file index-BZIXQ5qX.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 20.69KB 7.62KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 588.51KB 141.71KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 244.49KB 68.27KB
fields (index.js) 280.00KB 71.39KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 251.12KB 66.29KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.36KB 46.26KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 255.09KB 70.91KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.35KB 31.54KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 00f58c4c28026ef8db68e626aeaa0ccc8994b435
Local-runs: none

Inputs: card objectui#12242 (body, triage 6123790917, claim 6124373921, os-dev-report 6125454268), PR #12257 (body, its 21-file list, and the net diff against its merge-base 923504f on main), and the check-runs on the head. Spec claims were cross-read against @objectstack/spec 17.7.0, the version the lockfile resolves. Reader sweeps are git grep over the head's tracked tree, non-test sources only.

Check-runs on the head: every run completed; every non-skipped run is success (Type Check, Lint, Repo Gates, Spec Main Shape Gate, Line Citation Gate, Governed Surface Queue Guard, the five Changeset gates, Build Docs, Build and E2E, Bundle Analysis, Test dist pins and all twelve Test shards). The three skipped rows are matrix placeholders and dependabot. Governed Surface Queue Guard reads NOT GOVERNED, so this record is owed by the PR's Clause-②: yes, not by a governed path.

① Derived judgments

Public surface of @object-ui/types (the FieldMetadata family, exported from the package entry):

  1. BaseFieldMetadata.visible_on → ?: never — RIGHT. The spec's FieldSchema refuses the key by name (the pin asserts unrecognized_keys and a visibleWhen hint on the installed spec; CI green). Both readers (ObjectForm's object-field builder, sectionFields' name-string chain) are removed, and no non-test visible_on read remains at the head.
  2. BaseFieldMetadata.visibleWhen added as SpecField['visibleWhen'] — RIGHT. By-reference typing of a key FieldSchema declares (a CEL string or the { dialect, source } envelope); both form builders already carried it. A widening, declared in the changeset.
  3. BaseFieldMetadata.filterable → never — RIGHT. No spec twin; no field-metadata reader at the head (the remaining filterable spellings are component props).
  4. CurrencyFieldMetadata.currency → never, currencyConfig added as SpecField['currencyConfig'] — RIGHT on the type: the spec declares currencyConfig (currencyMode, defaultCurrency), and its own guidance text says currency is not a field key. Readers of the retired member remain; see ③ E1.
  5. DateFieldMetadata and DateTimeFieldMetadata min_date, max_date → never — RIGHT. No twin (the spec's min / max are numbers); zero readers at the head.
  6. LocationFieldMetadata.default_zoom, AutoNumberFieldMetadata.starting_number, VectorFieldMetadata.normalize → never — RIGHT. No twin; zero readers at the head.
  7. ObjectFieldMetadata.schema → never — RIGHT. ObjectField has no schema read. The spec's object-level json_schema validation rule (field, schema, plus the base rule's name, message, events) is what object.mdx now teaches, and the page's example matches that rule's shape.
  8. AutoNumberFieldMetadata.type 'auto_number' → 'autonumber' — RIGHT. autonumber is a spec FieldType; auto_number is not. No second union member carries 'autonumber'. The form widget key stays field:auto_number, which field-type-alias.ts resolves from both spellings, so nothing a form renders changes.
  9. ObjectFieldMetadata.type 'object' → 'json' — RIGHT. json is a spec FieldType; object is not. No second union member carries 'json'; json resolves to the same field:object editor and the same JsonCellRenderer.
  10. VisibilityCondition kept exported and marked @deprecated — RIGHT. Nothing is removed, so no accept-set change beyond the tag.
  11. The members the census found refused by the spec but read by a live widget with no twin (mobile_fullscreen, validate, pattern, pattern_message, dueLike, idField, picker, subtitle) and the geolocation type value are unchanged — RIGHT as a diff: no half-retirement, and the open positions stay on the card. The authoring face still offers keys the spec refuses there; that is the escalated remainder in ③, not a defect of this diff.

Other packages: @object-ui/plugin-form changes behaviour only (a predicate under visible_on gates nothing); @object-ui/plugin-detail is a bug fix (currencyConfig forwarded by the panel and the related list, beside the already-correct DetailSection enrichment). Neither moves an exported type. scripts/check-doc-component-types.mjs is a gate ledger, not a published surface; its entries follow the pages.

② Semver level

Clause-②: yes

The PR body and the @object-ui/types changeset both declare it, and the diff earns it twice: it widens the exported authoring face (visibleWhen and currencyConfig, by reference) and narrows it (eleven never tombstones and two type values). No single arm is written, which is right when both apply.

  • @object-ui/types: minor, with a BREAKING section carrying the FROM → TO mapping and the one-line fix for every retired member and both type values — RIGHT under this repo's version policy (objectui's own breaking changes ship as minor; the fixed group never declares major). Changeset Bump Policy and the no-major gate are green.
  • @object-ui/plugin-form: patch — RIGHT. No exported surface moves; the behaviour change is stated FROM → TO.
  • @object-ui/plugin-detail: patch — RIGHT. A fix, stated FROM → TO.
  • Presence: three released packages have src changes and three changesets are declared (Changeset Declaration green). The docs pages and the scripts ledger publish nothing.
  • The types changeset names every member and both type values the diff retires, both keys it adds, and the @deprecated tag on VisibilityCondition. Nothing the diff publishes is undeclared.

③ Boundary flags

Dev flags (the report's deviations):

  • D1 — files outside the claim's listed surface (RecordDetailPanel, RelatedList, their pin and changeset, the summaryChip fixture; scripts/check-doc-component-types.mjs): ANSWERED, accepted. currency entered the census under the claim's "any member the dev's measured census adds"; the panel and the related list are readers of it, and the claim moves a retiring member's readers in the same change. The fixture and the doc-types ledger are fixture triage, which the claim lists. All are named in the PR body.
  • D2 — no exported retired-keys map constant (also open question 4): ANSWERED, accepted as shipped. The triage's "mapped to its camelCase key" is met by each tombstone's @deprecated line and by the by-reference twin where one exists. GRID_FIELD_RETIRED_KEYS exists for two runtime refusals this family has none of; a zero-reader export would widen the public surface with no pull, and the triage's own "a new gate defaults to no" cuts the same way.
  • D3 — census beyond the name-only lead; stop-condition members kept: ANSWERED for scope (the claim admits census additions). The kept members are escalations E-Q1 to E-Q3 below.
  • D4 — the first commit message describes a reverted draft change: ANSWERED, no effect on the landed tree; a squash landing carries the PR title, which is accurate.
  • D5 — closing keyword: ANSWERED. The body's first line is the Part of form naming the card, right for a card with open positions.
  • D6 — objectui has no dispatch-gates.mjs, so the gate list was hand-derived: ANSWERED, immaterial here; the head's check-runs are the gate verdicts.
  • D7 — model-free commit trailers: ANSWERED, compliant.

Open questions (the report's open_questions):

  • E-Q1 — runtime-only widget keys (mobile_fullscreen, idField, picker, subtitle, dueLike) on the authoring face: ESCALATED to the owning seat and the maintainer. This is an objectui AGENTS.md #0.1 hand-off-type ruling; the dev recommends A (move them onto a runtime hand-off type). mobile_fullscreen was on the directed surface; keeping it is the claim's own "a reader that cannot be moved is reported", and that holds.
  • E-Q2 — pattern / pattern_message / validate, whose only twin is the object-level format rule: ESCALATED. The dev's A removes a client-side pre-check and re-points text.mdx; that is a behaviour decision. Until ruled, text.mdx still teaches keys the spec refuses.
  • E-Q3 — the geolocation type value: ESCALATED. A ruling by analogy with grid (objectui#11610 / objectui#11070) is not this record's to make.
  • Q4 — answered under D2.

Flags this review raises:

  • E1 — the retired currency member keeps readers. RecordDetailPanel and RelatedList still forward currency beside the new currencyConfig, and resolveFieldCurrency (@object-ui/i18n) reads currency before currencyConfig. The PR's acceptance notes declare the resolver and its reason (shared with surfaces where currency is a spec key; the spec's own FieldSchema guidance records that reader). ESCALATED: the seat decides whether the currency read family gets a card. Not a FAIL: the type-level retirement, the forwarders' changeset text and the pins are accurate.
  • E2 — app-shell RecordDetailView forwards currency without currencyConfig, the same shape fixed in plugin-detail, declared UNMEASURED by the dev. ESCALATED: the seat has it measured (DetailSection's enrichment from the object schema may restore it) or files it.
  • E3 — out-of-scope finding, class (c): auto-number.mdx still teaches a {Q} quarter token the spec's parseAutonumberFormat reads as a field interpolation. ESCALATED: a card, filed by the seat.
  • E4 — prose face, for the dispatching seat's ACCEPT: the SchemaExample fixtures embedded in auto-number.mdx and object.mdx are form nodes whose fields[].type is auto_number / object, the form widget vocabulary, and object.mdx's intro still lists object among the "field types" the editor serves. Both are correct in their own vocabulary; the pages do not say there are two. Not this record's face.
  • E5 — noted, no action owed by this PR: record-details.hideFieldsUncast-9965 timed out once locally under load (green alone and in CI); the flaky-test rule says find the race.

Implemented-by: claude/issue-12242-field-metadata-family
Reviewed-by: session_0149MdowTfyuWZuSSESLUYnE

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 12, 2026 14:01
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 12, 2026 14:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 12, 2026
Merged via the queue into main with commit 10aba37 Oct 12, 2026
34 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12242-field-metadata-family branch October 12, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: types plugin tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants