diff --git a/src/renderer/src/components/activity/activity-thread-hover-card.test.tsx b/src/renderer/src/components/activity/activity-thread-hover-card.test.tsx index ef1f109d50a..d64f60c11a1 100644 --- a/src/renderer/src/components/activity/activity-thread-hover-card.test.tsx +++ b/src/renderer/src/components/activity/activity-thread-hover-card.test.tsx @@ -262,4 +262,35 @@ describe('ActivityThreadHoverCard and ActivityThreadRow', () => { expect(onJump).toHaveBeenCalledWith(thread) }) + + it('flattens code blocks and tables in the reply preview so the line clamp cuts them', async () => { + const thread = createTestThread({ + currentAgentState: null, + responsePreview: + 'KK1-START\n\n```\n| metric | base median |\n| --- | --- |\n| p2 | 12 |\n```\n\n| a | b |\n| - | - |\n| 1 | 2 |' + }) + + await act(async () => { + root.render() + }) + + const pre = container.querySelector('pre') + const table = container.querySelector('table') + expect(pre).not.toBeNull() + expect(table).not.toBeNull() + const preview = pre?.closest('.line-clamp-3') + expect(preview).not.toBeNull() + expect(preview?.contains(table ?? null)).toBe(true) + // Why: the compact renderers draw both as scroll boxes; the row must undo that. + const previewClasses = preview?.className.split(/\s+/) ?? [] + expect(previewClasses).toEqual( + expect.arrayContaining([ + '[&_pre]:!max-h-none', + '[&_pre]:!overflow-visible', + '[&_pre]:!whitespace-normal', + '[&_div]:!overflow-visible', + '[&_:is(table,thead,tbody,tr,th,td)]:!inline' + ]) + ) + }) }) diff --git a/src/renderer/src/components/activity/activity-thread-row.tsx b/src/renderer/src/components/activity/activity-thread-row.tsx index fef63bda46e..207ba0d6021 100644 --- a/src/renderer/src/components/activity/activity-thread-row.tsx +++ b/src/renderer/src/components/activity/activity-thread-row.tsx @@ -133,7 +133,10 @@ export const ActivityThreadRow = React.memo(function ActivityThreadRow({ className={cn( 'min-w-0 break-words text-[13px] leading-5 text-foreground/80', compactMode ? 'line-clamp-2' : 'line-clamp-3', - '[&_*]:!m-0 [&_*]:!p-0 [&_br]:hidden [&_ol]:list-none [&_ul]:list-none' + '[&_*]:!m-0 [&_*]:!p-0 [&_br]:hidden [&_ol]:list-none [&_ul]:list-none', + // Why: code blocks and tables otherwise render as scroll boxes the line clamp cannot cut. + '[&_pre]:!max-h-none [&_pre]:!overflow-visible [&_pre]:!whitespace-normal [&_pre]:!rounded-none [&_pre]:!bg-transparent [&_pre]:![font:inherit] [&_pre_code]:![font:inherit]', + '[&_div]:!overflow-visible [&_:is(table,thead,tbody,tr,th,td)]:!inline [&_:is(table,thead,tbody,tr,th,td)]:!align-baseline [&_:is(th,td)]:!border-0 [&_:is(th,td)]:!pr-2 [&_table]:![font:inherit]' )} /> ) : (