refactor(web): composer editor stops mirroring props into refs with effects#25079

merged1
main
effect/split-web-chat-composer
opened4d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 4d ago

Problem

The Tiptap prompt editor copied six callback props into refs, each with its own useEffect. It also had an effect that copied the editor instance into a holder ref, and a layout effect that mirrored the controlled value. The composer's prompt-wrap measurement re-ran a layout effect keyed on an element held in state. The command-key hook copied prompt history into a ref with a layout effect on every render.

Change

  • ComposerPromptEditorTiptap: the editor config is built once, so its handlers now read the current callbacks through a single useStableCallback. ProseMirror handlers get the editor from view.dom.editor instead of a holder ref. The controlled-value layout effect records the committed value itself. Copy/cut is a React handler, so it reads buildContextClipboardFragment from props directly.
  • useComposerMultilinePrompt returns a ref callback. ChatComposer attaches it to the composer body, and it observes resizes and mutations while that element is mounted.
  • useComposerCommandKey called no hooks once its mirror ref was gone, so it is now the plain composerCommandKeyHandler in composerCommandKey.ts. ChatComposer builds it inside the stable key handler, so prompt history is read on the keypress.

What this improves

  • Removes 10 sync effects from the composer's typing path: 8 in the editor, 1 in the wrap measurement, 1 in the command-key hook.
  • The wrap observer now follows the element's lifetime. It no longer goes through a setComposerMenuAnchor state commit followed by a layout effect.
  • No bug fixes are claimed, and no user-visible behavior changes. Key handlers and editor callbacks still see the latest committed props.

Effects

  • ComposerPromptEditorTiptap.tsx: 13 -> 5. The remaining effects are setEditable, the attribute push, controlled-value sync, skill reconcile and one more editor sync, all real editor/DOM synchronization.
  • useComposerMultilinePrompt.ts: 1 -> 0
  • useComposerCommandKey.ts -> composerCommandKey.ts: 1 -> 0
  • ChatComposer.tsx: 26 -> 26. Only the wiring changed here.

Verification

  • Rebased on current main (aa11f7002e).
  • vp run check:changed: format, lint, knip, typecheck (34 packages) and tests (22 files, 525 tests) pass.
  • ComposerPromptEditorTiptap.input.browser.test.tsx, ComposerPromptEditorTiptap.citationDraft.browser.test.tsx and ComposerStashMenu.browser.test.tsx also pass when run one at a time (16/16).
  • I checked the output of the React Compiler build (oxc-transform-react, the one react({ compiler: true }) uses). It compiles ChatComposer, ComposerPromptEditorTiptap and useComposerMultilinePrompt without errors. It memoizes the composer body's inline ref callback on observeWrappedPrompt, so the observers are not detached and reattached on each render.

Model: Claude Opus 5.5, Claude Code harness.

🤖 Generated with Claude Code

Checks

22 successful · 17 skipped
All checks passed
aa11f70
Synced from GitHub

Approvability

Verdict: Approved at f914c45

Macroscope's review found this PR approvable — This is a contained composer refactor that removes effect-based prop/ref mirroring while preserving existing editor, keyboard, paste, and layout responsibilities. Its runtime impact is limited to callback freshness and composer measurement lifecycle, with cleanup and targeted tests included.

No code changes detected at aa11f70. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between b76a110aa4cf011345ec9d63d37c2dcdf4b751ad and f914c457b530908607be095f2464b3c48bd06525.

External image Files selected for processing: 4
  • apps/web/src/components/ComposerPromptEditorTiptap.tsx
  • apps/web/src/components/chat/ChatComposer.tsx
  • apps/web/src/components/chat/composerCommandKey.ts
  • apps/web/src/components/chat/useComposerMultilinePrompt.ts

Limit details: You’ve used all 10 included reviews currently available.

Walkthrough

The composer now uses a stable callback reader and DOM-based editor access in the Tiptap editor. Command-key handling uses a direct handler function, and multiline-prompt observation uses a callback ref with cleanup.

Changes: Composer updates

Layer / File(s)Summary
Tiptap editor callbacks
apps/web/src/components/ComposerPromptEditorTiptap.tsx
Callback access now uses a stable callback reader. Key and paste handlers get the editor from its DOM element. The controlled-value effect updates its ref before checking for an editor.
Command-key handler
apps/web/src/components/chat/composerCommandKey.ts, apps/web/src/components/chat/ChatComposer.tsx
composerCommandKeyHandler returns the key handler directly. Prompt-history navigation uses the messages captured by the creating render. ChatComposer passes the key event to the handler.
Multiline prompt observation
apps/web/src/components/chat/useComposerMultilinePrompt.ts, apps/web/src/components/chat/ChatComposer.tsx
The hook returns multiline state and an observer callback. The composer body ref starts observation when attached and stops it during cleanup.

Priority: External image Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Refactor

Suggested reviewers: t3dotgg

Merge Risk: External image Minimal · up to f914c

No actionable behavior regression has been established in the composer changes; the PR is mergeable after normal checks.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check nameStatusExplanationResolution
Description checkWarningThe description clearly explains the problem, implementation, claimed effects, and verification results. It omits the required "Scope and approval" section and does not provide a linked issue, maintai…Add a "## Scope and approval" section. Link the triaged issue or approval discussion and include the explicit maintainer approval comment. If this focused change qualifies for an exemption, explain why it is an obvious small fix or establis…
Passed Passed checks: 3 passed
Check nameStatusExplanation
Title checkPassedThe title names the main change: the composer's Tiptap editor stops mirroring props into refs through effects. It follows the conventional commit format, and the raw summary and objectives confirm the…
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: the composer's Tiptap editor stops mirroring props into refs through effects. It follows the conventional commit format, and the raw summary and objectives confirm the callback-ref and effect removal across ComposerPromptEditorTiptap, ChatComposer, and the command-key hook. Because the title is specific and accurate, it passes.

Full details: Description check

Explanation

The description clearly explains the problem, implementation, claimed effects, and verification results. It omits the required "Scope and approval" section and does not provide a linked issue, maintainer approval, or a valid explanation for an approval exemption.

Resolution

Add a "## Scope and approval" section. Link the triaged issue or approval discussion and include the explicit maintainer approval comment. If this focused change qualifies for an exemption, explain why it is an obvious small fix or established-capability configuration.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
đź§Ş Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Added 1 commit

  • refactor(web): composer editor stops mirroring props into refs with effectsaa11f70