Skip to content

fix(predicate): 空谓词与非谓词值不再是门 —— 收敛最后三处自带范围的判定 (#3955, #3957, #3960) - #3977

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3955-3957-3960-predicate-scope-converge
Aug 9, 2026
Merged

fix(predicate): 空谓词与非谓词值不再是门 —— 收敛最后三处自带范围的判定 (#3955, #3957, #3960)#3977
yinlianghui merged 1 commit into
mainfrom
claude/issue-3955-3957-3960-predicate-scope-converge

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3955
Fixes #3957
Fixes #3960

objectui#3850 把「这是不是一个已声明的门」下沉成 @object-ui/core 的唯一定义
hasDeclaredPredicate(PR #3966)。它的落点条款漏下三处,三处缺陷同构:
evaluateCondition 对「没有东西可求值」的唯一默认是 true(意为
visible/enabled),只要一个过宽的「已声明」判定把空谓词交给它、而键的极性是
反的,那个 true 就会为一个元数据从未表达过的值把控件关掉。本 PR 把这三处一并
收敛到同一个定义上,收敛完成后仓里没有第二处回答这个问题。

#3955 —— SchemaRendererhidden / hiddenOn

可见性链六条腿:四条 visible* 写成 !evaluateCondition(...),hidden /
hiddenOn 不取反。取反后那个 true 落在「显示」,与「没有门」同结果 ——
良性;不取反就是 HIDE,于是 hidden: '' / null / 纯空白 / 空信封让节点整个
消失。该块在 evaluatedSchemauseMemo不带 type 判定,覆盖走
SchemaRenderer 的全部节点;而且比 #3862 修掉的 disabled 孪生更难诊断 ——
置灰的控件还在界面上,没渲染的节点与「元数据本意隐藏」在界面上无法区分。

两条腿改读唯一定义。别名优先级有一处变化,按行为变更钉住而不是当作等价:
未声明的 hidden 不再短路,声明了的 hiddenOn 终于被读到。四条 visible*
#3850 裁决保持 !== undefined 不动(取反后空谓词本就落在显示,收窄只改
别名优先级)。

#3960 —— 「空」判据扩到信封的 source

定义 trim 了裸串却没 trim 信封的 source(toPredicateInput 只折叠
source === '',不 trim),于是 { dialect: 'cel', source: ' ' } 被判「已声明」,
而它的 verdict 恰恰来自 core 自己的 CEL 入口把同一个值称作「没有谓词」
(if (!source.trim()) return true)—— disabled 永久置灰,
ActionRunner.execute 返回 { success: false, error: 'Action is disabled' }
handler 从未被调用。空白判据现在在定义处一次覆盖两种拼法
(isBlankPredicateText)。

落点选择(定义处 vs 归一器),两个落点的影响面:

  • 定义处(采用):只影响「已声明」判定,消费者 = 动作面
    hasDeclaredVisibilityGate re-export 的五处 member-action 落点 +
    DeclaredActionsBar + record-quick-actionsSchemaRenderer
    disabled/disabledOn(与本 PR 新增的 hidden/hiddenOn)、ActionRunner
    两处执行门、ActionEngine(本 PR 新增)。真正翻转的只有反极性键:
    disabled 置灰→可用、执行门拒执行→执行、hidden 消失→渲染。
    visible/enabled/condition 上「已声明且求值为 true」与「未声明」同结果,
    不变。
  • 归一器(不采用):if (!src.trim()) return undefined 会让每一个
    evaluateCondition(toPredicateInput(x)) 消费者一起改判,包括从不问这个问题
    的容器级 visible 读点(action-bar / action-group / action-menu /
    RelatedList / record-alert)—— 无 dialect 的空白信封 { source: ' ' }
    归一成 '${ }' 求值为 falsy,在那些点上从隐藏变显示,属于没人裁过的面。
    更根本的理由:归一器答的是「求值器接受什么形状」,与「有没有条件」是两个概念
    (refactor(types)!: Page/App/Dashboard 渲染节点不再占用 spec 的文档名 (#4115 B 组) #3074 / #4115 的分界),把 trim 放进归一器等于用形状问题回答声明性问题。

归一器契约因此未动,并在钉里正面断言(toPredicateInput({dialect:'cel', source:' '}) 仍原样返回)。

#3957 —— ActionEngine.getActionsForLocationvisible 过滤(A 案)

收敛后最后一处自带范围的消费者:手工折三种空拼法,其余 Boolean(raw) 强转。
于是同一个值在引擎面与渲染面得到两个答案 —— visible: 0 / NaN 引擎面隐藏、
渲染面显示;' ' 引擎面根本走不到强转分支(归一成 '${ }',被求值为
falsy)而隐藏,那一行分叉是 PR #3966 落地当天新产生的。改读唯一定义,
Boolean(raw) 分支随之删除(声明了 ⇒ 归一后必有可求值的东西,undefined 分支
成为死代码)。谓词抛错时的 fail-CLOSED 姿态不动(throwOnError: true +
warnHiddenPredicate):「谓词求值失败」与「压根没有谓词」是两个事实。

行为变更(观察类,无已知用户路径)

value 引擎面 visible SchemaRenderer hidden disabled(动作面 + 通用路径) 执行入口 disabled
'' / null 显示 → 显示 消失 → 渲染 不变 不变
' '(纯空白) 隐藏 → 显示 消失 → 渲染 不变 不变
0 / NaN 隐藏 → 显示 消失 → 渲染 不变 不变
{} / [] 显示 → 显示 消失 → 渲染 不变 不变
{ dialect: 'cel', source: '' } 显示 → 显示 消失 → 渲染 不变 不变
{ dialect: 'cel', source: ' ' } 显示 → 显示 消失 → 渲染 置灰 → 可用 拒执行 → 执行
{ source: ' ' }(无 dialect) 隐藏 → 显示 消失 → 渲染 置灰 → 可用 拒执行 → 执行
true / false / 裸 CEL / ${…} / 非空信封 不变 不变 不变 不变

每一行都朝「这里没有门」移动,没有任何有 verdict 的值改变了 verdict:
声明了的 false 仍是 verdict 而不是缺门(#3812),空白判据是 trim() 而不是
「短」——{ dialect: 'cel', source: ' x ' } 仍是谓词。changeset 逐条写明。

钉子与更新的 PR #3966

新增/更新:

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

三处分别改回旧范围,红的条数与分布与预判逐项一致:

变异 预判 实测
#3955:hidden/hiddenOn 改回 !== undefined 19 项(空拼法 7×2 + junk 3 + 优先级 1 + #3862 文件里的收敛用例 1) Tests 19 failed / 59 passed,分布逐项吻合;SchemaRenderer.expressions.test.tsx 的真 hiddenOn 谓词全绿
#3960:去掉 isBlankPredicateText 的信封半边 21 项(declaredPredicate 6 + disabledGate 2 + conditionGate 1 + ActionEngine 2 + disabledDeclaredGate 4 + hiddenDeclaredGate 4 + parity 2) Tests 21 failed / 464 passed,分布逐项吻合
#3957:恢复引擎面手工范围 11 项(ActionEngine 5 + conditionGate 收敛用例 1 + parity 5) Tests 11 failed / 387 passed,分布逐项吻合

三次变异中 true / false / 真谓词 / 非空信封 / visible* 用例全程绿 ——
这三处改动都只会「少关一个控件」,不会「多关一个」,方向单一。#3960 那次变异
同时就是 disabledGate 表头那两行「本卡修法之后仍被拦」的测量出处(变异复现的
正是 ab3ad4f3f 上的范围)。

验证

  • pnpm exec vitest run packages/core packages/react --maxWorkers=2 ——
    Test Files 109 passed (109) / Tests 2096 passed (2096)
  • 消费半径(动作面 / app-shell / plugin-detail):
    pnpm exec vitest run packages/components/src/renderers/action packages/app-shell/src/views/__tests__/DeclaredActionsBar.test.tsx packages/plugin-detail/src/renderers/__tests__ --maxWorkers=2
    —— Test Files 16 passed (16) / Tests 263 passed (263)packages/components
    源码未动(visibility-gate re-export 面不需要变)。
  • 仓根 pnpm exec turbo run type-check --concurrency=2 —— Tasks: 78 successful, 78 total
  • node scripts/check-control-bytes.mjs OK;check-changeset-presence /
    check-changeset-no-major / check-changeset-fixed 均 OK。

🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

objectui#3850 把「这是不是一个已声明的门」下沉成 `@object-ui/core` 的唯一定义
`hasDeclaredPredicate`。它的落点条款漏下三处,缺陷同构:`evaluateCondition` 对
「没有东西可求值」的唯一默认是 `true`(意为 visible/enabled),只要一个过宽的
「已声明」判定把空谓词交给它、而键的极性是反的,那个 `true` 就会为一个元数据
从未表达过的值把控件关掉。

- **#3955 `SchemaRenderer` 的 `hidden` / `hiddenOn`**:问 `!== undefined` 且
  verdict 不取反,于是空谓词 = HIDE,节点整个消失 —— 且该块在 `useMemo` 里不带
  type 判定,覆盖走 SchemaRenderer 的全部节点。比 #3862 修掉的 `disabled` 孪生
  更难诊断:置灰的控件还在界面上,没渲染的节点与「元数据本意隐藏」无法区分。
  两条腿改读唯一定义。
- **#3960「空」判据扩到信封 `source`**:定义 trim 了裸串却没 trim 信封的
  `source`(`toPredicateInput` 只折叠 `source === ''`,不 trim),于是
  `{ dialect: 'cel', source: '   ' }` 是「已声明」的门,而它的 verdict 恰恰来自
  core 自己的 CEL 入口把同一个值称作「没有谓词」(`if (!source.trim()) return
  true`)—— `disabled` 永久置灰,`ActionRunner.execute` 返回
  `{ success: false, error: 'Action is disabled' }` 且 handler 从未被调用。
  空白判据现在在定义处一次覆盖两种拼法。落点选定义处而非归一器:归一器答的是
  「求值器接受什么形状」,与「有没有条件」是两个概念,把 trim 放进归一器会改到
  每一个 `useCondition(toPredicateInput(…))` 调用点(含从不问这个问题的
  action-bar / action-group / action-menu / RelatedList / record-alert 的容器级
  `visible`),影响面远大于本单。
- **#3957 `ActionEngine.getActionsForLocation` 的 `visible` 过滤**:收敛后最后
  一处自带范围的消费者 —— 手工折三种空拼法、其余用 `Boolean(raw)` 强转。改读
  唯一定义,强转分支随之删除,同一个值不再因「经引擎浮现」还是「独立渲染」
  得到两个答案(#3314 立的不变量)。谓词抛错时的 fail-CLOSED 姿态不动
  (`throwOnError: true` + `warnHiddenPredicate`):「谓词求值失败」与「压根没有
  谓词」是两个事实。

行为变更(观察类,无已知用户路径):引擎面 `visible: 0` / `NaN` / `'   '` /
`{ source: '   ' }` 从隐藏变显示;渲染面 `hidden: ''` / `null` / `'   '` / 空信封 /
非谓词值从消失变渲染;`disabled: { dialect, source: '   ' }` 从置灰变可用、执行
入口从拒执行变执行。逐条写进 changeset 与钉。别名优先级有一处变化并按变更钉住:
未声明的 `hidden` 不再短路,声明了的 `hiddenOn` 终于被读到。

`SchemaRenderer` 的四条 `visible*` 腿按 #3850 裁决保持 `!== undefined` 不动
(取反后空谓词本就落在显示,收窄只改别名优先级)。

反向验证(先预判方向再跑):把三处分别改回旧范围,对应钉精确翻红 ——
#3955 改回 `!== undefined` 红 19 项;#3960 去掉信封半边红 21 项;#3957 恢复
手工范围红 11 项;三处的真谓词 / `true` / `false` 用例全程绿。

Fixes #3955
Fixes #3957
Fixes #3960

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 7:35pm

Request Review

@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-C4bHid4S.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.00KB 106.47KB
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.45KB 56.31KB
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.53KB 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.03KB 59.17KB
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。#3955 + #3957 + #3960 三单结对落地 —— 空谓词收敛工程收官,仓内「已声明门」只剩一个定义、零自带范围的消费者。

核验记录(head 98ca2112e,基 62c644168,实物核验 + CI 亲读):

  1. 「空谓词」的第四种拼法:{ dialect: 'cel', source: ' ' }(source 只有空白)仍被判成已声明门 → disabled 侧仍永久置灰、执行入口仍拒执行(#3850 裁决枚举未覆盖) #3960 落点选择正确且论证完整:定义处(isBlankPredicateText)而非归一器 —— 归一器改判会波及从不问「已声明」的容器级读点(5 处未裁面)、且用形状问题回答声明性问题(refactor(types)!: Page/App/Dashboard 渲染节点不再占用 spec 的文档名 (#4115 B 组) #3074/#4115 概念分界);归一器契约未动并有正面钉。两处影响面枚举齐备。
  2. [finding] ActionEngine 的 visible 过滤仍用自己的「空/非谓词」范围:visible: 0 / NaN / ' ' 在引擎面隐藏、在渲染面显示 —— #3850 收敛后剩下的最后一处分叉 #3957 A 案落地:ActionEngine 改读唯一定义(实物 grep :256),Boolean(raw) 强转分支删除;fail-closed 抛错姿态不动且有钉;新 parity suite 修正了原 rendererVerdict 建模缺陷(只建模 verdict、不建模「已声明?」),17 行引擎面=渲染面=期望三方一致 —— ActionEngine.getActionsForLocation 把 CEL 信封塌回 legacy JS 求值路径,与每个渲染器的判决不一致 #3314 的「一值一答」在该键上闭环。
  3. SchemaRenderer 的 hidden / hiddenOn 是 #3862 的反极性孪生:hidden: '' / null / 空 source 信封在通用渲染路径上让节点永久消失 #3955:hidden/hiddenOn 改读,7 空拼法 ×2 + junk + 反删除钉 + visible 腿不变的双向钉;PR3966 三处 divergence/residue 钉全部转正为收敛断言。
  4. 反向验证三次单点变异:19/21/11 项翻红、分布逐项与预判吻合;真值/真谓词/visible* 全程绿证明方向单一(只会少关不会多关)。
  5. 诚实修正:派发词预期「ActionRunner.execute 的 disabled 门把「空谓词」当已禁用,拦掉执行(实测 handler 不跑),且与渲染器判定不一致 #3848 执行门钉翻红」实际不翻(SHAPES 无空白信封 fixture)—— 新增行并以 m2 变异取得变更前实测值,措辞不假称 checkout 旧 ref;in-radius 清理 conditionGate 里的内联拼法孪生(m2 里正是靠此翻红)—— 两处处置均正确。
  6. 门与规程:core+react 2096 测试绿 + 消费半径 263 绿;type-check 78/78;控制字节门;changeset 双包 patch 且行为变更表(7 value × 4 面)逐条写明;fable 0;⛔ releases/ 未触碰;hiddenOn 响应式撞键疑点已查证排除。
  7. CI 亲读终态:20/20 全 completed、0 失败(Test shard×4 至 19:41:28Z、Type Check 19:39:28Z)。

Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 19:54
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 0109f54 Aug 9, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3955-3957-3960-predicate-scope-converge branch August 9, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment