refactor(web): decompose RunChat.tsx + RunChat.test.tsx below line caps #194

Closed
opened 2026-07-22 00:02:10 +02:00 by dominik.polakovics · 0 comments

Decompose web/src/routes/RunChat.tsx (2956 lines, 40 touches/3mo) and web/src/routes/RunChat.test.tsx (4325 lines, 41 touches/3mo) — the repo's two worst churn×size files. This is the decomposition-design issue required by the line-cap policy (#193): one named responsibility per new file, agreed here before split code is written.

Behavior-preserving refactor. No visual or behavioral change, no test-semantics change — existing queries/test-ids keep working. The vitest suite must pass unmodified in spirit: tests move between files, but assertions don't change.

Component split (proposed layout: web/src/routes/runchat/, RunChat.tsx stays the route entry)

Seams follow the file's own top-level structure:

New file Responsibility (what moves)
routes/RunChat.tsx (keeps) Route component, data wiring (SSE subscription, message paging/debounce, desktop/mobile layout switch), composition of the parts below. Target well under 1000.
runchat/ChatHeader.tsx ChatHeader, ChatMenu — header row + overflow menu
runchat/Messages.tsx MessageView, ToolChip, ToolGroupView — transcript rendering
runchat/Markdown.tsx The banner-delimited markdown renderer (issue #13): Markdown, BlockView, ListView, TableView, InlineNodes, InlineNode, CodeBlock, CopyButton
runchat/Composer.tsx Composer, createInterrupt, submitOnEnter, InterruptButton — input, send, interrupt
runchat/Dialogs.tsx DialogCard, AnsweredDialog, AnswerLine, DialogPanel, OptionContent, OptionToggle, MultiQuestionForm — question/dialog surface

Adjust boundaries if reality disagrees (shared module-level state may force a runchat/shared.ts for constants/types like MESSAGE_LIMIT — fine), but each file must keep a nameable responsibility: no RunChat2.tsx, no grab-bag helpers.ts with a single consumer. Heavy import fan-out between the new siblings is the sign the seam is wrong — stop and rethink rather than shear.

Test split

Mirror the component split: RunChat.test.tsx splits into per-area test files (header/menu, transcript+markdown, composer/interrupt, dialogs, route-level wiring) plus one shared harness module for common setup/fixtures (render helpers, SSE fakes). Every resulting file under 2000 lines; the shared harness is source-class (cap 1000).

Sequencing & ratchet

  • Land after #193. In the same PR, drop the two baseline.json entries (all resulting files should be under cap — the ratchet test enforces this).
  • Subagent-friendly: one agent per extracted area, plus a verifier pass running the full web suite (vitest, eslint, prettier, vite build — same as the native CI gate).

Acceptance

  • All resulting files under their caps; baseline entries for both files removed.
  • Full web suite green with unchanged assertions; no user-visible change on /runs/:id (desktop + mobile).
Decompose `web/src/routes/RunChat.tsx` (2956 lines, 40 touches/3mo) and `web/src/routes/RunChat.test.tsx` (4325 lines, 41 touches/3mo) — the repo's two worst churn×size files. This is the decomposition-design issue required by the line-cap policy (#193): one named responsibility per new file, agreed here before split code is written. **Behavior-preserving refactor.** No visual or behavioral change, no test-semantics change — existing queries/test-ids keep working. The vitest suite must pass unmodified in spirit: tests move between files, but assertions don't change. ## Component split (proposed layout: `web/src/routes/runchat/`, RunChat.tsx stays the route entry) Seams follow the file's own top-level structure: | New file | Responsibility (what moves) | |---|---| | `routes/RunChat.tsx` (keeps) | Route component, data wiring (SSE subscription, message paging/debounce, desktop/mobile layout switch), composition of the parts below. Target well under 1000. | | `runchat/ChatHeader.tsx` | `ChatHeader`, `ChatMenu` — header row + overflow menu | | `runchat/Messages.tsx` | `MessageView`, `ToolChip`, `ToolGroupView` — transcript rendering | | `runchat/Markdown.tsx` | The banner-delimited markdown renderer (issue #13): `Markdown`, `BlockView`, `ListView`, `TableView`, `InlineNodes`, `InlineNode`, `CodeBlock`, `CopyButton` | | `runchat/Composer.tsx` | `Composer`, `createInterrupt`, `submitOnEnter`, `InterruptButton` — input, send, interrupt | | `runchat/Dialogs.tsx` | `DialogCard`, `AnsweredDialog`, `AnswerLine`, `DialogPanel`, `OptionContent`, `OptionToggle`, `MultiQuestionForm` — question/dialog surface | Adjust boundaries if reality disagrees (shared module-level state may force a `runchat/shared.ts` for constants/types like `MESSAGE_LIMIT` — fine), but each file must keep a nameable responsibility: no `RunChat2.tsx`, no grab-bag `helpers.ts` with a single consumer. Heavy import fan-out between the new siblings is the sign the seam is wrong — stop and rethink rather than shear. ## Test split Mirror the component split: `RunChat.test.tsx` splits into per-area test files (header/menu, transcript+markdown, composer/interrupt, dialogs, route-level wiring) plus one shared harness module for common setup/fixtures (render helpers, SSE fakes). Every resulting file under 2000 lines; the shared harness is source-class (cap 1000). ## Sequencing & ratchet - Land **after** #193. In the same PR, drop the two `baseline.json` entries (all resulting files should be under cap — the ratchet test enforces this). - Subagent-friendly: one agent per extracted area, plus a verifier pass running the full web suite (`vitest`, `eslint`, `prettier`, `vite build` — same as the native CI gate). ## Acceptance - All resulting files under their caps; baseline entries for both files removed. - Full web suite green with unchanged assertions; no user-visible change on /runs/:id (desktop + mobile).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#194
No description provided.