# refactor\(web\): ChatView's send paths live in useComposerSend · PR \#23062 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23062)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: refactor/split2-chatview-send

Target branch: main

Head commit: d17ebeae9f3fcceaefe276bbfda6ac610dd7b312

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/23062)

[Commits](https://git.cafe/pingdotgg/t3code/pull/23062/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/23062/diffs?format=markdown)

## Description

---

ChatView carried every send path inline: a ~1,200-line `onSend` plus resume, plan follow-up, and implement-in-a-new-thread, with the commands and draft-store setters only they use.

They move verbatim into `chat/useComposerSend.ts`. The hook selects its own orchestration commands and draft-store setters, and owns the state only sends touch (resuming keys, send generation, the unavailable-environment toast slot, and the approval refs a send reads after its uploads). ChatView passes what it already derives. No send logic changed.

| | Before | After |
|---|---|---|
| `ChatView.tsx` lines | 7,258 | 5,700 |

Hands-on in the dev server at the top of this stack, which carries this code unchanged (Claude, a sandbox git project): new thread from a draft, plain follow-ups in that thread, a send with an attached image (the reply named its color), a message queued behind a running turn and then promoted to steer, `/plan` as a standalone slash command, a plan request and a plan follow-up that revised it, "Implement in a new thread", a multiple-model start (two worktree threads started, the draft composer came back), and resuming an interrupted run. Each worked.

Renders, median of 3, counted with the commit-only fiber counter on a dev server seeded from real data
(stack top vs the stack's base; "ChatView" is ChatView's own render count):

| Interaction | Before: commits / renders / ChatView | After: commits / renders / ChatView |
|---|---|---|
| Type 20 chars in the composer | 22 / 351 / 1 | 22 / 351 / 1 |
| Streaming reply (12 lines) | 129 / 3412 / 18 | 134 / 3406 / 18 |
| Switch threads | 26 / 1520 / 5 | 26 / 1480 / 5 |
| Open and close the right panel | 14 / 603 / 4 | 14 / 569 / 4 |

No render boundary moved in a way that changes these: the hooks run inside ChatView, and the new leaf components are small. The point of the stack is readability and per-file compiler caching, not a render win.

Tests: `tsc-rs --noEmit` for apps/web, oxlint on the changed files, `vp test run src/components/ChatView.logic.test.ts`, and the React Compiler coverage test for every changed file (0 bailouts). ChatView stays in the coverage test's slow set.

Part of the ChatView split (7,258 → 3,975 lines across the stack).

Opus 5.5, Claude Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

