feat(spec)!: eight list members of object-grid, object-kanban and object-calendar take the shape each block reads; object-grid columns held (#21464, stage 2) - #21559
Conversation
… object-kanban and object-calendar (#21464 stage 2) Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…ss-ledger counts (#21464 stage 2) Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…stage 2) Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. 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 283c449372a495f572a38ad2028223b7034b53e2 && git checkout 283c449372a495f572a38ad2028223b7034b53e2
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 10454b3afa94d49e6e424cc16fbbff3a898f3ad8 8b276755c216f2e2a122ea25c63fad49347a94a1 && git checkout -B drift-repro 10454b3afa94d49e6e424cc16fbbff3a898f3ad8 && git merge --no-ff 8b276755c216f2e2a122ea25c63fad49347a94a1
node scripts/docs-audit/affected-docs.mjs --json 10454b3afa94d49e6e424cc16fbbff3a898f3ad8 |
Contract reviewServed-tier: Isolated contract reviewer for PR #21559 (card #21464, stage 2 / S-list), written 2026-10-03T07:24Z. Inputs: the card body and its ten comments (triage Gate verdicts on this head, as they stood at 07:23Z: 34 check-runs — 31 success · 0 failure · 1 in progress ( ① Derived judgments1. 2. 3. 4. 5. 6. 7. 8. 9. The enumeration pin. Nine ② Semver levelLevel and arm RIGHT; one migration row WRONG on a fact. ③ Boundary flags
Implemented-by: VERDICT: FAIL Failed item (one): Generated by Claude Code |
… authored column's options, which ListColumn does not declare (#21464 stage 2) Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…hold (#21464 stage 2) Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Isolated contract reviewer for PR #21559 (card #21464, stage 2 / S-list), second record, after the fix round on the FAIL record Gate verdicts on this head, at this read: 42 check-runs, all completed — 38 success · 0 failure · 0 in progress · 4 skipped ( ① Derived judgments1. The failed item of
2. The delta between the heads is confined to the hold — RIGHT. In 3. 4. 5. 6. 7. 8. 9. 10. The enumeration pin, whole — RIGHT. Eight 11. The kit — RIGHT in form. D3 entry ② Semver levelLevel, arm and text RIGHT. ③ Boundary flags
Implemented-by: VERDICT: PASS The one item record Generated by Claude Code |
ACCEPT — PR #21559 @
|
Part of #21464
Clause-②: yes (narrowing)
Fix round (contract review
5966757164, item 1)The review found that the grid reads
optionsoff an AUTHOREDobject-gridcolumn. The group-header formatter (plugin-grid/src/ObjectGrid.tsx:2997-3001at the pin) takes the column whosefieldis the grouping field, readscolOverride?.options || objectDefField?.optionswith the column winning, and draws the group-header labels from it. objectui pins that as behaviour ingridGroupingMembers-8071.test.tsx:260-301.ListColumndeclares nooptions, so the by-referencecolumnsnarrowing refused a value the grid draws, a working writer by the seat's test (5966636964). This round:object-gridcolumnstoz.unknown()and re-adds it to the enumeration pin's ledger asheld-for-decision, with the readerObjectGrid.tsx:2158and:2997-3001and the carrier finding(plugin-grid): the grouped grid's header labels readcolumns[].options, a keyListColumnSchemarefuses, blocking the spec from typingobject-gridcolumnsobjectui#11544, in the shape the pin uses for kanbanconditionalFormatting(finding(plugin-kanban): kanbanconditionalFormattingaccepts a native rule dialect the spec's list-view member refuses, blocking the spec from typing it objectui#11522);options: the member docblock, the changeset's FROM → TO table, the changeset's "Who is affected" paragraph, and this body's A3 list.editablestays described as unread, which the review confirmed;The other eight members stand as reviewed.
What this does
Stage 2 (S-list) of the
ComponentPropsMapz.unknown()close-out, per triage5961300594, the seat answer5963787404(staging A) and the claim5965611825. Eight members the list blocks read with a fixed shape werez.unknown()(an array of it for the lists). Any value passed the component-props gate, and the renderer dropped or substituted an off-shape one with no report. Each member now takes the shape its block reads, measured at the.objectui-shapin89cad75d55. The family's ninth,object-gridcolumns, is held (above).object-grid·columnsz.array(z.unknown())z.array(z.unknown())plugin-grid/src/ObjectGrid.tsx:2158normalizeColumns; the group-header formatter reads an authored column'soptions(:2997-3001), whichListColumndoes not declare (objectstack-ai/objectui#11544)object-grid·fieldsz.array(z.unknown())z.array(z.string()), the measured shape (no list-view counterpart)plugin-grid/src/ObjectGrid.tsx:1946; the draw path looks each entry up asobjectSchema.fields[fieldName](:3969,:4012)object-grid·selectionz.unknown()ListViewSchema.shape.selection(SelectionConfigSchema), by reference.type,:4799-4812object-grid·selectablez.unknown()boolean,'single'or'multiple', the measured shape:4813-4815, handed to the table at:5333;components/src/renderers/complex/data-table.tsx:644resolveSelectionModeobject-grid·rowActionsz.array(z.unknown())ListViewSchema.shape.rowActions, by reference:1834-1835,string[]object-grid·bulkActionsz.array(z.unknown())ListViewSchema.shape.bulkActions, by reference:4763batchActions ?? bulkActions, thenresolveBulkActionsby nameobject-grid·batchActionsz.array(z.unknown())bulkActionsbatchActionsfirstobject-kanban·columnsz.array(z.unknown()){ id, title, cards?, limit?, className?, collapsed? }(module-privateObjectKanbanLaneSchema)plugin-kanban/src/ObjectKanban.tsx:1177-1188dispatches on the first entry;index.tsx:129-158buckets byidand keeps staticcards;KanbanImpl.tsx:540,:568,:742readlimit,className,collapsedobject-calendar·calendarz.unknown()ListViewSchema.shape.calendar(CalendarConfigSchema), by referenceplugin-calendar/src/ObjectCalendar.tsx:294-297returns the block as the config;:857,:1056,:1141read its five bindingsA static kanban card is a record row. Its
idandtitleare typed, and the rest of the card is the row's own values. That one newz.unknown()member,object-kanban columns[].cards[].*, carries arecordsline in the enumeration pin.bulkActions/batchActions(A4). The grid readsschema.batchActions ?? schema.bulkActions(ObjectGrid.tsx:4763), sobatchActionsis the second spelling of one capability, read first. objectui's own type calls it the legacy alias.ListViewSchemadeclares onlybulkActions. Both members now holdbulkActions's def, and neither is retired here.selectiondefault.SelectionConfigSchemadefaultstypetonone. The grid reads an object with notypeas ON (objectui#9837, ruling A-prime: presence enables). That default reaches only a parsed document, never the bag the grid reads. It is the list view's declaration either way, and objectui#9837 holds the question. The member's docblock records it.The census (A1), whole
A writer is a page-component node: an object literal naming the type, a literal annotated with the block's type, a
schema={{…}}on the block's React component, a call into a local helper that builds the node, or a direct parse through the row. Each member's value is resolved through same-file constants. The control isobjectNameon the same nodes. The instrument is a TypeScript-AST walk over every.ts,.tsx,.js,.json,.md,.mdxand.yamlfile, with fenced code in the documents parsed too.object-gridnodesobject-kanbannodesobject-calendarnodesobjectName49161683fb(examples/,packages/incl.packages/apps/,content/,skills/,apps/)89cad75d55(whole tree)columns(held, not narrowed)fieldsselectionselectablerowActionsbulkActionsbatchActionscolumnscalendarThe objectui values were parsed through the built rows on this branch. Across the eight typed members, objectstack holds one value (the protocol docs' lane example, which parses), and objectui holds 241 static values: 233 parse and 8 are refused. The grid
columnsrow is listed for completeness. Its 5 objectstack values (the showcase's two grids among them) and 310 objectui values meet no new shape here.Writer parse results (A3)
No refused value is one the renderer draws. Each of the 8 refused objectui values is a test fixture whose value the renderer drops, skips or refuses:
{ name }entries inbulkActions(bulkActionMembers-8071,bulkActionDefsUnusableMember-8730). The fold skips them, and both tests assert the skip.fields(serverGroupedSelectIdentity-11105). They copy the node the list view hands the grid at run time, as that test's own header says, so they are not an authored page.color(objectKanbanColumnMembers-8071). The console retired it, and the test marks it an undeclared member. The lane now refuses it with a prescription namingclassName.dateField/endFieldaliases (calendar-date-alias-refusal-8355). The test asserts their refusal.The 24 values that are not static are helper parameters,
.mapresults and the run-time hand-offs (plugin-view/src/ObjectView.tsx:2462,plugin-designer). None is an authored page.The held member. Under the first head,
object-gridcolumnswas narrowed by reference, and this list carried 40 refused grid-column values. Among them, "16 column keys the grid never reads" counted 14editableand 2options, and said no authored-column read names either key. That was right foreditableand wrong foroptions: the 2optionsvalues (gridGroupingMembers-8071) are drawn in the group headers. Socolumnsis held, and its 40 values are no longer refused by this PR.Under the triage caveat ("a narrowing that would refuse a measured writer is reported, not shipped silently"), this list is the report. A3 is applied with the seat's test (
5966636964): a writer is a value the renderer draws. A refused fixture that probes the renderer's drop is not one. On that test the eight members stand, andcolumnsis held.A2, per member
Typed by reference (4): grid
selection,rowActions,bulkActions; calendarcalendar. Typed to the same def (1): gridbatchActions. Typed to the renderer's read (3): gridfields,selectable; kanbancolumns. Held for a ruling (1): gridcolumns(objectstack-ai/objectui#11544). None is runner-forwarded.The pin (A5)
stagedlines leave the enumeration pin's ledger, and itslist-familystage goes with them.object-gridcolumns[]stays asheld-for-decision. Onerecordsline is added forobject-kanban columns[].cards[].*.no-readeris removed. It had no user since PR feat(spec)!: ai:chat_window is retired — refused by name at the schema door, the floating chat overlay is the AI chat entry point (#21504) #21531, the pin's own checks never require a kind to be in use, and no other file pins its vocabulary (git grep no-readerfinds only an unrelated prose use inscripts/pm/check-half-states.mjs).component-list-family-typed-members.pin.test.ts, as finding(spec):ComponentPropsMap['object-grid']keepsresizableColumnsas a live alias ofresizable, and types six membersObjectGridreads with fixed shapes asz.unknown()(rowHeight: 42passes every door) #21445's were. §1 checks that each declared shape parses, byte-identical, or to what the list view's schema answers where a default materializes. §2 checks each refusal by code and path, and for the kanban's two-array union by the arm's own issue. §3 checks identity with the list view's defs and the measured vocabularies. §4 checks the D3 registration.8b276755c2:object-gridrowActionswas reverted toz.array(z.unknown()).optional(), with no ledger line. It landed: anchor x1 to x0, replacement x0 to x1, blob285edfb205to2b6b2f8643. Red: Tests 4 failed | 95 passed (99). The enumeration pin's §1 received exactly[ 'object-grid rowActions[]' ], its census-equals-ledger control failed, and the companion pin failed its tworowActionscases. The restore was proven: blob after restore285edfb205== blob at HEAD, andgit diff HEADwas empty. Green rerun: Tests 99 passed (99).870e7327ec, vianode scripts/ablation-replace.mjsin wrap mode. The mutation revertedobject-gridselectiontoz.unknown().optional(), with no ledger line. It landed: anchor x1 to x0, replacement x0 to x1, blobe60c46e776to2111ab1538. Both pins went red: Tests 7 failed | 101 passed (108). The enumeration pin's §1 received exactly[ 'object-grid selection' ], and its census-equals-ledger control failed. The typed-member pin failed its fiveselectioncases. The restore was proven: blob after restoree60c46e776== blob at HEAD, andgit diff HEADwas empty. The green rerun showed Tests 108 passed (108). The pins import./component.zodfrom source, so no build sits between mutation and run.The rest of the kit (A6, A7)
component-type-vocabulary.ts: theKNOWN_COMPONENT_TYPESdocblock now listsai:chat_windowamong the kept retired rows (contract review5965171663item 10). Comment only.ui-object-grid-kanban-calendar-list-members-typed, and its step-18 rationale fragment at order 66. [Decision] pagerequireson the page kinds whose source is never compiled at save (react,full,slotted): refuse it at parse, or keep admitting a key nothing derives? #21459's PR feat(spec)!: pagerequiresonly on the compiled kinds — refused at parse on react, full and slotted pages (#21459) #21547 landed (72af58c621) while this was in review, with its own fragmentpage-requires-non-compiled-kind-refusedalso at 66, in a different gap. The second merge kept both fragments and both D3 entries (each id found twice inregistry.ts), andcheck:migration-registryreads the generated regions current with no regeneration owed. The registry header allows equal orders, which render inidorder.check:generated --fix(only what it proved stale):content/docs/references/ui/component.mdx, and the strictness-ledger counts forui/. Theui/count moves from 189 to 191 sites: +1 strict (the lane) and +1 passthrough (the card). In the fix round only the reference page was stale, and itscolumnsrow is back toany[].@objectstack/specminor, a BREAKING banner,Clause-②: yes (narrowing), a FROM → TO table, the measured census, and the ADR-0087 markerregistered.ObjectGridPropsParseddocblock had said the parsed state differs "on exactly one key —data". That was already untrue after finding(spec):ComponentPropsMap['object-grid']keepsresizableColumnsas a live alias ofresizable, and types six membersObjectGridreads with fixed shapes asz.unknown()(rowHeight: 42passes every door) #21445, and this change adds theselection.typedefault. It now names the defaults.Gates, at
8b276755c2(after mergingorigin/maince532184d1throughos-regen-merge.sh)node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 114 commands (9 paths, 688 changed lines, merge basece532184d1). Every exit code was written to disk before any pipe.--ranreconciled: 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN.turbo run build(72 tasks), all 114 ran and every one exited 0. On the first head, six gates answeredPREREQUISITE NOT MET(exit 3) before the build; those lines were kept apart, not counted.pnpm --filter @objectstack/spec build: exit 0.check:generated: exit 0, "All 15 generated artifacts are up to date".check:liveness,check:migration-registry,check:strictness-ledger,check:authorable-surfaceandcheck:api-surface: exit 0.pnpm --filter @objectstack/spec test: Test Files 606 passed (606), Tests 17952 passed, 1 todo.typecheck: exit 0,check:test-typecheck: OK.pnpm --filter @objectstack/lint test(the one import side ofComponentPropsMap): 119 files, 5620 tests passed.pnpm check:doc-authoringandpnpm check:nul-bytes: exit 0.check-widening-tells:--declaration nogives exit 4 with 9 T1 tells, all at the new lane schema's keys inside the formerz.unknown()bag. That is the shape the gate's own text rules a true refusal, so declareyes.--declaration yesgives exit 0.--eventpayload; their verdict lines are in the dev report.pnpm lint. Reason: they are CI-owned. objectui's source indexesSpecObjectCalendarProps['data'],SpecObjectFormProps['layout']andSpecObjectKanbanProps['swimlaneField'], none of them a narrowed member.Acceptance notes
columns: everyListColumnSchemamember is read by the grid at the pin (the draw path,useColumnSummaryat:3170-3177forsummary), and the grid reads one keyListColumndoes not declare,options, in the group headers. Typingcolumnswaits on finding(plugin-grid): the grouped grid's header labels readcolumns[].options, a keyListColumnSchemarefuses, blocking the spec from typingobject-gridcolumnsobjectui#11544.@objectstack/spec. The objectui block mirror takes the grid row by reference (ObjectGridBlockSchema.properties). No objectui edit was made here.#21464 remains open for S-form, S-metric and S-objectui-held. object-kanban
conditionalFormattingstays held on objectstack-ai/objectui#11522, and object-gridcolumnson objectstack-ai/objectui#11544.Generated by Claude Code