From e20101fc02c867c8b608279c79677fe337bc5ecd Mon Sep 17 00:00:00 2001 From: Dmitrii Troitskii Date: Sat, 27 Jun 2026 10:39:53 +0000 Subject: [PATCH] fix(streamdown): disable singleTilde in remark-gfm to prevent ~ from rendering as strikethrough Set singleTilde: false in the default remark-gfm configuration so that single ~ characters are not interpreted as strikethrough markup. Only double tildes (~~text~~) will trigger strikethrough, consistent with GitHub GFM behavior. Fixes #545 --- .changeset/fix-single-tilde-545.md | 12 ++++++ .../__tests__/single-tilde.test.tsx | 39 +++++++++++++++++++ packages/streamdown/index.tsx | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-single-tilde-545.md create mode 100644 packages/streamdown/__tests__/single-tilde.test.tsx diff --git a/.changeset/fix-single-tilde-545.md b/.changeset/fix-single-tilde-545.md new file mode 100644 index 00000000..5e280698 --- /dev/null +++ b/.changeset/fix-single-tilde-545.md @@ -0,0 +1,12 @@ +--- +"streamdown": patch +--- + +fix: disable singleTilde in remark-gfm to prevent single ~ from rendering as strikethrough + +Single tilde (`~foo~`) should not be interpreted as strikethrough markup. +This aligns with GitHub GFM behavior where only double tildes (`~~text~~`) +create strikethrough. Set `singleTilde: false` in the default remark-gfm +configuration. + +Fixes #545 diff --git a/packages/streamdown/__tests__/single-tilde.test.tsx b/packages/streamdown/__tests__/single-tilde.test.tsx new file mode 100644 index 00000000..40f612fb --- /dev/null +++ b/packages/streamdown/__tests__/single-tilde.test.tsx @@ -0,0 +1,39 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { Streamdown } from "../index"; + +describe("Single tilde handling (#545)", () => { + it("should not render single tilde as strikethrough", () => { + const content = "~foo~ is not strikethrough"; + const { container } = render({content}); + + // Single tilde should NOT create a element + const del = container.querySelector("del"); + expect(del).toBeNull(); + + // Text content should contain the tilde characters + const text = container.textContent; + expect(text).toContain("~foo~"); + }); + + it("should still render double tilde as strikethrough", () => { + const content = "~~strikethrough~~"; + const { container } = render({content}); + + // Double tilde SHOULD create a element + const del = container.querySelector("del"); + expect(del).not.toBeNull(); + expect(del?.textContent).toBe("strikethrough"); + }); + + it("should not render single ~ between words as strikethrough", () => { + const content = "Temperature range is 20~25°C"; + const { container } = render({content}); + + const del = container.querySelector("del"); + expect(del).toBeNull(); + + const text = container.textContent; + expect(text).toContain("20~25"); + }); +}); diff --git a/packages/streamdown/index.tsx b/packages/streamdown/index.tsx index be32d9fd..f7f37b2f 100644 --- a/packages/streamdown/index.tsx +++ b/packages/streamdown/index.tsx @@ -263,7 +263,7 @@ export const defaultRehypePlugins: Record = { } as const; export const defaultRemarkPlugins: Record = { - gfm: [remarkGfm, {}], + gfm: [remarkGfm, { singleTilde: false }], codeMeta: remarkCodeMeta, } as const;