Skip to content

feat(pulse/telegram): render markdown in messages via HTML parse mode#1285

Closed
catchingknives wants to merge 1 commit into
danielmiessler:mainfrom
catchingknives:feat/telegram-markdown-html-render
Closed

feat(pulse/telegram): render markdown in messages via HTML parse mode#1285
catchingknives wants to merge 1 commit into
danielmiessler:mainfrom
catchingknives:feat/telegram-markdown-html-render

Conversation

@catchingknives

@catchingknives catchingknives commented May 18, 2026

Copy link
Copy Markdown

Why

The Telegram module sends ctx.reply(text) with no parse_mode, so the DA's **bold**, *italic*, `code`, code fences, and links arrive as literal characters on the phone. The existing TELEGRAM MODE OVERRIDE prompt worked around this by steering the DA toward plain prose — a render-side problem masquerading as a content-side restriction.

Change

  • New mdToHtml helper in modules/markdown-html.ts converts a useful subset of markdown to Telegram HTML: bold, italic, strikethrough, inline code, fenced code, links, ATX headers (→ bold lines), bullets (→ ).
  • Telegram module sends with parse_mode: "HTML" by default; plain-text fallback on any send error via .catch().
  • New markdown_mode: "html" | "plain" field in TelegramConfig, defaults to "html" — opt-out preserved for anyone who wants raw text.
  • TELEGRAM MODE OVERRIDE prompt flipped from "no formatting" to an inline cheatsheet of what renders, so the DA writes naturally-formatted prose.

Robustness choices

  • Code blocks are stash-and-restored before other transforms — bold/italic cannot leak into them.
  • Italic boundaries admit punctuation, whitespace, and HTML tag chars so **a _b_** nests correctly while snake_case and Array<T> survive untouched.
  • Unbalanced markers degrade to literal characters, so chunk-splitting at the 4096-char boundary cannot produce broken HTML.

Tests

Releases/v5.0.0/.claude/PAI/PULSE/modules/telegram.test.ts — 21 cases covering snake_case safety, nested formatting, HTML escaping, code-block isolation, unbalanced markers, URLs with underscores, multiple inline spans.

$ bun test ./Releases/v5.0.0/.claude/PAI/PULSE/modules/telegram.test.ts
21 pass, 0 fail

End-to-end validation

Showcase message exercising every transformation sent through a real Telegram bot; renders correctly on iOS.

Relationship to other open PRs

The Telegram module sends ctx.reply(text) with no parse_mode, so the
DA's **bold**, *italic*, `code`, code fences, and links arrive as
literal characters on the phone. The existing TELEGRAM MODE OVERRIDE
prompt worked around this by steering the DA toward plain prose — a
render-side problem masquerading as a content-side restriction.

Changes:

- New `mdToHtml` helper in modules/markdown-html.ts converts a useful
  subset of markdown to Telegram HTML: bold, italic, strikethrough,
  inline code, fenced code, links, ATX headers (→ bold lines),
  bullets (→ '•').

- Telegram module sends with parse_mode: 'HTML' by default;
  plain-text fallback on any send error via .catch().

- New `markdown_mode: "html" | "plain"` field in TelegramConfig,
  defaults to 'html' — opt-out preserved for users who want raw text.

- TELEGRAM MODE OVERRIDE prompt flipped from 'no formatting' to an
  inline cheatsheet of what renders, so the DA writes naturally-
  formatted prose.

Robustness:

- Code blocks are stash-and-restored before other transforms run —
  bold/italic cannot leak into them.

- Italic boundaries admit punctuation, whitespace, and HTML tag chars
  so '**a _b_**' nests correctly while 'snake_case' and 'Array<T>'
  survive untouched.

- Unbalanced markers degrade to literal characters, so chunk-splitting
  at the 4096-char boundary cannot produce broken HTML.

Tests:

  $ bun test ./Releases/v5.0.0/.claude/PAI/PULSE/modules/telegram.test.ts
  21 pass, 0 fail

End-to-end validation: showcase message exercising every transformation
sent through @nairyo_bot; renders correctly on iOS.
@danielmiessler

Copy link
Copy Markdown
Owner

Thanks @catchingknives, nicely built. The Telegram module was reworked since v5.0.0 and now deliberately steers to plain conversational prose for phone reading (with an egress sanitizer), so an HTML render layer would fight the current prompt contract. Closing as superseded rather than merging (the repo is generated from our source tree). If we revisit richer rendering, your escape-first mdToHtml is the reference. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants