diff --git a/static/js/markdown.js b/static/js/markdown.js
index 8735b83e7f..3eb2e36930 100644
--- a/static/js/markdown.js
+++ b/static/js/markdown.js
@@ -682,10 +682,24 @@ export function mdToHtml(src, opts) {
const rows = table.trim().split('\n');
if (rows.length < 2) return table;
+ // Per-column text alignment from the GFM separator row (row index 1):
+ // :--- -> left :---: -> center ---: -> right --- -> default (left,
+ // matching the previous behaviour). Columns without a spec stay left.
+ const hasSeparator = /^[\s|:\-]+$/.test(rows[1]);
+ const aligns = hasSeparator
+ ? splitTableRow(rows[1]).map((spec) => {
+ const left = spec.startsWith(':');
+ const right = spec.endsWith(':');
+ if (left && right) return 'center';
+ if (right) return 'right';
+ return 'left';
+ })
+ : [];
+
let html = '
';
rows.forEach((row, idx) => {
- if (idx === 1 && /^[\s|:\-]+$/.test(row)) {
+ if (idx === 1 && hasSeparator) {
html += '';
return;
}
@@ -694,9 +708,10 @@ export function mdToHtml(src, opts) {
html += '';
- cells.forEach(cell => {
+ cells.forEach((cell, i) => {
const tag = idx === 0 ? 'th' : 'td';
- html += `<${tag} style="padding: 8px; text-align: left; border-bottom: 1px solid var(--border);">${cell.trim()}${tag}>`;
+ const align = aligns[i] || 'left';
+ html += `<${tag} style="padding: 8px; text-align: ${align}; border-bottom: 1px solid var(--border);">${cell.trim()}${tag}>`;
});
html += '
';
diff --git a/tests/test_markdown_rendering_js.py b/tests/test_markdown_rendering_js.py
index 2ffe8914f7..e9c505891d 100644
--- a/tests/test_markdown_rendering_js.py
+++ b/tests/test_markdown_rendering_js.py
@@ -136,6 +136,43 @@ def test_table_separator_row_not_rendered_as_data(node_available):
assert "---" not in html
+def test_table_column_alignment_from_separator(node_available):
+ # GFM alignment markers in the separator row set per-column text-align:
+ # :--- -> left :---: -> centre ---: -> right. It applies to the header
+ # cells and every body cell in the column.
+ html = _run_markdown_case(
+ "| Name | Score | Amount |\n"
+ "| :--- | :---: | ---: |\n"
+ "| Alpha | 12 | 3.50 |\n"
+ "| Bravo | 7 | 128.00 |"
+ )
+
+ # Header cells carry the column's alignment.
+ assert 'text-align: left; border-bottom: 1px solid var(--border);">Name' in html
+ assert 'text-align: center; border-bottom: 1px solid var(--border);">Score' in html
+ assert 'text-align: right; border-bottom: 1px solid var(--border);">Amount' in html
+ # Body cells inherit the same per-column alignment.
+ assert 'text-align: left; border-bottom: 1px solid var(--border);">Alpha' in html
+ assert 'text-align: center; border-bottom: 1px solid var(--border);">12' in html
+ assert 'text-align: right; border-bottom: 1px solid var(--border);">3.50' in html
+ # One header + two body cells per column.
+ assert html.count("text-align: left") == 3
+ assert html.count("text-align: center") == 3
+ assert html.count("text-align: right") == 3
+
+
+def test_table_default_alignment_column_stays_left(node_available):
+ # A column whose separator has no colon (`---`) keeps the previous default,
+ # left alignment; a `---:` column is right-aligned; nothing becomes centre.
+ html = _run_markdown_case("| Key | Value |\n| --- | ---: |\n| id | 42 |")
+
+ assert 'text-align: left; border-bottom: 1px solid var(--border);">Key' in html
+ assert 'text-align: right; border-bottom: 1px solid var(--border);">Value' in html
+ assert 'text-align: left; border-bottom: 1px solid var(--border);">id' in html
+ assert 'text-align: right; border-bottom: 1px solid var(--border);">42' in html
+ assert "text-align: center" not in html
+
+
def test_process_with_thinking_handles_gemma4_thought_channel(node_available):
html = _run_markdown_case(
"<|channel>thought\ninternal reasoningFinal answer.",