From a2bcaaf8344eb494466230ebc4cd9026500f8a9b Mon Sep 17 00:00:00 2001 From: Bernhard Jackiewicz Date: Sat, 3 Oct 2026 18:32:28 +0200 Subject: [PATCH] fix(ui): wrap long lines in the expanded chat source block The facts and code under an answer's Source toggle used white-space: pre in a narrow panel. Long names and paths ran past the edge, and with hidden macOS scrollbars nothing showed that the text went on (K15). Signed-off-by: Bernhard Jackiewicz --- .../src/browser-ai/browser-chat-css.test.ts | 28 +++++++++++++++++++ graph-ui/src/browser-ai/browser-chat.css | 4 ++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 graph-ui/src/browser-ai/browser-chat-css.test.ts diff --git a/graph-ui/src/browser-ai/browser-chat-css.test.ts b/graph-ui/src/browser-ai/browser-chat-css.test.ts new file mode 100644 index 000000000..d9941c05d --- /dev/null +++ b/graph-ui/src/browser-ai/browser-chat-css.test.ts @@ -0,0 +1,28 @@ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +const css = readFileSync(new URL('./browser-chat.css', import.meta.url), 'utf8').replace(/\/\*[\s\S]*?\*\//g, ''); + +/** The declarations of the last rule whose selector list names `selector` exactly. */ +function declarations(selector: string): Record { + const result: Record = {}; + for (const match of css.matchAll(/([^{}]+)\{([^}]*)\}/g)) { + const selectors = match[1].split(',').map(item => item.trim()); + if (!selectors.includes(selector)) continue; + for (const part of match[2].split(';')) { + const [name, ...value] = part.split(':'); + if (name.trim() && value.length) result[name.trim()] = value.join(':').trim(); + } + } + return result; +} + +describe('the expanded source block of an answer', () => { + it('wraps long fact and code lines instead of clipping them at the panel edge', () => { + for (const selector of ['.cbm-chat-source-content pre', '.cbm-chat-source-content .cbm-chat-attachment pre']) { + const rule = declarations(selector); + expect(rule['white-space'], selector).toBe('pre-wrap'); + expect(rule['overflow-wrap'], selector).toBe('anywhere'); + } + }); +}); diff --git a/graph-ui/src/browser-ai/browser-chat.css b/graph-ui/src/browser-ai/browser-chat.css index 90af70ee9..2c2370adf 100644 --- a/graph-ui/src/browser-ai/browser-chat.css +++ b/graph-ui/src/browser-ai/browser-chat.css @@ -43,7 +43,9 @@ .cbm-chat-source-toggle { color: var(--chat-muted); font-size: 10px; font-weight: 400; } .cbm-chat-response-source > summary:hover .cbm-chat-source-toggle { color: #d0e8d8; } .cbm-chat-source-content { margin: 7px 0 10px; padding: 8px; max-height: 260px; overflow: auto; background: #0b1510; border: 1px solid var(--chat-border); border-radius: 6px; } -.cbm-chat-source-content pre { margin: 4px 0 8px; max-height: 200px; overflow: auto; white-space: pre; font: 11px/1.6 ui-monospace, SFMono-Regular, monospace; tab-size: 4; } +/* The panel is narrow and macOS hides scrollbars: long facts and paths wrap instead of ending at the edge. */ +.cbm-chat-source-content pre { margin: 4px 0 8px; max-height: 200px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font: 11px/1.6 ui-monospace, SFMono-Regular, monospace; tab-size: 4; } +.cbm-chat-source-content .cbm-chat-attachment pre { white-space: pre-wrap; overflow-wrap: anywhere; } .cbm-chat-source-content small { display: block; color: var(--chat-muted); font: 10px/1.5 ui-monospace, SFMono-Regular, monospace; overflow-wrap: anywhere; } .cbm-chat-source-content .cbm-chat-attachment:first-child { margin-top: 0; } .cbm-chat-answer { padding: 15px 0 0; }