fix: preserve frontmatter datetimes verbatim via string-preserving YAML engine - #85
Conversation
…ML engine gray-matter's default js-yaml engine (YAML 1.1 schema) parses timestamp-shaped scalars into JS Date objects, which matter.stringify re-serializes via toISOString() — converting local-offset ISO 8601 values like created: 2026-05-13T20:00:00-04:00 to UTC-Z on every property or body edit. Same instant, wrong representation. Swap the YAML engine to the yaml package (YAML 1.2 core schema, no timestamp type) via gray-matter's documented engines option: datetimes parse as plain strings and dump back unquoted, so frontmatter values round-trip byte-identically. Shared MATTER_OPTIONS threaded through every parse/stringify site in vault-filesystem, vault-patcher, and memory-store. search-index keeps the default engine (read-only, converts Dates for DB storage — separate concern). Side benefit: new memory files now stamp created: unquoted instead of js-yaml's single-quoted form, matching the vault convention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis pull request consolidates gray-matter YAML parsing and serialization across vault operations by introducing shared ChangesYAML Configuration Consolidation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
… wrappers Replace the exported MATTER_OPTIONS constant with a frontmatter.ts module exporting parseNote() and stringifyNote(), which close over the string-preserving engine options. The options can no longer be forgotten at a future call site — a bare matter() call would silently reintroduce the UTC-Z datetime bug. search-index.ts intentionally keeps its direct gray-matter import (read-only, default engine). Also strengthen the new-memory-file test: the created: stamp regex now requires an unquoted local-offset form (never Z-suffixed). Mutation audit: reverting the wrapper to the default engine fails exactly the 8 new/strengthened tests and nothing else. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comments now describe the behavior (datetime strings round-trip verbatim, never re-encoded to UTC-Z) rather than one vault's local-offset convention — adopters may prefer UTC or any offset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Summary Bump umm-actually from v0.3.14 to v0.4.0 (`65ccbe7`). ### Changes in v0.4.0 - feat: staged review phases — parallel and sequential modes (\#79) - feat: content-based cross-run dedup for shifted/reworded findings (\#85) - fix: content dedup follow-ups — coalesceAnchors, titleSimilarity, logging (\#86) - fix: filter self-negating findings (\#84) - chore: bump default context_budget_tokens from 80K to 300K (\#87) - docs: drop the early-development status section (\#83)
Problem
vault_update_properties,vault_patch_note, andvault_replace_in_noterewrite a note's frontmatter on every edit and convert offset-form ISO 8601 datetimes to UTC-Z:Same instant, wrong representation — and
createdshould never change after stamping.vault_write_note(merge path),vault_update_memory, andvault_delete_memoryhad the same latent bug.Root cause
gray-matter's default engine is js-yaml with the YAML 1.1 schema, which parses timestamp-shaped scalars into JS
Dateobjects;matter.stringifythen re-serializes them viatoISOString(). No upstream fix exists — gray-matter 4.0.3 is the latest release, and its documented solution is the built-inenginesoption.Fix
Swap the YAML engine to the
yamlpackage (eemeli, already in the dependency tree as a transitive dep). The YAML 1.2 core schema has no timestamp type: datetimes parse as plain strings and dump back unquoted, so untouched frontmatter values round-trip byte-identically.All gray-matter calls in
vault-filesystem,vault-patcher, andmemory-storeare routed through newparseNote()/stringifyNote()wrappers infrontmatter.ts, which close over the engine options — a future call site can't accidentally fall back to the default engine and reintroduce the bug.js-yaml itself can't be the replacement engine — even with a string-preserving schema, its dumper single-quotes colon-containing scalars (
created: '2026-…-04:00').search-index.tskeeps the default engine and a direct gray-matter import: it's read-only (never writes files back) and already converts parsedDates to ISO strings for DB storage.Behavioral notes
created:unquoted (js-yaml single-quoted it because of the colons).vault_read_notewithproperties_onlynow returns datetime properties as their original strings instead of UTC-Z-serializedDates.yes/no/on/offnow parse as strings, not booleans — more faithful to the file bytes.tags: [a, b]) still re-dump in block style when frontmatter is re-serialized — js-yaml did the same, no regression.Testing
writeNotebody-only,updatePropertiesof an unrelated key,patchNoteappend,replaceInNote, memory update/delete round-trips — all assert the exactcreated: …-04:00byte sequence survives.readNotePropertiesreturns datetimes as original strings.created:stamp asserted unquoted and offset-form (never Z-suffixed).tscclean.🤖 Generated with Claude Code