Day one. The Slack PM: “We need a markdown renderer in the chat surface — six syntax features, ship by Friday.” The engineer writes the obvious version: chain six .replace() calls with capturing regexes, return the result. Demo passes on "# Hi\n**world**". Ship.

Below is that renderer, exactly as shipped. Type your own markdown into the input — the regex chain runs in real-time and the output updates as you type. Or tap a quick-fill case from the menu to load a known trap. Run at least three of the six preset cases. Watch each one break in a DIFFERENT way — and the guard list that “fixes” each grow faster than the feature set.

Regex chain output
Output appears here as you type...
Quick-fill cases

Type your own markdown in the input, or tap a case below to load it. Each preset hits a different failure mode — feel at least three.

The first case ("Simple heading + bold") is the demo the junior passed. The other five are the bugs QA finds by lunch.

The bugs are not a cohesive class. Nested emphasis COLLAPSES because inline-code fired first. Unclosed markers EAT paragraphs because non-greedy .+? still binds to a far-off partner. Triple-star has THREE plausible parses and the regex silently picks one. Links with javascript: inject arbitrary JS. Nested lists FLATTEN because regex is line-shaped. Fenced code LEAKS because a global sweep has no “I'm inside code right now” memory.

The deeper problem isn't sloppy regex — it's that regex matches CHARACTER PATTERNS without memory of CONTEXT. “I'm inside a code block” isn't a pattern; it's a state. “This `**` belongs to the bold that opened five characters ago” isn't a pattern either. Markdown is built on state. Regex is built on finite patterns. No amount of guard-stacking crosses that gap — it's theoretical, not implementation.

Run at least three of the six canned cases. Each one breaks in a DIFFERENT way. Trapped so far: 0 / 6.