Skip to content

fix(markdoc): escape text that would be read back as a list, emphasis or tag - #1648

Open
ChangkeunJ wants to merge 1 commit into
Thinkmill:mainfrom
ChangkeunJ:markdoc-keep-escapes
Open

ChangkeunJ wants to merge 1 commit into
Thinkmill:mainfrom
ChangkeunJ:markdoc-keep-escapes

Conversation

@ChangkeunJ

Copy link
Copy Markdown

Fixes #1628.

Markdoc's formatter only escapes a leading *, # or > in a text node, plus *, _ and ~ directly inside strong, em and s. So when the markdoc or document field saves, a paragraph stored as 2026\. A turning point comes back as 2026. A turning point and is an ordered list on the next parse, \- not a list turns into a bullet, a \*literal\* word turns into emphasis and \{% braces %} turns into an unknown tag that stops the entry from opening again (same symptom as #1428).

Every format call in the package already goes through src/markdoc.js, so this wraps format there. Before formatting it walks the AST and escapes the text that would otherwise be read back as syntax: {% everywhere, * and _ outside strong, em and s (Markdoc already escapes those inside), and a -, +, 1. or 1) marker at the start of a line. Headings are skipped so ## 1. Intro doesn't change, a _ between two word characters is skipped so snake_case doesn't churn, and a * or _ with spaces on both sides can't open or close emphasis so it's skipped too. The original text is put back after formatting, so the caller's AST isn't changed.

One thing to be aware of: existing content with a bare * or _ next to a word will pick up a backslash on its next save. It renders the same, but it is a diff, so I kept the rule as narrow as I could. A literal backslash, backticks and ~~ outside an s node aren't covered; I left those out rather than guess at what the editor should do with them.

The new test in markdoc.test.tsx has the cases from the issue and fails on main. The keystatic test suite, tsc, eslint and prettier pass with the change.

… or tag

Markdoc's formatter only escapes a leading *, # or > in a text node and
*, _ and ~ directly inside strong, em and s. So a paragraph stored as
"2026\. A turning point" was saved back as "2026. A turning point" and
parsed as an ordered list the next time, "\- not a list" became a
bullet, "a \*literal\* word" became emphasis and "\{% braces %}" became
an unknown tag that stopped the entry from opening again.

Every format call in the package goes through src/markdoc.js, so wrap
format there: walk the AST first and escape {%, * and _ outside
strong/em/s, and a bullet or ordered list marker at the start of a line.
Headings, snake_case and a * or _ with spaces on both sides are left
alone. The original text is restored after formatting.

Fixes Thinkmill#1628
@ChangkeunJ
ChangkeunJ requested a review from emmatown as a code owner October 26, 2026 23:53
@changeset-bot

changeset-bot Bot commented Oct 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f0d42af

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@keystatic/core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

This branch has not been deployed

No deployments
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.

Markdoc field drops escapes on save: a paragraph starting with "2026." or "- " comes back as a list (0.6.9)

1 participant