Skip to content

🤖 refactor: move ChatInput control flow React Compiler can't lower into plain helpers - #4425

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

ThomasK33 merged 1 commit into
mainfrom
perf-compiler-chatinput

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

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 (#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. #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 returns 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 lets (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

@ThomasK33
ThomasK33 added this pull request to stack #4430 September 24, 2026 11:31
@ThomasK33 ThomasK33 changed the title perf compiler chatinput 🤖 refactor: move ChatInput control flow React Compiler can't lower into plain helpers 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-24T11:58:40.820566Z 650a55a Draft marked ready
🔒 Security Review ✅ Completed 2026-09-24T11:59:08.554329Z 650a55a Draft marked ready
ℹ️ 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.

@ThomasK33
ThomasK33 added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit edb6c72 Sep 24, 2026
30 of 31 checks passed
@ThomasK33
ThomasK33 deleted the perf-compiler-chatinput branch September 24, 2026 12:42
yermakoffivan pushed a commit to yermakoffivan/mux that referenced this pull request Sep 24, 2026
## 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 coder#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 (coder#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 coder#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`_

<!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high
costs=4.05 -->
yermakoffivan pushed a commit to yermakoffivan/mux that referenced this pull request Sep 25, 2026
## Summary

`ImmersiveReviewView` now compiles under React Compiler and leaves the
guard's `KNOWN_SKIPPED` baseline (17/18 hot components compile).
Iterating hunks in immersive review spends about 18% less script time.

## Background

Follow-up to the React Compiler coverage stack (coder#4422, coder#4425–coder#4429) and
coder#4439. The compiler skipped the whole component because of `??=`, a
`try/catch/finally` in the copy-file handler, three render-time ref
writes, and one ref read during render.

## Implementation

- `oldLineMap ??= …` becomes `oldLineMap = oldLineMap ?? …`.
- The copy-file handler's `try/catch/finally` uses `runWithCatchFinally`
with the same body, error handler, and cleanup. Early `return`s keep
their meaning because the `try` was the handler's last statement.
- `activeFilePathRef`, `activeFileContentVersionRef`, and
`handleCopyFileRef` are synced in `useLayoutEffect` instead of during
render. Layout effects run in the commit's task, before passive effects,
which is the ordering the existing comment requires for `isStale()`.
- `selectedLineSummary` (render path) read `hunkJumpLineRangeRef`
through `getCurrentLineSelection`. A state mirror, `hunkJumpLineRange`,
now feeds render. Every ref write also sets the state, next to the
cursor/selection updates it batches with, and the hot-path callbacks
keep reading the ref, so their identities stay stable. One visible
difference: after a comment submit or composer cancel that clears the
hunk-jump range while no line selection is active, the "Lines …" label
now updates right away. Before, it kept showing the cleared range until
the next render.

## Validation

- Guard: `17/18 hot components compile (1 known skipped)`; `make
static-check` green.
- `src/browser/features/RightSidebar/CodeReview` unit tests (79) and
`tests/ui/review` (14) pass.

## Measurements

Electron perf e2e, local, serial, 5 runs each, `main` at 334a5a6 vs
this branch. ScriptDuration per run:

| Scenario | main (sorted runs) | this PR (sorted runs) | Median |
| --- | --- | --- | --- |
| Immersive hunk iteration, 1500 lines / 150 hunks | 343, 351, 355, 356,
440 ms | 270, 273, 290, 294, 299 ms | 355 → 290 ms |
| Immersive mark-read, 1000 files | 14, 16, 18, 19, 19 ms | 14, 14, 20,
25, 26 ms | 18 → 20 ms (noise) |

Hunk-iteration wall time went from 448 to 376 ms (median). The regular
review reopen scenario, which doesn't render this component, moved about
10% between runs, which is this host's noise level. The hunk-iteration
ranges don't overlap.

---

_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