feat(spec)!: object-form customFields takes a closed runtime form field, and both forms' sections a page-block section shape (#21464, S-forms) - #21742
Conversation
…he ruled shapes (#21464, S-forms) [wip] Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…ers; ledger the new dropped-refinement sites [wip] Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…s, api-surface and export-origins [wip] Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
… line [wip] Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…p-reference probe values; the changeset counts helper parameters Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 33 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b6f02595a7c447b43f044238138e6061dffda98a && git checkout b6f02595a7c447b43f044238138e6061dffda98a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ff29410ed2133d8f6bcf955960f606e6bfbdbf54 716f4c6522652f16c82c086f6c11f77cc06b4c7a && git checkout -B drift-repro ff29410ed2133d8f6bcf955960f606e6bfbdbf54 && git merge --no-ff 716f4c6522652f16c82c086f6c11f77cc06b4c7a
node scripts/docs-audit/affected-docs.mjs --json ff29410ed2133d8f6bcf955960f606e6bfbdbf54
|
…object-manager dialog, and a closed option element [wip, red first] Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
… the form draws — label, value (a string, a number or a boolean), description, visibleWhen — closed [wip]
The option element was the form view's (FormSelectOptionSchema), whose value is
a stored field's lowercase identifier; the shipped object-manager dialog's
{ label: 'Box', value: 'Box' } options were refused.
Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…ction and option predicates in the expression conformance ledger [wip] Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…unts for the runtime option [wip] Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…n census — option lists evaluated, the object-manager writer parses, the sections refusals are the group-reference probes [wip] Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
Part of #21464
Clause-②: yes (narrowing)
What this does
The S-forms stage of the
ComponentPropsMapz.unknown()close-out. It executes the maintainer's rulings on forks 2 and 3 of the decision card #21704 (ruling record5978663135, batch #276, letters B and B), per the claim5979114945. Read points are at the.objectui-shapin2e818d0b51ec, under objectuipackages/unless named. Every cited reader file is byte-identical at objectuimainfd060f076, and the pin is an ancestor of thatmain.object-form·customFieldsz.unknown()name, only the members the form draws; itsoptionsentry is the closed runtime option the form's option controls drawobject-form·sectionsz.array(z.unknown())object-master-detail-form·sectionsz.array(z.unknown())sectionsto the form verbatim,plugin-form/src/MasterDetailForm.tsx:1692)The stored form view's
FormSectionSchema(view.zod.ts) is not edited. Forks 1, 4 and 5 keep their enumeration-pin lines. #21704 is not addressed beyond forks 2 and 3.The fix round (head
716f4c6522)The at-tier contract review of
b473439752(record5980612890) answered FAIL on one point, and the seat's order5980628111took its remedy 1. This round, by the sessionsession_016tKoy8NJa35Yih1FdzrVmn(the director seat's dispatch), changes:optionson the runtime form field is re-typed. It took the form view's option (FormSelectOptionSchema), whosevalueis a stored field's lowercase identifier, so the shippedobject-managerdialog's options ({ label: 'Box', value: 'Box' }, objectuiplugin-designer/src/ObjectManager.tsx:244-:245) were refused. It is now a closed option of the keys the form's option readers draw, measured at the pin (below):label,value(string | number | boolean, as objectui's runtime option declares on purpose,types/src/zod/form.zod.ts:142-:145),descriptionandvisibleWhen. Every other ruled member is unchanged..mapover a constant list as non-static and so never parsed the object manager's options; the instrument now evaluates it.expression-conformance.test.tsreported four UNCLASSIFIED surfaces (buildObjectFormRuntimeField.visibleWhen/.readonlyWhen/.requiredWhen,buildObjectFormSection.visibleWhen). They are classified inexpression-conformance.ledger.tsas rowcel-form-block-field-rule, and the new option'svisibleWhenas rowcel-form-block-option-visible, in the shape of the existingui/component.zod.tspage-block rows (feat(spec): ComponentPropsMap rows for action:button/group/menu/icon and element:definition-list/repeater #20420 is the precedent for a spec-lane edit of that ledger).simpleform type, an inline section field entry whose name is not in the field pool is skipped, while every other arm draws it, and objectstack now admits that entry on the page block objectui#11615.The widget-only keys question is answered A by the record: the field stays closed at the measured set.
Fork 2 B — the runtime form field
How a member reaches a draw.
customFieldsMerge.ts:78-108, called fromObjectForm.tsx:1178-1185and from every otherformTypearm, mergescustomFieldsover the generated fields. A member naming a generated field replaces its whole definition, and any other member is appended. A section's inline entry is drawn as it stands (sectionFields.ts:369-370). Either way the renderer hands the field to its widget as the metadata carrier (components/src/renderers/form/form.tsx:3171,field.field || field).The draw set, measured from the readers, member by member. These are not transcribed from objectui's
FormField.name,label,description,type,required,disabled,readonly,hidden,validation,visibleWhen,readonlyWhen,requiredWhen,colSpanform.tsxrenderFormFielddestructure:2675-2693;hidden:2696; the three rules:2732;validation:2795;colSpan:2988widget,multiple:2915-2918(widgetahead oftype, arity frommultiple)options,dependsOn:2934-2944(the cascading option list)placeholder,inputType:3185,:3174(the built-in input'stype)span,colSpanplugin-form/src/autoLayout.ts:162-172groupplugin-form/src/fieldGroups.ts:52(the object's field-group sections are derived over the drawn fields)rows·accept,multiple·dimensions·reference·min,maxfields/src/widgets/TextAreaField.tsx:102·FileField.tsx:147-148·VectorField.tsx:11·LookupField.tsx:326·NumberField.tsx:88-89minLength,maxLength,patternform.tsx:4080,:4146;patternrides onto the native control)returnType·summaryOperations·columnsFormulaField.tsx:22·SummaryField.tsx:15·GridField.tsx:588-589The option, measured from the option readers. Every option control the form reaches reads the same four keys:
label,valueform.tsx:3975-3977, the pick mapped back to the authored value bymatchOptionValue,:3955) and the four option widgetsSelectField,MultiSelectField,RadioField,CheckboxesField, which drawoptionDisplayLabel(core/src/evaluator/optionRules.ts:173) and stringifyvalueonly at the controlvisibleWhenresolveCascadingOptions→resolveVisibleOptions(optionRules.ts:97-110), fromform.tsx:2940and from each widget'suseCascadingOptionsdescriptionfields/src/widgets/LookupField.tsx:705-706)color(drawn only by the list and grid select cell renderer, never by a form option control),default, and objectui'sdisabled(read only by the standaloneselectnode renderer,components/src/renderers/form/select.tsx:91, which a form field never reaches) andicon(read by no option control on the form path) are refused, each with a prescription. Aliases namelabel(text,name,title),value(key,id) andvisibleWhen(visible,showWhen).Value types. Where this package already declares a member, its value schema is taken by reference: the object field's
FieldSchemamembers forrows,accept,dimensions,reference,minLength,maxLength,returnType,summaryOperationsandcolumns(inlineColumns);EvaluatedExpressionInputSchemafor the three*Whenrules; and the object field'sdependsOnlist beside a bare name. The option'slabelanddescriptionare the object field's option's own (SelectOptionSchema.shape, pinned def by def). ItsvisibleWhenis the evaluated predicate declared on the option itself, because the object field's option is also re-checked by the server on write and an inline option never is.grouptakes the field-group key grammar (SectionGroupKeySchema).label,descriptionandplaceholderare plain strings, because the renderer draws each as it stands and an inline locale map would be a React child.validationis{ required?, minLength?, maxLength?, min?, max? }. Each bound rule is{ value, message }.requiredis a string, because the renderer deletes the rule and reads only its message (form.tsx:2843,:2847).Read, and refused anyway, each with a prescription:
gridwidget's eight snake_case keys (min_rows,max_rows,allow_add,allow_delete,allow_reorder,total_field,add_label,sort_field), by the ruling. Their carrier is types(fields): thegridwidget's eight field-level keys are snake_case (min_rows,allow_add,total_field, …), so the spec's runtime form field cannot declare them under its camelCase rule objectui#11610.visibleOn(form.tsx:2767) and the legacycondition(:2717), two more spellings of the conditional-visibility predicate. ADR-0089 D1 makesvisibleWhenthe single canonical key. The only measuredvisibleOnwriter is a type-level test that never draws (below).id: the renderer keys the row byid ?? name(:2898), andnameis already unique in the drawn list.fields: the member claim of the section-divider row the form builds from a section, not a member of a field.The census's two keys outside objectui's 45 members:
groupis read (above), so it is typed.defaultValueis not read, so it is refused. The form opens oninitialValuesand on the object's declared defaults (schemaDefaults.ts), and objectui'sinitialRecordMerge-9760.test.tsxrow 7 pins that an inlinedefaultValueseeds nothing. The prescription moves the value into the block'sinitialValues.Fork 3 B — the page-block section shape
Section keys, read:
nameandlabel: the heading (ObjectForm.tsx:1693and the per-formTypemaps at:412,:492,:520,:556,:590).description.collapsible/collapsed(resolveSectionCollapse,:1702).visibleWhen(the divider row's predicate,:1720).columns(:1731).pane(SplitForm.tsx:445).group(sectionGroups.ts).fields.That is exactly
FormSectionSchema's key set, and objectui'sObjectFormSectiondeclares the same (types/src/objectql.ts:1497). The form view's group-reference rule rides with it (sectionGroupReferenceRefinement, the same derived-key lists).Canonical spellings only. A page block's
propertiesis never parsed on the way to the form, so the form view's two folds do not run there, and the form reads onlyvisibleWhenoff a section and only a numericcolumns(clampCol,:1599). A sectionvisibleOnand a stringcolumns: '2'were therefore dropped in silence. Both are refused with the canonical spelling.labelis a plain string, because the form draws the heading as it stands. No member carries a schema default.The three entry arms:
A field name.
The form view's
{ field }entry. Its members AREFormFieldSchema's object half, pinned def by def, with three differences that follow from how the page block reaches the form:visibleOnis refused, as above (the form readsvisibleWhen ?? visibleOn,sectionFields.ts:455);label,placeholderandhelpTextare plain strings (copied onto the drawn field as they stand,:386-388);spandrops the default the form view fills.Its sub-fields are this same entry, recursively, so the canonical rule holds at every depth.
The inline runtime form field. This is fork 2's instance, by identity (pinned).
Both rows share one section instance (pinned by identity).
A bare CEL predicate parses to its
{ dialect, source }envelope, as on every evaluated slot. Soobject-form's input and parsed types now differ, and the row leaves the type-alias pin's isomorphic family for anObjectFormPropsParsedalias (ADR-0122), asobject-master-detail-formdid on #20928. That alias is the one new export.The census (re-run in the fix round)
The instrument is a TypeScript-AST walk over
.ts.tsx.js.jsx.mjs.cjs.jsonand fenced code in.md/.mdx. It finds:type(flat or inproperties), literals annotated or asserted asObjectFormSchema/MasterDetailFormSchema, the block's React component'sschemaprop, and direct parses through the row. A member that is a parameter of the enclosing helper is resolved to the argument in that position at every same-file call site.customFieldsorsectionsin a file that names a form block.Values resolve through same-file constants and spreads, and, new in this round, through a
.mapover a constant list (an arrow with identifier parameters and an expression body). The first run read such a list as non-static, so the object manager's option lists were never parsed: that miss is the review's ① 7. Every static value was parsed through this head's rows, union arms judged by their best arm. Every value with a non-static part, and every refusal, was read by hand.Positive control. The same extracted values, parsed through
b473439752'scomponent.zod.ts, refuseObjectManager.tsx:239members 4 and 5 (icon,group) at everyoptions.N.value(invalid_format, the identifier rule). Through this head's rows they parse.customFieldssections(object-form·object-master-detail-form)316be321ef(the previous merge base; the three commitsmaingained since add no writer)examples/app-showcase/src/ui/pages/new-project-wizard.page.ts,packages/lint/src/validate-component-props.test.ts,packages/spec/src/ui/component.test.ts. Field names only.2e818d0b51ecmainfd060f0764054ec26802205b53010customFieldsat objectui:guideCrudAppRenders.test.tsx:170,objectFormCustomFieldsMembers-8071.test.tsx:191andsubmitTargetRefusal.test.tsx:155,:317;ObjectManager.tsx:239, the registeredobject-managercomponent's modal form. Its labels aret(...)calls (non-static); itsiconandgroupoptions are now evaluated fromICON_OPTIONSandOBJECT_GROUPS('Box','ShoppingCart','Custom Objects', …) and parse as runtime option values;EmbeddableForm.tsx:567hands to the form ascustomFields. Among them: the merge pins, the sections-and-members pins, the mobile fullscreen pin withrows/placeholder/field:textarea, the field-group row withgroup, andcontent/docs/guide/public-forms.md.main,apps/console/src/__tests__/objectname-neither-hint-11605.test.tsx:78and:84(a{ name, label, type }field each) parse as well.types/src/__tests__/p1-spec-alignment.test.ts:348: a type-annotated literal whosevisibleOn: '${data.industry != null}'is never rendered and is not CEL.plugin-form/src/__tests__/initialRecordMerge-9760.test.tsx:236: thememomember whosedefaultValuethe test's row 7 pins as seeding nothing.EmbeddableForm.tsx:567,ObjectView.tsx:2599, the arm forwards inDrawerForm,ModalForm,ObjectForm,SplitForm,TabbedForm,WizardForm) and helper parameters. No othercustomFieldsvalue has a non-staticoptions.sectionsat objectui:plugin-form/src/__tests__/formSectionGroupReference-7051.test.tsx:270,:307) states that this door refuses both shapes at parse: a section declaring neitherfieldsnorgroup("off-spec, so reachable only from a programmatic SDUI caller"), and a group-ownedlabel/collapsiblebesidegroup, which the renderer reports and ignores. These are the form view's own group-reference rule.plugin-designer/src/FieldDesigner.tsx:344:name,label,type,required,placeholder,disabled,options,visibleWhen). Its one non-staticoptions,flatTypeOptions, was read by hand:{ label: FIELD_TYPE_META[ft].label, value: ft }per field type, two keys the option declares;inputType);{ field }entry (cli/src/__tests__/spec-vocabulary-hint.test.ts:54);columns,paneandvisibleWhensections;main,objectname-neither-hint-11605.test.tsx:210(an inline field in a section).object-form, three on the master-detail form) are the form's own run-time hand-offs (ObjectForm.tsx:386,MasterDetailForm.tsx:1692,DrawerForm.tsx:878,ModalForm.tsx:1062,ViewPreview.tsx:150,ObjectView.tsx:2603) and test-helper parameters. Read by hand, they use declared keys only, with one exception:sectionStyleKeysRetired-13626.test.tsx, objectui's probe that a retiredclassName/gridClassNamereaches nothing (refused here, as objectui's own type refuses it).record:details, detail-view and object-view form-slot sections, which these rows do not judge.No measured working writer is refused under this head's shapes.
Changes
packages/spec/src/ui/component.zod.ts:validationblock, its option (new in the fix round), the form view's{ field }entry arm and the section shape — five module-private factories, each built once (a factory and not alazySchema, for the alias-integrity walk's reason, asobjectGanttMarker());ObjectFormPropsParsed;FormFieldSchema;FieldSchemaandSelectOptionSchemafrom../data/field.zod).packages/spec/src/ui/component-props-unknown-members.pin.test.ts:customFieldsand bothsections[]fork lines leave;customFields[]/sections[].fields[]visibleWhen/readonlyWhen/requiredWhen, an option'svisibleWhen, a grid column's two rules, andsections[].visibleWhen);summaryOperations.filter{}gets its ownsharedreason (FILTER_CONDITION: a querywhereover the child object's fields, judged by the filter schema's own refinement);forkstage's text drops the form field.packages/spec/src/ui/component-form-custom-fields-sections-typed.pin.test.ts(new):'Box','ShoppingCart',1,2,true,false, and an option'sdescriptionandvisibleWhen), the shippedobject-managerdialog's inline fields byte-identical, the predicate envelope (an option's barevisibleWhenincluded) and the absent case.codeandpath(the fix round adds an undeclared option key, an optioncolor, an optiondefault, an option with nolabel, and an objectvalue), plus the prescriptions, the option's included.description,label,value,visibleWhen; not the form view's option;labelanddescriptiondef-identical to the object field's option;valueaccepts a string, a number and a boolean and refusesnull,undefined, an object and an array), no snake_case key, the form view's section keys minusvisibleOn, the{ field }arm's members def by def, the inline arm's identity and the shared section instance.packages/qa/dogfood/test/expression-conformance.ledger.ts(fix round): rowscel-form-block-field-rule(the inline field's three rules and the section'svisibleWhen: objectuiresolveFieldRuleState→evalFieldPredicate, fail-soft-log) andcel-form-block-option-visible(the option'svisibleWhen:resolveCascadingOptions, fail-soft-log, UI gating only).packages/spec/src/type-alias-convention.pin.test.ts: theObjectFormPropsSchemaIso pin leaves (773 → 772), with its receipt. This file is outside the claim's list. It reds otherwise, and the convention's route is to declare the alias and delete the pin.packages/spec/dropped-refinements.baseline.json: five new sites. These are the refinements the new shapes carry by reference, whichz.toJSONSchemadrops: the section's group-reference rule, the inline grid column's rules, and the roll-up filter's comparand refinement.droppedRefinementSitesgoes 665 → 670. The fix round's option adds no site (the build's own check passes unchanged). This is hand-edited as the ledger requires, and no rule is weakened.18.ui-object-form-custom-fields-typed.tsand18.ui-object-form-sections-typed.ts(new D3 entries; the fix round corrects both census quotes and the option text, and the sections comment names theformSectionGroupReference-7051probes as the refused values);registry.ts: the semantic region is regenerated, and the step-18 rationale fragments sit at orders 78 and 79.RETIRED_KEYS_BY_MAJORrow: page-componentpropertiesis not parsed on the save or load path, and the refused values are nested member values.content/docs/references/ui/component.mdx(two member rows and three nested-shape tables; theoptionsrow now prints the runtime option),docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md(ui/198 → 204 andcomponent.zod.ts68 → 74, the six new strict sites),api-surface/ui.jsonandexport-origins/ui.json(ObjectFormPropsParsed)..changeset/21464-component-props-form-custom-fields-sections-typed.md:@objectstack/specminor, a BREAKING banner, theClause-②line, the ADR-0087registeredmarker naming both ids, FROM → TO (two option rows added) and the census, corrected.Measurements
These are at head
716f4c6522(merge baseff29410ed2:origin/mainwas merged in throughscripts/pm/os-regen-merge.shwith no conflict, and after a rebuild every generated artifact checks up to date) unless named.component.zod.tsis blob373d03336dd9fromc04a77716cto the head. Heavy runs went throughscripts/pm/os-verify-lock.sh, eachVERDICT command-exitread, and every exit code was captured before any pipe.Red first, the fix round. The new pin cases were committed alone (
1b08dec21d) and run against the unfixed rows: 9 failed, 61 passed (70). The runtime option values ('Box', a number, a boolean) and the object-manager dialog were refused (the old option refused evenvalue: 'a',too_smallunder the identifier rule), and an optioncolorwas accepted. On the fix (c04a77716c): 70 passed, and an undeclared option key is still refused (unrecognized_keysatcustomFields.0.options.0).Red first, the stage (at
b473439752). On the published@objectstack/spec@17.6.0(the npm tarball,ComponentPropsMap[row].safeParse), all 16 junk values are ACCEPTED:customFields: 42, a member with noname, a misspelled member,visibleOn,defaultValue,min_rowsandvalidation.required: true; onobject-form,sections: [42], a sectionvisibleOn,columns: '2',className, a section with neitherfieldsnorgroup, a{ field }entry'svisibleOnand an inline entry's unknown key; onobject-master-detail-form, a sectionvisibleOnand[42]. All 16 are refused with the code and path the pins assert.Tests:
pnpm --filter @objectstack/spec test: Test Files 614 passed (614); Tests 18285 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0.check:test-typecheckOK at 52 files / 246 errors / 135 signatures held.pnpm --filter @objectstack/lint test(its closure from the full build below): Test Files 119 passed, Tests 5627 passed.pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/expression-conformance.test.ts: Test Files 1 passed, Tests 7 passed. This is the test the Dogfood Regression Gate (3/3) failed on atb473439752.Public door.
validateComponentPropsover the built lint and spec: a stack shaped like the object-manager dialog ('Box','Custom Objects',1andtrueoption values) reports 0 findings; an optioncolorand an optionbogusreportcomponent-props-unknown-keyatcustomFields.0.options.0.color(with the prescription) andcustomFields.0.options.1.bogus.Ablation, the fix round (at
e948519edd;component.zod.tsblob373d03336dd9, the head's). The driver wrapsnode scripts/ablation-replace.mjsin its ownEXIT INT TERMtrap on the absolute path, with the HEAD blob as the restore target and an empty hash read as failure. The pins import./component.zodfrom source and the conformance test scans source, so no build or dist preflight is owed.options: z.array(buildObjectFormRuntimeOption())→z.array(z.unknown()), anchor x1 → x030c27aa10dd2cel-form-block-option-visiblewith its cover emptied1009587f14b8UNCLASSIFIED surface — add a ledger row (ADR-0060): ui/component.zod.ts:buildObjectFormRuntimeOption.visibleWhenAfter each leg the blob equals HEAD and
git diff HEADis empty. The stage's three legs (at3c4c7ce1a8:customFields,object-formsectionsandobject-master-detail-formsectionseach toz.unknown(), red 24, 21 and 5 failed) cover the members this round does not change.Derived gates.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 114 commands at716f4c6522(14 paths, +1375 / −75, under the 5000 threshold). All 114 ran at that head. On the first pass 112 exited 0, andcheck:skill-examplesandcheck:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: this fresh worktree had no package builds); both exited 0 after the fullturbo run build(72 tasks, 71 of them from the shared turbo cache, exit 0). The--ranreconciliation reads: 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN.Named extras, with this body as the
pull_requestpayload:check-changeset-no-major.mjs --base origin/main --eventexit 0 (declaration lineClause-②: yes (narrowing), armnarrowing);check-adr-0087-registration.mjsexit 0 (registered ui-object-form-custom-fields-typed, ui-object-form-sections-typed);check-empty-changeset.mjsexit 0.Narrowed lint.
eslint --no-inline-config --format jsonover the eight changed TS files: 8 files, 0 errors, 0 warnings.--print-configprintsundefined).eslint.config.mjsenables no type-aware linting (noparserOptions.project, its own note at about line 328), so this diff moves no untouched file's verdict.pnpm lintis CI's.Control bytes. A self-scan of the 14 changed files found no hit, and
check:nul-bytes(a derived gate) is green.NOT MEASURED:
pnpm lint. Reason: CI-owned; objectui was read at the pin and atmain, and not built against this spec.Acceptance notes (not filed)
ObjectFormblock's overlay typescustomFieldsasany[], and itssectionscome fromFormViewSchema(packages/spec/src/ui/react-blocks.ts, published asskills/objectstack-ui/references/react-blocks.md). The React tier is a separate, hand-declared contract for programmatic mounts, and a React prop is not authored metadata. Carrier: none.paneoff a split form andgroup/visibleWhen/ atruecollapse pair on a wizard step, inFormViewSchema's own refinement. These are row-level rules couplingformTypeto the sections, not section-shape rules, so the ruled shape does not carry them. On a page block, objectui answers a wizardgroupwith an empty step and a warning. Carrier: none.FormFieldvocabulary:scaleandstep(NumberField,SliderField),language(CodeField),maxSize,captureandcrop(FileField,ImageField), andLookupField'sdisplayField/idField/lookupColumns/ … No census writer uses one, and objectui's own strict face declares none. The record answered A: the field stays closed at the measured set and grows when a writer appears.type/widgetnamespace rule objectui's authoring face enforces (a colon-qualified id must namefield:) is not carried; both are strings here. Carrier: none.{ field }entry'svisibleOn, prints all three arms' refusals rather than the unknown-key rule id. Each arm's prescription is in the message, but the lint's single-arm routing needs exactly one arm with key-only issues. Carrier: none.simplearm skips an inline section entry its pool lacks (the review's ③ note 4). OnformType: 'simple'(the default),ObjectFormpasses its field pool into section resolution (plugin-form/src/ObjectForm.tsx:1617-:1627), andbuildSectionFieldscontinues on an entry the pool lacks (sectionFields.ts:480-:484). So a self-contained inline entry whosenameneither the object norcustomFieldsdeclares is skipped there, while the tabbed, wizard, split, drawer and modal arms resolve with no pool and draw it; objectui's own inline-sections test runs every arm exceptsimple(plugin-form/src/submitTargetRefusal.test.tsx:116,:331-:358). The spec admits the inline arm on everyformType, as fork 3 B rules; the gap is the renderer's and does not change the ruled shape. Carrier: plugin-form: on the defaultsimpleform type, an inline section field entry whose name is not in the field pool is skipped, while every other arm draws it, and objectstack now admits that entry on the page block objectui#11615.ObjectFormSection.fieldsTypeScript type has no{ field }arm (the review's ③ note 5). It is(string | FormField)[], andFormFieldrequiresname, so objectui's own TS face has no arm for the form view's{ field }entry thatsectionFields.ts:373-455draws (its zod mirror takesz.any()there). Carrier: the same card, plugin-form: on the defaultsimpleform type, an inline section field entry whose name is not in the field pool is skipped, while every other arm draws it, and objectstack now admits that entry on the page block objectui#11615, which names it.valueparses, and a select may refuse it (the fix round's out-of-scope note, carried here at the at-tier review's request). The runtime option'svalueisstring | number | boolean, as objectui's runtime option declares, so''parses. Neither the built-in select (form.tsx:3976) norSelectField(:212) guards an empty item value, and Radix Select refuses one. Refusing''on the shared element would over-refuse radio and checkbox options, and no writer authors it. Inference, not reproduced. Carrier: none.Deviations
issue_patch(PATCH /issues/21742) and sent without a footer. The first edit was stored byte-identical (32375 bytes sent and stored, read back over REST), so no footer was appended and none is carried; the attribution is the session named under the fix round above.type-alias-convention.pin.test.ts(the ADR-0122 route) anddropped-refinements.baseline.json(the ledger's own refusal printed the corrected entries). The fix round adds a third,packages/qa/dogfood/test/expression-conformance.ledger.ts, by the seat's order.{ field }arm readsFormFieldSchema's object half off its pipe (.in.shape), becauseview.zod.tsis not on the file surface and exports no base. One place does this. §3 pins every reused member's def against the form view's, so the read cannot drift unnoticed.visibleWhenis a new declaring position, not the object field's option's by reference. That one's describe and its ledger row (cel-select-option-visible) state the server's re-check on write, which an inline option never gets. So the option declares its own predicate, and the ledger carries a fifth row beyond the four the order named..mapevaluation. Its counts differ from the S-objectui-held stage's (27customFieldsvalues, 7 inline section entries), because pass 2 and the designer's code-composed nodes are counted here.