# refactor\(web\): ChatView's persistent terminal drawers live in their own component · PR \#22681 · pingdotgg/t3code

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

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

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: refactor/chatview-terminal-drawers

Target branch: refactor/chatview-helpers

Head commit: ff41fc5c94b5eecb6d9b6d17dbaebe27b3dc8906

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

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

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

## Description

---

ChatView subscribed to every thread's refs, every draft thread, and every thread's terminal-open flag, only so it could decide which terminal drawers stay mounted. Because of that, a change on any other thread re-rendered the whole chat view.

`chat/ChatTerminalDrawers.tsx` now holds those subscriptions, the mounted-drawer state, and the drawer presence. ChatView passes in only the active thread's values. The drawer's `onAddTerminalContext` now gets the stable callback; before, it got a new function on every render.

| | previous layer | this layer |
|---|---|---|
| ChatView.tsx lines | 8,585 | 8,531 |

Renders were counted with the `rc-init.js` fiber counter on a dev server seeded from real data (median of 3 runs; base [6a6c84f](https://github.com/pingdotgg/t3code/commit/6a6c84f990) vs the stack top before its rebase onto current main):

| Interaction | main: commits / renders (ChatView) | stack top: commits / renders (ChatView) |
|---|---|---|
| Type 20 chars in the composer | 22 / 678 (1) | 22 / 678 (1) |
| Switch threads | 14 / 779 (5) | 14 / 777 (5) |
| Open + close the terminal drawer | 15 / 494 (3) | 15 / 497 (3) |
| Open + close the right panel | 21 / 723 (4) | 21 / 726 (4) |
| Drag the right panel's resize handle | 13 / 139 (1) | 1 / 1 (0) |

The +3 renders are the new `ChatTerminalDrawers` and `ChatRightPanel` wrappers. The drag row is the [right panel layer](https://github.com/pingdotgg/t3code/pull/22700): the drag's final width commit used to re-render ChatView and the whole chat column, and now re-renders only `ChatRightPanel`.

**Background-thread measurement.** Tab A keeps thread A open while a second browser (B) on the same dev server acts on another thread. Renders were counted in A, comparing the stack base (current `main`, ca049dc) with this stack. Only ChatView's own renders are listed. For the new-thread rows, each run waited until B's message had actually been typed and sent.

| What B does | base: ChatView renders per run | stack: ChatView renders per run |
|---|---|---|
| Streams a real Claude reply in an existing thread (30 s) | 1, 0, 1 | 0, 1, 3 |
| Sends the first message of a new thread (~20 s) | 0, 1, 0 | 1, 0, 3 |
| Nothing at all (62 s, stack only) | — | 1, 3, 1 |

Every one of those ChatView renders is the clock. Once a minute a context update re-renders ChatView along with six `RelativeTimeLabel`s, for ~80 renders in one commit, and a second clock-driven state update (`state#61`) sometimes follows it. The idle row shows the same renders with nothing happening. In every run, a background thread's arrival or streaming caused no ChatView render on either side. `ChatTerminalDrawers` never rendered.

An earlier version of this table showed the base at 0 renders and the stack at 85 for the new-thread case. Both numbers were wrong. B's command palette had swallowed the typed prompt, so no thread was created in either run. The 85 renders were the minute tick landing inside the stack's window.

**Conclusion: there is no regression and no measured win.** The subscriptions this layer moves don't fire for any background event I could reproduce. The layer still keeps every-thread state out of ChatView and removes ~55 lines, so judge it as a readability change only.

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

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

