Skip to content

ObjectViewSchema 的 table / form 子配置类型零声明成员(Omit 被索引签名塌掉),且 ObjectView 的转发白名单钉在 ObjectGridSchema 的 legacy 键上 —— 规范键 pagination/selection/filter/sort 全部不读 #5102

Description

@yinlianghui

发现于 #5077 的实施(PR #5101)。Filed unassigned, not claiming.

两个机制叠在同一处:ObjectViewSchema.table / .form 既不告诉作者有哪些键,又只转发一小撮 legacy 拼法。

一、子配置类型零声明成员

// packages/types/src/objectql.ts:1440 附近
table?: Partial< Omit< ObjectGridSchema, 'type' | 'objectName' > >;
form?:  Partial< Omit< ObjectFormSchema, 'type' | 'objectName' | 'mode' > >;

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(本体) 61
ObjectFormSchema(本体) 67
NonNullable< ObjectViewSchema['table'] > 0
NonNullable< ObjectViewSchema['form'] > 0

即 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(转发,生效) ObjectGridSchema 上的规范继任者(不转发,写了无效)
pageSize(:846,@deprecated Use pagination.pageSize instead) pagination(PaginationConfig)
selectable(@deprecated Use selection.type instead) selection(SelectionConfig)
defaultFilters(Record< string, any >) filter
defaultSort(单个 { field, order },@deprecated Use sort instead) sort
fields(@deprecated Use columns instead) columns(这一个两边都转发,是唯一的例外)

后果:作者照 ObjectGridSchema 的规范键写 table: { pagination: { pageSize: 25 } }
—— 编译通过(见机制一)、语义正确、运行时完全无效;要真的生效必须写那个被
标记为 legacy 的 pageSize。#5077 的 PR 正是因此拒绝把 table.pagination 写进
README(会教一个不被读的键),改写 table.pageSize 并在文档里注明这层落差。

影响

可能的方向(交裁定,不预断)

  1. 把 table / form 显式建模成真实的子配置接口(不再走 Omit),声明面即转发面;
  2. 保留 Omit 但先摘掉 BaseSchema 的索引签名(影响面大,牵动 ObjectView 用 (schema as any) 从 object-view 节点读 31 个键转发给 renderListView,其中 27 个不是 ObjectViewSchema 声明成员 —— #5091 在 ObjectView 上的同形实例 #5097 同一根因);
  3. 补齐转发白名单,让规范键(pagination/selection/filter/sort)也生效,legacy 键保留别名。

同族

分级

交 PM 分诊(未打标签)。严重度落点在「规范键写了无效」这一条:它比纯声明面缺口更靠近
用户,作者拿不到任何失败信号。

Activity

  1. yinlianghui commented on Aug 18, 2026

    @yinlianghui
    CollaboratorAuthor

    (正文末尾的署名脚注被 issues 端点剥掉了,按纪律用评论补回。)


    Generated by Claude Code

  2. yinlianghui commented on Aug 18, 2026

    @yinlianghui
    CollaboratorAuthor

    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

  3. yinlianghui commented on Aug 18, 2026

    @yinlianghui
    CollaboratorAuthor

    补一条本卡决策会用到的事实,发现于 #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

  4. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    Contributor

    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 collapsed Omit with 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:queue in the same stroke.


    Generated by Claude Code

  5. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Claim: 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 on ObjectGridSchema; no contract accept/reject behaviour changes and no public surface widens.
    Serial constraints cleared: none. Near-miss checked and cleared — the in-flight #5233 edits packages/app-shell/src/views/ObjectView.tsx, a different file in a different package from this card's packages/plugin-view/src/ObjectView.tsx (same basename only). No other in-flight card touches plugin-view.

    Stale-premise check against origin/main @ bc2922a8: premise holds, line numbers drifted. The card cites ObjectView.tsx:833-848 / :856-880 / :666; actual current sites are :604, :607, :1030-1033, :1155, :1159. Legacy forwarding confirmed present (defaultFilters, defaultSort, pageSize, selectable); canonical pagination / selection / filter / sort confirmed absent as read points. Counter-probe schema.table?.fields = 4 hits, so the zero on the canonical keys is a reading, not an unreachable path.


    Generated by Claude Code

  6. claude commented on Aug 18, 2026

    @claude
    Contributor
    {
      "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

  7. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    ACCEPT — 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/types has zero files in the diff, so direction 1 (replacing the collapsed Omit) 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:

    1. 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/mergedSort for the delegated renderListView). 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.

    2. Precedence on the grid path was not the dev's to invent, and it correctly didn't invent it. ObjectGrid already 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 inside ObjectView would have put two layers in a position to disagree — and synthesising a pagination object out of a legacy pageSize would have flipped ObjectGrid's paginationEnabled for every view that only ever wrote the deprecated key. That is a regression my dispatch order did not anticipate and the dev did.

    3. The canonical filter/sort slots deliberately carry the table segment only. The two slots are not interchangeable downstream (ObjectGrid lowers the canonical one through toFilterNode, 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:


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions