Skip to content

fix(react-runtime): 给 react peer 范围补上上界 ^18.0.0 || ^19.0.0 (#3741) - #3870

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3741-react-runtime-peer-bound
Aug 8, 2026
Merged

fix(react-runtime): 给 react peer 范围补上上界 ^18.0.0 || ^19.0.0 (#3741)#3870
yinlianghui merged 1 commit into
mainfrom
claude/issue-3741-react-runtime-peer-bound

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3741

前提复核(先证实,再动手)

基线 origin/main @ 7fbef94a99ad7c0c2a8d4e737065c8b443619079

  • git show origin/main:packages/react-runtime/package.json 确认 "react": ">=18" 仍在。前提成立。
  • 实测全仓拼法分布(遍历所有 package.json 的 peerDependencies):声明 react peer 的 31 个包中 30 个是 ^18.0.0 || ^19.0.0,唯一例外就是 react-runtime 的 >=18;另有 25 个 react-dom peer,全部 25 个都是 ^18.0.0 || ^19.0.0。合计 56 条声明,55 条合规,越界的恰好一条。issue 正文给的数字准确,^18.0.0 || ^19.0.0 确是固定组规范。
  • 有没有「更宽」的正当理由?查了三处,均无支持:
    • >=18 是建包那天(d23d6ebfa,PR feat(page): kind:'html' (full HTML tags) + trusted kind:'react' tier #2105,2026-06-30)写下的,此后该行从未被任何 commit 重新审视过;
    • 该包全部 React 面只有 Component / createElement / isValidElement / ReactElement / ReactNode,React 19 一个都没改,也没碰任何 React 19 移除的 API;
    • 仓根 pnpm.overrides 把 react 钉在 19.2.8,19 是这个包的测试唯一跑过的 major —— 无上界的那一端从构造上就没有测试覆盖。

因此按缺省方向直接对齐,不留「故意更宽」的注记。

改动

  1. packages/react-runtime/package.json:peerDependencies.react>=18 收窄为 ^18.0.0 || ^19.0.0。同文件没有其它 peer(该包只声明 react 一条,无 react-dom),全仓也再无第二条无上界 peer。
  2. packages/react-runtime/README.md:restate 该范围的那句散文随之改写。
  3. scripts/__tests__/doc-version-claims.test.ts:ledger 条目与三处叙述更新(见下)。
  4. 新增 scripts/__tests__/react-peer-range-norm-3741.test.ts 防回归钉子。
  5. changeset:patch,照 fix(plugin-report): 放宽 react/react-dom peer 到 ^18.0.0 || ^19.0.0,对齐其余 29 个包 (#3690) #3727(同族先例,peer 范围变更)的级别。

为什么 README 与 ledger 必须同 PR 改

不是顺手扩面,是被现存门禁强制的联动。doc-version-claims.test.ts 的 peer-line 断言(#3717,#3750 扩面)逐字比对「README 的 peer 行」与「它自己的清单」。react-runtime 的 README 写着 react >= 18,只改清单会立刻让该门禁变红。

值得一提的是:这份门禁的文件头早就把这次改动写成了预言 —— 原文「objectui#3741, still open, proposes narrowing react-runtime's manifest range; the day someone does, this test goes red until that README follows」。反向验证里它精确地按这句话红了(见下)。该段现已改写为「历史 + 现状」两段(#3749/#3860 的写法)。

同样按「历史 + 现状」改写的还有两处原本会退化成失效断言的叙述:

  • sameRange 的空白归一化,其实测依据正是 react-runtime 的 >= 18 vs 清单 >=18 这一对。本次改完,实测 21 条 peer 语句全部与清单逐字节相等,0 条依赖空白归一化 —— 该归一化如今只由自己的单元测试覆盖,不再由语料覆盖。这一点在注释里如实写明(并说清为何仍然保留而不收紧到逐字节:它等同的两种写法都是合法 npm range,收紧买不到任何新的缺陷类)。
  • 对应的单元测试注释原写「the only place in the corpus where README and manifest differ by anything at all」,已随之修正;parsePeerStatement 的散文用例改为现行拼法,同时保留 >= 18 那条(RANGE_OPENS 仍接受 >,散文行最可能再被那样写)。

README 沿用散文拼法而非改成 **Peer Dependencies:** 列表块:它是语料里唯一触发 parser 散文分支的样本,改成列表块会白掉一条真实分支覆盖,且属于无谓改动。

防回归钉子:现有门禁看不到这一类

先查:仓内既有 peer 相关门禁两处 —— workspace-peer-dependency-edges.test.ts(peer 是否能被 turbo ^build 走到)与上述 peer-line 断言。两者都覆盖不到本 issue 这一类,而且 react-runtime 同时踩中两个盲点:

  1. peer-line 断言比的是「两侧互相是否一致」,不是「是否符合规范」。react-runtime 的 README 与清单当时都写 >=18,两边一致,于是这条无上界范围在已发布清单里躺了五周而门禁全绿。一致 ≠ 正确。
  2. 它只能看到「旁边有 README peer 行」的清单。39 个包里 11 个写了 peer 块,余下 28 个声明的 peer 无任何文档 restate,其范围不被任何东西判定

所以新增钉子问的是另一个问题:清单是否声明了固定组约定的那个范围。形状照抄近邻 workspace-peer-dependency-edges.test.ts(同目录、同主题、同「不设豁免名单」理由):读 pnpm-workspace.yaml 的 globs(遇到不认识的语法直接 throw 而非跳过)、遍历工作区清单、断言每条 react / react-dom peer 逐字等于规范值、带空转下限(包数 > 30、声明数 >= 50,实测 56),并按名钉住 #3741 修的这个包。

立钉子的理由是「三次同型」而非一次:react-runtime 是第三个出生即不合规的包,前两个都是各自单独修掉、没留下任何东西阻止第四个 —— plugin-dashboard 生来过窄、2026-05-08 在一个无关构建修复里(d2b6ecec6)顺手改掉;plugin-report 生来过窄,直到 #3690 / PR #3727,期间 React 19 用户装它会撞 ERESOLVE

规范值写成常量并附理由:下界 ^18.0.0 是真实支持底线,上界 ^19.0.0关键的那一半(阻止清单承诺尚不存在的 major)。将来真支持 React 20,须全仓 56 条一起动否则钉子变红 —— 这正是意图:一个 39 包同发的固定组不该有 per-package 的 React 支持窗口。

反向验证(方向先判、后跑)

预判:仅把清单改回 >=18(README 与 ledger 保持已修正状态),应当是常规的「红」向 —— 新钉子红 2 条(通用规范断言 + 按名钉住的 #3741 条),空转下限那条应保持绿;peer-line 断言红 1 条(两侧不再一致)。

实跑与预判一致,共 3 红 / 14 绿:

× declares every React peer as the norm, with no exemptions
× pins the package fixed in objectui#3741
× pins every peer statement to the range its own manifest declares
  - packages/react-runtime/README.md:27  react: README says "^18.0.0 || ^19.0.0", manifest says ">=18"
  - @object-ui/react-runtime (packages/react-runtime/package.json) declares "react": ">=18"
  - expected '>=18' to be '^18.0.0 || ^19.0.0'
 Test Files  2 failed (2)
      Tests  3 failed | 14 passed (17)

空转下限 discovers the workspace and its React peers 如期保持绿 —— 证明红是来自被判定的值,不是来自扫描塌缩。随后恢复,复跑全绿。

验证

全部在仓根、flock /tmp/os-heavy-verify.lock 下、NODE_OPTIONS=--max-old-space-size=4096--maxWorkers=2,未用 -- 转发形式。

  • pnpm --workspace-concurrency=2 --filter '@object-ui/react-runtime^...' buildNo projects matched the filters(该包无工作区依赖,唯一 dep 是外部 sucrase,如实记录)。
  • pnpm exec vitest run packages/react-runtime/ scripts/__tests__/doc-version-claims.test.ts scripts/__tests__/react-peer-range-norm-3741.test.ts scripts/__tests__/workspace-peer-dependency-edges.test.tsTest Files 4 passed (4) / Tests 39 passed (39)
  • 全仓 pnpm exec turbo run type-check --concurrency=2(CI 同命令)→ Tasks: 78 successful, 78 total
  • 补跑 pnpm type-check:scripts → exit 0。turbo run type-check 走 package.json 的 scripts,结构上到不了没有 package.json 的 scripts/([ci] scripts/ 在零 tsconfig 覆盖内:turbo type-check 从不检查 scripts/__tests__/*.ts——一批门禁 pin 测试自身无类型门 #3494 的原委),新钉子若只靠 turbo 会是编译器从未读过的钉子;另经 tsc --listFiles 确认该文件确实在被编译,并跑 scripts-type-check.test.ts → 8 passed。
  • pnpm exec eslint 两个 scripts 文件 → exit 0。
  • 三个 changeset 门禁 → 全 exit 0(no-major / fixed / presence)。
  • node scripts/check-control-bytes.mjs → OK(3763 文件);改动文件 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 零命中。
  • lockfile:pnpm installgit status --porcelainpnpm-lock.yaml —— peer 范围收窄未触发 lockfile 变化(root overrides 已把 react 钉在 19.2.8,解析结果不变)。

协调

文件面与在飞 #3849 / #3546 切片六 / #3735 / #3746 零相交。#3735 同在 scripts/,但本 PR 只新增一个独立文件(名带 3741)并改 doc-version-claims.test.ts,与其面不重叠。


Generated by Claude Code

…3741)

`peerDependencies.react` was `>=18` — the only unbounded react peer in the
workspace, against 30 siblings declaring `^18.0.0 || ^19.0.0`. An unbounded
range grows on its own: the day React 20 ships, an already-published manifest
claims a major nothing here has built against, with no commit to point at.

Nothing wanted the wider range. The package's entire React surface is
`Component`, `createElement`, `isValidElement`, `ReactElement`, `ReactNode`;
`>=18` was written when the package was created (d23d6eb, PR #2105) and no
commit since revisited it; and the root `pnpm.overrides` pins React to 19.2.8,
so 19 is the only major its tests have ever exercised.

The README sentence restating the range moves with the manifest, because
doc-version-claims.test.ts compares the two and its header had predicted this
exact red ("objectui#3741 ... the day someone does, this test goes red until
that README follows"). That prediction, the ledger entry, and the `sameRange`
comment whose measured specimen this change removes are rewritten as history
plus current state rather than left asserting dead facts.

Adds scripts/__tests__/react-peer-range-norm-3741.test.ts, asserting the norm
across every workspace manifest. react-runtime is the THIRD package born
off-norm (plugin-dashboard d2b6ece, plugin-report #3690/PR #3727) and the
first two were corrected in isolation with nothing left to stop a fourth. The
existing doc gate could not have caught any of them: it compares a README to
its own manifest, and react-runtime's two sides agreed while both said `>=18`.
@vercel

vercel Bot commented Aug 8, 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 8, 2026 10:33pm

Request Review

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

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index--Vewblue.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) 482.39KB 106.34KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
fields (index.js) 230.82KB 56.70KB
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) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 117.21KB 30.27KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 236.17KB 58.82KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.10KB 27.10KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 187.63KB 49.66KB
plugin-kanban (index.js) 48.30KB 13.28KB
plugin-list (index.js) 105.12KB 25.48KB
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) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
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 session_01GTRjn8xBqp75dk7kFupVRt)

实物核验:头 ae5bf38f4,5 文件 +276/−22;manifest 与 README 两行 diff 读过(>=18^18.0.0 || ^19.0.0 同 PR 联动);ledger 三处「历史+现状」改写略读确认叙述与事实同步(含 sameRange 归一化失去语料训练者后「为何保留而不收紧」的就地说明);trailer 0;与在飞 #3746/#3848 零相交。
CI 终态(独立复核):19 检查全部 completed,零失败。

裁定要点:

  • 前提复核超出要求:56 条 react/react-dom peer 声明普查(55 合规、唯一例外即本包)+ 三条「无理由更宽」反证(建包当天写下从未重审、React 面五个 API 在 19 无变更、仓根 overrides 钉 19.2.8 使无上界端构造上无覆盖)→ 直接对齐,正确。
  • README + ledger 联动是门禁强制的同 PR 动作(doc-version-claims 的 peer-line 断言,其文件头甚至预写了本单会触发它)—— 非扩面,采纳。
  • 新钉子 react-peer-range-norm-3741 立钉理由充分:三次同型(plugin-dashboard、plugin-report、本包)且现有门禁结构上看不见「两侧一致但都不合规」这一类;形状抄近邻、无豁免名单、带空转下限。
  • 诚实度记正分:--filter '^...' build 返回 No projects matched 如实记录而非伪造构建证据;pnpm type-check:scripts 补跑的理由(turbo 结构上到不了 scripts/,[ci] scripts/ 在零 tsconfig 覆盖内:turbo type-check 从不检查 scripts/__tests__/*.ts——一批门禁 pin 测试自身无类型门 #3494)写明;CI 未回来的项不以「全绿」措辞覆盖。
  • 反向验证 3 红 1 绿与预判一致,红文本来自被判定的值而非扫描塌缩。

转 ready 并挂 auto-merge。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 8, 2026 22:40
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit d11996e Aug 8, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3741-react-runtime-peer-bound branch August 8, 2026 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

packages/react-runtime 的 peerDependencies.react 是无上界的 >=18,允许尚不存在的 React 20+

2 participants