fix(react): SpecBridge lifts a bare exportOptions array to the spec's object form (#4585) - #4587
Merged
Merged
Conversation
… object form (#4585) The bridge copied `spec.exportOptions` onto the `object-grid` node verbatim, so a legacy bare format array reached ObjectGrid — which reads the object form and only that. `.formats` on an array is `undefined`, so the renderer's `['csv', 'json']` default won and the view's declared formats were dropped silently: a view declaring `['csv', 'xlsx']` rendered an export menu offering CSV and JSON. The bridge now mirrors the spec's own parse-time lift (objectstack#8010) at the assignment site — the same contract applied where `parse` cannot reach, not a second one. The object form passes through by reference; an empty array lifts to `{ formats: [] }` as the spec's transform does; a pre-retirement `'pdf'` is carried rather than filtered, since the spec refuses that value instead of dropping it. The P1SpecBridge array pin moves with the fix: it pinned the broken passthrough and now pins the lifted shape. New coverage at both levels — the bridge's output shape, and the rendered export menu end to end through ObjectGrid. 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
|
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 free
to 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.
Fixes #4585
What was wrong
packages/react/src/spec-bridge/bridges/list-view.tscopied the spec's export options onto theobject-gridnode verbatim:ObjectGridreads the OBJECT form and only that (schema.exportOptions?.formats)..formatson a bare array isundefined, so the renderer's['csv', 'json']default won. A spec-canonical view declaringexportOptions: ['csv', 'xlsx']and routed throughSpecBridgetherefore rendered an export menu offering csv and json — the declaredxlsxnever appeared, an undeclaredjsondid, and nothing said so.!!schema.exportOptionsstayed truthy for a non-empty array, so the export button still showed: the failure was silent, not absent.The spec's own parse-time lift (objectstack#8010) never rescued this path. The bridge's input is a TypeScript type —
Partial< ListView >— not a parsed value, and there is noparse/safeParseanywhere underspec-bridge/. A host that parses first hands over the object form; a host that forwards raw stored metadata hands over whatever was authored, and nothing in the bridge can tell them apart. That is why bumping the spec pin alone would not have closed this.The fix — the spec's lift, mirrored where parse cannot reach
The bridge now applies the spec's OWN transform at the assignment site (
@objectstack/specui/view.zod.ts, objectstack#8010):This is one contract applied one layer out, not a second de-facto one. The consumer-side
Array.isArrayfallback inObjectGridis what AGENTS.md #0.1 forbids, and the card rejected it for the same reason. Full-parse-in-the-bridge was rejected by the PM ruling: the input is deliberatelyPartial, and running the spec schema over host fragments is a behavior change out of proportion to one key's coercion.The lifted value is typed
ListViewExportOptionsfrom@object-ui/types(landed in #4584) — one spelling of the spec's five-key shape, no third copy. The bridge's public.d.tsis unchanged.Deliberately narrow, each half pinned:
toBe, nottoEqual);{ formats: [] }verbatim — measured, not assumed: the spec'sz.array()carries no.min(1), so[]is a legal input that wraps rather than defaults;exportOptionsleaves the key absent, unchanged;'pdf'stored before its retirement is carried, not filtered. The spec REFUSES'pdf'at parse with a migration prescription (objectstack#8010; PDF export declined as objectstack#1301 NOT_PLANNED) — it does not silently drop the value, so the mirror of its lift may not either. Such a format dies downstream in ObjectGrid's format-agnostic menu filter, kept deliberately at ReconcileexportOptionswith the spec's new object form (#8010 spec half): false alignment comment, local'pdf', and theas anystreamingread #4535 for metadata predatingos migrate meta --from 16.One behavior change, called out
exportOptions: []now lifts to{ formats: [] }, which ObjectGrid reads literally: no format is offered,exportableFormats.length > 0fails, and the export button is hidden. Before, the unreadable[]was merely truthy, so the button showed and offered a menu built entirely out of the['csv', 'json']default. A view that declares zero formats now offers zero. This follows from reading the spec's lift literally rather than from any extra rule here; it is pinned end to end.Red-first, both levels, predictions written first
Predictions were recorded before any run; the red lap took the fix out with
git checkout origin/main -- ...(nevergit stash) and restored it sha256-verified.@object-ui/reactaliases tosrcin the vitest config, so the revert is visible at both levels. 7 failures predicted, 7 observed, no others.Level (a) — bridge output:
Level (b) — end to end through
ObjectGrid:The DOM dump of the failing render contains the undeclared
Export as JSONitem — the card's mechanism, observed rather than inferred.The two must-not-change pins (object-form passthrough, view with no
exportOptions) were green on both sides of the revert, which is what makes the red meaningful.Authorized pin move
P1SpecBridge.test.tsshould pass through exportOptions string[] formatpinned the bare array reaching the node verbatim — the one shapeObjectGridcannot read. It asserted the bridge's output and never rendered it, so the bridge stayed green while the grid downstream could not read what it produced. It moves to pin the lifted shape and is renamed accordingly. This move is authorized by the PM ruling on #4585.Residue, recorded not built
objectui pins
@objectstack/spec@17.0.0-rc.6, whereListView.exportOptionsis still the bare array and the lift does not exist yet (measured in the pinned dist). Once the pin bumps past objectstack#8324, a stronger assertion replaces the hand-mirroring: parse the same input through the spec and requirebridge lift === spec parse output— one contract proven equal rather than copied. The intent is recorded in the new test file's header; nothing blocks on it.Verification
pnpm exec vitest run --maxWorkers=2 packages/react packages/plugin-grid/src/__tests__/{ObjectGrid.exportOptionsKeys,exportGate,exportServer,specBridgeExportFormats}— 47 files, 604 tests passedtype-check(bothtsc --noEmitandtsc -p tsconfig.test.json) for@object-ui/reactand@object-ui/plugin-grid— green.d.tsmeasured both ways withdist/andtsconfig.tsbuildinfocleared between builds — all 63 emitted.d.tsbyte-identical, hence the patch changeset--max-warnings; the 4 addedno-explicit-anywarnings are the siblingexportServer.test.tsxharness idiom this file reuses)check-control-bytes,check-changeset-presence,check-changeset-no-major,check-phantom-dependencies— all green; touched files self-scanned for control bytesObjectGrid.tsxand its existing tests are untouched — read-only landed context from fix(types): exportOptions matches the spec's object form — pdf retired, streaming typed (#4535) #4584/ReconcileexportOptionswith the spec's new object form (#8010 spec half): false alignment comment, local'pdf', and theas anystreamingread #4535Generated by Claude Code