Skip to content

calternal_notes_core::tasks::frontmatter

Byte-stable, line-scoped YAML frontmatter field reader + writer.

Task frontmatter is stored opaquely as frontmatter_raw (model.rs) and field-level YAML editing was deliberately deferred for round-trip safety (see dayfile.rs). The hard requirement (spec §13) is byte stability: editing one field must preserve EVERY other byte — key order, indentation, comments, blank lines, the \r\n vs \n line-ending style, and the trailing newline. A real YAML parser/serializer cannot meet this: round-tripping through serde_yaml/yaml-rust reorders keys, drops comments, re-quotes scalars, normalizes indentation and folds line endings. So we DON’T parse the document — we treat it as bytes and surgically rewrite exactly one line, concatenating the untouched byte slices on either side of the single edit.

Scalar single-line fields only — the fields the app actually sets: status, completed, title, due, scheduled, start, repeat, area, priority, created. recurrence remains a legacy spelling. It does not edit the tags: YAML list (a multi-line block sequence); that is out of scope for v1 (contract §5). A NEW field is appended as the last line before the closing --- (deterministic position so the result is reproducible).

Values are emitted through needs_quote so a scalar that would otherwise be mis-parsed by YAML (e.g. one containing ": ") is double-quoted and thus round-trips instead of silently becoming a mapping.

Source: crates/calternal-notes-core/src/tasks/frontmatter.rs

pub fn set_task_frontmatter_field(text: &str, field: &str, value: Option<&str>) -> String

Byte-stable single-field frontmatter editor.

value = Some(v) sets/replaces the field; value = None removes it. Every other byte — key order, spacing, comments, blank lines, line-ending style and the trailing newline — is preserved. See the module docs for the rationale and scope.

Behavior matrix:

  • field present + Some(v): rebuild that one line in place (preserving its indent and EOL). If the rebuilt line is byte-identical to the original, the original text is returned unchanged (idempotence lock, §13).
  • field present + None: drop exactly that line (including its EOL).
  • field absent + Some(v): append field: v as the last line before the closing ---.
  • field absent + None: no-op (returns text unchanged).
  • no frontmatter block + Some(v): synthesize a minimal ---…--- block and prepend it (so a frontmatterless file is not corrupted).
  • no frontmatter block + None: no-op.

Source: crates/calternal-notes-core/src/tasks/frontmatter.rs:382

pub fn set_task_tags(text: &str, tags: &[String]) -> String

Replace only the tags: frontmatter segment. Legacy block lists and new flow lists are both read; the writer emits a YAML-compatible JSON flow list and preserves every byte outside the old tag segment.

Source: crates/calternal-notes-core/src/tasks/frontmatter.rs:408