Skip to content

🤖 perf: let React Compiler compile ChatInput - #4426

Merged
ThomasK33 merged 1 commit into
mainfrom
perf-compiler-chatinput-compile
Sep 24, 2026
Merged

ThomasK33 merged 1 commit into
mainfrom
perf-compiler-chatinput-compile

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

ChatInputInner now compiles under React Compiler, and the guard's KNOWN_SKIPPED baseline drops it. The audit shows no CompileError for it.

Background

After #4425, three kinds of blockers were left: a react-hooks/exhaustive-deps suppression, ref writes during render, and manual useCallback/useMemo calls whose dependencies the compiler could not preserve (the compiler skips the whole component when it can't keep an existing manual memo).

Implementation

  • Edit-mode effect (lint suppression removed). It used to depend only on editingMessage?.id and 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.
  • Render-time ref writes → layout effects. editingMessageIdRef and handleSendRef are now synced in useLayoutEffect. 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.
  • Manual memos removed where the compiler rejected them: handleToastDismiss, idleCompactionProps, setPreferredModel, cycleToNextModel. The compiler memoizes these now. This follows AGENTS.md ("don't hand-memoize") and adds no new manual memoization.
  • The global keydown listener reads cycleToNextModel through a latest-value ref instead of re-subscribing when it changes (same pattern as composerApiRef in 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

  • Guard: 10/18 hot components compile (8 known skipped).
  • tests/ui/chat, tests/ui/compaction, ChatInput unit tests, useAIViewKeybinds tests pass.
  • Perf (Electron perf e2e, local, serial, 3 runs each): see Measurements.

Measurements

Electron perf e2e (tests/e2e/scenarios/perf.*.spec.ts, XUM_PROFILE_REACT=1), local, serial, 3 runs each, medians. Baseline is main at 349657d; "after" is the top of this stack (#4429), because the layers were measured together. React time is the summed actualDuration of the profiled subtrees.

Scenario Profiled subtree main stack top
Open chat with a 1000-file review chat-pane.transcript 388 ms 292 ms
Open chat with a 1000-file review chat-pane.input 76 ms 61 ms
Open chat with a 1000-file review all profiled 530 ms 424 ms
Open workspace, large history chat-pane.transcript 331 ms 295 ms
Open workspace, large history chat-pane.input 52 ms 46 ms
Open workspace, medium history chat-pane.transcript 206 ms 191 ms

Commit 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 CreationControls renders per run to 2.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $4.05

@ThomasK33
ThomasK33 added this pull request to stack #4430 September 24, 2026 11:31
@ThomasK33 ThomasK33 changed the title perf: let React Compiler compile ChatInput 🤖 perf: let React Compiler compile ChatInput Sep 24, 2026
@ThomasK33
ThomasK33 marked this pull request as ready for review September 24, 2026 11:55
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T12:45:23.030806Z 12b6a3f New commits
🔒 Security Review ✅ Completed 2026-09-24T12:46:46.166040Z 12b6a3f New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Base automatically changed from perf-compiler-chatinput to main September 24, 2026 12:42
@ThomasK33
ThomasK33 force-pushed the perf-compiler-chatinput-compile branch from 0d02470 to 12b6a3f Compare September 24, 2026 12:42
@ThomasK33
ThomasK33 added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 5154a18 Sep 24, 2026
57 of 60 checks passed
@ThomasK33
ThomasK33 deleted the perf-compiler-chatinput-compile branch September 24, 2026 14:45
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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant