# refactor\(web\): composer attachment tiles live in their own component · PR \#22689 · pingdotgg/t3code

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

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

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: refactor/chat-composer-attachments

Target branch: main

Head commit: 1122a852bc1639e9a64142e58de97f38125c1336

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

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

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

## Description

---

ChatComposer.tsx is about 7,400 lines, which makes it hard to read and gives the React Compiler one huge component to cache. This is the first layer of a stack that splits it up.

The image, SnapShot and video tiles, the non-media file list, and the draft file preview dialog move into `ComposerAttachmentStrip.tsx`. The strip reads upload progress from `useAttachmentUploadStore` itself.

ChatComposer builds the media row and the file list as element consts outside the form's JSX. That JSX is rebuilt on every keystroke, so building the elements there re-rendered every tile while you typed. ChatComposer passes its remove handlers straight through. The preview dialog gets the preview-id state setter and closes itself. This adds no `useStableCallback` uses; ChatComposer keeps the 34 it has on main. Behavior is unchanged.

| | main | this PR |
|---|---|---|
| ChatComposer.tsx lines | 7,403 | 7,016 |
| ComposerAttachmentStrip.tsx lines | n/a | 535 |
| Renders, 20 typed chars | 678 | 678 |
| Renders, 20 typed chars with one image attached | 702 | **622** (the tiles no longer re-render) |

Renders measured with the shared fiber counter (`rc-init.js`) on a seeded dev server: typing 20 characters into an existing thread's composer, median of 3 runs.

Tests run: `vp lint` and `vp fmt --check` on the changed files, `tsc-rs --noEmit` for apps/web, and `reactCompilerCoverage.test.ts` for the changed files (0 bailouts).

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

