diff --git a/frontend/e2e/chat.spec.ts b/frontend/e2e/chat.spec.ts index 38ccffd..d9976b8 100644 --- a/frontend/e2e/chat.spec.ts +++ b/frontend/e2e/chat.spec.ts @@ -187,6 +187,7 @@ test("manages settings, permanent memory, and model pull progress", async ({ pag }); test("renders a structured workbench transcript with markdown and code blocks", async ({ page }) => { + await page.setViewportSize({ width: 764, height: 787 }); const content = [ "# Result", "", @@ -195,9 +196,10 @@ test("renders a structured workbench transcript with markdown and code blocks", "- Input was validated", "- Calculation was run locally", "", - "| Input | Output |", - "| --- | ---: |", - "| 6 × 7 | 42 |", + "| Aspect | What It Is | Key Points |", + "| --- | --- | --- |", + "| Shape | Almost spherical, but a bit flattened at the poles. | About 384000 km away; that is roughly one quarter the distance from Earth to the Sun. |", + "| Why it matters | It provides a testing ground for lunar missions. | Its geology helps us study planetary evolution. |", "", "```python", "value = 6 * 7", @@ -243,7 +245,20 @@ test("renders a structured workbench transcript with markdown and code blocks", await expect(page.getByRole("article", { name: "Cortex message" }).getByText("Cortex")).toBeVisible(); await expect(page.getByRole("article", { name: "Your message" }).getByText("You")).toBeVisible(); await expect(page.getByRole("heading", { name: "Result", level: 1 })).toBeVisible(); - await expect(page.getByRole("cell", { name: "42" })).toBeVisible(); + await expect(page.getByRole("cell", { name: "Aspect" })).toBeVisible(); + await expect(page.getByRole("cell", { name: "Shape" })).toBeVisible(); + const tableLayout = await page.locator(".markdown-table-wrap table").evaluate((node) => { + const firstRow = node.querySelector("tr"); + const firstCell = firstRow?.firstElementChild; + return { + firstColumnWidth: firstCell?.getBoundingClientRect().width ?? 0, + tableWidth: node.getBoundingClientRect().width, + wrapperWidth: node.parentElement?.getBoundingClientRect().width ?? 0, + wrapperScrollWidth: node.parentElement?.scrollWidth ?? 0, + }; + }); + expect(tableLayout.firstColumnWidth).toBeGreaterThan(120); + expect(tableLayout.wrapperScrollWidth).toBeGreaterThanOrEqual(tableLayout.wrapperWidth); await expect(page.getByRole("button", { name: "Copy python code" })).toBeVisible(); await expect(page.getByText("Reasoning")).toBeVisible(); await expect(page.getByText("Sources")).toBeVisible(); diff --git a/frontend/src/components/SafeMarkdown.tsx b/frontend/src/components/SafeMarkdown.tsx index 567a303..f68cb0f 100644 --- a/frontend/src/components/SafeMarkdown.tsx +++ b/frontend/src/components/SafeMarkdown.tsx @@ -51,6 +51,14 @@ function CodeCopyButton({ value, language }: { value: string; language: string } return ; } +function Table({ children, ...props }: ComponentProps<"table">) { + return ( +
+ {children}
+
+ ); +} + export function SafeMarkdown({ content }: { content: string }) { return ( null, + table: Table, }} > {content} diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index 1dbe891..8b57776 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -304,8 +304,10 @@ a { color: inherit; text-decoration: none; } .markdown-body a { color: var(--accent); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--accent) 42%, transparent); text-underline-offset: 3px; } .message-user .markdown-body a { color: #fff; } .markdown-body blockquote { margin: 14px 0; padding: 2px 0 2px 15px; border-left: 3px solid var(--accent); color: var(--text-muted); } -.markdown-body table { width: 100%; margin: 15px 0; border-collapse: collapse; font-size: 0.86rem; } -.markdown-body th, .markdown-body td { border: 1px solid var(--line); padding: 8px 10px; text-align: left; vertical-align: top; } +.markdown-table-wrap { max-width: 100%; margin: 15px 0; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-gutter: stable; } +.markdown-body table { width: 100%; min-width: 520px; margin: 0; border-collapse: collapse; font-size: 0.86rem; table-layout: auto; } +.markdown-body th, .markdown-body td { border: 1px solid var(--line); padding: 8px 10px; text-align: left; vertical-align: top; overflow-wrap: break-word; word-break: normal; } +.markdown-body th:first-child, .markdown-body td:first-child { width: 18%; min-width: 10rem; } .markdown-body th { background: var(--surface-soft); color: var(--text); font-weight: 720; } .markdown-body pre { position: relative; overflow: auto; margin: 18px 0; padding: 0; border: 1px solid var(--line-strong); border-radius: 10px; background: color-mix(in srgb, var(--bg) 88%, var(--surface)); color: var(--text); } .markdown-body code { font-family: "Cascadia Code", "SFMono-Regular", Consolas, monospace; font-size: 0.82rem; }