docs: root-cause why the compiler rejects Home, and close two dead ends

Diagnosis was unblocked by temporarily installing
babel-plugin-react-compiler and reading its logger events, which the
Rust port swallows. Every failure in Home comes from the compiler, not
from this repository's code: 24 errors under the stable 1.0.0, all
prefixed Todo: (the compiler's own marker for unimplemented syntax) --
13 for try/finally, 9 for throw inside try/catch, 2 for a
non-reorderable MemberExpression. All 13 finally blocks do real cleanup
(clearing a timeout, releasing in-flight guards, resetting loading
flags), so deleting them to please the compiler would trade correctness
for speculative memoization.

Two paths are now closed by measurement rather than assumption. Newer
compiler builds fix the try/finally gap but Home then hits two
consecutive internal Invariant panics, which are compiler bugs. And
project-wide the version makes no difference at all: across 373 files
both versions compile exactly 134 functions, with the same 21 files
failing, so switching implementations buys nothing.

The diagnostic dependency is therefore removed and the lockfile
restored via npm ci. Deliverable is unchanged: config still rolled
back, frontend/src untouched.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-17 16:13:47 +08:00
parent f78ad09fcc
commit 89d37b6506
3 changed files with 68 additions and 4 deletions
+3 -2
View File
@@ -6,8 +6,9 @@
## React Compiler 接管 page.tsx2026-08-17,分支 codex/react-compiler-20260817 ## React Compiler 接管 page.tsx2026-08-17,分支 codex/react-compiler-20260817
- **`Home` 的编译失败原因无法定位,需要授权新增依赖。** Next 16.3.1 的 Rust 版 React Compiler 拒编 `page.tsx``Home`(2730 行),且不报任何错误或警告。`panicThreshold: "all_errors"` 在 Rust 版下形同虚设(试过两次,构建既不报错也不打印诊断),因此拿不到「哪一行挡住了编译器」。唯一能出诊断的路是换 Babel 版(`experimental.turbopackRustReactCompiler: false`),但它需要新增依赖 `babel-plugin-react-compiler`next 的可选 peer,未装;Next 只内置了 compiler runtime,没内置这个 plugin),撞任务书「不新增依赖」,故未做。想继续定位需要授权装这个依赖,或等上游 Rust 版补上诊断输出 - **~~`Home` 的编译失败原因无法定位~~ 已解除并已定位(2026-08-17,用户授权新增依赖后完成)。** 结论:`Home` 的失败全部来自上游编译器未实现的语法与内部断言失败,**没有一条是本仓库代码写错**。稳定版 `babel-plugin-react-compiler@1.0.0` 报 24 条错误、三类全带 `Todo:`(编译器标记「尚未实现」):13 条 `try/finally`、9 条 `try/catch` `throw`、2 条 `MemberExpression cannot be safely reordered`。更新的 experimental 版已修好前两类,`Home` 随即撞上两个连续的编译器内部断言失败(`Invariant:`,即编译器 bug):`page.tsx:2666``catch (error)` 绑定被闭包捕获,以及 `page.tsx:1837` 的提升函数 `refreshAccount`。诊断用的依赖已卸载、锁文件已用 `npm ci` 权威还原、`git diff` 为空,不进交付——因为它买不到任何东西,见下一条
- **`Home` 要真正受益很可能得拆组件,属业务代码重构,本轮明令禁止。** 证据是同一文件里两个小组件(原始行 760、815)被正常编译、全项目另有 44 个函数被编译,唯独这个 2730 行、24 个 `useState`、18 个 `useEffect` 的函数被拒。任务书要求「哪处代码挡住了编译器就写进 BLOCKED.md,不要自行改写」,故 `frontend/src/**` 一行业务代码未动(仅反向验证时临时加删一行 `"use no memo"`,已还原,`git diff` 为空) - **换编译器版本或换 Babel 版实现,对本仓库零增益(已量化,这条路可以关掉了)。** 对 `frontend/src` 全部 373 个文件跑批量诊断:稳定版 1.0.0 与 experimental 版**编译成功的函数数完全相同,都是 134 个**,失败文件都是同样 21 个,仅报错事件从 74 降到 48。原因是被上游修掉的错误类别,所在函数都还有别的拦路错误,于是一个函数都没多编译出来。所以别再指望「升个编译器版本就好了」
- **`Home` 要真正受益很可能得拆组件,属业务代码重构,本轮明令禁止。** 诊断结果进一步说明这条路该怎么走:为迎合编译器去改 `Home`(删 `finally`、重排 6 个被声明前引用的函数、逐个绕内部断言)是被编译器 bug 牵着走的打地鼠,每步都拿确定的正确性换不确定的收益;值得做的是按职责把 `Home` 拆小。 证据是同一文件里两个小组件(原始行 760、815)被正常编译、全项目另有 44 个函数被编译,唯独这个 2730 行、24 个 `useState`、18 个 `useEffect` 的函数被拒。任务书要求「哪处代码挡住了编译器就写进 BLOCKED.md,不要自行改写」,故 `frontend/src/**` 一行业务代码未动(仅反向验证时临时加删一行 `"use no memo"`,已还原,`git diff` 为空)。
- **~~待决策~~ 已决策(2026-08-17):维持回滚,不为那 44 个组件保留 `reactCompiler: true`;若要翻案,前置条件是先测出那 44 个的渲染基准。** 本轮按任务 2 止损条款已回滚该配置。数据是:构建耗时无可测量变化(噪声内),`/` 首屏 JS gzip +12031 B / +2.50 %,换来 44 个函数自动记忆化,其中 `app-sidebar`112 槽)、`sidebar-session-row`73 槽)、`birth-date-picker`(58 槽)与三个引导 hook 都在 `/` 上渲染。这 44 个的实际运行时收益本轮未测量(无渲染基准),所以不替决策者拍板。重新开启只需在 `frontend/next.config.ts` 顶层加 `reactCompiler: true``experimental` 里加 `turbopackRustReactCompiler: true` - **~~待决策~~ 已决策(2026-08-17):维持回滚,不为那 44 个组件保留 `reactCompiler: true`;若要翻案,前置条件是先测出那 44 个的渲染基准。** 本轮按任务 2 止损条款已回滚该配置。数据是:构建耗时无可测量变化(噪声内),`/` 首屏 JS gzip +12031 B / +2.50 %,换来 44 个函数自动记忆化,其中 `app-sidebar`112 槽)、`sidebar-session-row`73 槽)、`birth-date-picker`(58 槽)与三个引导 hook 都在 `/` 上渲染。这 44 个的实际运行时收益本轮未测量(无渲染基准),所以不替决策者拍板。重新开启只需在 `frontend/next.config.ts` 顶层加 `reactCompiler: true``experimental` 里加 `turbopackRustReactCompiler: true`
- **`compilationMode: "all"` 不可用,会破坏构建。** 它会编译模块作用域的普通回调,`frontend/src/components/birth-time-intake.tsx:22``Array.from({ length: 24 }, (_, index) => ...)` 被插入 `useMemoCache`,预渲染 `/` 时抛 `TypeError: Cannot read properties of null (reading 'useMemoCache')`。这是该模式本身的性质(官方标注为不安全),不是本仓库代码的缺陷,未做任何改动。 - **`compilationMode: "all"` 不可用,会破坏构建。** 它会编译模块作用域的普通回调,`frontend/src/components/birth-time-intake.tsx:22``Array.from({ length: 24 }, (_, index) => ...)` 被插入 `useMemoCache`,预渲染 `/` 时抛 `TypeError: Cannot read properties of null (reading 'useMemoCache')`。这是该模式本身的性质(官方标注为不安全),不是本仓库代码的缺陷,未做任何改动。
- **顺手活一律未做,登记在此:** 其一,`frontend/src/lib/skill-package-registry.ts:523``readFileSync(currentRegistryPath, "utf8")` 触发 Turbopack 构建警告「Dynamic filesystem access causes tracing of the whole project」,会把整个项目(含 `public/`)打进 server 产物,影响部署体积;升级前后都存在,与本轮无关,未改。其二,`npx eslint` 有 4 个既有 `no-unused-vars` warning,分别在 `birth-time-candidate-result.tsx:143``birth-time-candidate-completion.ts:10,11``tests/identity-auth-factory.test.ts:48`,未改。其三,`eslint-config-next` 仍是 16.2.10、与 next 16.3.1 版本号不同步,但 eslint 实测 0 error,按「不许顺手升别的依赖」未动。 - **顺手活一律未做,登记在此:** 其一,`frontend/src/lib/skill-package-registry.ts:523``readFileSync(currentRegistryPath, "utf8")` 触发 Turbopack 构建警告「Dynamic filesystem access causes tracing of the whole project」,会把整个项目(含 `public/`)打进 server 产物,影响部署体积;升级前后都存在,与本轮无关,未改。其二,`npx eslint` 有 4 个既有 `no-unused-vars` warning,分别在 `birth-time-candidate-result.tsx:143``birth-time-candidate-completion.ts:10,11``tests/identity-auth-factory.test.ts:48`,未改。其三,`eslint-config-next` 仍是 16.2.10、与 next 16.3.1 版本号不同步,但 eslint 实测 0 error,按「不许顺手升别的依赖」未动。
+63
View File
@@ -197,6 +197,69 @@ React Compiler 在所有六种配置下都拒编 `Home`,包括显式 `"use mem
而那 44 个组件的运行时收益没有测量数据支撑首屏 +2.50 % 的代价。 而那 44 个组件的运行时收益没有测量数据支撑首屏 +2.50 % 的代价。
若日后要改变这个结论,前置条件应当是先给那 44 个组件测出渲染基准,而不是直接开配置。 若日后要改变这个结论,前置条件应当是先给那 44 个组件测出渲染基准,而不是直接开配置。
## 追加轮 · 授权新增依赖后的根因诊断(2026-08-17)
用户授权新增依赖,于是把 `BLOCKED.md` 第一条(拿不到编译器诊断)解掉。做法:临时
`npm i -D babel-plugin-react-compiler`,用 `@babel/core``transformSync` 单独跑 `page.tsx`
`parserOpts.plugins = ["jsx", ["typescript", {isTSX:true}]]`),给插件传 `logger` 钩子逐函数收事件。
脚本在 `/tmp/rc_diagnose.mjs``/tmp/rc_batch.mjs`,不进仓库。
### `Home` 到底被什么挡住
稳定版 `1.0.0``page.tsx` 报 26 个事件:`CompileSuccess` 2、`CompileError` 24。
`Home`(行 1002)的 24 条去重后三类,**全部带 `Todo:` 前缀** —— 这是 React Compiler
标记「该语法尚未实现」的前缀,不是用户代码违规:
| 条数 | 原因 |
| --- | --- |
| 13 | `Todo: (BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause` |
| 9 | `Todo: (BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch` |
| 2 | `Todo: (BuildHIR::node.lowerReorderableExpression) Expression type MemberExpression cannot be safely reordered` |
那 13 个 `finally` 做的全是 `finally` 该做的事:`window.clearTimeout(bootstrapTimeout)`
`polling = false``rectificationOpenInFlight.current = false`
`cancellationRequests.current.delete(requestId)`,以及 8 处 `setProfileSaving(false)`
`setAvatarSaving(false)``setCreatingSession(false)``setBirthTimeAssessmentPhase(null)` 这类加载态复位。
删掉任何一个,try 块抛错时 UI 就永久卡在加载态 —— 那是拿真 bug 换假优化,所以一行没动。
顺带交叉验证了之前的产物取证:Babel 版报告编译成功的是 `BirthLocationFields @ 760`
`ProfileFields @ 815`,和先前从 Rust 版构建产物 source map 反查出的槽 1124(行 760815
逐一对上。两条完全独立的证据链互相印证,取证方法没问题。
### 更新版本能不能救
`experimental` 版(`0.0.0-experimental-a1856f3-20260507`)里那 22 条 `try/finally``throw`
错误已被上游修好,`CompileError` 从 24 降到 2。但 `Home` 立刻撞上编译器内部断言失败:
`Invariant: Expected all references to a variable to be consistently local or context references`
`page.tsx:2666``catch (error)` 的绑定既被直接使用、又被 `setRequestError((current) => ...)`
的闭包捕获)。在仓库外的副本上把这一处改掉,又冒出下一个
`Invariant: [PruneHoistedContexts] Unexpected hoisted function``page.tsx:1837``refreshAccount`
被 1626 行的 `useEffect` 提前引用)。`Invariant:` 在 React Compiler 分类里是**编译器 bug**。
`Home` 共 50 个函数声明,其中 6 个被声明前引用,要满足编译器得跨千行重排这 6 个定义。
一句话:打地鼠,且每步都由编译器 bug 而非代码质量驱动。
### 换版本/换实现的全项目收益:零
`frontend/src` 全部 373 个文件跑批量诊断,两版对比:
| | 稳定版 1.0.0 | experimental 版 |
| --- | --- | --- |
| 编译成功的函数数 | **134** | **134** |
| bailout 事件数 | 74 | 48 |
| 有 bailout 的文件数 | 21 | 21 |
成功数一个没多。原因是被上游修掉的错误类别,所在函数都还有别的拦路错误。
所以「升编译器版本」或「改用 Babel 版实现」这两条路由数据关闭,
`babel-plugin-react-compiler` 诊断完即卸载,锁文件用 `npm ci` 权威还原(`npm uninstall` 会留下
3 条 `dev``devOptional` 的元数据漂移,已还原掉),`git diff` 为空,不进交付。
### 这一轮改变了什么结论
任务 2 的验收结论不变(`Home` 未被编译),但**原因的性质变了**:
从「不明原因的静默失败」变成「上游编译器缺陷,本仓库代码无错」。
这直接影响后续该怎么做:不该去改 `page.tsx` 迎合编译器,该做的是按职责拆小 `Home`
交付物本身零变化 —— 配置仍是回滚态,`frontend/src/**` 仍是零改动。
## 收尾 ## 收尾
`docs/BUG_HISTORY.md` 追加 `BUG-255`。按 BUG-253 的防复发要求先检索最大编号:本地基点 `docs/BUG_HISTORY.md` 追加 `BUG-255`。按 BUG-253 的防复发要求先检索最大编号:本地基点
+2 -2
View File
@@ -3839,10 +3839,10 @@
- 最近更新:2026-08-17 - 最近更新:2026-08-17
- 影响面:`/` 主对话页 `frontend/src/app/page.tsx` 的重渲染性能,以及后续任何「靠 React Compiler 免除手写记忆化」的计划。 - 影响面:`/` 主对话页 `frontend/src/app/page.tsx` 的重渲染性能,以及后续任何「靠 React Compiler 免除手写记忆化」的计划。
- 用户现象:无终端用户可见现象。对维护者而言的现象是:`next.config.ts``reactCompiler: true` 开启后构建打印 `✓ turbopackRustReactCompiler`、退出码 0、测试全绿,**看起来完全成功**,但 `Home` 一个函数都没被优化。这个失败不产生任何警告、错误或日志,只看构建输出无法察觉。 - 用户现象:无终端用户可见现象。对维护者而言的现象是:`next.config.ts``reactCompiler: true` 开启后构建打印 `✓ turbopackRustReactCompiler`、退出码 0、测试全绿,**看起来完全成功**,但 `Home` 一个函数都没被优化。这个失败不产生任何警告、错误或日志,只看构建输出无法察觉。
- 根因:`page.tsx` 有 3738 行,其中 `export default function Home()` 单个函数占 2730 行(10023738),带 24 个 `useState`、18 个 `useEffect`、0 个手写 `useCallback`/`useMemo`。Next 16.3.1 的 Rust 版 React Compiler 会正常编译同一文件里的其他函数,却拒编 `Home`:默认 `infer` 模式下全项目 44 个函数拿到缓存槽(`app-sidebar` 112 槽、`use-birth-time-guided-journey` 84 槽、`sidebar-session-row` 73 槽等),`page.tsx` 内部两个小组件(原始行 760、815)也拿到了 11 和 24 槽,唯独 `Home` 的生成代码仍以裸 `useState` 序列开头、没有 `_c(N)` 前导。把 `compilationMode` 设为 `all`(绕过组件识别启发式、强制编译每个函数)后 `page.tsx` 被编译函数从 2 涨到 47`Home` 依然不在其中——既然 `all` 模式下不存在「未被识别为组件」,只剩一种解释:编译器尝试了 `Home` 并失败,然后按 `panicThreshold` 默认值 `none`(官方文档原话 "skips components which cannot be compiled")静默跳过。具体`Home` 哪一处代码触发失败,本轮**未能定位**:`panicThreshold: "all_errors"` 在 Rust 版下形同虚设,设了也不报错、不打印任何编译器诊断 - 根因:`page.tsx` 有 3738 行,其中 `export default function Home()` 单个函数占 2730 行(10023738),带 24 个 `useState`、18 个 `useEffect`、0 个手写 `useCallback`/`useMemo`。Next 16.3.1 的 Rust 版 React Compiler 会正常编译同一文件里的其他函数,却拒编 `Home`:默认 `infer` 模式下全项目 44 个函数拿到缓存槽(`app-sidebar` 112 槽、`use-birth-time-guided-journey` 84 槽、`sidebar-session-row` 73 槽等),`page.tsx` 内部两个小组件(原始行 760、815)也拿到了 11 和 24 槽,唯独 `Home` 的生成代码仍以裸 `useState` 序列开头、没有 `_c(N)` 前导。把 `compilationMode` 设为 `all`(绕过组件识别启发式、强制编译每个函数)后 `page.tsx` 被编译函数从 2 涨到 47`Home` 依然不在其中——既然 `all` 模式下不存在「未被识别为组件」,只剩一种解释:编译器尝试了 `Home` 并失败,然后按 `panicThreshold` 默认值 `none`(官方文档原话 "skips components which cannot be compiled")静默跳过。具体原因经授权后已定位(`panicThreshold: "all_errors"` 在 Rust 版下形同虚设,设了也不报错,因此改用临时安装 `babel-plugin-react-compiler` 并挂 `logger` 钩子的方式取诊断,诊断完已卸载):**`Home` 的失败全部来自编译器自身未实现的语法与内部断言失败,没有一条是本仓库代码写错。** 稳定版 `babel-plugin-react-compiler@1.0.0``Home` 报 24 条错误,去重后三类,全部带 `Todo:` 前缀(React Compiler 用 `Todo:` 标记「该语法尚未实现」):13 条 `Todo: (BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`、9 条 `Todo: (BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch`、2 条 `Todo: (BuildHIR::node.lowerReorderableExpression) Expression type MemberExpression cannot be safely reordered`。即编译器当时还不支持 `try/finally``try/catch` 内的 `throw`,而 `Home` 有 13 个 `finally` 和 9 个这样的 `throw`。这 13 个 `finally` 做的全是 `finally` 该做的事——`window.clearTimeout(bootstrapTimeout)``polling = false``rectificationOpenInFlight.current = false``cancellationRequests.current.delete(requestId)`,以及 8 处 `setProfileSaving(false)` / `setAvatarSaving(false)` / `setCreatingSession(false)` / `setBirthTimeAssessmentPhase(null)` 之类的加载态复位;删掉任何一个,try 块抛错时 UI 就永久卡在加载态,是拿真 bug 换假优化。另在更新的 `0.0.0-experimental-a1856f3-20260507` 上复测:那 22 条 `try/finally``throw` 错误已被上游修好,但 `Home` 随即撞上编译器内部断言失败 `Invariant: Expected all references to a variable to be consistently local or context references``page.tsx:2666``catch (error)` 的绑定同时被直接使用和被 `setRequestError((current) => ...)` 的闭包捕获);在仓库外的副本上把这一处改掉后,又冒出下一个 `Invariant: [PruneHoistedContexts] Unexpected hoisted function``page.tsx:1837``refreshAccount`,被 1626 行的 `useEffect` 提前引用)。`Invariant:` 在 React Compiler 的分类里是编译器 bug 而非用户代码违规。`Home` 共 50 个函数声明,其中 6 个被声明前引用(`refreshAccount` 1837/1626、`editDeclaredBirthTimeDetails` 2280/1098、`completeGuidedBirthTime` 2308/1097、`openRectificationFromHomepage` 2515/2373、`openRectificationSession` 2519/1982、`handleRectificationProfileIncomplete` 2531/2457),要满足编译器就得在 2700 行的组件里跨千行重排这 6 个定义,且照上述规律修完还会有下一个内部 panic。诊断顺带交叉验证了产物取证的正确性:Babel 版报告成功编译的两个函数是 `BirthLocationFields @ 760``ProfileFields @ 815`,与先前从 Rust 版构建产物 source map 反查出的两个缓存槽(行 760/815,槽 11/24)逐一对上,两条独立证据互相印证
- 修复:未修复。`next.config.ts``reactCompiler: true``experimental.turbopackRustReactCompiler: true` 已回滚到与 `origin/staging` 逐字节一致;Next 16.2.10 → 16.3.1 的升级保留(该升级本身独立验证通过,是 Rust 版编译器的前置条件)。本轮共试六种配置全部失败:`reactCompiler: true`、加 `panicThreshold: "all_errors"`、再加文件顶部 `"use memo"``compilationMode: "all"``"all"` + `all_errors``compilationMode: "annotation"` + `Home` 体内 `"use memo"`。其中 `compilationMode: "all"` 还会直接把构建搞坏:它会编译模块作用域的普通回调,`birth-time-intake.tsx:22``Array.from({length:24}, (_, index) => ...)` 被插入 `useMemoCache`,预渲染 `/` 时抛 `TypeError: Cannot read properties of null (reading 'useMemoCache')`。注解模式(领导指定的退路)反而最差:全项目 0 个缓存槽,连原本能编的 44 个也停了。 - 修复:未修复。`next.config.ts``reactCompiler: true``experimental.turbopackRustReactCompiler: true` 已回滚到与 `origin/staging` 逐字节一致;Next 16.2.10 → 16.3.1 的升级保留(该升级本身独立验证通过,是 Rust 版编译器的前置条件)。本轮共试六种配置全部失败:`reactCompiler: true`、加 `panicThreshold: "all_errors"`、再加文件顶部 `"use memo"``compilationMode: "all"``"all"` + `all_errors``compilationMode: "annotation"` + `Home` 体内 `"use memo"`。其中 `compilationMode: "all"` 还会直接把构建搞坏:它会编译模块作用域的普通回调,`birth-time-intake.tsx:22``Array.from({length:24}, (_, index) => ...)` 被插入 `useMemoCache`,预渲染 `/` 时抛 `TypeError: Cannot read properties of null (reading 'useMemoCache')`。注解模式(领导指定的退路)反而最差:全项目 0 个缓存槽,连原本能编的 44 个也停了。
- 验证:`npx tsc --noEmit` 无输出;`npx eslint` 0 error4 个既有 warning 在未改动文件里);`npx next build` 退出码 0;非数据库套件 199 个文件 1592 条全绿、fail 0 skipped 0 todo 0(与开工基线一致)。取证方式:`Home` 在产物里被压缩改名,`function Home(` 搜不到,改用 source map 反查——解 `.next/server/chunks/ssr/*.js.map` 的 VLQ mappings,把每个缓存槽 `_c(N)`(压缩后真实形态是 `(0,X.c)(N)`)归属回原始文件与行号。反向验证:只加/删 `page.tsx` 顶部一行 `"use no memo"``page.tsx` 被编译函数数在 2 与 0 之间可见切换,且切换只影响 `page.tsx`、其他文件槽数不变,证明取证方法不是恒为真;两种状态下都判定 `Home` 未编译。构建代价(各测 4 次取最快,同机噪声大):开启前 36.65s / 关闭后 35.11s,差异落在噪声内,Rust 版没有可测量的构建变慢;`/` 首屏 JS gzip 470.1 KB → 481.9 KB+12031 B / +2.50%,低于 5% 阈值。 - 验证:`npx tsc --noEmit` 无输出;`npx eslint` 0 error4 个既有 warning 在未改动文件里);`npx next build` 退出码 0;非数据库套件 199 个文件 1592 条全绿、fail 0 skipped 0 todo 0(与开工基线一致)。取证方式:`Home` 在产物里被压缩改名,`function Home(` 搜不到,改用 source map 反查——解 `.next/server/chunks/ssr/*.js.map` 的 VLQ mappings,把每个缓存槽 `_c(N)`(压缩后真实形态是 `(0,X.c)(N)`)归属回原始文件与行号。反向验证:只加/删 `page.tsx` 顶部一行 `"use no memo"``page.tsx` 被编译函数数在 2 与 0 之间可见切换,且切换只影响 `page.tsx`、其他文件槽数不变,证明取证方法不是恒为真;两种状态下都判定 `Home` 未编译。构建代价(各测 4 次取最快,同机噪声大):开启前 36.65s / 关闭后 35.11s,差异落在噪声内,Rust 版没有可测量的构建变慢;`/` 首屏 JS gzip 470.1 KB → 481.9 KB+12031 B / +2.50%,低于 5% 阈值。
- 待跟进:其一,定位 `Home` 里究竟哪一处挡住编译器,需要能出诊断的编译器;Rust 版不出诊断,Babel 版(`turbopackRustReactCompiler: false`)能出,但要新增依赖 `babel-plugin-react-compiler`next 的可选 peer,未装;Next 只内置了 compiler runtime,没内置这个 plugin),需授权。其二,是否为了那 44 个能编的组件而保留 `reactCompiler: true`:它们大多在 `/` 上渲染(侧边栏、会话行、日期选择器、三个引导 hook),收益真实但本轮未测量,代价是首屏 +2.5%;本轮按止损条款选择回滚,这个取舍留给决策。其三,若要让 `Home` 真正受益,最可能的路是把它拆成若干个小组件——但那是业务代码重构,本轮明令禁止碰 `frontend/src/**` - 待跟进:其一(已完成,结论见根因):定位 `Home` 的失败原因已获授权并完成,答案是上游编译器缺陷,不是本仓库代码问题。同时用同一套诊断量化了「换编译器版本能否提高覆盖率」这个问题,答案是**不能**:对 `frontend/src` 全部 373 个文件跑批量诊断,稳定版 1.0.0 与 experimental 版**编译成功的函数数完全相同,都是 134 个**,失败文件也都是同样的 21 个,只是报错事件从 74 降到 48——被上游修掉的那些错误类别,所在函数都还有别的拦路错误,所以一个函数都没多编译出来。因此换 Babel 版或换更新版本都买不到任何东西,`babel-plugin-react-compiler` 诊断完即卸载(`npm ci` 从锁文件权威还原,`git diff` 为空),不进交付。复现诊断的方法:临时 `npm i -D babel-plugin-react-compiler`,用 `@babel/core``transformSync``parserOpts.plugins = ["jsx", ["typescript", {isTSX:true}]]` 单独跑该文件,给插件传 `logger: { logEvent(file, event) {} }``event.kind``CompileError` 的即为 bailout`event.detail.message` 是原因。其二,是否为了那 44 个能编的组件而保留 `reactCompiler: true`:它们大多在 `/` 上渲染(侧边栏、会话行、日期选择器、三个引导 hook),收益真实但本轮未测量,代价是首屏 +2.5%;本轮按止损条款选择回滚,这个取舍留给决策。其三,若要让 `Home` 真正受益,最可能的路是把它拆成若干个小组件——但那是业务代码重构,本轮明令禁止碰 `frontend/src/**`诊断结果还把这条路的性质说清了:为迎合编译器去改 `Home`(删 `finally`、重排 6 个提升函数、逐个绕内部断言)是被编译器 bug 牵着走的打地鼠,每一步都拿确定的正确性换不确定的记忆化收益,不该做;真正值得做的是按职责把 `Home` 拆小,那样每个小组件天然落在编译器能处理的范围内,同时也解决可维护性问题。
- 防复发:**「配置开启 + 构建绿」绝不等于「React Compiler 生效」**。编译器放弃某个组件时不报错、不警告、不留日志,这是它的默认行为(`panicThreshold: "none"`)而非缺陷。任何启用 React Compiler 的改动都必须在构建产物里定位目标组件、确认存在编译器注入的缓存槽,并用 `"use no memo"` 做一次反向验证证明取证方法会随编译状态变化;只贴构建退出码等于没验证。另外 `"use memo"` / `"use no memo"` 是函数体内的指令,写在文件顶部时 `"use no memo"` 可作整文件退出、但 `"use memo"` 不构成整文件加入。 - 防复发:**「配置开启 + 构建绿」绝不等于「React Compiler 生效」**。编译器放弃某个组件时不报错、不警告、不留日志,这是它的默认行为(`panicThreshold: "none"`)而非缺陷。任何启用 React Compiler 的改动都必须在构建产物里定位目标组件、确认存在编译器注入的缓存槽,并用 `"use no memo"` 做一次反向验证证明取证方法会随编译状态变化;只贴构建退出码等于没验证。另外 `"use memo"` / `"use no memo"` 是函数体内的指令,写在文件顶部时 `"use no memo"` 可作整文件退出、但 `"use memo"` 不构成整文件加入。
- 相关记录:BUG-260 无前序同类记录。本记录三次改号:初次写作取 255;第一次 rebase 到 `origin/staging``c8d9ec64`)时远端已占 254255,改 256;第二次 rebase 到 `e1db5762` 时远端又占到 259,改 260。每次都按 BUG-254 的防复发要求处理——标题与远端各记录均不同,故另分新号而非合并。BUG-253 所记的抢号失效模式在同一轮交付里连续复现两次,暴露出「追加前检索最大编号」这条措施的边界:它只在写记录那一刻成立,防不住推送前远端继续前进。可靠做法是把定号推迟到推送前最后一次 rebase 之后。 - 相关记录:BUG-260 无前序同类记录。本记录三次改号:初次写作取 255;第一次 rebase 到 `origin/staging``c8d9ec64`)时远端已占 254255,改 256;第二次 rebase 到 `e1db5762` 时远端又占到 259,改 260。每次都按 BUG-254 的防复发要求处理——标题与远端各记录均不同,故另分新号而非合并。BUG-253 所记的抢号失效模式在同一轮交付里连续复现两次,暴露出「追加前检索最大编号」这条措施的边界:它只在写记录那一刻成立,防不住推送前远端继续前进。可靠做法是把定号推迟到推送前最后一次 rebase 之后。
- 修复版本:本地未提交候选 - 修复版本:本地未提交候选