WORK-693
ID:WORK-693Status:done

A block field reads its node's children with a nested model

SPEC-186 D2: a field matching a block node may declare model, a content model applied to that node's children, whose members resolve to dotted names.

Priority:mediumComplexity:moderateMilestone:v0.47.0Source:SPEC-186

Criteria completion

Criteria completion: 5 of 5 (100%) checked; history from Nov 1 to Nov 30%25%50%75%100%Nov 1Nov 3
Branches 1
History 3
  1. 736e582
    • ☑ A block field accepts `model`, and still matches the whole node (D2.1)
    • ☑ Its members resolve to dotted names (`quote.attribution`), to any depth
    • ☑ A composition places a member by its dotted name or the whole node by the field's name, not both (D2.2)
    • ☑ An unmatched child inside the node is reported `content-unmatched`, naming the field (D2.4)
    • ☑ A nested model on a non-block field is rejected at load, and the composed-runes guide documents nested models
    by bjornolofandersson
  2. 6e4cec3
    Created (ready)by bjornolofandersson
  3. f315698
    Content editedby Claude
    Withdraw pullquote variant="plain"; v0.47.0 planned for SPEC-185 and SPE

Acceptance Criteria

  • A block field accepts model, and still matches the whole node (D2.1)
  • Its members resolve to dotted names (quote.attribution), to any depth
  • A composition places a member by its dotted name or the whole node by the field's name, not both (D2.2)
  • An unmatched child inside the node is reported content-unmatched, naming the field (D2.4)
  • A nested model on a non-block field is rejected at load, and the composed-runes guide documents nested models

References

  • SPEC-186: D2, D6.

Resolution

Completed: 2026-11-03

Branch: claude/milestone-v0-44-0-ism7ni

What was done

  • packages/types/src/content-model.ts: ContentFieldDefinition.model (a SequenceModel).
  • packages/runes/src/lib/resolver.ts:
    • takeField resolves a non-greedy block match's children with its nested model into name.member keys (deeper levels are already dotted);
    • dropped children are recorded per sink with the field path, and findUnmatchedContent publishes them for unmatchedInsideField.
  • packages/runes/src/lib/index.ts: content-unmatched names the nested model's field.
  • packages/runes/src/lib/composition.ts:
    • the model key, parsed with parseContentModel, sequence only, block fields only (content-nested-invalid);
    • slotFields adds dotted members;
    • the placement rule: whole or every member, never both (nested-placed-twice), with partial member placement reported through field-unplaced.
  • packages/runes/test/content-model-nested.test.ts; the composed-runes guide section and error rows.