🤖 perf: let React Compiler compile ChatInput - #4426
Merged
Merged
Conversation
ThomasK33
added this pull request to stack #4430
September 24, 2026 11:31
ThomasK33
marked this pull request as ready for review
September 24, 2026 11:55
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
ThomasK33
force-pushed
the
perf-compiler-chatinput-compile
branch
from
September 24, 2026 12:42
0d02470 to
12b6a3f
Compare
yermakoffivan
pushed a commit
to yermakoffivan/mux
that referenced
this pull request
Sep 24, 2026
…to plain helpers (coder#4425) ## Summary First of two PRs that let React Compiler compile `ChatInput`. This one moves control flow the compiler can't lower out of the component body, without changing behavior. `ChatInput` still stays in the guard's `KNOWN_SKIPPED` baseline until the next PR (coder#4426) removes its last blockers. ## Background React Compiler 1.0 skips a whole component when its body, including nested callbacks, contains `try/finally`, `try` without `catch`, `for await`, `??=`, or a conditional/logical/optional-chaining expression inside `try/catch`. `ChatInput` had all of these, so it was never compiled and got no memoization. coder#4422 added the coverage guard; this stack works through its baseline. ## Implementation - New `src/browser/utils/compilerSafeControlFlow.ts` with three plain helpers: `runWithCatch`, `runWithFinally`, `runWithCatchFinally`. The compiler compiles only components and hooks, so the `try` lives in these helpers. - Large `try` blocks in `handleSend` become named async closures at the same indentation (`const runWorkspaceSend = async () => { … }; await runWithFinally(runWorkspaceSend, …)`), so the diff stays reviewable. Early `return`s keep their meaning because each converted `try` was the last statement of its scope. - The command-resolution `try/finally` now returns a result object instead of assigning outer `let`s (destructuring assignment into captured variables is also unsupported). - The provider-config subscription's `for await` loop moves to the module-level `forEachProviderConfigChange`. - `sendResolutionAbortRef.current ??= new AbortController()` becomes an explicit read and write. ## Risks Low. Every converted block keeps its body, catch, and cleanup code; the only timing difference is one extra microtask before a `finally` cleanup runs, from the helper's `await`. Areas touched: message send (workspace and creation), edit-send, the preference-persistence wait before send, workflow run card projection, and voice-input provider availability. ## Validation - `make static-check`; guard: `9/18 hot components compile (9 known skipped)` (unchanged at this layer). - `tests/ui/chat` and `tests/ui/compaction` (19 suites, 70 tests) and `src/browser/features/ChatInput` unit tests pass on the stack. --- _Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high` • Cost: `$4.05`_ <!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high costs=4.05 -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ChatInputInnernow compiles under React Compiler, and the guard'sKNOWN_SKIPPEDbaseline drops it. The audit shows noCompileErrorfor it.Background
After #4425, three kinds of blockers were left: a
react-hooks/exhaustive-depssuppression, ref writes during render, and manualuseCallback/useMemocalls whose dependencies the compiler could not preserve (the compiler skips the whole component when it can't keep an existing manual memo).Implementation
editingMessage?.idand hid the draft callbacks from its deps. It now lists all deps and uses an applied-edit-id ref, so it still applies the edit draft once per edit target and never clobbers in-progress edit text. The ref resets when editing ends, so re-editing the same message re-applies it, as before.editingMessageIdRefandhandleSendRefare now synced inuseLayoutEffect. Layout effects run in the same task as the commit, so no async callback can observe the old value after the commit, which is the guarantee the existing comment asked for.handleToastDismiss,idleCompactionProps,setPreferredModel,cycleToNextModel. The compiler memoizes these now. This follows AGENTS.md ("don't hand-memoize") and adds no new manual memoization.cycleToNextModelthrough a latest-value ref instead of re-subscribing when it changes (same pattern ascomposerApiRefin this file).Risks
Medium-low. Edit-mode entry and the global model-cycle shortcut are the behavior-sensitive paths; both keep their semantics. The memoization is now compiler-managed.
Validation
10/18 hot components compile (8 known skipped).tests/ui/chat,tests/ui/compaction, ChatInput unit tests,useAIViewKeybindstests pass.Measurements
Electron perf e2e (
tests/e2e/scenarios/perf.*.spec.ts,XUM_PROFILE_REACT=1), local, serial, 3 runs each, medians. Baseline ismainat 349657d; "after" is the top of this stack (#4429), because the layers were measured together. React time is the summedactualDurationof the profiled subtrees.chat-pane.transcriptchat-pane.inputchat-pane.transcriptchat-pane.inputchat-pane.transcriptCommit counts are unchanged; the gain is less work per commit. Typing in a large chat and the review hunk specs show no change beyond noise. ChatInput alone, measured before #4418 landed: New Workspace typing went from 165
CreationControlsrenders per run to 2.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$4.05