Markdown formatting
crocbot formats outbound Markdown by converting it into a shared intermediate representation (IR) before rendering channel-specific output. The IR keeps the source text intact while carrying style/link spans so chunking and rendering can stay consistent across channels.Goals
- Consistency: one parse step, multiple renderers.
- Safe chunking: split text before rendering so inline formatting never breaks across chunks.
- Channel fit: map the same IR to Telegram HTML without re-parsing Markdown.
Pipeline
- Parse Markdown -> IR
- IR is plain text plus style spans (bold/italic/strike/code/spoiler) and link spans.
- Offsets are UTF-16 code units.
- Tables are parsed only when a channel opts into table conversion.
- Chunk IR (format-first)
- Chunking happens on the IR text before rendering.
- Inline formatting does not split across chunks; spans are sliced per chunk.
- Render per channel
- Telegram: HTML tags (
<b>,<i>,<s>,<code>,<pre><code>,<a href>).
- Telegram: HTML tags (
IR example
Input Markdown:Where it is used
- Telegram outbound adapters render from the IR.
Table handling
Markdown tables are not consistently supported across chat clients. Usemarkdown.tables to control conversion per channel (and per account).
code: render tables as code blocks (default for most channels).bullets: convert each row into bullet points.off: disable table parsing and conversion; raw table text passes through.
Chunking rules
- Chunk limits come from channel adapters/config and are applied to the IR text.
- Code fences are preserved as a single block with a trailing newline so channels render them correctly.
- List prefixes and blockquote prefixes are part of the IR text, so chunking does not split mid-prefix.
- Inline styles (bold/italic/strike/inline-code/spoiler) are never split across chunks; the renderer reopens styles inside each chunk.
Link policy
- Telegram:
[label](url)-><a href="url">label</a>(HTML parse mode).
How to add or update a channel formatter
- Parse once: use the shared
markdownToIR(...)helper with channel-appropriate options (autolink, heading style, blockquote prefix). - Render: implement a renderer with
renderMarkdownWithMarkers(...)and a style marker map. - Chunk: call
chunkMarkdownIR(...)before rendering; render each chunk. - Wire adapter: update the channel outbound adapter to use the new chunker and renderer.
- Test: add or update format tests and an outbound delivery test if the channel uses chunking.
Common gotchas
- Telegram HTML requires escaping text outside tags to avoid broken markup.
- Preserve trailing newlines for fenced code blocks so closing markers land on their own line.
