Uh oh!
There was an error while loading. Please reload this page.
fix(types): exportOptions matches the spec's object form — pdf retired, streaming typed (#4535) - #4584
Merged
Conversation
…d, streaming typed (#4535) `ObjectGridSchema.exportOptions` carried four keys under a comment claiming alignment with `@objectstack/spec`'s `ListViewSchema.exportOptions`. The comment was false in both directions: the spec declared a bare format ARRAY, and the renderer read a fifth key — `streaming` — that appeared in no declaration at all, reachable only through an `as any` cast. objectstack#8010 closed that upstream by declaring `ListViewExportOptionsSchema` with exactly the five keys this renderer reads. This lands the objectui half: - the five keys become one exported `ListViewExportOptions`, shared by `ObjectGridSchema` and `NamedListView`, with a comment naming the spec symbol and version it mirrors; - `streaming` is declared and both `as any` casts are deleted; - `'pdf'` is retired from the local format union (`ListViewExportFormat`) — declined platform-side as objectstack#1301, gone from the spec enum in 17.0.0. Two guards keep it from re-opening: a compile-time key-set assertion on the type, and a source scan of every property `ObjectGrid` reads off `exportOptions` that fails on any key the type does not declare. Part of #4535 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 13, 2026 13:52
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #4535
Lands the objectui half of the
exportOptionsreconciliation that objectstack#8010 opened. Items 1–3 of the card; item 4 is measured, answered, and left as the card's residue for the reason below.Gate-check first: the pin does NOT carry the new spec form
The card's four items split on whether objectui's pinned
@objectstack/specalready carriesListViewExportOptionsSchema(the objectstack#8324 change). Measured on this branch's base (eb7f586b6):packages/types/package.jsonandpackages/plugin-grid/package.jsonboth request^17.0.0-rc.6;pnpm-lock.yamlresolves@objectstack/spec@17.0.0-rc.6.'pdf'included, and nostreamingkey at all:So the new object form is not importable from the pin, and this PR restates the five keys locally instead of deriving them from the spec symbol. The shape it restates IS the new spec shape, so nothing here changes when the pin bumps — only the option of deriving it opens up. That is stated in the type's own doc comment rather than left for the next reader to rediscover.
Per the ruling, this is the items-1-to-3 branch, and the PR opens
Part of #4535rather thanFixes.What landed
Item 1 — the type and its false comment. The five keys are now one exported
ListViewExportOptionsinpackages/types/src/objectql.ts:formats,maxRecords,includeHeaders,fileNamePrefix,streaming. The old comment claimed alignment with the spec'sListViewSchema.exportOptionswhile the spec declared an array — false in both directions. The replacement names the symbol (ListViewExportOptionsSchema), the version (@objectstack/spec17.0.0), the issues (objectstack#8010 / objectstack#8324), and the pin lag, so the claim is checkable.NamedListView.exportOptions— the saved-view half in the same file, which declared the same four keys separately — now references the same type. Two authoring surfaces for one spec key cannot drift apart if there is only one declaration. This was measured before it was done: the downstream consumer sweep (below) is green, so nothing was relying on the two being different.Item 2 —
'pdf'retired. The local format union is now the exportedListViewExportFormat='csv' | 'xlsx' | 'json'. PDF export was declined platform-side (objectstack#1301 NOT_PLANNED) and the value left the spec's enum in 17.0.0, where authoring it is a parse-time refusal carryingos migrate meta --from 16.ObjectGrid.handleExporttakes that type instead of the inline four-member union.Item 3 —
streamingtyped, both casts deleted.(schema.exportOptions as any)?.streamingand(exportConfig as any)?.streamingare now plain reads.Item 4: the renderer has no array tolerance to keep — and raw arrays can still reach it
The card asks to confirm which path feeds ObjectGrid its schema and to keep the renderer's own array tolerance only if raw un-parsed metadata can still reach it. Measured, the presumption behind the question does not hold, so reporting the measurement rather than forcing the form:
ObjectGrid has never had array tolerance. It reads
schema.exportOptions?.formatsand nothing else; there is noArray.isArraybranch anywhere in its export region.plugin-list'sListViewis the component that normalizes both spellings (ListView.tsx:1104-1113) — and it does not forwardexportOptionsto the child grid at all: theobject-gridschema it builds (ListView.tsx:1844-1868) is an explicit key list that omits it, because ListView renders its own export toolbar. So nothing was kept or dropped here.Raw bare arrays can still reach ObjectGrid, on a path that no parse touches:
packages/react/src/spec-bridge/bridges/list-view.tsbuilds a node oftype: 'object-grid'(:125) and assignsnode.exportOptions = spec.exportOptionsverbatim (:158). The bridge's input is a TypeScriptPartialof the spec'sListView— there is no zodsafeParseorparseanywhere underpackages/react/src/spec-bridge/, so the spec's parse-time array lift never runs on this path, at this pin or after a bump. Its own pinned test asserts the passthrough (P1SpecBridge.test.ts:390-397, expectingnode.exportOptionsto equal['csv', 'xlsx']). At the current pin the spec's canonical form IS the array, so this is the ordinary case, not an edge one, and the grid reads.formatsoff an array asundefinedand falls back to the csv/json default.Adding tolerance in the renderer is not the fix and is not in this card: a consumer-side
Array.isArrayfallback is a second de-facto contract, and the producer is where it belongs — the bridge should lift, or its host should hand it parsed metadata. Filed separately rather than smuggled in here; the residue is recorded on #4535, which stays open.Red-first
Deleting the casts against the unfixed four-key type, before touching
packages/types:Both compile once the key is declared. That red is the whole of item 3: the cast was load-bearing only because the declaration was missing.
The card's constraint, as a test
"No sixth undeclared-but-read key" is now mechanical, in the shape of the objectui#4302 package-door guard.
packages/plugin-grid/src/__tests__/ObjectGrid.exportOptionsKeys.test.tsscansObjectGrid.tsxfor every property read offexportOptions— throughschema.exportOptionsand through any identifier bound to it — and scansListViewExportOptionsinpackages/typesfor what it declares, then fails on anything read but not declared. It tolerates a wrapping paren, anas Tassertion and?.between the root and the key, precisely because a cast is howstreamingstayed invisible for releases.packages/types/src/__tests__/objectql.exportOptions.test.tspins the other direction at compile time: an exactkeyofequality against the spec's five, plus@ts-expect-errorpins on a retired'pdf'and on an undeclared sixth key.Reverse-verified, each restored byte-exact afterwards (sha256-checked, via patch and
git checkout):exportConfig?.compression)expected [ 'compression' ] to deeply equal [], and the cast guard red toocompression?: string)TS2344on the key-set assertion,TS2578unused@ts-expect-error, and the pin's exact-five test red'pdf'restored to the unionTS2344on the format assertion,TS2578unused@ts-expect-errorThe drift pin also caught a false positive in its own first run —
const declared = schema.exportOptions?.formatswas being read as an alias of the options, sodeclared.filter(...)scanned as an undeclared keyfilter. Fixed with a lookahead and written down in the file, since that is the failure mode a future edit would hit again.Pin moves, declared
exportGate.test.tsx's two'pdf'cases are kept, re-worded, not deleted. The card expected theconsole.warndrop path to become dead; measured, it does not. The filter is format-agnostic (declared.filter(f => supported.includes(f))) with no'pdf'branch, so it remains live for the case it was written for —xlsxdeclared with no server stream — and it keeps covering the legacy case for free: metadata stored before the retirement still carries'pdf'untilos migrate meta --from 16rewrites it, and such a value must keep reaching the user as "absent from the menu" rather than as a dead menu item. Deleting those cases would drop live coverage to satisfy a presumption that measurement did not support. The test schema is alreadyany, so no cast was needed and no assertion changed — only the prose saying what they now pin, and one title (drops pdfbecomesdrops a legacy pdf).Verification
type-check(tsc runs twice per package:--noEmitandtsconfig.test.json) — green.vitest run --maxWorkers=2 packages/plugin-grid packages/types: 97 files, 1009 tests passed. The fix(plugin-grid): the cross-page select-all banner works under external pagination (#4464) #4503 / fix(plugin-grid): select-all-matching replays the host's real query — or abstains — instead of fanning out unfiltered (#4501) #4510 / fix(plugin-grid): the link column renders a real anchor when the host publishes record URLs (#4490) #4531 / fix(fields): the date formatter's last three en-US channels thread the display locale (#4272) #4544 / fix(plugin-grid): the record-detail date fallback threads the display locale (#4541) #4552 pins stay green....@object-ui/types(= the 40 packages that consume it, not its dependencies): build thentype-check, zero errors. This is the sweep that matters here — narrowingformatsand pointingNamedListViewat the shared type are both changes that only bite downstream..d.tsmeasured both ways withdist/andtsconfig.tsbuildinfocleared between builds.@object-ui/plugin-grid: byte-identical (thehandleExportsignature is internal).@object-ui/types: two new exported types,formatsnarrowed,streamingadded, bothexportOptionssites pointing at the shared type — hence minor / patch.check:control-bytesOK (4331 files),check:phantom-depsOK,check:spec-symbolsOK. Control-byte self-scan over every touched file: clean.plugin-grid617 to 615 warnings (the two deletedas any);types286 to 290, the fourno-unused-varson the type-level assertion aliases — the same shape this package's existingpage-node-type-contract.test.tscarries, so the pattern is the house one rather than a new exemption.Generated by Claude Code