From 51cd15cb2d07127899089de5e5146088414845ed Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 04:24:02 +0000 Subject: [PATCH] Drop a leading byte order mark when adding frontmatter to a document [patch] AddFrontmatter on a document with no header trimmed it with TrimBody, which does not treat U+FEFF as whitespace, so the BOM landed after the new header at the start of the body and hid a first-line heading from markdown renderers. TrimBody now skips a leading BOM before it trims. Fixes ktsu-dev/Frontmatter#160 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QiAEodt8KsqJyMYzdfoAfo --- Frontmatter.Test/DelimiterLineTests.cs | 21 +++++++++++++++++++++ Frontmatter/Frontmatter.cs | 8 +++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Frontmatter.Test/DelimiterLineTests.cs b/Frontmatter.Test/DelimiterLineTests.cs index 198892b..baa4c9a 100644 --- a/Frontmatter.Test/DelimiterLineTests.cs +++ b/Frontmatter.Test/DelimiterLineTests.cs @@ -184,6 +184,27 @@ public void AddFrontmatter_OpeningDelimiterHasTrailingWhitespaceOrBom_MergesInto Assert.AreEqual($"---{Nl}title: A{Nl}author: B{Nl}---{Nl}Body{Nl}", result); } + [TestMethod] + public void AddFrontmatter_BomPrefixedDocumentWithoutHeader_LeavesNoBomInTheBody() + { + string input = $"# Heading{Nl}{Nl}text{Nl}"; + + string result = Frontmatter.AddFrontmatter(input, new Dictionary { ["title"] = "T" }); + + Assert.AreEqual($"---{Nl}title: T{Nl}---{Nl}# Heading{Nl}{Nl}text{Nl}", result); + Assert.AreEqual($"# Heading{Nl}{Nl}text", Frontmatter.ExtractBody(result)); + } + + [TestMethod] + public void AddFrontmatter_BomPrefixedDocumentOpeningWithBlankLines_LeavesNoBomInTheBody() + { + string input = $"{Nl}{Nl}# Heading{Nl}"; + + string result = Frontmatter.AddFrontmatter(input, new Dictionary { ["title"] = "T" }); + + Assert.AreEqual($"---{Nl}title: T{Nl}---{Nl}# Heading{Nl}", result); + } + [TestMethod] public void HasFrontmatter_DelimiterWithoutLineEnding_IsNotFrontmatter() { diff --git a/Frontmatter/Frontmatter.cs b/Frontmatter/Frontmatter.cs index 76b1f83..1f59f33 100644 --- a/Frontmatter/Frontmatter.cs +++ b/Frontmatter/Frontmatter.cs @@ -496,13 +496,15 @@ private static bool IsFollowOnBlock(string block, List<(int Start, int End)> lin /// Trims a document body for output, dropping the blank lines before its first content line and the /// whitespace after its last. Unlike , it keeps the first content line's /// indentation, so a body that opens with an indented code block or nested list content survives. + /// A leading byte order mark is dropped too: it is not whitespace, so it would otherwise be carried + /// past a newly written header and hide the first line from markdown renderers. /// /// The body to trim. - /// The body without leading blank lines or trailing whitespace. + /// The body without a byte order mark, leading blank lines or trailing whitespace. private static string TrimBody(string body) { - int start = 0; - for (int i = 0; i < body.Length; i++) + int start = OpeningLineStart(body); + for (int i = start; i < body.Length; i++) { char c = body[i]; if (c == '\n')