What's wrong
Frontmatter/YamlSerializer.cs (~lines 74–82) says:
// Convert dictionary keys to strings and preserve the first occurrence of duplicate keys
foreach (KeyValuePair<object, object> pair in rawData)
{
...
// Only add the key if it doesn't already exist
if (!result.ContainsKey(key))
By the time this loop runs, YamlDotNet's Deserialize<Dictionary<object, object>> has already collapsed duplicates and kept the last value. The ContainsKey guard never sees a duplicate, so the "first occurrence" logic is dead code.
Repro
Frontmatter.ExtractFrontmatter("---\ntitle: First\ntitle: Second\n---\nbody\n");
// title = "Second"
Why it matters
The actual behaviour (last wins within a block) contradicts both this code's stated intent and the first-block-wins rule CombineFrontmatterObjects applies across blocks. A document that accidentally repeats a key (common after hand merges) gets the opposite value from the one the library says it keeps, and Combine then persists that choice.
Suggested fix
Pick one behaviour and make the code match it:
- First wins (matches the comment and the cross-block rule): parse with the representation model (
YamlStream / YamlMappingNode) and skip keys already seen.
- Reject duplicates: build the deserializer with
.WithDuplicateKeyChecking(), treat the block as unparseable, and fix the comment.
Acceptance criteria
- The repro yields the documented value (
First, or a parse failure if you choose rejection).
- A test pins the behaviour.
What's wrong
Frontmatter/YamlSerializer.cs(~lines 74–82) says:By the time this loop runs, YamlDotNet's
Deserialize<Dictionary<object, object>>has already collapsed duplicates and kept the last value. TheContainsKeyguard never sees a duplicate, so the "first occurrence" logic is dead code.Repro
Why it matters
The actual behaviour (last wins within a block) contradicts both this code's stated intent and the first-block-wins rule
CombineFrontmatterObjectsapplies across blocks. A document that accidentally repeats a key (common after hand merges) gets the opposite value from the one the library says it keeps, and Combine then persists that choice.Suggested fix
Pick one behaviour and make the code match it:
YamlStream/YamlMappingNode) and skip keys already seen..WithDuplicateKeyChecking(), treat the block as unparseable, and fix the comment.Acceptance criteria
First, or a parse failure if you choose rejection).