Skip to content

Commit 4dfdcc3

Browse files
os-justinclaude
andauthored
fix(types): bind the ListView exportOptions mirror to the spec field, drop the ListView casts (#6956) (#7763)
The zod mirror of `ListViewSchema` restated a pre-objectstack#8010 shape for `exportOptions` — `'pdf'` accepted in both spellings, no `streaming`, a non-strict object — while the installed `@objectstack/spec@17.2.0` refuses `'pdf'` with an `os migrate meta --from 16` prescription, is strict on the object form and declares `streaming`. Because `ListViewInferred` is `z.input` of the mirror, the `ListViewSchema` type the ListView renderer is written against disagreed with its sibling `ObjectGridSchema['exportOptions']` and the renderer read `streaming` through `as any`. The member is now `SpecListViewSchema.shape.exportOptions` by reference: the spec's two-branch union (bare array lifted to `{ formats }` at parse, strict five-key object), with the spec's own description. `ListViewSchema['exportOptions']` is measured to be exactly `ListViewExportFormat[] | ListViewExportOptions`. ListView: the two `as any` `streaming` reads and the `'pdf'` in the fold's cast are deleted; they compile without annotation. The bare-array fold stays — nothing on the render path parses, so a stored array reaches the renderer un-lifted (objectui#4535 item 4). The parity pin is widened to the mirror: identity with the spec field, the four verdicts the card names (lift / refuse pdf / refuse sixth key / accept streaming with the value surviving), the TS face pins, and a neighbouring member as control. One fixture that authored `'pdf'` under the ListView type (`p1-spec-alignment.test.ts`) drops it — the test is about the object form, not the value. Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2760075 commit 4dfdcc3

7 files changed

Lines changed: 288 additions & 37 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
**Breaking for authored metadata:** the `exportOptions` member of the ListView
6+
zod mirror (`ListViewSchema` in `@object-ui/types/zod`) is now `@objectstack/spec`'s
7+
own `ListViewSchema.shape.exportOptions`, bound by reference rather than
8+
restated (objectui#6956). A `list-view` document that authors the retired `'pdf'`
9+
format — in either spelling, `exportOptions: ['csv', 'pdf']` or
10+
`exportOptions: { formats: ['pdf'] }` — or a sixth key on the object form
11+
(`{ formats: ['csv'], compression: 'gzip' }`) no longer validates through this
12+
package's mirror. It never validated at the platform's publish gate:
13+
`@objectstack/spec` 17.0.0 removed `'pdf'` from the format enum (objectstack#8010;
14+
PDF export itself was declined as objectstack#1301 NOT_PLANNED) and made the
15+
object form strict, so the mirror was passing locally what the platform refuses
16+
with an `os migrate meta --from 16` prescription — an author saw green here and
17+
a refusal upstream. `streaming`, the fifth spec key, is now declared on this face
18+
(the renderer honoured it; no local declaration carried it).
19+
20+
**What was measured, on this branch's base.** The mirror declared a pre-#8010
21+
shape of its own — `'pdf'` in both branches, no `streaming`, a non-strict
22+
`z.object` — and `ListViewInferred` is `z.input` of that mirror, so the
23+
`ListViewSchema` TYPE the ListView renderer is written against disagreed with
24+
its sibling `ObjectGridSchema['exportOptions']` (the clean five-key
25+
`ListViewExportOptions`), and the renderer could only read `streaming` through
26+
`as any`. Against the installed pin (`@objectstack/spec@17.2.0`, not a working
27+
tree), `ListViewSchema.shape.exportOptions` from `@objectstack/spec/ui` lifts
28+
`['csv', 'xlsx']` to `{ formats: ['csv', 'xlsx'] }`, refuses `['csv', 'pdf']`
29+
with the migration prescription, refuses `{ formats: ['csv'], compression: 'gzip' }`
30+
(strict), and accepts `{ formats: ['csv'], streaming: true }` with the value
31+
intact. The mirror now IS that schema object, so the four verdicts are the
32+
spec's by construction; `export-options-spec-parity.test.ts` pins the identity,
33+
the four verdicts, and the survival of `streaming` through a parse.
34+
35+
**The TS face follows.** `ListViewSchema['exportOptions']` is now the spec's
36+
INPUT type: `ListViewExportFormat[] | ListViewExportOptions` — the bare array
37+
stays admissible on input because nothing on the render path parses, and the
38+
object arm IS the same `ListViewExportOptions` that `ObjectGridSchema` and
39+
`NamedListView` carry. One spec key, one type, on every local authoring surface;
40+
`'pdf'` is a compile-time refusal in both spellings.
41+
42+
**Who is NOT affected.** A document authoring `['csv', 'xlsx']`,
43+
`{ formats: ['csv', 'json'] }` or any combination of the five spec keys is
44+
untouched; absent stays valid; the member's description is now the spec's own
45+
text. The spec's parse-time lift of a bare array to `{ formats }` now runs for
46+
whoever parses through this mirror as well.
47+
48+
**Migration:** delete `'pdf'` (the surviving formats are `'csv'`, `'xlsx'` and
49+
`'json'`); delete any key outside `formats` / `maxRecords` / `includeHeaders` /
50+
`fileNamePrefix` / `streaming`. `os migrate meta --from 16` lists the mechanical
51+
edits for existing sources.
52+
53+
Graded `minor`, not `patch`: this narrows the accepted input set, which is
54+
breaking for any author who wrote the tolerated value. It is not `major` per
55+
this repo's fixed-group convention (objectui's own breaking changes ship as
56+
`minor`; the group's major tracks `@objectstack` — AGENTS.md 版本号策略,
57+
mechanically enforced by `scripts/check-changeset-no-major.mjs`).
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/plugin-list': patch
3+
---
4+
5+
`ListView` reads `exportOptions.streaming` without a cast (objectui#6956). The
6+
two `as any` reads — the `exportableFormats` server-availability check and
7+
`handleExport`'s server-eligibility gate — and the `'pdf'` in the bare-array
8+
fold's cast are gone: the `ListViewSchema` type now carries `streaming` and not
9+
`'pdf'`, because `@object-ui/types`' zod mirror binds the spec's `exportOptions`
10+
field by reference. No behaviour change: the same formats are offered,
11+
`streaming: false` still forces the client-side path, and the bare-array fold
12+
(`resolvedExportOptions`, a stored `['csv', 'xlsx']` folded to `{ formats }`)
13+
STAYS — nothing on the render path parses and `ObjectView` forwards a stored
14+
value verbatim, so the spec's parse-time lift never runs before this renderer
15+
and the fold is load-bearing rather than legacy. A `'pdf'` stored before the
16+
retirement still arrives as data and is still dropped from the export menu with
17+
the existing one-time warning.

packages/plugin-list/src/ListView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,7 +1301,7 @@ export const ListView = React.forwardRef<ListViewHandle, ListViewProps>(({
13011301
if (!schema.exportOptions) return undefined;
13021302
// Spec format: simple string[] like ['csv', 'xlsx']
13031303
if (Array.isArray(schema.exportOptions)) {
1304-
return { formats: schema.exportOptions as Array<'csv' | 'xlsx' | 'json' | 'pdf'> };
1304+
return { formats: schema.exportOptions };
13051305
}
13061306
// ObjectUI format: already an object
13071307
return schema.exportOptions;
@@ -1321,7 +1321,7 @@ export const ListView = React.forwardRef<ListViewHandle, ListViewProps>(({
13211321
const declared = resolvedExportOptions?.formats || ['csv', 'json'];
13221322
const serverAvailable = typeof dataSource?.exportDownload === 'function'
13231323
&& !!schema.objectName
1324-
&& (resolvedExportOptions as any)?.streaming !== false;
1324+
&& resolvedExportOptions?.streaming !== false;
13251325
const supported = serverAvailable ? ['csv', 'xlsx', 'json'] : ['csv', 'json'];
13261326
return declared.filter((f: string) => supported.includes(f));
13271327
}, [resolvedExportOptions, dataSource, schema.objectName]);
@@ -2973,7 +2973,7 @@ export const ListView = React.forwardRef<ListViewHandle, ListViewProps>(({
29732973
const serverEligible = (format === 'csv' || format === 'xlsx' || format === 'json')
29742974
&& typeof dataSource?.exportDownload === 'function'
29752975
&& !!schema.objectName
2976-
&& (exportConfig as any)?.streaming !== false;
2976+
&& exportConfig?.streaming !== false;
29772977
if (serverEligible) {
29782978
const fields = effectiveFields
29792979
.map((f: any) => columnIdentity(f))

0 commit comments

Comments
 (0)