Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/cleanbody-multiline-tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@dualmark/core": patch
---

Fix `cleanBody` leaking raw tags when a tag spans multiple lines.

The HTML-tag replacement (e.g. `<Highlighted>…</Highlighted>` to `**…**`, and any custom `htmlTagReplacements`) used a regex without the dotAll flag, so `.` never matched newlines. A tag whose content spanned more than one line was left untouched, leaking raw markup into the cleaned markdown that AI clients read. The regex now uses the `s` flag while keeping the lazy match, so multi-line tags are converted and adjacent tags are not merged.
6 changes: 5 additions & 1 deletion packages/core/src/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ export function cleanBody(body: string, opts: CleanBodyOptions = {}): string {
let out = stripImg ? stripImages(body) : body;

for (const [tag, marker] of Object.entries(replacements)) {
const re = new RegExp(`<${tag}>(.*?)<\\/${tag}>`, "g");
// `s` (dotAll) so a tag whose content spans multiple lines is still
// replaced; without it a multi-line `<Tag>…</Tag>` leaks its raw markup
// into the cleaned markdown. `.*?` stays lazy, so it still stops at the
// first closing tag.
const re = new RegExp(`<${tag}>(.*?)<\\/${tag}>`, "gs");
out = out.replace(re, `${marker}$1${marker}`);
Comment on lines +77 to 78
}

Expand Down
12 changes: 12 additions & 0 deletions packages/core/test/text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ describe("cleanBody", () => {
expect(cleanBody("see <Highlighted>this</Highlighted>")).toBe("see **this**");
});

it("converts a tag whose content spans multiple lines", () => {
expect(cleanBody("see <Highlighted>multi\nline</Highlighted> end")).toBe(
"see **multi\nline** end",
);
});

it("keeps tag replacement lazy across multiple tags", () => {
expect(cleanBody("<Em>a</Em> mid <Em>b</Em>", { htmlTagReplacements: { Em: "*" } })).toBe(
"*a* mid *b*",
);
});

it("converts <br> to newline", () => {
expect(cleanBody("line1<br>line2<br/>line3")).toBe("line1\nline2\nline3");
});
Expand Down
Loading