Add dark mode and GFM table support to HTML/PDF export

Dark: added a prefers-color-scheme: dark CSS block, so HTML export
adapts to the browser/OS theme it's later viewed in. PDF is baked at
export time so it can't respond live — instead the offscreen WKWebView
used for PDF rendering has its appearance explicitly set to
NSApp.effectiveAppearance, matching whatever mode the app is in right
now at export time.

Tables: the renderer had no table support at all (previously
documented as an intentional scope cut), so GFM pipe tables were
falling through to the plain-paragraph path and showing up as literal
"| --- | --- |" text. Added detection (a row line immediately followed
by a valid dashes/colons separator row) plus alignment parsing from
the separator's colons.
This commit is contained in:
2026-07-29 08:08:16 +02:00
parent 634b83f284
commit 53736d4c42
2 changed files with 125 additions and 1 deletions
@@ -101,4 +101,35 @@ struct ConversationExportServiceTests {
#expect(body.contains("<code>**not bold**</code>"))
#expect(!body.contains("<strong>"))
}
// MARK: - Tables
@Test("Renders a GFM pipe table as a proper HTML table, not raw pipe text")
func rendersPipeTable() {
let markdown = "| Name | Age |\n| --- | --- |\n| Alice | 30 |\n| Bob | 25 |"
let body = ConversationExportService.renderMarkdownBody(markdown)
#expect(body.contains("<table>"))
#expect(body.contains("<th>Name</th>"))
#expect(body.contains("<th>Age</th>"))
#expect(body.contains("<td>Alice</td>"))
#expect(body.contains("<td>30</td>"))
#expect(body.contains("<td>Bob</td>"))
#expect(!body.contains("|---|"))
#expect(!body.contains("| --- |"))
}
@Test("Renders table column alignment from the separator row's colons")
func rendersTableAlignment() {
let markdown = "| Left | Center | Right |\n| :--- | :---: | ---: |\n| a | b | c |"
let body = ConversationExportService.renderMarkdownBody(markdown)
#expect(body.contains("text-align:left"))
#expect(body.contains("text-align:center"))
#expect(body.contains("text-align:right"))
}
@Test("A line containing a pipe with no valid separator row after it is not treated as a table")
func doesNotTreatOrdinaryPipeTextAsTable() {
let body = ConversationExportService.renderMarkdownBody("run `cmd1 | cmd2` to pipe output")
#expect(!body.contains("<table>"))
}
}