claude-code-best/src/components
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__ fix: 优化压缩错误消息和自动压缩提示的可理解性 2026-05-05 07:19:19 +08:00
agents chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
ClaudeCodeHint style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
CustomSelect style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
design-system chore: 清理 src 下 33 项死代码和类型断言 2026-05-05 16:07:30 +08:00
DesktopUpsell style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
diff style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
FeedbackSurvey fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误 2026-05-08 13:17:25 +08:00
grove style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
HelpV2 fix: 优化权限提示用词和 Help 页面新手引导 2026-05-05 00:09:27 +08:00
HighlightedCode style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
hooks style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
LogoV2 chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
LspRecommendation style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ManagedSettingsSecurityDialog style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
mcp style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
memory style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
messages fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误 2026-05-08 13:17:25 +08:00
Passes style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
permissions chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
PromptInput fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误 2026-05-08 13:17:25 +08:00
sandbox style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
Settings chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
shell style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
skills style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
Spinner style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
StructuredDiff style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
tasks Merge pull request #416 from znygugeyx-ctrl/feat/subagent-fork-render 2026-05-06 09:57:52 +08:00
teams chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
TrustDialog fix: 优化用户交互文案,为错误消息添加可操作提示 2026-05-05 00:05:35 +08:00
ui style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ultraplan style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
wizard chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
AgentProgressLine.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
AntModelSwitchCallout.tsx fix: 修复 USER_TYPE=ant 时 TUI 无法启动的问题 2026-04-02 20:31:04 +08:00
App.tsx fix: theme switching always defaults to dark mode 2026-04-30 16:15:27 +08:00
ApproveApiKey.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
AutoModeOptInDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
AutoUpdater.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
AutoUpdaterWrapper.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
AwsAuthStatusBox.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
BaseTextInput.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
BashModeProgress.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
BridgeDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
BypassPermissionsModeDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ChannelDowngradeDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ClaudeInChromeOnboarding.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ClaudeMdExternalIncludesDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ClickableImageRef.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
CompactSummary.tsx fix: 优化压缩错误消息和自动压缩提示的可理解性 2026-05-05 07:19:19 +08:00
ConfigurableShortcutHint.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ConsoleOAuthFlow.tsx chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
ContextSuggestions.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ContextVisualization.tsx feat: 添加 prompt 缓存命中率检测与警告功能 2026-05-07 10:49:06 +08:00
CoordinatorAgentStatus.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
CostThresholdDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
CtrlOToExpand.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
DesktopHandoff.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
DevBar.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
DevChannelsDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
DiagnosticsDisplay.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
EffortCallout.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
EffortIndicator.ts feat: 添加 UI 组件增强与测试覆盖 2026-04-22 22:38:10 +08:00
ExitFlow.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ExportDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
FallbackToolUseErrorMessage.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
FallbackToolUseRejectedMessage.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
FastIcon.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
Feedback.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
FileEditToolDiff.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
FileEditToolUpdatedMessage.tsx fix: 恢复消息流中 diff 高亮渲染功能 2026-05-07 08:28:28 +08:00
FileEditToolUseRejectedMessage.tsx fix: 恢复消息流中 diff 高亮渲染功能 2026-05-07 08:28:28 +08:00
FilePathLink.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
FullscreenLayout.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
GlobalSearchDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
HighlightedCode.tsx fix: 优化内存峰值与 CPU 性能,降低 100-300MB 内存占用 2026-05-02 00:45:03 +08:00
HistorySearchDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
IdeAutoConnectDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
IdeOnboardingDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
IdeStatusIndicator.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
IdleReturnDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
InterruptedByUser.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
InvalidConfigDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
InvalidSettingsDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
KeybindingWarnings.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
LanguagePicker.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
LogSelector.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
Markdown.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MarkdownTable.tsx perf: 表格渲染效率升级 2026-05-03 10:10:47 +08:00
MCPServerApprovalDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MCPServerDesktopImportDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MCPServerDialogCopy.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MCPServerMultiselectDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MemoryUsageIndicator.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
Message.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
messageActions.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MessageModel.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MessageResponse.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MessageRow.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
Messages.tsx fix: 内存优化 — 预测性 compact 阈值、增量 lookups orphaned 修复、deferred slice 引用优化 2026-05-02 20:32:00 +08:00
MessageSelector.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
MessageTimestamp.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ModelPicker.tsx fix: 优化 ModelPicker 副标题和 resume 错误提示的可操作性 2026-05-05 02:14:07 +08:00
NativeAutoUpdater.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
NotebookEditToolUseRejectedMessage.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
OffscreenFreeze.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
Onboarding.tsx chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
OutputStylePicker.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
PackageManagerAutoUpdater.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
PrBadge.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
PressEnterToContinue.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
QuickOpenDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
RemoteCallout.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
RemoteEnvironmentDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ResumeTask.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
SandboxViolationExpandedView.tsx chore: 清理 src 下 113 项未使用导入和死代码 2026-05-05 20:05:15 +08:00
ScrollKeybindingHandler.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
SearchBox.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
SentryErrorBoundary.ts feat: 更新 sentry 错误上报 2026-04-03 09:39:25 +08:00
SessionBackgroundHint.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
SessionPreview.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ShowInIDEPrompt.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
SkillImprovementSurvey.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
Spinner.tsx fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误 2026-05-08 13:17:25 +08:00
Stats.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
StatusLine.tsx feat: 状态栏支持 refreshInterval 定时刷新 2026-05-06 22:50:11 +08:00
StatusNotices.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
StructuredDiff.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
StructuredDiffList.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TagTabs.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TaskListV2.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TeammateViewHeader.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TeleportError.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TeleportProgress.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TeleportRepoMismatchDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TeleportResumeWrapper.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TeleportStash.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TextInput.tsx fix: 修复条件式 hook 调用导致的 "Rendered fewer hooks than expected" 错误 2026-05-08 13:17:25 +08:00
ThemePicker.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ThinkingToggle.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
TokenWarning.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
ToolUseLoader.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
UndercoverAutoCallout.tsx fix: 修复 USER_TYPE=ant 时 TUI 无法启动的问题 2026-04-02 20:31:04 +08:00
ValidationErrorsList.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
VimTextInput.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
VirtualMessageList.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
WorkflowMultiselectDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00
WorktreeExitDialog.tsx style: 完成所有文件的lint 2026-05-01 21:39:30 +08:00