Add mdToMrkdwn: convert outgoing markdown to Slack mrkdwn - #5
Merged
Conversation
Dispatch producers (e.g. an LLM) commonly emit standard markdown — headers, **bold**, [text](url) links — none of which render as intended in Slack. mdToMrkdwn is a conservative, dependency-free regex transform applied in toTagThread's post() so every TagThread.post() call gets it automatically. Code fences and inline code spans are protected with a NUL-delimited placeholder token first and restored untouched — NUL can't appear in ordinary text, so there is no collision surface between a real placeholder and literal text that happens to look like one (e.g. a model asked to echo something verbatim). Closes CL-5012: add mdToMrkdwn to normalize outgoing markdown for Slack
TheGreatAxios
force-pushed
the
tag-slack-markdown-to-mrkdwn
branch
from
August 2, 2026 17:03
e5b81e3 to
3f40545
Compare
convertLinks had no guard against a preceding `!`, so ``
was silently rewritten to `!<url|alt>` — a clickable link with a
stray leading `!`, and the fact that it was an image lost entirely.
Untouched it would at least render as literal markdown; converted it
was actively wrong, and models emit image markdown constantly. Fixed
with a negative lookbehind excluding image syntax from link
conversion.
Also:
- Add `~~text~~` -> `~text~` strikethrough conversion (Slack's
single-tilde form), same conservative regex approach as the other
inline conversions.
- Add an escape hatch: `TagThread.post()` takes an optional
`{ convertMarkdown: false }` to skip conversion. The caller
composing a message is the one who knows whether it emitted
markdown or an intentional literal; conversion firing unconditionally
took that decision away from it. Conversion stays the default.
- Move the NUL-delimiter rationale from a test comment into mrkdwn.ts,
next to the placeholder constants it explains — the place someone
editing this file will actually see it before "cleaning up" the
control character. Also notes the bytes are invisible to plain
`grep`/most diff viewers.
- Document mdToMrkdwn's formatting contract: inline only, not tables,
images, or nested lists — those need Block Kit.
- Move the mdToMrkdwn export onto its own line in the package index;
it was wedged inside the slack-users.ts export group, breaking the
file's group-by-source-module pattern.
convertMarkdown lives on tag-core's TagThreadPostOptions but so far only tag-slack reads it; tag-core has no README, so the doc comment is the authoritative spot. Notes that honoring the option is expected of any TagThread implementation, not a Slack-specific detail.
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mdToMrkdwn, a conservative regex-based transform from common markdown constructs (**bold**/__bold__,[text](url)links,#headers,-/*bullets) to Slack's mrkdwn dialect. No AST, no new dependency — code fences and inline code spans are protected with a NUL-delimited placeholder before conversion and restored byte-for-byte after. NUL can't appear in ordinary text, so a placeholder token never collides with literal text that happens to look like one (e.g. a model asked to echo something verbatim).toTagThread'spost()inpackages/tag-slack/src/wire.ts, so everyTagThread.post()call — including the thinking-indicator's edit-in-place path — gets markdown normalized automatically before it reaches Slack.mdToMrkdwnfrom the package's public surface (index.ts) for hosts that want to call it directly.Motivated by dispatch producers (e.g. an LLM) that emit standard markdown even when asked not to, which otherwise renders literally in Slack.
Closes CL-5012: add mdToMrkdwn to normalize outgoing markdown for Slack
Test plan
bun run typecheck && bun run testpackages/tag-slack/src/mrkdwn.test.tscovers bold, links, headers, bullets, indentation, fenced code blocks, inline code spans, bare*(non-bold), combined constructs, a no-op on already-clean mrkdwn, and a placeholder-collision regression (literal text containing aMRKDWN_CODE_0-shaped substring survives untouched).packages/tag-slack/src/wire.test.tsadds two cases:tagThread.post()converts markdown before the fake thread receives it, and the thinking-indicator's placeholder edit also receives converted text.