diff --git a/internal/webui/static/app.js b/internal/webui/static/app.js
index 0968167..68b0c46 100644
--- a/internal/webui/static/app.js
+++ b/internal/webui/static/app.js
@@ -124,6 +124,32 @@ function renderMarkdown(text) {
'$1',
);
+ // Tables: consecutive lines containing pipes
+ html = html.replace(/(?:^\|.+\|\s*(?:\n|$))+/gm, (match) => {
+ const rows = match.trim().split("\n");
+ if (rows.length < 2) return match;
+ // Check if second row is a separator (e.g. |---|---|)
+ const sepTest = rows[1].replace(/\s/g, "");
+ const isSep = /^\|?[\-:|]+(?:\|[\-:|]+)+\|?$/.test(sepTest);
+ if (!isSep) return match;
+
+ const parseRow = (row) =>
+ row.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
+
+ const headerCells = parseRow(rows[0]);
+ const thead = "" + headerCells.map((c) => `| ${c} | `).join("") + "
";
+
+ const bodyRows = rows.slice(2);
+ const tbody = bodyRows.length
+ ? "
" + bodyRows.map((r) => {
+ const cells = parseRow(r);
+ return "" + cells.map((c) => `| ${c} | `).join("") + "
";
+ }).join("") + ""
+ : "";
+
+ return ``;
+ });
+
// Unordered lists: consecutive lines starting with - or *
html = html.replace(/(?:^[*\-] .+(?:\n|$))+/gm, (match) => {
const items = match
@@ -148,8 +174,8 @@ function renderMarkdown(text) {
html = html.replace(/\n/g, "
");
// Clean stray
adjacent to block elements
- html = html.replace(/
(<\/?(?:h[2-5]|pre|ul|ol|li|hr))/g, "$1");
- html = html.replace(/((?:<\/(?:h[2-5]|pre|ul|ol|li)>|
))
/g, "$1");
+ html = html.replace(/
(<\/?(?:h[2-5]|pre|ul|ol|li|hr|table|thead|tbody|tr|th|td))/g, "$1");
+ html = html.replace(/((?:<\/(?:h[2-5]|pre|ul|ol|li|table|thead|tbody|tr|th|td)>|
))
/g, "$1");
// Restore protected code
html = html.replace(/\x00CB(\d+)\x00/g, (_, i) => codeBlocks[i]);
diff --git a/internal/webui/static/style.css b/internal/webui/static/style.css
index ab32587..f07df35 100644
--- a/internal/webui/static/style.css
+++ b/internal/webui/static/style.css
@@ -346,6 +346,29 @@ input:focus, select:focus, textarea:focus {
.bubble.assistant strong { font-weight: 600; }
.bubble.assistant em { font-style: italic; }
+.bubble.assistant table {
+ border-collapse: collapse;
+ width: 100%;
+ margin: 6px 0;
+ font-size: 13px;
+}
+
+.bubble.assistant th,
+.bubble.assistant td {
+ border: 1px solid var(--border);
+ padding: 5px 10px;
+ text-align: left;
+}
+
+.bubble.assistant th {
+ background: var(--bg);
+ font-weight: 600;
+}
+
+.bubble.assistant tr:nth-child(even) td {
+ background: var(--bg-elev-2);
+}
+
.bubble.error {
align-self: stretch;
background: var(--bad);