claude-code-best/src
Bonerush 8ba51edec1 fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误
修复在 dev 模式下按下 Ctrl+O 切换 transcript 视图时 React 抛出
"Rendered fewer hooks than expected" 崩溃的问题。

## 根因分析

项目中有大量 hook(useState / useMemo / useRef / useSyncExternalStore 等)
被包裹在 `feature()` 三元表达式中条件调用,例如:

    const value = feature('X') ? useHook() : defaultValue;

在 build 模式下 `feature()` 是编译时常量,死代码消除会移除未使用的分支,
hooks 数量在编译后是确定的。但在 dev 模式下(scripts/dev.ts 注入
--feature 启用全部 31 个 feature),`feature()` 是运行时调用,
但始终返回 true,因此所有 hooks 都会被调用,原本不会出问题。

真正的触发器是 REPL.tsx 第 5381 行的提前返回:

    if (screen === 'transcript') { return transcriptReturn; }

当用户按下 Ctrl+O 进入 transcript 模式时,该提前返回之后的所有 hooks
(如 displayedAgentMessages 的 useMemo)都不会被调用,导致 React 在
下一次渲染时检测到 hooks 数量与上次不一致而崩溃。

此外,其他文件中也存在相同的条件式 hook 模式——虽然 dev 模式下
feature() 返回 true,所以这些路径实际上不会被触发,但它们是
潜在的隐患:若将来有人通过环境变量关闭某个 feature,
同样的崩溃会立即出现。

## 修复策略

采用统一模式:**始终无条件调用 hook,将 feature() gate 应用到值上**。

    // Before (unsafe — hook count varies by feature flag)
    const value = feature('X') ? useHook() : defaultValue;

    // After (safe — hook always called, gate on the value)
    const rawValue = useHook();
    const value = feature('X') ? rawValue : defaultValue;

## 修改清单

### 核心修复(REPL.tsx)
- 将 `displayedAgentMessages` useMemo 及依赖变量(viewedTask /
  viewedTeammateTask / viewedAgentTask / usesSyncMessages /
  rawAgentMessages / displayedMessages)从 transcript 提前返回
  之后移至之前,确保两模式下 hooks 调用顺序一致
- 修复 `disableMessageActions` / `useAssistantHistory` /
  `voiceIntegration` 的条件式 hook 调用

### 条件式 hook 修复(11 个文件)
- src/hooks/useGlobalKeybindings.tsx — isBriefOnly / toggleBrief
  keybinding 改为 isActive 门控
- src/hooks/useReplBridge.tsx — 5 个 BRIDGE_MODE 选值改为无条件调用
- src/hooks/useVoiceIntegration.tsx — 4 个 VOICE_MODE 选值修复
- src/components/PromptInput/Notifications.tsx — 4 个 feature 选值修复
- src/components/PromptInput/PromptInput.tsx — briefOwnsGap /
  companionSpeaking 修复
- src/components/PromptInput/PromptInputFooterLeftSide.tsx — 4 个
  VOICE_MODE 选值修复
- src/components/PromptInput/PromptInputQueuedCommands.tsx — isBriefOnly
- src/components/Spinner.tsx — briefEnvEnabled 修复
- src/components/TextInput.tsx — voiceState / audioLevels /
  animationFrame 修复
- src/components/messages/AttachmentMessage.tsx — isDemoEnv 修复
- src/components/messages/UserPromptMessage.tsx — isBriefOnly /
  viewingAgentTaskId / briefEnvEnabled 修复
- src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx
  — isBriefOnly 修复

### 其他修复
- src/components/FeedbackSurvey/useFrustrationDetection.ts — 将 3 个
  提前返回合并为 shouldSkip 变量,handleTranscriptSelect 提前 return
- src/hooks/useIssueFlagBanner.ts — useRef 移到 USER_TYPE 检查之前
- src/hooks/useUpdateNotification.ts — useState 改为 useRef,
  避免版本号变化触发不必要重渲染

### 构建/开发配置
- build.ts — 添加 `sourcemap: 'linked'`
- scripts/dev.ts — NODE_ENV 从 'production' 改为 'development'

Closes #434
2026-05-08 13:17:25 +08:00
..
__tests__ style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
assistant chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
bootstrap docs: 合并性能分析报告并优化内存管理 2026-05-02 09:11:12 +08:00
bridge chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
buddy chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
cli chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
commands Merge pull request #416 from znygugeyx-ctrl/feat/subagent-fork-render 2026-05-06 09:57:52 +08:00
components fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误 2026-05-08 13:17:25 +08:00
constants feat: 添加自动邮箱映射功能并完善署名系统 2026-05-07 11:12:40 +08:00
context chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
coordinator feat: 工具层及 mcp 大重构 (#252) 2026-04-13 09:52:05 +08:00
daemon chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
entrypoints feat: ai 的随机修复 2026-05-05 19:36:38 +08:00
environment-runner style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
hooks fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误 2026-05-08 13:17:25 +08:00
jobs Feat/integrate lint preview (#285) 2026-04-16 20:59:29 +08:00
keybindings chore: 清理 src 下 33 项死代码和类型断言 2026-05-05 16:07:30 +08:00
memdir style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
migrations fix: 禁用 opus[1m] 自动迁移,尊重用户手动删除 [1m] 后缀的选择 2026-05-07 09:33:56 +08:00
moreright style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
native-ts/file-index style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
outputStyles feat: build 2026-03-31 19:22:47 +08:00
plugins feat: 接入内建 weixin channel(同 #301 重构版本) (#303) 2026-04-19 21:33:27 +08:00
proactive feat: harden autonomy lifecycle, OOM bounds, and provider-boundary finalization 2026-04-29 14:04:27 +08:00
query style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
remote style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
schemas chore: 清理仓库审计问题——修正 CLAUDE.md、删除冗余 yoga-layout、清除 621 个未使用的类型 stub (#354) 2026-04-25 08:54:18 +08:00
screens fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误 2026-05-08 13:17:25 +08:00
self-hosted-runner style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
server style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
services fix: third-party API user_id validation error (DeepSeek, etc.) 2026-05-06 17:47:31 +08:00
skills chore: 清理仓库审计问题——修正 CLAUDE.md、删除冗余 yoga-layout、清除 621 个未使用的类型 stub (#354) 2026-04-25 08:54:18 +08:00
ssh feat: 实现 SSH Remote — 本地 REPL + 远端工具执行 2026-04-24 14:25:56 +08:00
state Merge pull request #416 from znygugeyx-ctrl/feat/subagent-fork-render 2026-05-06 09:57:52 +08:00
tasks style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
types chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
upstreamproxy feat: 问就是封包 2026-03-31 23:32:58 +08:00
utils feat: 改为使用 ccb 的邮箱 2026-05-07 12:06:40 +08:00
vim feat: build 2026-03-31 19:22:47 +08:00
voice feat: 集成豆包 ASR 语音识别后端,支持 /voice doubao 切换 (#357) 2026-04-25 13:57:30 +08:00
commands.ts style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
context.ts feat: build 2026-03-31 19:22:47 +08:00
cost-tracker.ts feat: ACP 协议版本 remote control (#293) 2026-04-18 21:54:22 +08:00
costHook.ts feat: build 2026-03-31 19:22:47 +08:00
dialogLaunchers.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
history.ts feat: 添加服务层增强与零散改进 2026-04-22 22:38:10 +08:00
interactiveHelpers.tsx chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
main.tsx Merge pull request #416 from znygugeyx-ctrl/feat/subagent-fork-render 2026-05-06 09:57:52 +08:00
projectOnboardingState.ts feat: build 2026-03-31 19:22:47 +08:00
query.ts feat: 添加 prompt 缓存命中率检测与警告功能 2026-05-07 10:49:06 +08:00
QueryEngine.ts chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
replLauncher.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
setup.ts feat: 添加服务层增强与零散改进 2026-04-22 22:38:10 +08:00
Task.ts feat: build 2026-03-31 19:22:47 +08:00
tasks.ts feat: build 2026-03-31 19:22:47 +08:00
Tool.ts feat: harden autonomy lifecycle, OOM bounds, and provider-boundary finalization 2026-04-29 14:04:27 +08:00
tools.ts style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00