Repository navigation
feat(spec)!: an object-grid block's exportOptions is the list view's export options object, and a bare format array is refused (#21229) - #21287
Conversation
…ptions object by identity (#21229) The page-component row was z.unknown(), so a bare ['csv'] passed every door and ObjectGrid exported its csv/json default. The row now takes ListViewExportOptionsSchema - the strict five-member object, moved verbatim with its format enum and 'pdf' prescription into the non-barrel ui/list-view-export-options.ts (VIEW_HISTORY into ui/view-history.ts) - by identity, not the list view's lifting union. A bare array is refused with the object form named. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…tionale and changeset (#21229) Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…tness counts (#21229) Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…ject-grid-export-options
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 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 22bdd03121e88757b3a36bee9dd2aa16c006873f && git checkout 22bdd03121e88757b3a36bee9dd2aa16c006873f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 748b2407235a6a32d2cd7f61f36e1f69f95d775e 032865c93cd45e071663e062a385af5f0b1b50c2 && git checkout -B drift-repro 748b2407235a6a32d2cd7f61f36e1f69f95d775e && git merge --no-ff 032865c93cd45e071663e062a385af5f0b1b50c2
node scripts/docs-audit/affected-docs.mjs --json 748b2407235a6a32d2cd7f61f36e1f69f95d775e
|
Contract reviewServed-tier: Isolated contract review of PR #21287 for card #21229, on the triage ruling ① Derived judgmentsAccept set of
The sharing change.
ADR-0087 disposition (D3 only, entry
Registry order. The STEP18 rationale fragment takes Regenerated pages and ledgers. The pin test ( Published prose, sentence by sentence. Changeset: title TRUE; objectui at the pin Check-runs on ② Semver levelThe diff narrows the accept set of a published authoring surface ( ③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #21229
Clause-②: yes
Dispatched by the PM claim
5943166878(PM loop round 1,domain:specseat 1), on the triage ruling5939380297.ComponentPropsMap['object-grid'].exportOptionswasz.unknown(). It now takesListViewExportOptionsSchema, the list view's strict five-member export options object, by identity. It does not take the list view's union, whose legacy bare-array arm lifts to{ formats }. A bare array is refused with the object form named. The changeset carries the(narrowing)arm, the BREAKING banner atminor, and the ADR-0087 markerregistered ui-object-grid-export-options-closed. It is D3 only; the reading is below.What changed
packages/spec/src/ui/list-view-export-options.ts. It holds the export options block, moved verbatim out ofview.zod.ts: the retired-'pdf'prescription, thecsv/xlsx/jsonformat enum, and the strict five-member object.invalid_typeon an array input with a prescription naming{ formats: ['csv', 'xlsx'] }.strictObject()builds one:closedObject(z.object(shape, { error }).strict())with the same registeredstrictObjectErrordeclaration. Theprimehandle is forwarded, soclosedObject's terminal unknown-key contract is kept.packages/spec/src/ui/view-history.ts.VIEW_HISTORYmoved here, verbatim, so the moved block keeps the refusal sentence it has always carried.view.zod.tsimports both modules, and its 53VIEW_HISTORYuses are unchanged.component.zod.ts:exportOptions: ListViewExportOptionsSchema.optional(). The "Unvalidated here" describe text is gone. A docblock records the ruling and the door reading.18.ui-object-grid-export-options-closed.ts, regenerated intomigrations/registry.tsbygen:migration-registry.STEP18_RATIONALEfragmentui-object-grid-export-options-closed,order: 59.mainholds 57 (PR feat(spec)!: record:line_items gets its ComponentPropsMap row, its columns are the inline grid column contract, and the showcase Tasks grid binds by name (#21142) #21244) and 58 (PR feat(spec)!: an analytics dataset dimension's and measure's field is a column reference (#21220) #21240) at the mergeb91e40bc89. It is inserted at its sorted position.component-object-grid-export-options-members.pin.test.ts, rewritten. The docs(spec):ComponentPropsMap['object-grid'].exportOptions's describe names 2 of the 5 members the renderer reads #17166 version asserted the key was still unvalidated, so that this change would red there and be decided deliberately; it did. The new file holds the triage pins:invalid_typeatexportOptions, with the object form named. The control is the same array on a list view, which still lifts to{ formats: ['csv'] };{}and absent;invalid_valueatexportOptions.formats.1, andpdfwith its retirement text;unrecognized_keysatexportOptions, namingthis export options blockand themaxRecord→maxRecordsrename.content/docs/references/ui/component.mdx(the row's type, and a new nested-shape table) anddocs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md(see the strictness-ledger note under Acceptance notes).Measured: why a non-public module, not an export (Zone 2 item 1)
ui/index.tsisexport * from './view.zod', so any export fromview.zod.tsis public.export { ListViewExportOptionsSchema }toview.zod.tsthroughscripts/ablation-replace.mjsin wrap mode, then rebuilt spec. The first attempt was refused by the tool as a no-op, because the anchor was inside the replacement; it was re-anchored and re-run. Results:check:api-surfaceturned red with./ui + ListViewExportOptionsSchema (const)("0 breaking, 1 added"), andcheck:export-originsturned red;ui/ListViewExportOptionsintojson-schema.manifest/ui.json(a new published JSON Schema def) and 5ui/ListViewExportOptions:*rows intoauthorable-surface/ui.json.git diff HEADempty. The two dirtied artifacts were restored withgit checkout HEAD, and after a clean rebuildcheck:api-surfacereads "unchanged ✓".31971ff1e28f,packages/types/src/__tests__/export-options-spec-parity.test.tsassertsexpect(specUi.ListViewExportOptionsSchema).toBeUndefined(). An export would red that test at objectui's next spec bump.check:api-surface,check:export-origins,check:declaration-mapandcheck:authorable-surfaceare unchanged. One declaration; zero public surface. This is theanalytics-column-reference.ts/analytics-carrier-filter.ts/section-group-reference.tsprecedent.Measured: the ADR-0087 disposition is D3 only (Zone 2 item 2)
Every pre-PR shape that the pinned renderer handles and the PR refuses was put through every door, before (
f148852752) and after (8b2c2bb558, the schema commit, samesrc/uias HEAD). The reading uses the builtdist,getMetadataTypeSchema('page')(the save door's per-type parse),defineStack, the props gatevalidateComponentProps, andruntimeAuthoringRulesFor('page').object-gridnode'sproperties)ObjectGrid.tsxat31971ff1e28f)defineStack['csv']!!exportOptionsis true, so the menu shows the csv/json default; the list is droppedinvalid_typecomponent-props-invalid{ formats: ['csv'], foo: 1 }foois never readunrecognized_keyscomponent-props-unknown-key{ formats: ['pdf'] }/['xml']console.warninvalid_valuecomponent-props-invalidnull!!nullis false, so no menu, the same as absentinvalid_typecomponent-props-invalidtrue,'csv',{ formats: 'csv' },{ maxRecords: -1 },{ streaming: 'false' }component-props-invalidobject-gridprop gives the props-gate warningcomponent-props-unknown-key. An undeclared page key is REFUSED at the save door and THROWS indefineStack. The accepted object forms ({ formats: ['csv','xlsx'] },{}, all five, absent) give no finding anywhere.pageruns one rule,validatePresetComparands. The props gate istier: 'advisory',surfaces: CLI_ONLY.objectui validate/checkCLI.SchemaRendererruns a structuralvalidateSchemain dev only.pdfandnullhave one (delete it), but nothing stops loading. The bare array has none that both keeps today's menu ({}) and honours the author's list ({ formats }), and that choice is the upgrader's, which the D3 entry states. Triage also ruled out a lift.Census (Zone 2 item 3), on
f148852752object-gridblocks authorexportOptionsinexamples/**, the package fixtures,content/docs/**andskills/**.object-gridblocks. Nine are TypeScript nodes (two showcase pages and seven package-test fixtures). One is the YAML example incontent/docs/protocol/objectui/layout-dsl.mdx. TheexportOptionsmatcher is lit on the 4 list-view authorings:app-crm×2, the showcase task view, and the lint showcase fixture.skills/**: nothing teachesexportOptions.skills/objectstack-ui/rules/pages.mdnamesobject-gridin prose only, so no Tier H follow-up is owed.objectui (Zone 2 item 4, Post-Task Checklist #4)
Nothing the pinned objectui imports moves:
check:api-surfaceis unchanged, and no export was removed or renamed.ListViewSchema.shape.exportOptionsis still a two-arm union with one five-key object arm, which is what objectui'sSPEC_EXPORT_OPTIONS_OBJECT_SHAPEpeel reads.ListViewExportOptionsSchemais still not exported, so objectui's floor test holds.exportOptionsand expects a bare array to pass. Six pinned tests mentionexportOptionsnear the row; two of them carry only prose that will go stale (see Acceptance notes).Changes outside the row, stated
exportOptionsaccepts and lifts exactly what it did, and its top-level messages are unchanged. Only when a bare array also fails the array arm (['docx']) does the object arm's nested branch message read the new prescription instead ofInvalid input: expected object, received array. Measured ondist. Both carriers read one declaration, and the text is worded to be true on both. The changeset says so.Tests and gates: all on
032865c93c(HEAD, the merge oforigin/mainb91e40bc89throughos-regen-merge.sh)pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 597 files passed, 17475 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0.check:test-typecheckis OK (52 files / 246 errors / 135 pinned signatures, unchanged), so the rewritten pin compiles undertsconfig.test.json....@objectstack/spec) direction, limited to the three the dispatch names:@objectstack/lint(the props gate): 119 files / 5515 tests passed;@objectstack/metadata-protocol: 200 passed + 3 skipped files / 2973 passed + 19 skipped tests;@objectstack/spec: as above.pnpm --filter @objectstack/spec check:generated: 15 of 15 up to date after--fixregenerated the 2 it proved stale (check:docs,check:strictness-ledger).dispatch-gates.mjs --commands(no paths) derived 112 commands; all 112 ran and exited 0.lint,client-reactandobjectqlhad nodist:check:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:docs-transcript-drift,check:dual-build-cjs-loadsandcheck:lean-entry-closure. Each re-ran green once the dists existed.--ranwithcmd :: exit N: "112 derived, 112 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)".probe.tstyped against the rebuiltdist/ui/index.d.mts, usingObjectGridProps, was compiled withtsc:exportOptions: ['csv']givesTS2559;{ formats: ['xml'] }givesTS2322;{ formats: ['csv'], maxRecord: 1 }givesTS2561;{ formats: ['csv','xlsx'], maxRecords: 10 }compiles.unknown(the reference page renderedany).Acceptance notes
check:strictness-ledger) counts.zod.tsfiles only. The moved block is one CLOSED site and now lives in a non-.zod.tsmodule, like the other non-barrel helpers, so the regeneratedui/counts read 189 → 188 sites and 179 → 178 strict. The strip count, which is the ratchet's target, is unchanged at 7. Naming the module.zod.tswould have kept the site counted, at the price of a hand-written ledger row and of opting a non-public module into the.zod.tsgenerators' discovery.z.unknown(): theObjectGrid.exportOptionsKeys.test.tsdocblock, and theobject-grid.exportOptionsrow inregistry-inputs-spec-parity.test.ts'sMEMBER_PINS. Neither asserts it, so nothing goes red. Once the spec version carrying this lands, both describe a past state. Carrier: objectui's next spec pin-bump PR. Noted, not filed.propertiesbag. On objectuiorigin/main, the bag arm from objectui#11399 judges the bag byComponentPropsMap['object-grid']by reference. Once objectui installs this spec, itsobjectui validaterefuses a bare array in the bag, as its flat mirror has since objectui#7762. No objectui change is owed beyond the pin bump.ListViewExportOptionsSchemastays possible as its own decision. It movesapi-surface(+1 const),json-schema.manifest(+1 def) andauthorable-surface(+5 rows), as measured above. It is not done here.Generated by Claude Code