Skip to content

fix(plugin-detail): record:related_list 的 filter 接通查询,Add 按钮门与 picker 对话框对齐 (objectstack#7118, #3895) - #3981

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-7118-3895-relatedlist-gates
Aug 9, 2026
Merged

fix(plugin-detail): record:related_list 的 filter 接通查询,Add 按钮门与 picker 对话框对齐 (objectstack#7118, #3895)#3981
yinlianghui merged 1 commit into
mainfrom
claude/issue-7118-3895-relatedlist-gates

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes objectstack-ai/objectstack#7118
Fixes #3895

两单同文件(packages/plugin-detail/src/RelatedList.tsx),拆开必然串行,故一 PR 双 Fixes。基 5bfaabde0(PR #3969 的 objectstack#6953 落地)。

一、objectstack#7118 — record:related_list.filter 声明了却无人读

前提复核(先验证,再实施)

issue 正文的事实在基 sha 上逐条成立:

作者按已发布的 inputs 写 filter,得到该父记录下的全部子行 —— 不报错、os validate 绿、SDUI prop walk 也绿(已声明键不触发 unknown-prop)。objectstack#4413 的形状。

方向枚举与选择(AGENTS.md #0.1)

决定:接通。 两案在两条轴上的分析:

A. 接通(本 PR) B. 撤声明
长期正确性 filterspec 的键 —— @objectstack/specjson-schema/ui/RecordRelatedListProps.json 明确声明它,item 形状是完整的 ViewFilterRule,描述 "Additional filter criteria for related records"。给它一个读点即是让实现追上契约。 只删 objectui 的 inputs 条目不能消除这条声明:spec 仍然声明、渲染器仍然不读,缺陷一字未改,只是变得更难被发现(反而制造 spec↔registry 的反向不一致,正是 #3808add/relationshipValueField 修掉的方向 —— 声明面与运行时互相矛盾且无人报告)。真正的"撤声明"是在 objectstack 仓按 ADR-0049 退休一个 spec 公共属性,超出本 PR 文件面,也是一次公共契约删除。
让 AI 写的 metadata 难写错 声明 = 强制:作者写的约束真的进查询;而且 dataSource: { object, view } 里 saved view 的 filter 不再被丢弃 —— 列表不可能再比它引用的 view 更宽(objectstack#5576 立下要禁止的方向)。 撤掉后作者失去唯一的合规写法,只能退回 lookupFilters 之类把作者约束降级成用户可编辑建议的路子(#3831 已论证过这是错的)。

成本上 A 也不贵:合成用仓库既有的单一 filter 汇聚点 mergeFilterNodes,不新增任何转换方言。

实现

  • RelatedList 新增 filter prop(spec 的 ViewFilterRule[],或 ElementDataSourceGate 合成出的 AST 节点),与 { [referenceField]: parentId }mergeFilterNodesAND 合成。父关系条件不可协商:"additional" 只能收窄本父记录的子集,永远不能放宽。
  • 没写 filter 时查询逐字节不变(仍是 MongoDB 风格对象,不是等价的新 AST)—— 差别在屏幕上看不见,在每一个钉住 wire 的调用点上看得见。
  • filter: [](designer 的"未配置")视作未声明。
  • RECORD_RELATED_LIST_DATA_SOURCEfilter: true;PR fix(sdui): PageComponentSchema.dataSource 逐 block 接线,record_picker 不再丢 view (objectstack#6953) #3969 那条反向断言翻转为它自己注释里预告的正向断言。
  • 遗留的 raw-URL 兜底路径(无 adapter,查询语言只有 filter[字段]=值,连操作符都表达不了):声明了 filter 就拒绝取数并说明原因,而不是静默丢掉 —— 答得比 metadata 要求的更宽,正是本单要消除的那一类。
  • issue 第 4 条(核对 inputstype 与实际形状):type: 'array' 与 spec 的 z.array(ViewFilterRuleSchema) 一致,无需改;description 补上成员词汇与合成规则(ComponentInput 是平的,数组入参的元素形状只能写在自己的 prose 里)。

二、objectui#3895 — Add 按钮不判 dataSource

方向按分诊席 2026-08-09 的晋级评论既定(方向 1,不重开)。add 链上三个消费点原本两判一不判:对话框判 dataSourcehandleAddRecords 判、按钮不判。于是无 adapter 的宿主里按钮照常渲染,点击把 pickerOpen 置 true 而对话框根本不存在 —— 没有任何可见反应,也没有任何提示。补上同一条件即可,与 #3838 同一原则往前一格:能力不具备就不呈现 affordance。

可达宿主是真实的一类:renderers/record-related-list.tsxdataSource={ctx?.dataSource},任何未绑定 RecordContext 的挂载(Studio designer 预览、无上下文嵌入)都是 undefined。所以第一条钉子走渲染器而不是组件 —— 门只在真会产生缺失 adapter 的挂载形态上才值得钉。

反向验证(先预判方向,再跑;变异均未提交)

变异 预判 实测
撤掉按钮门的 dataSource 无-dataSource 两钉翻红,两条有-dataSource 正向钉保持绿 2 failed / 2 passed —— 红的正是 withholds the Add button…renders neither button nor dialog…
queryFilter = parentScope(断开合成) listFilter 里三条 AND 钉翻红;"未声明"/"空数组"/兜底两钉保持绿(它们断言的就是裸父作用域) 3 failed / 12 passed,红的三条与预判逐条一致
从 mapping 去掉 filter: true 绑定合成两钉翻红;"无绑定按引用透传"那条保持绿(与 mapping 无关) 2 failed / 6 passed,同预判

第二个变异不会动到 RecordRelatedListRenderer.elementDataSource.test.tsx,这是预判的一部分而不是漏洞:该文件钉的是渲染器那一跳(什么值到达 RelatedList 的 props),组件内部查询合成的变异按定义碰不到它 —— 所以才需要第三个变异专门钉那一跳。

另:两条无-dataSource 钉最初因 i18n 未挂载而假绿(默认按钮渲染的是原始 key detail.add,选择器 /^Add$/ 两个方向都匹配不到 —— 绿是因为什么都没匹配上)。已把 add.label 显式写进 fixture 并按该名字选择,负向钉不再空转;这一点写在测试文件的注释里。

消费半径扫查

add prop 在仓内只有 plugin-detail(测试)与本渲染器使用;metadata-admin designer 的 record:related_list inspector 不含 add,deriveRelatedLists 既不产 add 也不产 filter。故本改动的 fixture 面不外溢 —— 仍按规程连 apps/console 的 binding-reach 一起跑了。

验证输出

# 三个新增/翻转的测试文件
pnpm exec vitest run packages/plugin-detail/src/__tests__/RelatedList.listFilter.test.tsx \
  packages/plugin-detail/src/__tests__/RelatedList.addGateDataSource.test.tsx \
  packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.elementDataSource.test.tsx
  Test Files  3 passed (3)
       Tests  19 passed (19)

# 规程指定的受影响面全量
pnpm exec vitest run packages/plugin-detail apps/console/src/__tests__/public-block-binding-reach.test.tsx --maxWorkers=2
  Test Files  67 passed (67)
       Tests  542 passed (542)

# 仓根类型检查(末次编辑后)
pnpm exec turbo run type-check --concurrency=2
  Tasks:    78 successful, 78 total

node scripts/check-control-bytes.mjs
  OK (scanned 3874 tracked text file(s); skipped 85 binary)

eslint(改动文件):0 errors(仅既有 no-explicit-any warning)

文档与 changeset

  • content/docs/guide/data-source.md 的逐 block 覆盖表:record:related_listfilter 由 "— (see below)" 改为 ✅,两处缺口记录减为一处(object-form),并写明合成是与父关系 AND 而非替换。
  • packages/plugin-detail/README.md:补 filter 的语义/词汇/兜底路径行为,以及 Add affordance 的门。
  • changeset:@object-ui/plugin-detail patch。

边界

只动 plugin-detail(RelatedList.tsxrenderers/record-related-list.tsxindex.tsx 的一条 input description、测试)+ 两处文档 + changeset。未碰共享层本体(packages/react 的 gate、packages/core 的 filter sink 一行未改),未碰 objectstack#7121 在飞的 block 外壳,未碰 releases/


Generated by Claude Code

…ataSource (objectstack#7118, objectui#3895)

`RecordRelatedListProps.filter` was declared by the spec and published as a
registry input while nothing read it: `RelatedList` built its query from
`{ [referenceField]: parentId }` alone, so an authored filter passed every gate
and was dropped — the list answered with every child of the parent. It is now
AND-combined with the parent condition (never substituted for it: "additional"
criteria may only narrow), lowered through the repo's single filter sink so both
the spec's `ViewFilterRule[]` vocabulary and the AST `ElementDataSourceGate`
composes are accepted without a second dialect. With nothing authored the query
is byte-identical to before. `RECORD_RELATED_LIST_DATA_SOURCE` gains
`filter: true`, so a saved view named through `dataSource: { object, view }` no
longer contributes columns/sort/limit while its filter is discarded, and #6953's
honest reverse assertion flips to the positive one its own comment predicted.

The legacy raw-URL fallback path cannot express an operator, so a declared filter
there is refused with a console explanation rather than dropped: answering wider
than the metadata asked is the class this wiring removes.

Separately, the Add button now requires `dataSource`, matching the dialog it
opens and the callback it ends in. Without it the button rendered and did
nothing at all when clicked — real in hosts that bind no `RecordContext`
(Studio designer previews, context-free embeds), since the renderer passes
`dataSource={ctx?.dataSource}`.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 9, 2026 8:19pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation plugin tests labels Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-C2G6exr9.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 483.34KB 106.60KB
core (index.js) 3.00KB 1.14KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
fields (index.js) 227.92KB 56.45KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.54KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.23KB 12.45KB
plugin-charts (index.js) 61.49KB 17.48KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 118.39KB 30.67KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 237.80KB 59.48KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.29KB 27.16KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 187.90KB 49.79KB
plugin-kanban (index.js) 48.53KB 13.38KB
plugin-list (index.js) 109.73KB 26.55KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.71KB 7.95KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。objectstack#7118 + objectui#3895 结对落地。

核验记录(head caabbd7cd,基 5bfaabde0,实物核验 + CI 亲读):

  1. #7118「接通」方向的两轴论证成立,PM 采纳:filter 是 spec 键(RecordRelatedListProps.json 完整声明)而非 registry 私产 —— 撤 registry 条目不消灭声明、只造 parity 门的反方向没推到全仓:7 个 block 共 15 个 spec 已声明的顶层键没有任何 inputs 发布,其中 3 个渲染器实读(record:details.hideFields / record:related_list.relationshipValueField / element:text_input.defaultValue) #3808 修过的反向分叉,真撤除是 spec 仓的 ADR-0049 退役;接通使 declared = enforced,并堵上「命名 view 静默拿到更宽列表」的洞。两轴同向,无需升级。
  2. 实现质量:复用唯一 filter sink(toFilterNode + mergeFilterNodes,不造第二方言);内容键 memo(防身份键重取);两处相邻路径的同缺陷一并堵住(raw-URL fallback 拒取并告警而非静默放宽、filterKey 进翻页重置)—— 超字面范围但同缺陷同修,已钉已披露,接受。
  3. RelatedList 的 Add 按钮不判 dataSource,而 picker 对话框判 —— 无 dataSource 的宿主(Studio designer / 无 RecordContext 的嵌入)里是个点了没反应的死按钮 #3895 方向 1 精确执行:Add 门对齐对话框与回调(实物 grep 两处 add && pickerObject && dataSource);无 RecordContext 挂载形态钉住。
  4. 反向验证三次 + 假绿自查:三变异红绿分布逐条命中(含「变异 2 刻意不动 renderer hop、变异 3 补位」的分层设计);初版 no-dataSource 钉因 i18n 裸键双向空匹配被自查出来并修正选择器 —— 假绿工具库又添一例,处置正确。
  5. PR3969 的诚实后果钉按其自身预告翻转为正向断言;#6953 的 mapping 一行收尾(filter: true)与 docs 覆盖表更新齐备。
  6. 门与规程:规定面 542 测试绿(末次编辑后重跑);type-check 78/78;控制字节门 + 自扫;changeset patch 三门绿;fable 0(trailer 处置同先例);⛔ releases/ 未触碰;与 main 前进部分零交集已核。
  7. CI 亲读终态:20/20 全 completed、0 失败(Test shard×4 至 20:25:56Z)。

concerns 采信:wire 形状沿共享层既定组合形(#3431 实测背书),若真实后端拒绝属共享层性质非本 PR;types 镜像宽松与 FLS 客户端不查两条边界判断合理。os#7119 上的 sink 约束评论(ObjectGrid 跳过 sink 的唯一消费者)已成为该决策箱卡的实施约束 —— 有价值的移交。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 20:29
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit c4768a7 Aug 9, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-7118-3895-relatedlist-gates branch August 9, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment