diff --git a/src/renderer/styles.css b/src/renderer/styles.css index ad88c4c06..e8e9d7861 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -3219,14 +3219,41 @@ body, max-width: 100%; } -.term-md__content table { +/* Tables in rendered Markdown — the ⌘M output view, the editor preview and the ⌘M chat view (its + messages and plan/question cards all render through `.term-chat__text`). Gridlines strong enough + to read (`--border` is a 10% separator, which made cells blur together), a tinted header row, + and a horizontal scroll inside the table for one wider than the view, so the thread never + scrolls sideways. A column's `:---:` alignment arrives as an `align` attribute, which an author + `text-align` would override — so only a header WITHOUT one is set (to the start edge, matching + its cells, instead of the browser's centred default). */ +.term-md__content table, +.term-chat__text table { + display: block; + width: max-content; + max-width: 100%; + overflow-x: auto; border-collapse: collapse; + margin: 0 0 10px; } .term-md__content th, -.term-md__content td { - border: 1px solid var(--border); - padding: 4px 8px; +.term-md__content td, +.term-chat__text th, +.term-chat__text td { + border: 1px solid rgba(var(--tint-rgb), 0.22); + padding: 4px 10px; + vertical-align: top; +} + +.term-md__content th, +.term-chat__text th { + background: rgba(var(--tint-rgb), 0.06); + font-weight: 600; +} + +.term-md__content th:not([align]), +.term-chat__text th:not([align]) { + text-align: start; } /* Chat panel — replaces the markdown overlay on chat-capable agent nodes (Cmd+M). */ diff --git a/src/renderer/styles.markdown-table.test.ts b/src/renderer/styles.markdown-table.test.ts new file mode 100644 index 000000000..fb8c500e7 --- /dev/null +++ b/src/renderer/styles.markdown-table.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest' +import { readFileSync } from 'node:fs' +import { join } from 'node:path' + +/** + * Rendered-Markdown tables get gridlines in every reading view. The chat view had no table rules + * at all (cells ran together with no lines), and the output view's lines used the 10% separator + * token. One rule group now covers both containers; this pins that neither drops out of it. + */ +const CSS = readFileSync(join(__dirname, 'styles.css'), 'utf8').replace(/\r\n/g, '\n') + +/** The body of the rule whose selector list is exactly `selectors` (joined by `,\n`). */ +function group(selectors: string[]): string { + const head = `${selectors.join(',\n')} {` + const at = CSS.indexOf(`\n${head}`) + if (at < 0) throw new Error(`no rule for ${selectors.join(', ')}`) + return CSS.slice(at, CSS.indexOf('}', at)) +} + +describe('Markdown table gridlines', () => { + it('every cell of both views has a visible border', () => { + const cells = group(['.term-md__content th', '.term-md__content td', '.term-chat__text th', '.term-chat__text td']) + + expect(cells).toMatch(/border:\s*1px solid rgba\(var\(--tint-rgb\), 0\.22\)/) + }) + + it('header cells are tinted in both views', () => { + expect(group(['.term-md__content th', '.term-chat__text th'])).toMatch(/background:/) + }) + + it('a wide table scrolls inside itself in both views', () => { + const table = group(['.term-md__content table', '.term-chat__text table']) + + expect(table).toMatch(/border-collapse:\s*collapse/) + expect(table).toMatch(/overflow-x:\s*auto/) + expect(table).toMatch(/max-width:\s*100%/) + }) + + it('leaves text-align to the Markdown column alignment, start-aligning only unaligned headers', () => { + const cells = group(['.term-md__content th', '.term-md__content td', '.term-chat__text th', '.term-chat__text td']) + const unaligned = group(['.term-md__content th:not([align])', '.term-chat__text th:not([align])']) + + expect(cells).not.toMatch(/text-align/) + expect(unaligned).toMatch(/text-align:\s*start/) + }) +})