fix(plugin-grid,plugin-detail,plugin-list,core): 不物化列的字段类型不再给出排序头 (#3950) - #4965
Merged
Conversation
…ader from an unmaterialized column (#3950) A `formula` value is computed on read and no driver materialises a column for it, so a server `$orderby` naming one has nothing to order by. The sort never worked; until objectstack#6994 the platform answered `200` with the requested values unordered (asc byte-identical to desc on a real SQL driver), and it now answers `400 INVALID_SORT`. Either way a clickable header offers a sort that cannot be performed. `ObjectGrid` gated the affordance on the reference-bearing family alone (#3096). Unmaterialized types are a second reason a server sort is impossible, not a different mechanism, so the same seam now reads both — as do the two sort entry points of a related list, which derive the rule separately (measured: the `sortable` derivation is NOT shared across the data-table renderers). Membership moved out of a private set in `ListView` into `@object-ui/core` (`UNMATERIALIZED_FIELD_TYPES` / `isUnmaterializedFieldType`), bound to `@objectstack/spec`'s own storage predicate rather than restated, and pinned narrower than the spec's write contract: `summary` and `autonumber` have real columns and keep their headers. Client-side sorting is untouched (the key is the value the cell shows, which is honest), and a sort declared in view metadata is still sent and still refused by name rather than silently dropped. Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Aug 17, 2026
…materialized rule (#3950) Both packages document the relational carve-out; the same paragraphs now carry the second reason a server sort is withheld. `plugin-list`'s closing sentence also had to be corrected rather than extended: it claimed column-header sorting "is unaffected: it is client-side over the rows already loaded". That stopped being true at objectui#3106, which made a header click a server `$orderby` whenever the grid shows one window of a larger collection — so the sentence asserted the opposite of the rule this change applies there, and the two errors are the same sentence. The client-side half is kept and stated where it actually holds (inline data and the grouped view, which holds every row it groups). Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Aug 17, 2026
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收 ✅ ACCEPT(#3950,批次 19) 实物核验(按真实 merge-base 对账):12 files,+669/−72 —— 三处入口收敛(ObjectGrid CI 亲读:20/20 check runs completed,零失败(dependabot/coverage 两项 path-filter skipped,计绿)。 反向验证读数:变异 2 预判 1 红实测 3 红 —— 方向一致,多出的两条是 corpus 两处真实标本(CRM undraft + auto-merge(squash)。 Generated by Claude Code |
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 #3950
结论先说
formula列的排序头是修改前后都错的:它提供一个平台无法执行的排序。framework#6994 之前平台不说话(200,返回的正是它被要求据以排序的那些值,但没排序 —— 真实 SqlDriver 上 asc 与 desc 逐字节相同);之后是400 INVALID_SORT。所以这个修法不依赖 pin 推进,无 Blocked-by。卡面两个开放验证点(先做,结论如下)
① RelatedList 等 data-table 渲染器是否共享 sortable 派生路径 —— 不共享。 服务端排序的 affordance 判定在三处各自调用
isExpandableFieldType,谁也不读谁:packages/plugin-grid/src/ObjectGrid.tsxwithSortabilitymanualSortingOnpackages/plugin-detail/src/RelatedList.tsxsortableColumnswindowedpackages/plugin-detail/src/RelatedList.tsx排序按钮行data-list才有)windowedsort-values.ts的文档注释其实已经记下了这个状态:「surfaces that sort on the server must instead not offer relational columns as sort keys (that half of #3096 lives at each entry point)」。第四处是ListView工具栏的排序选择器,它已经排除 formula(#4243),但用的是自己文件里的一份私有集合。派生路径不共享,所以三处都改;并且把成员集合上提到
@object-ui/core,让四处读同一个判断。ListView渲染的正是type: 'object-grid',所以 ObjectGrid 一处即覆盖 ListView 的表头与独立 ObjectGrid 两个入口。② corpus 里有没有 list view 真渲染 formula 列 —— 有,两处,所以这是今天就用户可见的缺陷:
examples/app-crm/src/views/opportunity.view.ts的默认columns与listViews.all.columns都含{ field: 'expected_revenue' },而expected_revenue是crm_opportunity上的Field.formula(opportunity.object.ts:51);examples/app-showcase/src/ui/views/project.view.ts:24的columns含{ field: 'budget_remaining' },是 showcaseproject上的Field.formula(project.object.ts:65)。两者都真的会渲染出值(
isProjectableField只看字段是否声明,formula 已声明 → 进$select,服务端 on-read 水合),所以是「有值、可点、点了没用/报错」。即:CRM 的 Opportunities 列表、showcase 的 Projects 列表,点「Expected Revenue」/「Budget Remaining」表头 —— pin 推进前是静默的无效点击,推进后是用户可点出来的 400。改法
一个门,两个理由,同一个机制。列被当作排序键的前提是两条同时成立:
lookup/master_detail/user/tree):存的是外键 id,显示的是关联记录名;服务端只能按 id 排(objectstack#4256 已定不做 join)。formula):没有任何驱动为它物化列,压根没有可排的键。成员集合从
ListView的私有拷贝搬到@object-ui/core(UNMATERIALIZED_FIELD_TYPES/isUnmaterializedFieldType),并且绑定到@objectstack/spec自己的存储事实而不是在这里重述一遍 —— 平台的 filter 门对自己用的就是这个理由(「so this gate and the drivers cannot disagree about which types have a column」)。重述会漂移,漂移的两个方向都坏:要么收掉一个能用的 affordance,要么留一个会 400 的。刻意窄于 spec 的写入契约
COMPUTED_VALUE_TYPES(formula/summary/autonumber):summary和autonumber各有真实维护的列、排序正常,按写入契约收会误伤两个能用的类型 —— 平台自己的 conformance 用例点名钉了这个坑。测试里两边都钉住。刻意不改的两件事
manualSortingOn/windowed)。validate-searchable-fields),不在本 PR 范围。文档(AGENTS.md #2)
两个包的 README 都有「Sorting」小节在讲这条规则,一并跟上:
packages/plugin-detail/README.md—— 原本准确描述了 relational 那一半,补上第二个理由,并点明这条规则作用在本卡片的两个排序入口上。packages/plugin-list/README.md—— 这一段不是「补充」而是纠正,请 reviewer 特意看一眼:它原先写着 column-header sorting 「is unaffected: it is client-side over the rows already loaded」。这句自 objectui#3106 起就不成立(窗口化时表头点击本身就是服务端$orderby),于是它恰好断言了与本次改动相反的事实 —— 两个错误是同一句话,无法只改一个。客户端那一半保留,但挪到它真正成立的地方(inlinedata、以及持有全部行的 grouped 视图)。测试
新增/改动 27 个断言(全绿):
packages/core/src/utils/__tests__/unmaterialized-fields.test.ts—— 成员恰好是formula;引用同一性(不是值相等:忠实拷贝写下的当天就通过一切值比较,之后静默漂移);summary/autonumber反向钉住;与EXPANDABLE_FIELD_TYPES互斥(两个理由若重叠,消费端会有一支变成死代码,且两者给的补救建议不同)。packages/plugin-grid/src/__tests__/ObjectGrid.unmaterializedSort.test.tsx—— formula 表头无排序 affordance;点它不产生任何 refetch(所以没有请求给平台去回 400);误伤对照:text/currency/percent/autonumber/summary五列逐个真点表头并断言$orderby发出;客户端模式下 formula 表头保持可点。packages/plugin-detail/src/__tests__/RelatedList.unmaterializedSort.test.tsx—— 两个排序入口各自钉住(表头 +data-list的按钮行),各带 stored 列对照与客户端模式对照。packages/plugin-list/src/__tests__/ListView.relationalSort.test.tsx—— [console] List sort: filterableFields doubles as the sort whitelist, and a header click discards the view's multi-level default with no way back #4243 的 formula 规则此前只有文字描述、没有行为钉子(fixture 里根本没有 formula 字段)。往共享 fixture 加一个,该文件原有的精确相等断言就顺带变成了这条规则的钉子。反向验证(先预判后跑)
变异 1 —— 把 ObjectGrid + RelatedList 两处新加的门撤回(保留 ListView)。预判:恰好 4 红(grid 的表头与点击、related list 的表头与按钮),8 个误伤对照与 2 个客户端模式全绿,ListView 选择器全绿 —— 因为那条规则先于本 PR 存在。实测完全一致:
Tests 4 failed | 23 passed。变异 2 —— 删掉 ListView 选择器里的第二条规则。预判 1 红,实测 3 红,方向一致而数量更多:多出来的两条是该文件原有的精确相等断言(
toEqual(['Name','Amount'])、toEqual([…,'Owner (by ID)'])),因为score进了共享 fixture,它们免费变成了这条规则的钉子。一处照直说明,不套模板:变异 2 下
keeps a formula field the current sort already names仍然绿。这是可预期的 —— 它断言的是「被保留」,而规则被删掉后该字段本来就不会被收掉,所以它是因为什么都没发生而通过。单独看它不承重;承重的是紧邻的 withholding 断言,两条合起来才区分「被例外保留」与「从未被收掉」。保留它是为了钉住例外的标签形状(不带 by-ID 后缀 —— 那是关于关系键的陈述,这个字段压根没有键可描述)。变异都在 commit 之后做、用
git checkout --还原(不用 stash),还原后重跑 27 全绿。门与检查
changeset:
.changeset/formula-column-sort-header-3950.md(patch × 4 包)。相邻发现,均已立卡、未认领,不在本 PR:objectstack#9257(SORT 轴缺 authoring gate)、objectui#4966(
ListView.tsx一个 docblock 错位)、并在 objectui#4559 上留了坐标漂移说明(它的取证正落在本 PR 改写的那个注释块里,且它本身未被本 PR 修掉)。