Skip to content

fix(feishu): render markdown tables as aligned code fences - #21872

Open
mytangyh wants to merge 1 commit into
NousResearch:mainfrom
mytangyh:sync/feishu-table-from-v0.13.0
Open

fix(feishu): render markdown tables as aligned code fences#21872
mytangyh wants to merge 1 commit into
NousResearch:mainfrom
mytangyh:sync/feishu-table-from-v0.13.0

Conversation

@mytangyh

@mytangyh mytangyh commented May 8, 2026

Copy link
Copy Markdown

What changed

Replace the force-plain-text fallback for markdown tables with a code-fence conversion that preserves monospace column alignment.

Why

Feishu's md renderer does not render markdown tables. The current approach (8e18d103) detects tables and forces the entire message to plain text mode, which loses all markdown formatting (bold, links, inline code, etc.) in the same message.

This PR instead converts markdown tables to space-padded rows inside code fences. The monospace rendering preserves column alignment while the rest of the message retains normal markdown formatting.

How

  • Enhanced _MARKDOWN_TABLE_RE to capture full tables including body rows (not just header + separator)
  • Added _display_width() — CJK / emoji / zero-width character aware width measurement for column alignment
  • Added _convert_md_tables() — parses markdown tables, measures column widths, pads cells, and wraps in code fences
  • Replaced the force-text-mode early return in _build_outbound_payload() with a _convert_md_tables() call

Before / After

Before: A message containing a markdown table + bold text → entire message sent as plain text (no formatting at all).

After: Table converted to aligned code fence, rest of message rendered with full markdown support.

Testing

Tested with mixed-content messages containing markdown tables alongside bold, links, and CJK text. Verified column alignment in Feishu client.

Feishu's post-type 'md' elements do not render markdown tables.
The current approach forces plain text mode for any content containing
a table, which loses all markdown formatting (bold, links, etc.) in
the same message.

Instead, convert markdown tables to space-padded rows inside code
fences.  The monospace rendering preserves column alignment while
allowing the rest of the message to use normal markdown formatting.

Changes:
- Enhance _MARKDOWN_TABLE_RE to capture full table including body rows
- Add _display_width() for CJK / emoji / zero-width char width measurement
- Add _convert_md_tables() to parse, align, and wrap tables in code fences
- Replace force-text-mode early return with _convert_md_tables() call

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for preserving rich Markdown around Feishu tables. The underlying problem remains on current main, but this needs a targeted salvage rather than a direct apply.

Problems

  • The active adapter moved from gateway/platforms/feishu.py to plugins/platforms/feishu/adapter.py in 552adbe0827c32df8ed9bb19e908c26eff43add7; current plain-text fallback remains at plugins/platforms/feishu/adapter.py:4524-4534.
  • gateway/platforms/feishu.py:220 globally substitutes table-shaped blocks, including tables inside existing fenced code blocks. The current post builder deliberately tracks fence state at plugins/platforms/feishu/adapter.py:577-623.
  • This payload-routing change has no regression tests; tests/gateway/test_feishu.py currently has no table or _build_outbound_payload coverage.

Suggested changes

  • Port the fix to plugins/platforms/feishu/adapter.py.
  • Make conversion fence-aware, then add mixed-markdown, fenced-table, and CJK/emoji payload tests.

Automated hermes-sweeper review.

def _replace(match: re.Match) -> str:
return "```\n" + _table_to_simple(match.group(0)) + "\n```"

return _MARKDOWN_TABLE_RE.sub(_replace, text)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This global substitution also matches a table-shaped example already inside a fenced code block, inserting a second pair of fences and breaking the fence state consumed by the post builder. Please scan line-by-line while tracking fence state and leave existing fenced blocks unchanged.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/feishu Feishu / Lark adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants