You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A | block scalar loses its trailing newline when it is the last key in the frontmatter (and CombineFrontmatter rewrites it as >-) #153
The text handed to the YAML parser has its final line ending removed, so the same YAML parses differently depending on where a key sits:
TrySplitFrontmatterBlocks slices each block to lines[close - 1].End (Frontmatter/Frontmatter.cs, ~lines 385–387), which excludes the last line's line ending.
The parse path then does string section = block.Trim(); (~line 329).
A literal (|) or keep (|+) block scalar on the last key therefore loses its trailing newline(s).
Repro
Frontmatter.ExtractFrontmatter("---\ntitle: T\nnotes: |\n line1\n line2\n---\nbody\n");// notes = "line1\nline2" <- clip chomping should give "line1\nline2\n"Frontmatter.ExtractFrontmatter("---\nnotes: |\n line1\n line2\ntitle: T\n---\nbody\n");// notes = "line1\nline2\n" <- correct, only because another key follows// keep chomping: "---\ncode: |+\n x = 1\n\n\n---\nbody\n"// code = "x = 1" (YamlDotNet on the same YAML directly gives "x = 1\n\n\n")
CombineFrontmatter on the first document writes the value back as
notes: >- line1 line2
so the trailing newline is permanently lost from the file, and the scalar style changes on what should be a no-op round trip.
Why it matters
Moving a key, or adding one after it, changes its value. Consumers that rely on a trailing newline (code snippets, |+ content, text compared byte-for-byte) get different data. Every CombineFrontmatter pass also rewrites the file.
Suggested fix
Parse the block with its final line ending kept: slice to lines[close].Start instead of lines[close - 1].End. Use string.IsNullOrWhiteSpace only for the empty-block check, and don't Trim() the text that gets parsed. Leading Trim() also strips first-line indentation, which would break uniformly indented frontmatter.
Acceptance criteria
notes parses to "line1\nline2\n" regardless of key position.
The |+ example yields "x = 1\n\n\n".
Combine with AsIs naming and no merging leaves a trailing | block's value unchanged.
Priority: Medium. A |/|+ scalar's value depends on where its key sits. CombineFrontmatter then permanently removes the trailing newline and rewrites the scalar as >- on what should be a no-op round trip. The data change is small but silent and gets persisted.
Area / suggested assignee: block slicing in TrySplitFrontmatterBlocks (lines[close - 1].End) and block.Trim() in the parse path of Frontmatter.cs. Owner: @matt-edmondson
Next step: slice to lines[close].Start, use IsNullOrWhiteSpace only for the empty check, and stop trimming the text that gets parsed. Add the clip, keep and Combine round-trip tests from the acceptance criteria.
Activity
matt-edmondson commentedon Sep 27, 2026
Triage
|/|+scalar's value depends on where its key sits.CombineFrontmatterthen permanently removes the trailing newline and rewrites the scalar as>-on what should be a no-op round trip. The data change is small but silent and gets persisted.TrySplitFrontmatterBlocks(lines[close - 1].End) andblock.Trim()in the parse path ofFrontmatter.cs. Owner: @matt-edmondsonTrySplitFrontmatterBlocks; watch for conflicts. No open PR covers this.lines[close].Start, useIsNullOrWhiteSpaceonly for the empty check, and stop trimming the text that gets parsed. Add the clip, keep and Combine round-trip tests from the acceptance criteria.Generated by Claude Code
matt-edmondson commentedon Sep 30, 2026
Covered by #180, which redesigns this bug cluster as a whole. Implement it through that issue rather than individually.
Generated by Claude Code