# refactor\(web\): ChatView's send preparation is pure functions in ChatView.logic · PR \#22734 · pingdotgg/t3code

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

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

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: refactor/chatview-send-plan

Target branch: refactor/chatview-draft-environment-routing

Head commit: 9fdc654554c96a2d9bbb78957cb34a7dd6d129d4

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

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

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

## Description

---

`onSend` is about 1,200 lines. Its preparation was written inline, and some of it twice:
- copying what the composer held at send time;
- planning the dispatch: worktree base branch, compact-before-send, and queueing;
- binding attachment ids for context records;
- the inline-context fallback for older servers;
- the optimistic row's attachments and the title fallbacks;
- resolving one thread per model for a multiple-model send.

These are now pure functions:
- In `ChatView.logic.ts`: `snapshotComposerSend`, `planComposerTurn`, `boundAttachmentIds`, `withMessageContext`, `optimisticMessageAttachments` and `sendTitleFallbackLabels`. All have tests except the last two.
- In `chat/chatViewSend.ts`: `resolveMultipleModelTargets`, which lives there because `composerProviderState` pulls in picker components. ChatView still validates each target's text with the composer in selection order and stops at the first problem, as before.

The two places that put a failed send back into the draft now call one `restoreComposerDraft` helper.

The dispatch itself stays in ChatView. That covers uploads, the dock transition, the multiple-model start loop, the turn start, and failure recovery. Moving any of it would need a 30-field input object, which wouldn't make it easier to read. This layer adds no `useStableCallback` calls or layout-effect ref assignments, and doesn't change render boundaries.

| | previous layer | this layer |
|---|---|---|
| ChatView.tsx lines | 7,372 | 7,258 |

Tests: `vp test run src/reactCompilerCoverage.test.ts` for ChatView and every new file (0 bailouts), `vp test run src/components/ChatView.logic.test.ts` (5 new cases), `tsc-rs --noEmit` for apps/web, and oxlint on the changed files.

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

