fix(plugin-list): 消费 PageComponentSchema.dataSource —— saved view 按名引用首次可用,写了 dataSource 不再把组件弄坏 (objectstack#5576) - #3929
Merged
Conversation
…iew reference works, and writing the binding no longer breaks the block (objectstack#5576)
The spec declares a per-element data binding on every page component
(`dataSource: { object, view?, filter?, sort?, limit? }`) and objectui read none
of it on `list-view`. `ViewDataProvider.resolveElementDataSource` forwarded
`filter`/`sort`/`limit` and dropped `view` entirely, and had no caller outside
its own test; nothing mapped `object` onto the `objectName` a list actually
reads. "Reference a saved view by name" was published, validated and inert, so
every page that wanted a saved view's columns/filter/sort had to inline a second
copy of them.
Writing the binding also BROKE the block, for a reason unrelated to `view`:
`SchemaRenderer` spread the schema's `dataSource` metadata onto the component as
a React prop, and that is the prop name the host uses to inject the data-source
ADAPTER. The plain `{ object, view }` object shadowed the adapter, so the first
`dataSource.find(...)` threw `dataSource.find is not a function` and `list-view`
rendered "Couldn't load records" — a spec-compliant component failing next to
identical ones that omitted the binding.
- react: `SchemaRenderer` strips `dataSource` from the props it spreads (it is
schema metadata, like `visibleWhen`); renderers read it off `schema`. An
explicit React `dataSource` prop is unaffected. New
`useElementDataSource(schema, dataSource?)` resolves a binding, fetching the
named saved view from the object definition's `listViews` and the metadata
overlay's `listViews()`.
- core: new `element-data-source` module — `isElementDataSourceConfig`,
`collectSavedViews`, `resolveSavedView`, `composeElementDataSource`,
`elementDataSourceViewNotFoundMessage`. `resolveElementDataSource` honours
`view` through an optional `DataFetcher.fetchViews` and reports an
unresolvable view as an error instead of returning every record.
`resolveViewId` moved here from app-shell (re-exported there) so one matcher
serves both the object page and a page component.
- plugin-list: `ListViewBlock` maps the binding onto the props `ListView` reads.
`dataSource.*` keys are authoritative, view-supplied values are a baseline the
component's own keys override, and `filter` AND-combines at every level (the
spec calls the binding's filter "additional criteria"), so a binding can
narrow a saved view but never widen it. A `view` name that does not resolve
renders a configuration error naming the object's actual views and issues no
query — no fall back to the default view, because that turns a typo into a
silently wider answer.
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
✅ 验收通过(objectui 分片 PM,session 实物核验:base 验收要点:
范围外 finding objectstack#6953(record_picker 等其余 block 的逐 block 接线,Blocked-by 本单)立单规范,冻结期不派。 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 objectstack-ai/objectstack#5576
结论先说:「Couldn't load records」的真实机理不是 view 键被丢弃
issue 正文把两件事归到了一起。实测(单变量对照,见下)后它们是两个独立缺陷,必须分别修:
resolveElementDataSource透传filter/sort/limit、把view整个丢掉,且除测试外零调用方;渲染路径上也没有任何地方把dataSource.object映射到 list-view 真正读的objectName。所以「按名字引用 saved view」这条能力在运行时不存在。dataSource反而坏掉 —— 与view无关,是同名撞车:SchemaRenderer会把 schema 上所有非元数据键 spread 成 React props,而dataSource恰好正是宿主注入数据源适配器用的那个 prop 名。于是 spec 合法的dataSource: { object, view }这个纯 JSON 对象把适配器遮蔽掉,第一次dataSource.find(…)抛dataSource.find is not a function,list-view 把它渲染成 "Couldn't load records"。实测原始读数(改前,
packages/plugin-list一次性探针,已删):也就是说:分诊预判的「view 键丢弃导致查询坏」不成立;
view被丢弃的表现是静默空表/全量,而 "Couldn't load records" 来自 prop 撞车。两条都在本 PR 修掉。合成语义论证(spec 自己定了关键的一条)
ElementDataSourceSchema的 describe 文本把唯一有歧义的键定死了 ——filter是 "Additional filter criteria"。「additional」只能读成:view 已经限定的基础上再限定,所以 view filter 与 binding filter 必须and合成,binding 只能收窄、不能放宽。这也是 AI 产出的元数据里最安全的方向:一个写错的 per-element filter 不可能把 saved view 排除掉的行重新暴露出来。其余键 spec 没有 additional 之说,且都是单值(一个排序、一个上限),按分诊定的「view 提供基线、显式键覆盖」:
objectcolumnsfilterand合成("additional")sortlimitpagination.pageSize或 bindingviewType组件自身写的键与 view 的关系单独说一句:
view是引用,组件上直接写的键比它指向的东西更具体,所以组件自身的键盖过 view(空的columns: []视为「没写」—— 那是设计器对「未配置」的产物,而指定 view 的用意正是由 view 提供列)。dataSource.*是作者在这一处写的,标准更高:它同时盖过 view 和组件自身的同名键。view 名解析不到:报错,不是静默空表(语义已定并钉住)
解析不到时渲染一个配置错误面板(
role="alert",列出该对象实际有哪些 view),并且不发任何查询。刻意不回退到对象默认 view:那会把一个拼写错误变成更宽的答案(整个对象 vs saved view 选出的行),而页面看上去还是好的 —— 这正是 AI 生成的元数据最容易藏住的一类错。姿态与SchemaRenderer既有的 "Unknown component type" 红框一致(同一类:作者写的元数据指向了不存在的东西),因此沿用英文文案而未新造 10 个 locale 的 i18n 键。另有一条相邻但不同的事实要分开报:「该对象没有这个 view」与「这个数据源压根答不了 view 有哪些」是两件事,给两条不同的错误文案,不合并。
改了什么
@object-ui/react——SchemaRenderer把dataSource加入「schema 元数据、不作为 prop spread」名单(与visibleWhen同列)。消除撞车的根,对所有 data-bound block 一次生效;显式 ReactdataSourceprop 不受影响(它走...props,最后 spread)。新增useElementDataSource(schema, dataSource?),从对象定义的listViews与元数据 overlay 的listViews()两处取 saved view(与 app-shellObjectView同一优先级:overlay 覆盖 metadata)。@object-ui/core—— 新增element-data-source.ts:isElementDataSourceConfig(binding 与适配器的判别式)、collectSavedViews、resolveSavedView、composeElementDataSource、elementDataSourceViewNotFoundMessage。resolveElementDataSource不再丢弃view:经可选的DataFetcher.fetchViews解析,解析不到就返回error(而不是回退成全量)。resolveViewId从 app-shell 移到 core(app-shell 原路径改为 re-export),使对象页与页面组件用同一个名字匹配器,而不是两份会漂移的实现。@object-ui/plugin-list—— 新ListViewBlock取代原来的一行ListViewRenderer,把 binding 映射到ListView真正读的 props;并保留一层判别式兜底(即使有宿主把 binding 塞进dataSourceprop,也不会被当成适配器)。验证(两方向对照 + 反向验证)
packages/plugin-list/src/__tests__/ListView.elementDataSource.test.tsx就是正文那份单变量复现:dataSource: { object, view }→find('account', …),$filter= view 的 filter、$orderby= view 的 sort、$select= view 的 columns,且list-error-state面板为 null;dataSource→ 行为逐字不变;find零调用。命令与读数:
反向验证:两处各自独立,方向不同,先预判后跑
两个缺陷是分开修的,所以反向验证也必须分开做,否则一处的红会掩盖另一处压根没被钉住。
RV-B —— 把
plugin-list/src/index.tsx换回 origin/main(binding 消费撤掉,SchemaRenderer的剥离保留)。预判:view 系测试全红(
objectName未设 → 压根不发查询),但「不报 Couldn't load records」与「不带 dataSource 行为不变」两条仍绿 —— 前者靠的是剥离,不是 binding 消费。实测:6 条通过的正是预判的那 6 条(2 条 plugin-list + 4 条
SchemaRenderer.dataSourceBinding)。RV-A —— 把
packages/react/src/SchemaRenderer.tsx换回 origin/main(撞车恢复,ListViewBlock保留)。预判(与「改回去就红」的直觉相反,故先写下来):plugin-list 的 13 条仍然全绿,因为
ListViewBlock自己那层isElementDataSourceConfig兜底会拦住被塞进 prop 的 binding;转红的只有SchemaRenderer.dataSourceBinding里那条「不把 binding spread 成 prop」。实测读数与预判一致:ListView.elementDataSource.test.tsx的 13 条一条没红。这条反向读数本身就是一个结论:两层各自有独立的钉子,剥离(根)与判别式(兜底)不是同一件事的两种写法。未做浏览器实证,如实说明
任务建议用
verifyskill 做浏览器读数。没做,理由写清而不是补一个看起来像验证的东西:verifyskill 的作用域是 metadata-admin designer 的 preview gallery(无后端),而本改动的关键路径恰恰是「从适配器取 saved view 并解析」—— 无后端时它必然走到「无法列出该对象的 saved view」那条分支,得到的读数不能支撑正向结论。真正的端到端实证需要一张type: 'home'页面上带dataSource: { object, view }的 list-view,而那份元数据在 objectstack 仓的 example app 里,超出本 PR 范围。替代证据是上面的真实单变量复现(含TypeError现场与ListView.tsx:1257行号)与两个方向的反向验证。顺手记录的越界发现(未在本 PR 修)
list-view之外仍无人消费:element:record_picker读了object/filter/sort/limit但丢view;object-grid/object-form/object-kanban/object-calendar等整条绑定都不读(按 spec 写dataSource渲染出空)。本 PR 已把崩溃面(prop 撞车)一次性消除,剩下的接线用本 PR 落地的公共 helper 逐 block 做,单独立单以便每个 block 有自己的钉子。Generated by Claude Code — session
session_01GTRjn8xBqp75dk7kFupVRtGenerated by Claude Code