WORK-692
ID:WORK-692Status:done

A content model reads groups and claims a trailing node

SPEC-186 D1 and D3: a field may be a group, which matches all of its required members in order or takes nothing; and a field may claim its container's last node with position: last.

Priority:mediumComplexity:moderateMilestone:v0.47.0Source:SPEC-186
claude/milestone-v0-44-0-ism7ni View source

Criteria completion

Criteria completion: 5 of 5 (100%) checked; history from Nov 1 to Nov 20%25%50%75%100%Nov 1Nov 2
Branches 2
claude/milestone-v0-44-0-ism7ni current done
changeset-release/main donemain done
History 3
  1. 1b0004f
    Content editedby bjornolofandersson
  2. 6f41760
    • ☑ A group matches when its required members match in order, takes nothing otherwise, and leaves the nodes it looked at for the next field (D1.1)
    • ☑ Group members may be optional; a group may be required or optional and may contain groups; member names resolve flat and must be unique across the model (D1.2–D1.4)
    • ☑ `position: last` reserves the container's last node when the field matches it, the fields before it reading the rest, and reserves nothing otherwise (D3)
    • ☑ At most one `position: last` field per model, and only as its last field; anything else is rejected at load
    • ☑ Composition definitions accept `group` and `position: last`, with load-time errors documented in the composed-runes guide (D6)
    by bjornolofandersson
  3. f315698
    Created (ready)by bjornolofandersson

Acceptance Criteria

  • A group matches when its required members match in order, takes nothing otherwise, and leaves the nodes it looked at for the next field (D1.1)
  • Group members may be optional; a group may be required or optional and may contain groups; member names resolve flat and must be unique across the model (D1.2–D1.4)
  • position: last reserves the container's last node when the field matches it, the fields before it reading the rest, and reserves nothing otherwise (D3)
  • At most one position: last field per model, and only as its last field; anything else is rejected at load
  • Composition definitions accept group and position: last, with load-time errors documented in the composed-runes guide (D6)

References

  • SPEC-186: D1, D3, D6.

Resolution

Completed: 2026-11-02

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

What was done

  • packages/types/src/content-model.ts: ContentFieldDefinition.group and position: 'last'.
  • packages/runes/src/lib/resolver.ts:
    • takeField, the single-field step factored out of resolveSequence with no change in behaviour;
    • takeGroup, which matches all-or-nothing, handles optional members and nested groups, and resolves members flat with the group's name set to true;
    • resolveSequence reserves the last significant node for a position: last field.
  • packages/runes/src/lib/composition.ts:
    • FIELD_KEYS gains group and position;
    • parseFields parses groups recursively;
    • checkModelFields runs per top-level field list, with the codes content-group-invalid, content-field-duplicate and content-position-invalid;
    • slotFields flattens group members into slots.
  • packages/runes/src/reference.ts: serialises and renders groups and position: last.
  • packages/runes/test/content-model-groups.test.ts.
  • Composed-runes guide: the "Groups" and "position: last" sections, and the three Errors rows.

Notes

  • A required group that fails takes nothing and drops nothing, which is D4.2's rule ahead of WORK-694. A required plain field still drops and reports its child until WORK-694 changes that.
  • The load checks run for composition definitions. Native code models are not checked; the resolver honours position: last only on the last field.