Repository navigation
ObjectViewSchema 的 table / form 子配置类型零声明成员(Omit 被索引签名塌掉),且 ObjectView 的转发白名单钉在 ObjectGridSchema 的 legacy 键上 —— 规范键 pagination/selection/filter/sort 全部不读 #5102
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 18, 2026 PM 分诊(session_01GTRjn8xBqp75dk7kFupVRt,objectui 分片):挂
bug+needs-user-decision,入决策箱。table/form子配置类型被索引签名塌成零声明成员(实测 0 vs 本体 61/67)+ 转发白名单钉在 legacy 键上、规范继任者一个都不读 —— 作者按规范键写编译通过、语义正确、运行时无效,是「strict 声明 + 运行时另一套」家族的又一实例。裁定建议与 #5097 / #5099 / #5091 / #5068 同读一次定调:五卡机制相邻(索引签名致盲 / as-any 转发 / 运行时容忍支路 / legacy 白名单),收敛方向(收紧类型 vs 归一读点 vs 扩白名单)一次裁定可少裁四次。本卡的即时事实已由 PR #5101 在 README 侧如实教学(教 legacy 拼法并写明落差),不预断方向。
Generated by Claude Code
补一条本卡决策会用到的事实,发现于 #5088 的实施(PR #5109)。不另立卡 —— 同一条转发白名单、同一个机制,直接记在这里。
本卡现在的表述是「转发白名单钉在
ObjectGridSchema的 legacy 键上,规范键pagination/selection/filter/sort全部不读」。实测下来,这条白名单还是路径相关的:同一个table.columns,在网格路径上读、在非网格路径上不读。读数取自
origin/main@405d54e4d:-
网格路径(
defaultViewType为grid)——gridSchema同时转发两者(packages/plugin-view/src/ObjectView.tsx:838-839):fields: currentNamedViewConfig?.columns || activeView?.columns || schema.table?.fields, columns: currentNamedViewConfig?.columns || activeView?.columns || schema.table?.columns, -
非网格路径(
kanban/gallery/calendar/timeline/gantt/map,经SchemaRenderer渲染子 schema)——generateViewSchema的baseProps只读table.fields(:666):fields: currentNamedViewConfig?.columns || activeView?.columns || schema.table?.fields,schema.table?.columns在这个分支没有任何读点。
后果:作者写
defaultViewType: 'kanban'加table: { columns: [...] }时,字段列表是空的 —— 而同样一份table配置换成defaultViewType: 'grid'就生效。两条||链的前两段(listViews条目的columns、activeView.columns)都通,只有table这一段的键名两边不一致,所以症状只在「不用具名视图、直接写table」时出现。这与本卡已记的 legacy/规范落差不是同一条:
columns恰恰是那批 legacy 键里唯一被网格路径转发的规范拼法,所以它不能用「白名单只认 legacy」解释;它是白名单在两条渲染路径上不一致。本卡要决定table子配置的类型与转发口径时,这条决定了「修Partial< Omit< ... > >塌陷」是否足够 —— 只补类型声明不会让非网格路径开始读columns。PR #5109 的文档侧按现状如实写:非网格路径的字段来源写成
table.fields(或具名视图的columns),不写table.columns,不预断本卡的决策。
Generated by Claude Code
-
Maintainer ruling recorded — maintainer, 2026-08-18, batch acceptance of the triage seat's decision-inbox recommendations, verbatim: 「同意」. Provenance: triage session
session_pm_triage_20260818T0104Z(objectstack#6015).Ruled: direction 3 now, direction 1 deferred to the #5043 family track. Concretely: extend ObjectView's forwarding whitelist so the canonical keys (
pagination/selection/filter/sort) take effect, keeping the legacy spellings (pageSize/selectable/defaultFilters/defaultSort) as working aliases — an author writing the canonical shape stops being silently ignored today. The explicit sub-config modeling (direction 1 — replacing the collapsedOmitwith real interfaces) and any index-signature surgery (direction 2) belong to the #5043 structural track and are ⛔ not to be attempted inside this card. Docs follow-through: the pages PR #5101 wrote to teach the legacy spellings can teach the canonical keys again once this lands — file that follow-up in the same PR's out-of-scope list rather than riding it.Label
needs-user-decision→pm:queuein the same stroke.
Generated by Claude Code
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsClaim: PM loop round 12
Session:session_01RV6yuVCxymHYE16PL9vQkE
Branch:claude/issue-5102-objectview-canonical-key-forwarding
Worktree:objectui-issue-5102
Domain:repo:objectui(plugin-view)
File surface:packages/plugin-view/src/ObjectView.tsx+ its tests (stop on breach; explain in the report)
Container & model: M,mode:subagent,model: opus
Clause-②: no — the maintainer ruling adds reads for keys already declared onObjectGridSchema; no contract accept/reject behaviour changes and no public surface widens.
Serial constraints cleared: none. Near-miss checked and cleared — the in-flight #5233 editspackages/app-shell/src/views/ObjectView.tsx, a different file in a different package from this card'spackages/plugin-view/src/ObjectView.tsx(same basename only). No other in-flight card touchesplugin-view.Stale-premise check against
origin/main@bc2922a8: premise holds, line numbers drifted. The card citesObjectView.tsx:833-848/:856-880/:666; actual current sites are:604,:607,:1030-1033,:1155,:1159. Legacy forwarding confirmed present (defaultFilters,defaultSort,pageSize,selectable); canonicalpagination/selection/filter/sortconfirmed absent as read points. Counter-probeschema.table?.fields= 4 hits, so the zero on the canonical keys is a reading, not an unreachable path.
Generated by Claude Code
{ "issue": 5102, "status": "done", "branch": "claude/issue-5102-objectview-canonical-key-forwarding", "pr": "https://github.com/objectstack-ai/objectui/pull/5274", "premise_still_valid": true, "summary": "Implements the maintainer ruling (2026-08-18, 「同意」) direction 3: ObjectView's forwarding whitelist now carries the canonical table keys pagination/selection/filter/sort, with pageSize/selectable/defaultFilters/defaultSort kept as working aliases. Premise re-verified independently on origin/main @ bc2922a82 before editing — all four canonical keys were 0 read points, counter-probe schema.table?.fields was 4, so the zero was a real reading. The card's line numbers had drifted and the forwarding turned out to span THREE clusters, not the one the card quoted: the non-grid data fetch (:603-608), gridSchema (:1041-1073), and mergedFilters/mergedSort for the delegated renderListView (:1163-1173); all three are fixed so the four keys behave identically on every path. Precedence is canonical-wins, and on the grid path it was not mine to invent: ObjectGrid ALREADY resolves all four pairs canonical-first (ObjectGrid.tsx:2647/:2354/:1098/:2701), so ObjectView forwards both slots and lets the existing downstream rule arbitrate — re-resolving in ObjectView would let two layers disagree, and synthesising a pagination object from a legacy pageSize would flip ObjectGrid's paginationEnabled for every legacy-only view. On the grid path the canonical filter/sort slots deliberately carry the table segment only (filter: viewFilter ? undefined : schema.table?.filter): the two slots are not interchangeable downstream (ObjectGrid lowers the canonical one through toFilterNode and raw-assigns the legacy one), and forwarding unconditionally would let a table default outrank the active named view. packages/types/src/objectql.ts was NOT touched — the collapsed Omit and the BaseSchema index signature stay with the #5043 structural track, per the ruling. Roll-back clause not triggered: nothing forced a types edit and no accept/reject behaviour changed.", "tests": "All figures from the FINAL commit c2648b494. (1) `pnpm exec vitest run packages/plugin-view/` (repo root, per AGENTS.md — a package-level filter silently runs the console suite, objectui#3378) -> `Test Files 14 passed (14) / Tests 153 passed (153)`. (2) New file packages/plugin-view/src/__tests__/ObjectView.canonicalTableKeys.test.tsx, 26 tests: each canonical key takes effect; each legacy spelling still works; precedence when both are written; the named-view guard; and that no canonical value is synthesised from an alias alone. (3) REVERSE VERIFICATION, predicted before running: 15 red / 11 green, per block 6/0/1/0/5/3. Observed EXACTLY that — `Tests 15 failed | 11 passed (26)`, every red in the predicted block. The two blocks predicted green are the point: they pin behaviour this PR must not change. Method: implementation committed FIRST (3333f6e4b), then ObjectView.tsx restored from origin/main with the new tests kept, run, then restored via `git checkout <branch> -- <path>`; no git stash at any point. REBUILD STATEMENT: no rebuild is involved or claimed for this ablation — the test reaches the subject through the relative import `../ObjectView`, so the mutated subject is compiled from src, never resolved through a dependency's dist/exports; ablation-vs-dist staleness cannot apply. Mutation reach was still confirmed directly (canonical read counts 0 while ablated, back to 1/1/3/3 after restore). (4) `pnpm --filter @object-ui/plugin-view type-check` -> exit 0, script name echoed (`tsc --noEmit && tsc -p tsconfig.test.json`) so it really ran rather than zero-matching. (5) `lint` -> exit 0, 0 errors / 181 warnings, all pre-existing house style (the new test file's `no-explicit-any` warnings match sibling ObjectView.filterSources.test.tsx exactly; 155 such warnings package-wide). (6) `build` -> exit 0. (7) Gates derived from my actual diff (objectui has no scripts/pm/dispatch-gates.mjs — that is an objectstack-repo mechanism): check:control-bytes -> `OK (scanned 4663 tracked text file(s))`, check:self-import -> OK, check:spec-symbols -> OK; plus a manual control-byte self-scan over the three changed files, no hits. (8) Downstream direction check: `@object-ui/app-shell` type-check green AFTER building its dependency closure. Before the closure build it reported TS2307 on @object-ui/auth / plugin-chatbot / plugin-list — stale-dist noise, not this change; recorded so it is not chased later.", "open_questions": [], "out_of_scope_findings": [ "filed as #5269: ObjectView forwards table.columns on the grid path only — the non-grid (generateViewSchema baseProps, :842) and delegated renderListView (:1247) paths read table.fields alone, so defaultViewType:'kanban' + table:{columns:[...]} yields an empty field list. Re-verified on bc2922a82; filed with the exact one-line diff and a THIRD read point the card's third comment did not list. Not fixed here: columns is not among the four keys the ruling names. Separability confirmed — this PR never touched those two `fields:` lines and did not rewrite baseProps, so the fork the dispatch warned about did not arise.", "filed as #5270: a named view's `sort` ARRAY (NamedListView.sort is Array<{field,order}>) is forwarded into gridSchema.defaultSort, declared a SINGLE {field,order}; ObjectGrid re-wraps it to [[...]] and parseSchemaSort (ObjectGrid.tsx:69) skips the nested entry, so the grid shows no sort at all, while the fetch path builds the literal string 'undefined undefined' as $orderby. Pre-existing on both sides of this diff and confined to the two precedence segments the card ruled out of bounds. Current behaviour is pinned in my new test as explicitly unchanged; that pin should be updated, not deleted, by whoever takes it.", "filed as #5271: the docs follow-through the ruling itself asked for — the plugin-view pages PR #5101 wrote to teach the legacy spellings can teach the canonical keys again now that they take effect. Filed rather than ridden on this PR, per the ruling's explicit instruction.", "no new issue — #4869 (finding, open) already records that the non-grid path passes $orderby through without the shared sort sink. It quotes cluster A, which this PR extends but does not normalise, so it remains accurate as written and stays open. Cross-linked from #5270 rather than duplicated." ] }
Generated by Claude Code
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsACCEPT — PR #5274 flipped ready and enqueued. All 21 checks resolved: 19 success, 2 skipped by design.
Path surface via
git diff --name-only origin/main...FETCH_HEAD: the changeset,packages/plugin-view/src/ObjectView.tsx, and its new test. No governed surface. I verified the ruling's boundary mechanically —packages/typeshas zero files in the diff, so direction 1 (replacing the collapsedOmit) and direction 2 (index-signature surgery) were genuinely left to the #5043 structural track rather than merely promised.Three things in this implementation are better than what my dispatch order asked for:
-
The forwarding spans three clusters, not the one the card quoted. Cluster A (
:603-608, the non-grid data fetch feeding kanban/gallery/calendar/timeline/gantt/map), B (:1041-1073,gridSchema), C (:1163-1173,mergedFilters/mergedSortfor the delegatedrenderListView). All three fixed, so the four keys behave identically on every path — a fix to only the quoted cluster would have left two paths still silently dropping canonical keys. -
Precedence on the grid path was not the dev's to invent, and it correctly didn't invent it.
ObjectGridalready resolves all four pairs canonical-first (:2647,:2354,:1098,:2701). So the fix forwards both slots and lets the existing downstream rule arbitrate. Re-resolving insideObjectViewwould have put two layers in a position to disagree — and synthesising apaginationobject out of a legacypageSizewould have flippedObjectGrid'spaginationEnabledfor every view that only ever wrote the deprecated key. That is a regression my dispatch order did not anticipate and the dev did. -
The canonical
filter/sortslots deliberately carry thetablesegment only. The two slots are not interchangeable downstream (ObjectGridlowers the canonical one throughtoFilterNode, raw-assigns the legacy one), and forwarding unconditionally would let a table default outrank the named view the user is actually looking at. Both pinned.
Reverse verification predicted 15 red / 11 green with a per-block breakdown, and observed exactly that — including which blocks stay green, since those pin behaviour this PR must not change.
Three follow-ups filed rather than ridden, per the ruling:
- ObjectView forwards
table.columnson the grid path only — kanban/gallery/calendar and the delegated renderer readtable.fieldsalone, so a canonicaltable: { columns: [...] }yields an empty field list #5269 —table.columnsforwarded on the grid path only; the non-grid and delegated paths readtable.fieldsalone, sodefaultViewType: 'kanban'+table: { columns: [...] }still yields an empty field list. Filed with the exact one-line diff and a third read point the card's original note did not list. Correctly excluded:columnsis not among the four keys the ruling names, and the separability was confirmed rather than assumed. - A named view's
sortarray is forwarded intogridSchema.defaultSort, declared a SINGLE{ field, order }— ObjectGrid re-wraps it andparseSchemaSortdrops it, so the grid shows no sort at all #5270 — new, and the sharpest of the three: a named view'ssortarray is forwarded intogridSchema.defaultSort, which is declared a single{field, order}.ObjectGridre-wraps it andparseSchemaSortskips the nested entry, so the grid shows no sort at all, while the fetch path builds the literal string'undefined undefined'as$orderby. Pre-existing on both sides of this diff and confined to the two precedence segments the ruling put out of bounds. Current behaviour is pinned here as explicitly unchanged — that pin should be updated, not deleted, by whoever takes it. - Docs follow-up to #5102: the plugin-view pages can teach the canonical
tablekeys again (pagination/selection/filter/sort) now that they take effect #5271 — the docs follow-through the ruling itself asked for.
Generated by Claude Code
-
- added a commit that references this issue
on Aug 19, 2026
发现于 #5077 的实施(PR #5101)。Filed unassigned, not claiming.
两个机制叠在同一处:
ObjectViewSchema.table/.form既不告诉作者有哪些键,又只转发一小撮 legacy 拼法。一、子配置类型零声明成员
ObjectGridSchema extends BaseSchema,而BaseSchema带[key: string]: any,于是
keyof ObjectGridSchema退化为string(实测:把它赋给字面量类型报Type 'string' is not assignable to type '1')。Omit的实现是Pick< T, Exclude< keyof T, K > >,Exclude< string, 'type' | 'objectName' >仍是
string—— 结果Pick只剩索引签名。实测(checker.getPropertiesOfType,对构建产物):
ObjectGridSchema(本体)ObjectFormSchema(本体)NonNullable< ObjectViewSchema['table'] >NonNullable< ObjectViewSchema['form'] >即 JSDoc 写着「Inherits from ObjectGridSchema」的那个子配置,在类型上是个
纯
{ [k: string]: any }:补全给不出任何键,table: { colums: [...] }这类错拼 rc=0 静默通过(#5077 的改前探针 H 已实测)。
二、转发白名单钉在 legacy 键上
ObjectView并不透传table/form,而是逐键白名单转发(
packages/plugin-view/src/ObjectView.tsx:833-848建gridSchema,:856-880建 formSchema)。
table侧转发的 10 个键里,有 5 个是ObjectGridSchema自己标了
@deprecated的 legacy 键,而它们各自的规范继任者一个都不读:table.K(转发,生效)pageSize(:846,@deprecated Use pagination.pageSize instead)pagination(PaginationConfig)selectable(@deprecated Use selection.type instead)selection(SelectionConfig)defaultFilters(Record< string, any >)filterdefaultSort(单个{ field, order },@deprecated Use sort instead)sortfields(@deprecated Use columns instead)columns(这一个两边都转发,是唯一的例外)后果:作者照
ObjectGridSchema的规范键写table: { pagination: { pageSize: 25 } }—— 编译通过(见机制一)、语义正确、运行时完全无效;要真的生效必须写那个被
标记为 legacy 的
pageSize。#5077 的 PR 正是因此拒绝把table.pagination写进README(会教一个不被读的键),改写
table.pageSize并在文档里注明这层落差。影响
两边还不是同一撮。AI 作者按规范键写出来的元数据静默失效,正是要消除的静默成功类。
可能的方向(交裁定,不预断)
table/form显式建模成真实的子配置接口(不再走Omit),声明面即转发面;Omit但先摘掉BaseSchema的索引签名(影响面大,牵动 ObjectView 用(schema as any)从 object-view 节点读 31 个键转发给 renderListView,其中 27 个不是 ObjectViewSchema 声明成员 —— #5091 在 ObjectView 上的同形实例 #5097 同一根因);pagination/selection/filter/sort)也生效,legacy 键保留别名。同族
(schema as any)从 object-view 节点读 31 个键转发给 renderListView,其中 27 个不是 ObjectViewSchema 声明成员 —— #5091 在 ObjectView 上的同形实例 #5097 —— 同一节点上 27 个as any未声明键转发(同根因:BaseSchema 索引签名)。(schema as any)读的 4 个键不在GRID_QUERY_INPUTS里 —— #4648 要消除的「渲染器读得到、声明面否认」在这些键上仍然存在 #5091 /ObjectMapSchema(types)声明的键几乎与 ObjectMap 实际读的键无交集:map块整体未建模,组件 props 类型写的是ObjectGridSchema,每处 map 读取都走as any#5018 —— ObjectGrid / ObjectMap 上的同类声明面缺口。分级
交 PM 分诊(未打标签)。严重度落点在「规范键写了无效」这一条:它比纯声明面缺口更靠近
用户,作者拿不到任何失败信号。