…l key under the one spelling GridFieldMetadata declares (objectui#11070 round 8)
- reorder: GridField reads `allow_reorder` (declared and taught) instead of
the undeclared `reorderable`, so `allow_reorder: false` removes the drag
handles.
- total: `total_field` is the one spelling read and is now declared; it names
the CHILD column summed (the spec's `amountField`). The `amount_field` /
`amountField` reads had no producer and are retired.
- `add_label` is declared (MasterDetailForm writes it from `addLabel`).
- `allow_duplicate` and `show_line_numbers` had no producer and no
declaration: retired under ADR-0049, keeping their default behaviour.
- `sort_field` is held as is (it has a live producer); GridField types its
reads as `GridFieldMetadata` plus that one named undeclared key.
- LineItemsPanel maps the grid total from `amountField` exactly as
MasterDetailForm does.
- grid.mdx teaches the field-level key set.
Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
Refs #11070
Clause-②: yes (narrowing).
GridFieldMetadata.allow_reordereither retires or becomes the read key, and the undeclared reads are declared or retired. It is priced in the changeset.As landed:
allow_reorderbecame the read key. The publishedGridFieldMetadataface widens by two optional members (total_field,add_label), and what the grid widget honours narrows by five retired reads (reorderable,amount_field,amountField,allow_duplicate,show_line_numbers). Priced minor with the break stated in.changeset/11070-grid-field-keys-round8.md(the fixed group never declaresmajor).Round 8 of objectui#11070: the grid widget's field-level keys, the round the round-7 seat review (
5926481945) answered A. Claim5927734710, sessionsession_01TdiauJaVCHuj45EzZGUxHh. Measured first; each key now has one spelling, and that spelling is declared and read.Per-key table (base
5638529e→ head)Readers and producers are cited by symbol, not by line address (AGENTS.md #11).
GridField(before → after)allow_reorderGridFieldMetadata→ samegrid.mdx's typed fencereorderabletotal_fieldGridFieldMetadataMasterDetailFormfield={…}(from the detail'samountField),LineItemsPanelgridField(fromamountField), consoleDevLookupdev pageamount_fieldamountField(on the widget)add_labelGridFieldMetadataMasterDetailForm(fromsubforms[].addLabel/details[].addLabel;detail.addin form mode)allow_duplicateallow_add)show_line_numberssort_fieldMasterDetailFormfromd.sortField, which is the authoreddetails[].sortFieldor elsederiveDetail's derived sort fieldUndeclaredGridKeys)min_rows/max_rowsGridFieldMetadataminRows/maxRows)allow_add/allow_deleteGridFieldMetadataLineItemsPanel(fromreadonly)GridFieldnow reads its config as aPartialofGridFieldMetadataplus the one named undeclared key, so a read of an undeclared key fails to compile (ablation A5 below).Where the keys come from (measured)
gridfield type in the spec. Installed@objectstack/spec17.5.0FieldTypehas nogrid, and neither has objectstackorigin/main7a606a9a. Everytype: 'grid'hit in objectstack (examples, platform objects,cligenerators,metadata-protocol) is a LIST VIEW, not a field.FieldSchema.inlineEdit: 'grid'withinlineTitle/inlineColumns/inlineAmountField, whichattachInlineSubforms(app-shellMetadataProvider) turns into derivedsubforms[]entries (childObject,relationshipField,inlineMode,title,columns,amountField; nosortField); the strict formsubforms[]entry (childObject,relationshipField,columns,amountField,totalField,title,addLabel,minRows,maxRows, plus its folded aliases; nosortField);object-master-detail-formdetails, which the spec typesz.array(z.unknown()); andrecord:line_items, which has no spec row (objectstack's showcase page writesamountFieldthere, nototalField).MasterDetailFormwritescolumns,total_field,sort_field,min_rows,max_rows,add_label(the six-key setmasterDetailDetailsMembers-8071pins, unchanged);LineItemsPanelwritescolumns,total_field,min_rows,max_rows,allow_add,allow_delete.type: 'grid': objectui'sexamples/schema-catalog/src/schemas/fields-grid/*(threeformdocuments whosefields[]entry istype: 'grid'; they writename,label,type,columnsandreadonly, no field-level key) andcontent/docs/fields/grid.mdx. objectstack: none. A census of all twelve snake_case keys across objectstackorigin/mainreads 0 files each (control:amountFieldhits 10).The two adapters' total, aligned
MasterDetailFormshowed the grid total wheneveramountFieldwas set;LineItemsPanelonly whentotalFieldwas ALSO set. The alignment is the onetotal_field:line inLineItemsPanel'sgridField, now mapped exactly asMasterDetailFormmaps it (amountField, else'amount'when onlytotalFieldis set). The widget key is the CHILD column summed (the spec'samountField), never the spec'stotalField.Held at the key:
sort_field(needs a seat answer)It has a live producer, so per the brief it stops at the key: no declaration, no retirement.
deriveDetailderives the sort field from the child object's fields (position/sort_order/sequence/line_no/line_number/sort) when none is authored, andMasterDetailFormwrites it assort_field; objectui#11144 made that derivation reach details with authored columns. The strictsubforms[]carrier does not declaresortField, and the spec has no inline sort-field key. Whether it becomes a declared key is in the report'sopen_questions.Reorder probe (
GridField, two rows)allow_reorder: falsereorderable: false5638529eThe base row is the same new pin file run against base
GridField: 6 red, 5 green.M3 (
scripts/measure-strict-authoring-face.mjs --json)Base
5638529e→ headf877cc6b(pre-merge; the merge brought no corpus change to these keys):totalsand every per-component row are identical (2229 nodes, 77 strict-refused, 46 strict-only, 31 red today; 601 documents, 72 strict-refused whole tree). Hypothesis H4 holds: the widget's config is built in code, and no corpus document writes a field-level key. Only two diagnostic fields moved:rendererMentionsOfUndeclaredKeys(a raw word count over renderer sources, moved by this diff's new comments and tests) andunloadableModules(an environmental reading: app-shell was unbuilt at the base run).Ablations (predictions written before each run)
Each mutation went through objectstack's
scripts/ablation-replace.mjs(anchor hit 1 → 0, blob changed, restored to HEAD withgit diff HEADempty).reorderableagain. Predicted 2 red; got 2 red, 9 green.total_field || amount_field || amountFieldagain. Predicted the two retired-spelling rows red; got 2 red, 9 green.LineItemsPanel. Predicted "amountFieldalone shows the footer total" red; got 1 red, 1 green.allow_duplicate: read it again. Predicted 1 red; got 1 red, 10 green.show_line_numbers: gate the#header on it again. Predicted 1 red; got 1 red, 10 green.GridFieldMetadatalosestotal_fieldandadd_labeland gains the retiredreorderable;@object-ui/typesrebuilt, and the dist markers checked (planted 0 → 1, declared 1 → 0 each). Predicted and got: fieldstsc --noEmitTS2339 on thetotal_fieldread and bothadd_labelreads; fieldstsc -p tsconfig.test.jsonTS2353 on thedeclaredfixture and the twogrid({…})calls, plus TS2578 (unused@ts-expect-error) onretiredReorderable;check:doc-snippets1 failed block,grid.mdxTS2353total_field. Restore leg: source restored,@object-ui/typesrebuilt, markers back (0 / 1 / 1),git status --porcelainempty.Carried check (round-6 note):
formula.mdx'saddDays/daysBetweenBoth evaluate. A probe through the installed
@objectstack/formula17.5.0ExpressionEngine.evaluate(CEL, pinnednow):addDays(record.created_at, 7)with2026-09-28gives2026-10-05;daysBetween(record.start_date, record.end_date)gives 29, and −29 with the arguments swapped. Control: an unregisteredaddWeeks(…)fails withfound no matching overload. Both are registered in objectstack'spackages/formulastdlib (registerStdLib).formula.mdxis unchanged.Local verification
At head
1f2f81cf(the merge oforigin/mainad58cc15):@object-ui/types,@object-ui/layoutand@object-ui/fieldsbuilds;type-checkgreen for@object-ui/types,@object-ui/fields,@object-ui/plugin-formand@object-ui/example-schema-catalog. Both new test files are in their packages'tsconfig.test.jsonprograms (--listFiles).packages/types/plus everyGridField*andLineItemsPanel*test andmasterDetailDetailsMembers-8071: 338 files, 8164 tests passed. Before the merge, atf877cc6b, the four whole packages (packages/types/,packages/fields/,packages/plugin-form/,examples/schema-catalog/) ran 733 files passed and 1 skipped, 15543 tests passed and 8 skipped. The merge touched none offields,plugin-formorschema-catalog, nor any of their dependencies' sources; it touched threetypestests, which the post-merge run covers.check:doc-snippets(closure built through the lock): 768 of 768 blocks, 0 failed.check:doc-fences,docs:check-links,check:control-bytes,check:i18n-keys, the line-citation gate (0 new) andcheck-type-check-coverageare green.check-changeset-presence,changeset:check,check-changeset-overwrite,check:changeset-claimsandcheck:pending-changeset-literalsare green. The claims gate names two pending changesets that cite a touched file (6357-basechema-bind-declaration, which namesgrid.mdx;6625-retire-fieldmeta-decimals, which namesGridField.tsx). Both paragraphs were read and still hold, so no dated note is owed..ts/.tsxfiles: 0 errors. The population is the eslint config's own**/*.{ts,tsx}blocks, and.mdx/.mdmatch none of them. The file count, 5, is read from--format json. The config is not type-aware (tseslint.configs.recommended, noparserOptions.project, no custom rule reading the filesystem), so this diff cannot move a verdict on an untouched file. Warnings went 49 → 48 on the three modified files (thecfganyis gone), plus 1 on the new line-items test (itsas anyadapter mock, the sibling tests' convention).check-governed-queue-guard --testover the changed paths: NOT GOVERNED.Acceptance notes
FormField/FormFieldSchemadeclare none ofmin_rows,max_rows,allow_add,allow_delete,allow_reorder, nor nowtotal_field/add_label. So aformfields[]grid entry that writes one reaches the widget at runtime, and the strict face refuses it by name. This predates the round for the first five keys; no corpus document writes any of them (M3 does not count them). Mirroring isform.ts/form.zod.tsterritory (open PR fix(components,types): eight renderers stop reading undeclared node keys; input-otp and loading read their declared spellings (objectui#11347) #11360), excluded by the claim, and is raised in the report'sopen_questions, not done here.docs/adr/0001-master-detail-subform.md(governed, Tier H) still namesallow_reorderas future work andsort_field. An ADR records the decision taken; left as is, the seat's standing disposition for ADRs.grid.mdx's "Features" list claims a "Pagination Preview … Showing X of Y" thatGridFielddoes not render. That prose is outside this round's key set; noted, not changed.fields-gridfixtures are unchanged: they write no field-level key, so they already carry only declared and read keys.sort_fieldis described above and left as is.Generated by Claude Code