Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

0010. set_value accepts collections, emitted in the target node’s style

  • Status: accepted
  • Date: 2026-08-31
  • Authors: Noyalib contributors

Context

set_value was documented scalar-only, so replacing a collection-valued key (tags, menu, media) went through set(path, fragment) with verbatim text — and the caller had to detect the target’s style first, because noyalib::to_string renders block style and splicing - a\n- c over tags: [a, b] fails the re-parse guard (block sequence entries are not allowed in this context, #328). Every consumer re-derived what the emitter owns.

docs/ROADMAP-TO-10.md A4 deliberately keeps set verbatim; this is the typed path, not a change to set.

Decision

set_value accepts Value::Sequence / Value::Mapping when the target node is itself a collection, rendering in the target’s own style:

  • a flow node ([…] / {…}, or any node inside a flow collection) is replaced by the serializer’s single-line flow rendering;
  • a block node is replaced by a block rendering at the document’s indent_unit(), with every continuation line shifted to the old value’s own column.

The span comes from the loader’s span tree (the green resolver mis-spans an indentless tags:\n- a sequence), trimmed of the leading indent it sweeps up. Before any byte moves, the candidate document is parsed and compared against the expected typed value — the document with exactly this path replaced — so a rendering the site cannot hold refuses byte-identically.

Replacing a scalar with a collection stays refused: growing a value onto its own lines is a layout decision set expresses with a fragment. Equal-value writes stay byte no-ops (#337’s rule, already in place, applies unchanged).

Replacing a block collection with a scalar is accepted, and the scalar goes where the collection’s content sat rather than where its span begins. The resolver widens a block collection’s span to its first line so that a read slice is uniformly indented; a scalar spliced over that span would start at the key’s own column, which this parser reads back and other implementations reject. When the collection sat at the key’s own column, which a block sequence is allowed to do, the scalar goes one indent step past the key instead. That is the column remove already picks when it empties a sole entry, and it keeps set_value consistent with what it already does for a scalar that was on its own line: the new value goes where the old value’s content began.

Consequences

  • Positive: doc.set_value("tags", &value) now covers the collection-valued frontmatter keys downstream editors replace, with no style detection in the caller.
  • Positive: the oracle-first ordering means the failure mode is a clean refusal, never a corrupted document.
  • Negative: one more rendering path (replace_collection_value) whose style decisions must stay in step with the serializer’s.
  • Neutral: comments inside the replaced collection go with it — they document bytes that are being replaced; bytes outside the span are untouched and pinned by test.

Alternatives considered

A separate set_collection_value

The issue offered it. One method with a documented collection arm reads better at the call site; the scalar/collection split is a fact about the argument, not two different intents.

Callers render the fragment themselves

The status quo; rejected for the duplication described in Context.