Skip to content

fix(types): bind the ListView exportOptions mirror to the spec field; drop the ListView casts (#6956) - #7763

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-6956-listview-export-options-spec
Sep 5, 2026
Merged

fix(types): bind the ListView exportOptions mirror to the spec field; drop the ListView casts (#6956)#7763
os-justin merged 2 commits into
mainfrom
claude/issue-6956-listview-export-options-spec

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #6956

Tier CONTRACT_REVIEW_TIER (Clause-② yes): this narrows a published validator's accept set ('pdf' refused in both spellings, the object branch strict) and declares streaming. Draft for the domain:spec seat's review; dispatched from session session_01BAZFhALsQsGqxui8sNqM8s. Generic type arguments in this body are written in words — GitHub's body sanitizer eats short tag-shaped fragments — and pipes are kept out of table cells.

Premise, re-verified on 83c77dc3 (and again on the merged head 216c7490)

Assertion Reading
mirror is the legacy union — 'pdf' in both branches, no streaming, non-strict z.object packages/types/src/zod/objectql.zod.ts:514 to :522, byte-identical on 9587fc95, 83c77dc3 and 3e377c93 (no commit between the tips touched the file)
the three cast sites ListView.tsx:1304 a cast to an array of csv, xlsx, json, pdf; :1324 and :2976 (… as any)?.streaming !== false
the fold at :1299 to :1308 present: bare array folded to { formats }
normalize-list-view.ts 0 exportOptions hits; control userActions 6
TS face already clean objectql.ts:572 ListViewExportFormat = csv, xlsx, json; :607 ListViewExportOptions five keys incl. streaming
installed pin @objectstack/spec@17.2.0; packages/types and the spec resolve the SAME zod@4.4.3 instance (node_modules/.pnpm/zod@4.4.3), so a by-reference member is a schema from the same class graph

The four spec readings, through the installed pin (not a working tree)

ListViewSchema.shape.exportOptions from @objectstack/spec/ui at @objectstack/spec@17.2.0:

  1. ['csv', 'xlsx'] → ACCEPT → {"formats":["csv","xlsx"]} (the lift)
  2. ['csv', 'pdf'] → REFUSE, code invalid_union; the message names the removal in @objectstack/spec 17.0.0 (objectstack#8010; PDF export declined as objectstack#1301 NOT_PLANNED), prescribes deleting the value, lists the surviving formats csv / xlsx / json, and points at os migrate meta --from 16
  3. { formats: ['csv'], compression: 'gzip' } → REFUSE, code invalid_union (strict; the object arm's catchall is never)
  4. { formats: ['csv'], streaming: true } → ACCEPT → {"formats":["csv"],"streaming":true}

Structure read off the schema object: wrapper chain optional → union; arms pipe (array of the enum csv / xlsx / json, then the transform) and object (five optional keys, no defaults, catchall never). The same probe on { formats: ['csv'], streaming: false } returns the value intact.

What moved

  1. The mirror (packages/types/src/zod/objectql.zod.ts, the exportOptions member only): exportOptions: SpecListViewSchema.shape.exportOptions — the spec field by reference, under the file's existing SpecListViewSchema import alias. The spec field is already .optional() (re-wrapping would nest a second optional) and carries the spec's own description, so neither is re-applied. Measured: the import is usable at module load — the same alias already feeds thirty-odd fields by reference on this schema, the package builds and type-checks, and the parity test asserts identity (toBe) between the mirror's member and the spec's. ListViewInferred (z.input of the mirror) now spells the member as the spec's INPUT type, measured by a type-level Equal pin: NonNullable of ListViewSchema['exportOptions'] is exactly ListViewExportFormat[] | ListViewExportOptions — the array of csv / xlsx / json, or the strict five-key object with streaming. Two prose sites that called exportOptions "intentionally broader than spec's" are corrected (the schema's doc comment at :300 and packages/types/src/zod/README.md:22). Nothing else in the file moves; LIST_VIEW_LOCAL_OVERRIDES keeps the key, as it does for userActions, aria and the per-view configs that are likewise redeclared below the by-reference extend.
  2. The casts (packages/plugin-list/src/ListView.tsx, exactly three lines): :1304 return { formats: schema.exportOptions }; (the cast is gone entirely — the fold's return type follows); :1324 resolvedExportOptions?.streaming !== false; :2976 exportConfig?.streaming !== false. PM mechanism assumption, measured: they compile with NO annotation — pnpm --filter @object-ui/plugin-list type-check exits 0 against the rebuilt types dist, and the ablation below turns exactly those two lines red (TS2339) against the old mirror, so the green is the rebuilt .d.ts, not a cache. The fold at :1299 to :1308 STAYS. handleExport (:2943) still names 'pdf' in its parameter type: measured, no live export path implements it — serverEligible admits csv / xlsx / json only, the client fallback branches on csv and json (a 'pdf' call falls through both and does nothing), and exportableFormats never offers it (filtered by supported). The parameter type is left alone as dispatched: a runtime signature, not the authoring contract.
  3. The pin (packages/types/src/__tests__/export-options-spec-parity.test.ts) widened to the mirror: identity with the spec field; the bare array lifts to { formats } through the whole list-view node while the TS face admits it (satisfies); 'pdf' refused in both spellings on the exportOptions path with the migration prescription, and refused by the TYPE in both spellings (@ts-expect-error); sixth key refused (strict) at both levels; streaming: true / false accepted with the value SURVIVING the parse; spec-vs-mirror parity verdict-for-verdict AND output-for-output on the four readings, with a non-vacuity assertion on the verdict vector [true, false, false, true]; type-level pins that the face carries streaming as boolean | undefined and that the array arm's element is csv / xlsx / json; controls: conditionalFormatting (both shapes accept, a non-array is refused), allowExport, and the bare envelope. The objectui#4535 legs are unchanged.
  4. Fixture triage: one fixture authored 'pdf' under the ListView type — p1-spec-alignment.test.ts:240 (should accept exportOptions as ObjectUI object format). Its subject is the object form, not the value, so the spelling changes (['csv', 'json']), not the test. plugin-list's ListView.test.tsx:2228 and :2264 author 'pdf' through as any to pin that a stored dead format is dropped from the menu; they compile as-is and stay (no other line in plugin-list moves).
  5. Changesets: @object-ui/types: minor ("Breaking for authored metadata", naming the spec authority and the four readings) and @object-ui/plugin-list: patch (cast removals, no behaviour change, the fold stays).

Gates (exit captured before any pipe; verdict lines quoted from the gate)

HEAD for every row is the merged head 216c7490; identical readings were taken on a7e32588 before the merge.

Gate Exit Verdict
closure build — turbo run build --filter='@object-ui/app-shell^...' --concurrency=2, under the verify lock 0 Tasks: 28 successful, 28 total; VERDICT command-exit 0; dist marker objectql.zod.d.ts pdf 0, streaming 2
pnpm --filter @object-ui/types type-check (three tsc projects incl. tsconfig.test.json) 0 0 error TS
pnpm --filter @object-ui/plugin-list type-check 0 0 error TS
pnpm --filter @object-ui/app-shell type-check (forwards exportOptions at views/ObjectView.tsx:2266) 0 0 error TS
pnpm exec vitest run --maxWorkers=2 packages/types/ packages/plugin-list/, under the verify lock 0 Test Files 187 passed (187), Tests 2906 passed (2906); VERDICT command-exit 0
pnpm --filter @object-ui/types lint, pnpm --filter @object-ui/plugin-list lint 0, 0 0 errors each (pre-existing warnings only)
check-changeset-presence · -fixed · -no-major · -overwrite 0 ✅ each (presence: 5 source files of 2 released packages, 2 changesets declared)
check-control-bytes 0 ✅ 6324 tracked text files
pnpm check:spec-symbols 0 ✅ "2 declared deliberate copies, 19 unbacked claims" — the CLAIM_DEBT ledger is untouched and stays green under the derivation
check:doc-types · check:doc-fences · check-doc-links 0
check:phantom-deps · check:unreferenced-sources · check:esm-specifiers 0
governed-surface predicate (check-governed-merges.mjs --test on all 7 paths) 0 NOT governed

Ablation — mirror reverted to the 9587fc95 blob (HEAD committed first; restore trap on EXIT / INT / TERM)

Mutation proven on disk: git hash-object of the file = 70fec93c… = git rev-parse 9587fc95:… (HEAD blob b20e082b…); marker counts: new-member 0, old-enum 2. Types dist rebuilt and proven to have moved: objectql.zod.d.ts pdf 0 → 4, streaming 2 → 0.

Restore proven: on-disk blob = HEAD blob b20e082b…, git diff HEAD 0 lines, dist markers back to pdf 0 / streaming 2, plugin-list tsc 0 errors, types test-project tsc 0 errors, parity file 14 / 14.

The parity test imports the mirror from ../zod/objectql.zod.js (source, relative), so the vitest legs measure source; the two tsc legs measure the rebuilt dist.

Out of scope

Merged origin/main at 83c77dc3 and again at 3e377c93 (PRs #7737, #7736, #7739 — none touch these files). Session: session_01BAZFhALsQsGqxui8sNqM8s.


Generated by Claude Code

… drop the ListView casts (#6956)

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.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types plugin tests labels Sep 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3186.9 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-BxTsXMfX.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.63KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.37KB
plugin-dashboard (index.js) 132.87KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

needs:contract-review cleared (2026-09-05T16:11Z) — provenance: in-seat contract review at tier PASSED on head 216c7490, recorded on the card as objectui#6956 comment 5553073374 (carries the Implemented-by: / Reviewed-by: pair and the three-item judgment). Cleared by the domain:spec execution seat, session session_01BAZFhALsQsGqxui8sNqM8s, via read-current → write-union-minus-target → read-back. If this head moves, the label is re-hung and the delta re-reviewed before any flip.


Generated by Claude Code

@os-justin
os-justin marked this pull request as ready for review September 5, 2026 16:19
@os-justin
os-justin added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 4dfdcc3 Sep 5, 2026
34 checks passed
@os-justin
os-justin deleted the claude/issue-6956-listview-export-options-spec branch September 5, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: types plugin tests

Projects

None yet

2 participants