From f3e867b23813d64573e43e56917f8848350413d6 Mon Sep 17 00:00:00 2001 From: "pouriade.com" Date: Tue, 27 Oct 2026 22:32:45 -0700 Subject: [PATCH] Gather file runs before drawing a message's parts withFileRuns groups side-by-side files into one row up front, so the render no longer looks back or casts, and PartOf draws a part through its view in the three places that did it by hand. --- .../conversation/components/PartsView.tsx | 80 +++++++++++-------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/apps/web/src/features/conversation/components/PartsView.tsx b/apps/web/src/features/conversation/components/PartsView.tsx index 7d744f4..875c9a8 100644 --- a/apps/web/src/features/conversation/components/PartsView.tsx +++ b/apps/web/src/features/conversation/components/PartsView.tsx @@ -19,13 +19,19 @@ interface Props { type ContentBlock = Extract -const isFile = (block: Block | undefined) => - block?.kind === 'content' && kind(block.part) === 'filePart' +/** A block, or a run of files side by side, which share a row. */ +type Row = Block | { kind: 'files'; key: string; files: ContentBlock[] } -/** The run of files that starts at `start`. */ -function filesFrom(blocks: Block[], start: number): ContentBlock[] { - const end = blocks.findIndex((block, i) => i > start && !isFile(block)) - return blocks.slice(start, end === -1 ? undefined : end) as ContentBlock[] +/** The blocks with each run of files gathered into one row. */ +function withFileRuns(blocks: Block[]): Row[] { + const rows: Row[] = [] + for (const block of blocks) { + const last = rows.at(-1) + if (block.kind !== 'content' || kind(block.part) !== 'filePart') rows.push(block) + else if (last?.kind === 'files') last.files.push(block) + else rows.push({ kind: 'files', key: block.key, files: [block] }) + } + return rows } /** @@ -36,58 +42,49 @@ function filesFrom(blocks: Block[], start: number): ContentBlock[] { */ export function PartsView(props: Props) { const { blocks, sources } = groupParts(props.parts) + const rows = withFileRuns(blocks) return ( <> - {blocks.map((block, i) => { - if (block.kind === 'steps') { + {rows.map((row, i) => { + const last = i === rows.length - 1 + if (row.kind === 'steps') { // A finished reply's steps open from its footer, so only work still going shows. Steps // that are done stay mounted while the reply streams, hidden, so their clocks record how // long they ran. if (!props.live) return null - const current = i === blocks.length - 1 return ( -