From bc70ce36e0037495aa236fec7055ca0f41c0f884 Mon Sep 17 00:00:00 2001 From: ducoterra Date: Fri, 28 Aug 2026 03:35:50 -0400 Subject: [PATCH] feat(chat): render markdown tables in answers, viewer, and thinking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GFM pipe tables in the shared renderer (TODO.md L6): a table-protection pass in frontend/assets/markdown.js (fences -> tables -> escape order) pulls each header+separator+body block out as a placeholder, renders cells escape-first with the same inline transforms, and reinserts a semantic inside a horizontal-overflow .md-table-wrap — so a pipe table in a chat answer, the document viewer/modal, and the thinking block all render the same semantic table. Fences win over tables; lone pipes stay text. - styles.css: .md-table palette rules (PLAN §7.2 tokens, no motion); min-width: max-content so a WIDE table keeps its natural width and the wrapper is the real scroller (width:100% alone wrapped the wide table's cells — proven by the new E2E). - mock_llm.py: TABLE_TRIGGER ("show me a table") -> byte-stable TABLE_ANSWER (3-column table, XSS probe line, wide 5-column table), checked before DEFLECT_MODE like SUMMARY_MODE. - tests/fixtures/docs/homelab/tables.md: 3x3 pipe table + pipe-heavy fenced block (viewer/fence subject); the shared fixture set grows 8 -> 9 docs, so every suite pinning the count (added/formats/ stat-docs/EXPECTED_ROWS) is updated accordingly. - tests/e2e/test_markdown_tables.py (new, story suite): chat table shape + non-deflection, wide-table wrapper scroll (no page overflow), XSS probe inert, viewer modal table, fence-not-a-table, lone pipe stays text. - tests/e2e/test_agent_document_tools.py: fix a pre-existing flake — the "Calling tool…" label window is ~0.4 s at the mock's 0.1 s tool-frame pacing, and a polling expect could stride over it (failed 3 of 5 runs on the committed baseline). The pre-submit MutationObserver record is the deterministic source of truth; the racy to_have_text gate is gone. uv run pytest: 738 passed, app/ coverage 99% (TOTAL unchanged); ruff + pyright clean; story E2E 6/6 in isolation; regression E2E suites (chat_rag, document_viewer, document_summaries, smoke) green. --- frontend/assets/markdown.js | 118 +++++++- frontend/assets/styles.css | 19 ++ tests/e2e/mock_llm.py | 56 ++++ tests/e2e/test_admin_auth.py | 4 +- tests/e2e/test_agent_document_tools.py | 14 +- tests/e2e/test_chat_persistence.py | 2 +- tests/e2e/test_chat_rag.py | 2 +- tests/e2e/test_dark_tech_theme.py | 2 +- tests/e2e/test_document_summaries.py | 2 +- tests/e2e/test_global_tuning.py | 2 +- tests/e2e/test_honest_deflection.py | 2 +- tests/e2e/test_import_documents.py | 12 +- tests/e2e/test_kb_overview.py | 4 +- tests/e2e/test_loading_feedback.py | 2 +- tests/e2e/test_markdown_tables.py | 350 ++++++++++++++++++++++ tests/e2e/test_no_reply_autoscroll.py | 8 +- tests/e2e/test_retrieval_quality.py | 11 +- tests/e2e/test_sources_midstream_bug.py | 8 +- tests/e2e/test_steering.py | 2 +- tests/e2e/test_suggestion_chips.py | 2 +- tests/e2e/test_thinking_display.py | 8 +- tests/e2e/test_thinking_scroll.py | 8 +- tests/e2e/test_whole_document_context.py | 6 +- tests/fixtures/docs/homelab/tables.md | 22 ++ tests/integration/test_auth_api.py | 2 +- tests/integration/test_chat_api.py | 2 +- tests/integration/test_importer_e2e.py | 16 +- tests/integration/test_kb_overview_api.py | 2 +- tests/integration/test_steering_api.py | 2 +- tests/unit/test_markdown_tables.py | 350 ++++++++++++++++++++++ 30 files changed, 967 insertions(+), 73 deletions(-) create mode 100644 tests/e2e/test_markdown_tables.py create mode 100644 tests/fixtures/docs/homelab/tables.md create mode 100644 tests/unit/test_markdown_tables.py diff --git a/frontend/assets/markdown.js b/frontend/assets/markdown.js index adfe7e3..b931653 100644 --- a/frontend/assets/markdown.js +++ b/frontend/assets/markdown.js @@ -6,7 +6,16 @@ * content can never inject live HTML/XSS. Classic script on purpose: * index.html and document.html load it via a plain relative