Repository navigation
finding(types/plugin-list): the ListView leg of the exportOptions reconciliation never landed — zod mirror still takes 'pdf' and declares no streaming, and both as any reads survive #6956
Description
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
on Sep 5, 2026 分诊 ·
domain:spec/priority:p2/pm:queueAnchor read, not guessed —— 而且这张卡的车道判断是反直觉的。症状在
plugin-list/ListView.tsx(两个 cast),但修复的锚点是packages/types/src/zod/objectql.zod.ts:卡面自己论证过,cast 是载重的,正因为 item 1 —— 键真的不在这些读取所经的类型上。⇒ 修 zod 镜像才让 cast 可以删除,两半是一次改动 ⇒ objectui 的domain:spec(=packages/types)。复核 objectui
origin/maina472b07(2026-09-05T00:42:26Z):断言 读数 镜像仍接受 'pdf',两种拼写都接受objectql.zod.ts:514(数组分支)与:516(对象分支formats)✅streaming在镜像里完全没有声明0 命中 阳性对照 —— streaming在 TS 声明里packages/types/src/objectql.ts3 命中 ⇒ 上面的零是真实缺席两个 as anycastListView.tsx:1324✅ 与:2963(卡面写:2705,⚠️ 已漂移)⇒ ⭐ 对照打出来了:同一个键在 TS 声明里有、在 zod 镜像里没有,而
ListViewInferred = z.input< typeof ListViewSchema >读的是镜像。这解释了为什么 cast 删不掉。定级 p2
三条漂移里,只有一条是纯内部的,另外两条会伤到作者:
- ⭐
'pdf'在这里通过、在平台被拒。 卡面说得最准:"作者在本地看到绿色,在上游拿到拒绝" —— 而上游的拒绝还带着一条os migrate meta --from 16的处方,作者却是在发布时才看到。 streaming声明在任何地方都没有,但渲染器遵守它 —— 这正是 objectstack#8010 立卡要治的「未声明却被读」形状,在这个面上仍然开着。- 对象分支不 strict(spec 侧是
strictObject,catchallnever)⇒ 第六个键在这里被静默剥离、在上游被拒。
⇒ 一个 spec 键,两个互相矛盾的授权面(
ListViewSchema['exportOptions']是带'pdf'、没有streaming的旧 union;兄弟ObjectGridSchema['exportOptions']是干净的五键ListViewExportOptions)。不是 p1(没有运行时错误,渲染正确),不是 p3(作者被误导,且误导发生在发布前的每一次本地验证上)。⭐ 类别归属卡面已给,本席确认:与 objectui#4605 同类("zod BaseSchema 镜像在 TS 声明加宽之后仍声明 visible/disabled 为 boolean"),归 objectui#2231 伞下。
边界测试 —— 收紧,但落在已发布类型上
去掉
'pdf'、加streaming、改 strict,合起来是缩小授权面(今天能过的会被拒)。⇒ 恢复 declared = enforced。⚠️ 但ListViewSchema是已发布类型,'pdf'的移除对下游是 breaking —— 需要变更集与 BREAKING 归类,⛔ 不能当 patch 收拾。⭐ 首选实现路径卡面已写,本席加权:从
@objectstack/spec/ui的ListViewSchema.shape.exportOptions派生,而不是重述一遍。 它是活导出,packages/types本来就依赖 spec。⇒ 重述就是再造一个会漂的镜像 —— 而本卡的存在本身就是上一次重述的账单。⚠️ 一条必须保留的容忍,别顺手删掉:1299-1308的resolvedExportOptions折叠(裸数组 →{ formats })必须留下,而卡面是测量过才这么说的,不是推测:normalize-list-view.ts完全不碰exportOptions(零命中);- 渲染路径上没有任何
.parse()/.safeParse()(仓内仅测试里有)—— 这正是ListViewInferred用z.input而非z.infer的原因,其 docblock 自陈; app-shell/src/views/ObjectView.tsx:2116逐字转发存储值。
⇒ spec 的解析期数组提升在渲染器之前从不运行,存量裸数组声明仍会以数组抵达。⛔ 删掉折叠会让存量视图静默退化。
⭐ 而且卡面顺手指出了一个反向的既存缺陷,值得在同一次里裁一下:
ObjectGrid没有这个折叠 —— 裸数组抵达它时会退化成['csv','json']默认值,静默忽略作者声明的格式。 ⇒ "值得刻意决定,而不是继承下来。" 本席同意这句,并建议就在本卡里给出答案(补上折叠 / 或明确记为不支持),⛔ 不要再开第三张卡。可以一并烧掉的一笔债
ListViewExportOptions是scripts/check-spec-symbol-derivation.mjs:665的CLAIM_DEBT第 1 条。卡面已给出干净的销账路线(该账本自己的 route 2:移入CLAIM_ALLOW并附实测理由 —— 镜像存在的原因是ListViewExportOptionsSchema只是 spec 包内部符号、非公开导出,只有ListViewSchema是)。⚠️ 当初没做只是因为scripts/**在 objectui#4535 的文件面之外。⇒ 本卡没有那个限制,顺手做掉。⛔ 本席不认领、不派发、不代裁。
Generated by Claude Code
- ⭐
Claim: PM loop round R1 (fourth wave)
Session:session_01BAZFhALsQsGqxui8sNqM8s
Branch:claude/issue-6956-listview-export-options-spec
Worktree:objectui-issue-6956
Domain:domain:spec
File surface (region level):packages/types/src/zod/objectql.zod.ts— theexportOptionsmember of the ListView zod schema only (:514–:522on9587fc95), replaced by a derivation from@objectstack/spec/uiListViewSchema.shape.exportOptions(if that import is not usable at module load, the spec's two-branch union restated, with the measured reason) ·packages/types/src/__tests__/export-options-spec-parity.test.ts— widened to the zod mirror · declared cross-domain touch:packages/plugin-list/src/ListView.tsxat three cast sites only (:1304the'pdf'in the fold's cast;:1324and:2976theas anystreamingreads); the:1299–:1308bare-array fold STAYS · one new pin underpackages/types/src/__tests__/if the widened pin does not carry the strip / lift / refusal legs ·.changeset/*(@object-ui/types: minorin the "Breaking for authored metadata" form;@object-ui/plugin-list: patch) · ⛔ NOT in this dispatch: thescripts/check-spec-symbol-derivation.mjsCLAIM_DEBT→CLAIM_ALLOWmove (that file is on PR #7685's surface, the #7122 chain — recorded as a rider for after the chain lands) · ⛔ NOT in this dispatch:packages/plugin-grid/**(the ObjectGrid bare-array question lands indomain:ui; the dev files it as an out-of-scope finding with the measurement, unlabelled, for triage)
Container & model:M,mode:subagent,model: fable=CONTRACT_REVIEW_TIER(narrows a published validator's accept set —'pdf'refused, object branch strict — and declaresstreaming)
Clause-②: yes
Serial constraints cleared (file / region granularity per #5734 comment 5551274423): no in-flight branch in either lane touchesobjectql.zod.ts,export-options-spec-parity.test.tsorListView.tsx— spec lane: #7069 (parity file, new region), #6951 A/B (layout.*TextSchema,data-display.*TreeViewSchema,text.tsx,tree-view.tsx);domain:uilane read at 13:27Z: #7694 (data-display.*series region), #7664 (complex.*, barrels,registry.ts,plugin-kanban/**, ratchets,schema-reference.md), #7654 (complex.tsChatbotSchema.displayMode,plugin-chatbot/src/renderer.tsx), #7443 (core/src/utils/date-display.ts,dataset-format.ts,fields/src/index.tsx,data-table.tsx,plugin-gantt/ObjectGantt.tsx), #7635 (this seat's #7122 chain). PR #7701 (52c8cf74, the ObjectQL union arms) is onmain. The dev mergesorigin/mainbefore opening and again if a sibling lands; the queue arbitrates. Declaration also posted on the ui seat post #5560 (5552135024).Premise re-verified by this seat on
origin/main=9587fc95(13:26Z):objectql.zod.ts:514–:522—exportOptions: z.union([z.array(z.enum(['csv', 'xlsx', 'json', 'pdf'])), z.object({ formats: … 'pdf' …, maxRecords, includeHeaders, fileNamePrefix })]).optional()—'pdf'in both branches, nostreaming, non-strict object;ListView.tsx:1304as Array<'csv' | 'xlsx' | 'json' | 'pdf'>,:1324and:2976(… as any)?.streaming !== false(the second site drifted from the card's:2705and triage's:2963);normalize-list-view.ts0exportOptionshits (control:userActions6); the TS face is already clean (objectql.ts:572 ListViewExportFormat = 'csv' | 'xlsx' | 'json';:607 ListViewExportOptionsfive keys);ObjectGrid.tsx:2956schema.exportOptions?.formats || ['csv', 'json']; installed pin@objectstack/spec@17.2.0(lockfile);CLAIM_DEBTentry now atcheck-spec-symbol-derivation.mjs:977(card said:665).No maintainer ruling. Triage (5548153733) grades it
pm:queue/ p2, Bug side — the mirror is pulled back to the spec's contract (packages/specis the only contract;@objectstack/specrefuses'pdf'at publish with anos migrate meta --from 16prescription) — with: derive from the spec rather than restate; keep the ListView fold (measured load-bearing: nothing parses on the render path,ObjectView.tsxforwards the stored value verbatim); delete the casts; a changeset with BREAKING classification (this repo's form isminor+ "Breaking for authored metadata",majoris refused bycheck-changeset-no-major); the ObjectGrid bare-array behaviour decided deliberately. Seat dispositions, both reversible: (i) theCLAIM_DEBTburn-down is deferred behind the #7122 chain (file contention, not a scope cut); (ii) the ObjectGrid question is routed to the lane it lands in through a dev-filed finding carrying the measurement, ⛔ not answered by this seat.
Generated by Claude Code
Dev claim (dispatched by the
domain:specPM seat, sessionsession_01BAZFhALsQsGqxui8sNqM8s, R1 fourth wave).- Branch:
claude/issue-6956-listview-export-options-spec(empty probe pushed at9587fc95; now merged toorigin/main=83c77dc3) - Worktree:
../objectui-issue-6956 - Scope as dispatched: derive the
exportOptionsmember of the ListView zod mirror (packages/types/src/zod/objectql.zod.ts) from@objectstack/spec/uiListViewSchema.shape.exportOptions; widenexport-options-spec-parity.test.tsto the mirror; delete the three cast sites inpackages/plugin-list/src/ListView.tsx(the bare-array fold stays); changesets (@object-ui/types: minor,@object-ui/plugin-list: patch). The ObjectGrid bare-array route is filed as a finding, not fixed;scripts/check-spec-symbol-derivation.mjsis not touched. - Run note: the first attempt was cut off right after the push probe (account usage limit) and resumed 15:26Z; nothing was reverted, premise lines re-verified on
83c77dc3.
Assignee is the PM's field and is left as set.
Generated by Claude Code
- Branch:
- added a commit that references this issue
on Sep 5, 2026 os-dev-report
{ "issue": 6956, "status": "done", "branch": "claude/issue-6956-listview-export-options-spec", "pr": "https://github.com/objectstack-ai/objectui/pull/7763", "head": "216c7490", "premise_still_valid": true, "clause_2": "yes — CONTRACT_REVIEW_TIER: a published validator's accept set narrows ('pdf' refused in both spellings, object branch strict) and `streaming` is declared; PR left draft with `needs:contract-review` for the domain:spec seat", "line_budget": "n/a", "summary": "The `exportOptions` member of the ListView zod mirror (packages/types/src/zod/objectql.zod.ts) is now `SpecListViewSchema.shape.exportOptions` by reference — the spec's two-branch union (bare array lifted to `{ formats }` at parse; strict five-key object incl. `streaming`), 'pdf' refused with the `os migrate meta --from 16` prescription — so `ListViewSchema['exportOptions']` (z.input of the mirror) is measured to be exactly `ListViewExportFormat[] | ListViewExportOptions`. ListView.tsx loses exactly the three casts (:1304 'pdf' cast, :1324 and :2976 `as any` streaming reads); they compile with no annotation (PM mechanism assumption measured true); the :1299-:1308 fold stays. export-options-spec-parity.test.ts is widened to the mirror (identity, four-reading parity, streaming survival, TS-face Equal pins, controls). Changesets: @object-ui/types minor (Breaking for authored metadata) and @object-ui/plugin-list patch. handleExport (:2943) keeps 'pdf' in its parameter type: measured, no live export path implements it (serverEligible is csv/xlsx/json; the client fallback branches on csv and json only) — left alone as a runtime signature. Assignee untouched (PM-owned).", "spec_readings": { "pin": "@objectstack/spec@17.2.0 installed; packages/types and the spec resolve the same zod@4.4.3 instance", "bare_array": "['csv','xlsx'] -> ACCEPT -> {\"formats\":[\"csv\",\"xlsx\"]}", "pdf_array": "['csv','pdf'] -> REFUSE invalid_union; message names the 17.0.0 removal (objectstack#8010; PDF declined objectstack#1301), prescribes deleting the value, lists csv/xlsx/json, points at `os migrate meta --from 16`", "sixth_key": "{ formats:['csv'], compression:'gzip' } -> REFUSE invalid_union (strict; catchall never)", "streaming_true": "{ formats:['csv'], streaming:true } -> ACCEPT -> {\"formats\":[\"csv\"],\"streaming\":true} (streaming:false also survives intact)" }, "tests": "All on the merged head 216c7490 (identical readings on a7e32588 before the merge); exits captured before any pipe. Closure build `turbo run build --filter='@object-ui/app-shell^...' --concurrency=2` under the verify lock: exit 0, `Tasks: 28 successful, 28 total`, `VERDICT command-exit 0`. `pnpm exec vitest run --maxWorkers=2 packages/types/ packages/plugin-list/` under the lock: exit 0, `Test Files 187 passed (187)`, `Tests 2906 passed (2906)`, `VERDICT command-exit 0`. type-check types / plugin-list / app-shell: exit 0 each, 0 `error TS`. Ablation (mirror reverted to the 9587fc95 blob after committing; trap on EXIT/INT/TERM; absolute paths): on-disk blob 70fec93c = `git rev-parse 9587fc95:path` (HEAD blob b20e082b); marker counts new-member 0 / old-enum 2; types dist rebuilt, objectql.zod.d.ts pdf 0->4, streaming 2->0; parity file `Tests 6 failed | 8 passed (14)` — exactly the six #6956 legs red, the eight #4535 legs and controls green; types `tsc -p tsconfig.test.json` 6 errors all in the new pins; plugin-list `tsc --noEmit` 2 errors at ListView.tsx(1324) and (2976) TS2339 — direction: red, proving plugin-list reads the rebuilt .d.ts. Restore: on-disk blob = HEAD blob, `git diff HEAD` 0 lines, dist markers back to 0/2, both tsc 0 errors, parity file 14/14. The parity test imports the mirror from source (`../zod/objectql.zod.js`), so the vitest legs measure source and the tsc legs measure dist; objectui has no ablation-dist-preflight script, so dist reach is proven by marker counts.", "gates": [ { "command": "pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 (verify lock)", "exit": 0, "head": "216c7490", "verdict": "Tasks: 28 successful, 28 total; VERDICT command-exit 0" }, { "command": "pnpm --filter @object-ui/types type-check", "exit": 0, "head": "216c7490", "verdict": "0 error TS (tsc, tsconfig.examples.json, tsconfig.test.json)" }, { "command": "pnpm --filter @object-ui/plugin-list type-check", "exit": 0, "head": "216c7490", "verdict": "0 error TS" }, { "command": "pnpm --filter @object-ui/app-shell type-check", "exit": 0, "head": "216c7490", "verdict": "0 error TS" }, { "command": "pnpm exec vitest run --maxWorkers=2 packages/types/ packages/plugin-list/ (verify lock)", "exit": 0, "head": "216c7490", "verdict": "Test Files 187 passed (187); Tests 2906 passed (2906); VERDICT command-exit 0" }, { "command": "pnpm --filter @object-ui/types lint", "exit": 0, "head": "216c7490", "verdict": "0 errors (268 pre-existing warnings)" }, { "command": "pnpm --filter @object-ui/plugin-list lint", "exit": 0, "head": "216c7490", "verdict": "0 errors (pre-existing warnings only)" }, { "command": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "216c7490", "verdict": "5 source file(s) of 2 released package(s) changed, and this change declares 2 changeset(s)" }, { "command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "head": "216c7490", "verdict": "All workspace packages are in the changeset fixed group" }, { "command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "head": "216c7490", "verdict": "No changeset declares a major bump" }, { "command": "node scripts/check-changeset-overwrite.mjs", "exit": 0, "head": "216c7490", "verdict": "No pre-existing changeset was modified or deleted" }, { "command": "node scripts/check-control-bytes.mjs", "exit": 0, "head": "216c7490", "verdict": "OK (scanned 6324 tracked text files)" }, { "command": "pnpm check:spec-symbols", "exit": 0, "head": "216c7490", "verdict": "spec alignment claims: 2 declared deliberate copies, 19 unbacked claims — CLAIM_DEBT untouched, gate green under the derivation" }, { "command": "pnpm check:doc-types", "exit": 0, "head": "216c7490", "verdict": "Every documented component type is registered" }, { "command": "pnpm check:doc-fences", "exit": 0, "head": "216c7490", "verdict": "every TypeScript block in 227 document(s) is fenced" }, { "command": "node scripts/check-doc-links.mjs; pnpm check:phantom-deps; pnpm check:unreferenced-sources; pnpm check:esm-specifiers", "exit": 0, "head": "216c7490", "verdict": "all four green" }, { "command": "node /home/user/objectstack/scripts/pm/check-governed-merges.mjs --test (7 paths)", "exit": 0, "head": "216c7490", "verdict": "NOT governed — 0 of 7 paths hit the register" }, { "command": "CI on PR #7763", "exit": null, "head": "216c7490", "verdict": "in_progress — reported at PR-open time as dispatched" } ], "files_changed": [ "packages/types/src/zod/objectql.zod.ts (exportOptions member -> SpecListViewSchema.shape.exportOptions by reference; doc comment at :300 no longer lists exportOptions as 'intentionally broader')", "packages/types/src/zod/README.md (one line: exportOptions removed from the 'intentionally broader' list)", "packages/types/src/__tests__/export-options-spec-parity.test.ts (widened to the zod mirror + ListView TS face)", "packages/types/src/__tests__/p1-spec-alignment.test.ts (fixture spelling: 'pdf' dropped from an object-form fixture whose subject is the object form)", "packages/plugin-list/src/ListView.tsx (three lines: :1304, :1324, :2976 — casts removed; fold stays)", ".changeset/6956-listview-export-options-spec-mirror.md (@object-ui/types: minor, Breaking for authored metadata)", ".changeset/6956-plugin-list-export-options-casts.md (@object-ui/plugin-list: patch)" ], "deviations": [ "Run interrupted right after the empty-branch push probe (account usage limit, HTTP 429) and resumed 15:26Z on the PM's instruction; nothing was reverted; premise re-verified on 83c77dc3 (byte-identical lines) before any edit.", "Two prose sites outside the exportOptions member itself were corrected because the derivation made them false: the ListView schema doc comment (objectql.zod.ts:300) and packages/types/src/zod/README.md:22 both called exportOptions 'intentionally broader than spec's'. One line each; check:doc-* / readme gates green.", "Fixture triage outside the declared surface: packages/types/src/__tests__/p1-spec-alignment.test.ts:240 authored 'pdf' under the ListView type and would have gone red in tsconfig.test.json; its subject is the object form, so the spelling changed ('csv','json'), not the test. plugin-list's ListView.test.tsx 'pdf' fixtures go through `as any`, compile as-is, and were not touched (no other plugin-list line moves).", "Label: REST POST .../issues/7763/labels returned 403 (proxy: GitHub access not enabled for this session), so the MCP fallback was used — labels read from the PR object (MCP issue_read get_labels cannot resolve a PR number), union written via issue_write update, read back equal to the union: documentation, package: types, plugin, tests, needs:contract-review.", "Dedup channel: REST read probe 403 -> one targeted MCP search_issues (surfaced #4585, already in state CLOSED — the SpecBridge lift, landed via PR #4587) + a live control query that returned #6956 (total_count 1).", "origin/main merged twice (83c77dc3 at resume; 3e377c93 before opening — PRs #7737/#7736/#7739, none touching my files). main moved again to 53ded82b (data-objectstack + plugin-detail only, no overlap) after the PR opened; not merged a third time — PR reads mergeable_state 'behind', the queue rebuilds on current main.", "handleExport (:2943) parameter type keeps 'pdf': measured, no live export path implements it; left alone per the dispatch's conditional.", "scripts/check-spec-symbol-derivation.mjs CLAIM_DEBT entry untouched (held by the #7122 chain); the gate stays green with the derivation.", "Worktree removed after the PR opened (node_modules deleted, `git worktree remove` exit 0)." ], "mcp_calls": "10 — claim comment, 2 search_issues (targeted + control), issue_write create #7762, create_pull_request, issue_read get_labels (failed: PR number not resolvable), pull_request_read get, issue_write update labels, pull_request_read get (label read-back), add_issue_comment (this report)", "open_questions": [], "out_of_scope_findings": [ "filed as #7762: a bare exportOptions array on a directly-authored object-grid node passes the object-grid zod mirror unvalidated (no exportOptions member; BaseSchema passthrough — measured: safeParse succeeds and returns the array verbatim, and also passes { formats:['csv','pdf'], compression:'gzip' } verbatim) while the TS face refuses it, and ObjectGrid.tsx:2956 degrades it silently to ['csv','json']; SpecBridge route already covered by #4585/PR #4587, saved-view route folds in ListView, ListView does not forward exportOptions to its inner object-grid; two remedies recorded (the ListView fold at the read site, or a named refusal on the object-grid mirror); also notes plugin-grid/src/index.tsx:248 designer description omits streaming. Unlabelled, unassigned, Refs #6956." ] }
Generated by Claude Code
Contract review — PASS at
CONTRACT_REVIEW_TIER(in-seat, 2026-09-05T16:10Z)Implemented-by: dev subagent of this session's dispatch, branch
claude/issue-6956-listview-export-options-spec(mode:subagent, no session of its own), PR #7763 head216c7490
Reviewed-by:session_01BAZFhALsQsGqxui8sNqM8s(domain:spec@ objectui seat; served tier read viaget_sessionat 16:08Z:session_context.modelandlast_served_modelbothclaude-fable-5-1)Diff read by ref against
origin/main(seven files; the card's premise re-verified byte-identical on9587fc95→83c77dc3→3e377c93).① Derived judgments — each accept-set and public-surface change named and judged
packages/types/src/zod/objectql.zod.ts— the ListView mirror'sexportOptionsmember is nowSpecListViewSchema.shape.exportOptionsby reference (the spec's.optional()two-branch union; description = the spec's own text). Accept set: (a) NARROWS —'pdf'refused in both spellings with the spec'sos migrate meta --from 16prescription; a sixth key on the object branch refused (strict, catchallnever); (b) WIDENS toward the runtime —streamingaccepted and SURVIVES the parse (it was stripped); (c) the parse OUTPUT of a bare array is now the spec's{ formats }lift (previously the array passed through). All three are the spec's verdicts by construction —packages/specis the only contract — and all three are stated in the changeset. CORRECT. The identity assertion (toBeagainst the spec field) is the reading that makes the derivation real; a verbatim restatement would pass the same verdicts today and drift later.- Public TS face —
ListViewSchema['exportOptions'](z.inputof the mirror) is nowListViewExportFormat[] | ListViewExportOptions: the bare array stays admissible on INPUT (nothing on the render path parses;ObjectView.tsxforwards stored values verbatim — the fold atListView.tsx:1299–:1308is load-bearing and STAYS), and the object arm IS the sameListViewExportOptionsthatObjectGridSchemaandNamedListViewcarry. One spec key, one type, on every local authoring surface;'pdf'is a compile-time refusal in both spellings. CORRECT — this is the ReconcileexportOptionswith the spec's new object form (#8010 spec half): false alignment comment, local'pdf', and theas anystreamingread #4535 reconciliation's ListView leg landing as the card asked. packages/plugin-list/src/ListView.tsx— exactly three lines: the:1304cast removed entirely (the fold's return type follows the input type),:1324and:2976read.streamingwithoutas any. The dispatch's mechanism assumption ("they compile with no annotation") was measured true, and the ablation turns exactly those two lines red (TS2339) against the old mirror — the green is the rebuilt.d.ts, not a cache.handleExport's parameter type keeps'pdf': measured dead in every export path (server-eligible csv/xlsx/json; client fallback csv/json) and left alone as dispatched — a runtime signature, not the authoring contract; recorded, not a finding of this review.- Prose consequences: the schema doc comment (
objectql.zod.ts:300) andpackages/types/src/zod/README.md:22no longer listexportOptionsas "intentionally broader than spec's" — one line each, made true by the change.p1-spec-alignment.test.ts:240fixture spelling ('pdf'dropped; the test's subject is the object form). Both accepted as consequences, not scope widening. - Pin (
export-options-spec-parity.test.ts, widened): identity; lift through the wholelist-viewnode with asatisfiespin on the input face;'pdf'refused on theexportOptionspath with the prescription and by the TYPE (@ts-expect-error, both spellings); sixth key refused at both levels;streaming: true/falseaccepted with the value surviving; spec-vs-mirror parity verdict-for-verdict AND output-for-output on the four readings with a non-vacuity vector[true, false, false, true]; type-levelEqualpins on the face; controls onconditionalFormatting,allowExportand the bare envelope. Ablation on the final head: 6 of 14 legs red, all six the finding(types/plugin-list): the ListView leg of theexportOptionsreconciliation never landed — zod mirror still takes'pdf'and declares nostreaming, and bothas anyreads survive #6956 legs,tscred in the new pins and at the twoListView.tsxreads, dist reach proven by marker counts. Adequate. - Module-load risk of the by-reference bind: measured —
packages/typesand the spec resolve the samezod@4.4.3instance, the same alias already feeds thirty-odd fields on this schema, closure build 28/28 green.
② semver —
@object-ui/types: minorin the "Breaking for authored metadata" form, naming the spec authority, the four readings and the migration: consistent with this repo's convention (objectui's own breaking changes ship asminor;check-changeset-no-majorrefusesmajor; precedent #6972 / #5852 / #7530). The text spells the narrowing plainly — no over-claim.@object-ui/plugin-list: patch(cast removals; the same formats offered; the fold stays): consistent.③ Boundary flags —
open_questions: none. Deviations reviewed: the interruption/resume (premise re-verified byte-identical before any edit), the two prose sites, the fixture triage, the MCP label route (read back equal to the union), the dedup control (#4585closed, control#6956hit),mainnot merged a third time (the queue rebuilds on the current tip;mergeable_state: behindenqueues fine),CLAIM_DEBTuntouched as dispatched (gate green under the derivation), worktree removed. Out-of-scope finding #7762 (the ObjectGrid bare-array route, with the measurement and two remedies) filed unlabelled for triage — as dispatched, not answered here.Verdict: PASS. Same stroke:
needs:contract-reviewis cleared on the PR (this card never carried it) with a provenance comment citing this PASS; landing follows once every check run on216c7490iscompleted: success(5 in progress at 16:08Z: four test shards andType Check;Lint,Doc Snippet Type Check,Bundle Analysisalready green). Head moves ⇒ the label is re-hung and the delta re-reviewed.
Generated by Claude Code
Landing note — PR #7763 enqueued (2026-09-05T16:20Z)
Pre-landing three:
- In-seat contract-tier PASS on record: comment 5553073374 on this card, head
216c7490, carrying theImplemented-by:/Reviewed-by:pair and the three-item judgment (served tier read at 16:08Z). - Both carriers cleared: this card never carried
needs:contract-review; the PR-side label was cleared with provenance comment 5553075136 and read back at 16:11Z (documentation,package: types,plugin,tests).node scripts/pm/check-clause2-carriers.mjs --pair 7763exits 3 from this seat (REST session-gated, seat post §4) — the three limbs were verified by hand instead. - Check runs on
216c7490: 29completed: success+ 3skipped, none failed, none pending (full farm read by name at 16:19Z);mergeable_state: clean.
Landing actions:
draft:falseat 16:19Z.- Auto-merge enabled with
mergeMethod: SQUASHexplicitly at 16:20Z (tool echoes empty method/time — known reading, seat post §4). - Positive probe at 16:20Z:
git ls-remote origin 'refs/heads/gh-readonly-queue/main/*'showsrefs/heads/gh-readonly-queue/main/pr-7763-2760075ffe32e2e631423a270a05258ff80b57feat4dfdcc3c;main=2760075f(PR test(types): watch the mirror-wider-than-declared direction with a fifth parity ledger #7758's squash); the queue was otherwise empty.
Same-file note: #7322's dev (in flight) edits
objectql.zod.ts'sObjectKanbanSchemaregion and has been told to mergemainafter this lands. Follow-up #7762 (the ObjectGrid bare-array route) stays unlabelled for triage; theCLAIM_DEBTburn-down forListViewExportOptionsstays deferred behind the #7122 chain.Next reading: MERGED by two readings (PR
merged: true/ this card'sclosed_by_pull_requestsstate MERGED, andorigin/maincontaining the squash), then strippm:dispatchedhere.
Generated by Claude Code
- In-seat contract-tier PASS on record: comment 5553073374 on this card, head
MERGED (2026-09-05T16:35Z) — PR #7763 landed through the merge queue as squash
4dfdcc3conmain. Three readings: PRmerged: true(merged_at16:35:03Z); this card'sclosed_by_pull_requestsreference to PR #7763 readsMERGED;git log origin/mainat 16:35:31Z shows4dfdcc3c fix(types): bind the ListView exportOptions mirror to the spec field, drop the ListView casts (#6956) (#7763)at the tip.pm:dispatchedstripped in this stroke (remaining:finding,priority:p2,domain:spec). Follow-up filed by the dev as objectui#7762 (object-gridbare-arrayexportOptionsunvalidated), unlabelled for triage. Sessionsession_01BAZFhALsQsGqxui8sNqM8s.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026
Measured on
origin/main@b03ba3a(2026-08-31) while closing objectui#4535, whose declared file surface waspackages/types/src/objectql.ts+packages/plugin-grid/src/ObjectGrid.tsx. The defect below sits outside that surface, so it was reported rather than fixed in that PR.Note on spelling: generic type arguments below are written with a space after the opening bracket (
z.input< typeof X >). GitHub's body sanitizer silently eats short tag-shaped fragments, and it ate one of these on the first publish of this card.What landed, and what did not
objectui#4535 / objectstack#8010 reconciled
exportOptionswith the spec's five-key object form. The reconciliation landed for ObjectGrid:ListViewExportOptionsinpackages/types/src/objectql.tsdeclares exactlyformats,maxRecords,includeHeaders,fileNamePrefix,streaming;'pdf'is out ofListViewExportFormat;ObjectGrid.tsxreadsstreamingwith no cast; two guards pin both directions.The ListView leg did not.
ListViewis the componentObjectViewactually renders for a saved view, and it carries a second, unreconciled declaration of the same key.1. The zod mirror still declares the pre-#8010 shape
packages/types/src/zod/objectql.zod.ts:511-518:Three separate drifts against
@objectstack/spec@17.2.0(the installed pin), each measured against the spec's ownListViewSchema.shape.exportOptions:'pdf'is accepted here, in both spellings. The spec's enum is["csv","xlsx","json"]and a declared'pdf'is a parse-time refusal carrying anos migrate meta --from 16prescription. objectui validates a value the platform then refuses at publish — the author sees green locally and a refusal upstream.streamingis declared nowhere. It is the fifth spec key and the renderer honours it (below). This is precisely the undeclared-but-read shape objectstack#8010 was filed for, still open on this surface.strictObject(catchallnever), so a sixth key is refused upstream and silently stripped here.This is not cosmetic:
packages/types/src/objectql.ts:2121isexport type ListViewSchema = ListViewInferred & ListViewRuntimeProps, andListViewInferred = z.input< typeof ListViewSchema >(the zod one) reads from this declaration. SoListViewSchema['exportOptions']— the TYPE the ListView renderer is written against — is the legacy union with'pdf'and withoutstreaming, while its siblingObjectGridSchema['exportOptions']is the clean five-keyListViewExportOptions. Two authoring surfaces for one spec key, disagreeing.Same failure class as objectui#4605 (
the zod BaseSchema mirror still declares visible/disabled as boolean ... after the TS declarations widened), and it lives under the objectui#2231 umbrella.2. Both
as anystreamingreads survive in ListViewpackages/plugin-list/src/ListView.tsx::1324—&& (resolvedExportOptions as any)?.streaming !== false;:2705—&& (exportConfig as any)?.streaming !== false;These are objectui#4535 item 3's exact defect, in the other renderer. The casts are not gratuitous — they are load-bearing because of item 1: the key is genuinely absent from the type these reads go through. Fixing the zod mirror is what lets them be deleted, so the two halves are one change.
:1304carries the same'pdf'in its cast:3. Keep the array tolerance — measured, not assumed
:1299-1308'sresolvedExportOptionsfold (bare array to{ formats }) must STAY. objectui#4535 item 4 asked whether raw un-parsed metadata can still reach the renderer, and it can:normalizeListViewSchema(packages/core/src/utils/normalize-list-view.ts) does not touchexportOptions— zero occurrences in the file..parse()/.safeParse()on a view schema; the only such calls in the repo are in tests. That is exactly whyListViewInferredisz.inputand notz.infer, as its own doc comment records.packages/app-shell/src/views/ObjectView.tsx:2116forwards the stored value verbatim:exportOptions: viewDef.allowExport === false ? undefined : (viewDef.exportOptions ?? listSchema.exportOptions).So the spec's parse-time array lift never runs before the renderer, and a stored bare-array declaration still arrives as an array. The tolerance is load-bearing, not legacy. Note
ObjectGridhas none — a bare array reaching it degrades to the['csv','json']default, silently ignoring the author's declared formats. Worth deciding deliberately rather than inheriting.Suggested shape
zod/objectql.zod.ts'sexportOptionsas the spec's two-branch union:z.array(SpecFormatEnum).transform((formats) => ({ formats }))union az.strictObjectof the five keys,'pdf'gone from both. Ideally derived fromListViewSchema.shape.exportOptionsin@objectstack/spec/uirather than restated — it is a live export, andpackages/typesalready depends on the spec.as anycasts inListView.tsxand the'pdf'in the:1304cast; keep the array fold.packages/types/src/__tests__/export-options-spec-parity.test.ts(landed with objectui#4535) explicitly scopes itself to the TS declaration and states the zod mirror is unmeasured. Widen it once this is fixed.Also worth folding in
ListViewExportOptionsis entry 1 in the@object-ui/typeslist ofCLAIM_DEBTinscripts/check-spec-symbol-derivation.mjs:665— the shrink-only ledger of unbacked spec-alignment claims. Its claim is now backed by a real parity test, and the mirror's reason is measured (ListViewExportOptionsSchemais internal to the spec bundle, not a public export — onlyListViewSchemais). That makes it a clean burn-down via the ledger's own route 2: move it toCLAIM_ALLOWwith that reason. Not done in objectui#4535's PR becausescripts/**was outside its file surface and editing a gate script pulls in that script's own test suite.Re-check commands
Spec side, against the installed pin rather than a working tree: