refactor(web): composer editor stops mirroring props into refs with effects#25079
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 singleuseStableCallback. ProseMirror handlers get the editor fromview.dom.editorinstead of a holder ref. The controlled-value layout effect records the committed value itself. Copy/cut is a React handler, so it readsbuildContextClipboardFragmentfrom props directly.useComposerMultilinePromptreturns a ref callback.ChatComposerattaches it to the composer body, and it observes resizes and mutations while that element is mounted.useComposerCommandKeycalled no hooks once its mirror ref was gone, so it is now the plaincomposerCommandKeyHandlerincomposerCommandKey.ts.ChatComposerbuilds 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
setComposerMenuAnchorstate 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 -> 0useComposerCommandKey.ts->composerCommandKey.ts: 1 -> 0ChatComposer.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.tsxandComposerStashMenu.browser.test.tsxalso pass when run one at a time (16/16).- I checked the output of the React Compiler build (
oxc-transform-react, the onereact({ compiler: true })uses). It compilesChatComposer,ComposerPromptEditorTiptapanduseComposerMultilinePromptwithout errors. It memoizes the composer body's inline ref callback onobserveWrappedPrompt, so the observers are not detached and reattached on each render.
Model: Claude Opus 5.5, Claude Code harness.
🤖 Generated with Claude Code
Checks
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.
No actionable comments were generated in the recent review.
External image Recent review info
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: effect/split-web-chat-composer
- File: .coderabbit.config.ts
- Commit: f914c45
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
81dbedd0-c017-4a42-9bde-67ffa5f72ed2
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.tsxapps/web/src/components/chat/ChatComposer.tsxapps/web/src/components/chat/composerCommandKey.tsapps/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.tsxCallback 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.tsxcomposerCommandKeyHandlerreturns the key handler directly. Prompt-history navigation uses the messages captured by the creating render.ChatComposerpasses the key event to the handler.Multiline prompt observation
apps/web/src/components/chat/useComposerMultilinePrompt.ts,apps/web/src/components/chat/ChatComposer.tsxThe 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 name Status Explanation Resolution Description check Warning 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, 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 name Status Explanation Title check Passed 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… Linked Issues check Passed Check skipped because no linked issues were found for this pull request. Out of Scope Changes check Passed Check 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