Skip to content

fix(components): FilterBuilder 切换 field 时把算子归到新桶内 (#4768) - #4779

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-4768-filter-field-switch-operator
Aug 16, 2026
Merged

fix(components): FilterBuilder 切换 field 时把算子归到新桶内 (#4768)#4779
yinlianghui merged 1 commit into
mainfrom
claude/issue-4768-filter-field-switch-operator

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #4768

问题

FilterBuilder 的算子桶是按字段类型分的,且互不包含:select 桶有 in / notIntext 桶没有;between 只有日期桶有。切换一行的 field 时组件只写 { field },算子原样留在了新桶里根本没有的位置——比如 select 改 text 之后行上仍是 in

Radix 的 SelectValue 是拿实际挂载的 SelectItem 去匹配的,桶里没有该项,trigger 就渲染成空白:用户既看不见也点不到自己正在用的算子,只能删掉整行重来;行本身还在按那个算子过滤。

改动

把「改字段」和「定算子、定 value 形状」合成一次编辑,与 #3958 / PR #4762 在算子侧确立的机制同形(那边是「改算子」和「重整 value 形状」合成一次编辑):

  • 新桶仍然提供的算子予以保留,value 一并不动——contains 从一个文本列换到另一个文本列不该悄悄变成 equals
  • 新桶无法提供的算子换成该桶首项,再由 reshapeFilterValue 按新算子的家族重整 value:in 下的列表塌成首项标量,between 的区间保留下界,未填过的 [] 变回 ''

归属判断走 spec 自己的 normalizeFilterOperator——正是 filterValueArity 已经在用的那次折叠。存量视图读回来可能拼成 not_in,而下拉里列的是别名 notIn,两者是同一个算子,不能因为拼写不同就把作者的算子重置掉。折叠在本组件的算子词表上是单射的(defaultOperators 的 22 个 id 折叠出 22 个互异的规范算子),这才使「按规范拼写比较」是安全的;测试把这条单射性钉住,将来加入的算子一旦破坏它就会红。

renderers / form 未触碰(#4014 在飞)。

反向验证(先预判、后执行、不提交)

预判:只把接线退回 updateCondition(condition.id, { field: value })(helper 不动),前两个 describe 的 9 个 UI 驱动钉转红,「新桶仍提供则不重置」整个 describe 与全部纯函数钉保持绿——后者是防止修复过度(无条件重置会把它们弄红),不是「必须变化」的钉。

实测与预判逐条一致:

 Test Files  1 failed | 1 passed (2)
      Tests  9 failed | 37 passed (46)

红的正是预判的 9 条(4 条「切换后算子落回新桶」+ 4 条「value 按新家族重整」+ 1 条 #4762 家族衔接),PR #4762 自己那 24 条钉全绿。随后已还原修复。

验证


Generated by Claude Code

算子桶按字段类型划分且互不包含(select 有 in/notIn,text 没有;between
只有日期桶有)。切换一行的 field 只写 { field },算子就留在了新桶没有的
位置——Radix 的 SelectValue 拿实际挂载的 SelectItem 匹配,于是 trigger
渲染成空白,用户看不见也点不到自己正在用的算子。

现在把「改字段」和「定算子、定 value 形状」合成一次编辑,与 #3958 /
PR #4762 在算子侧的机制同形:新桶仍提供的算子予以保留(value 一并不动),
新桶不提供的换成该桶首项并按新家族重整 value。归属判断走 spec 自己的
normalizeFilterOperator,所以存量视图读回的 not_in 会被认成下拉里列的
notIn,不会被无谓重置;该折叠在本组件的 22 个算子 id 上是单射的,测试
把这条性质一并钉住。

Co-authored-by: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 16, 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 16, 2026 5:53am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
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) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
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) 5.02KB 0.88KB
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) 495.08KB 110.01KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 157.05KB 43.28KB
fields (index.js) 231.21KB 57.42KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.96KB 10.85KB
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) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.89KB 1.22KB
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.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.86KB 12.91KB
plugin-charts (index.js) 64.44KB 18.28KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 128.14KB 32.58KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.81KB 59.97KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.12KB 27.65KB
plugin-gantt (index.js) 164.30KB 40.02KB
plugin-grid (index.js) 192.27KB 51.31KB
plugin-kanban (index.js) 52.72KB 14.54KB
plugin-list (index.js) 111.23KB 26.97KB
plugin-map (index.js) 18.16KB 5.81KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.38KB 11.09KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
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) 27.64KB 9.44KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
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 (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 3.05KB 1.52KB
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 验收(座位 repo:objectui,session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。#4768 落地 —— field 切换后 operator 不在新桶内则重置为首项、value 随新家族重整,「下拉显示空、行带着看不见的 operator」关闭。

核验记录(实物核验 + CI 亲读):

  1. 机制与 fix(components): FilterBuilder 为 in/not_in/between 渲染匹配形状的值输入,家族判定改读 spec 词表 (#3958) #4762 同家族(reconcileOperatorForField 纯 helper),钉③「新桶仍提供则不重置」是防过度修复的守卫 —— 反向验证预判明确区分「必须红的 9 条」与「守卫必须绿」的两类,实测逐条吻合,PR fix(components): FilterBuilder 为 in/not_in/between 渲染匹配形状的值输入,家族判定改读 spec 词表 (#3958) #4762 自身 24 钉全绿不回归。
  2. 契约钉的诚实标注:helper 契约钉(规范拼写折叠、单射性、跨字段类型全量不变式)在 main 上是不编译而非失败 —— 如实写明不套 before-red 模板。
  3. 消费半径外扩到 app-shell fold 整链(59 例绿)。
  4. 门与规程:components 1196 绿;type-check 81/81;十道 gate 绿;fable 0;releases/renderers 零触碰;22 新钉。
  5. CI 亲读终态:20/20 completed、0 失败(shard×4 至 06:03:08Z)。

out-of-scope #4781(field 切换后 value 残留的三选项语义)已入决策箱 —— dev 对「这是产品语义不是契约机械」的判断被 PM 采纳,倾向 B 的论证在卡上待维护者拍板。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 16, 2026 06:10
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 16, 2026
Merged via the queue into main with commit c4533dc Aug 16, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4768-filter-field-switch-operator branch August 16, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FilterBuilder 切换行的 field 后 operator 不重置 —— select 改 text 后 operator 可停留在下拉里已不存在的 in,trigger 显示空

2 participants