WORK-604
ID:WORK-604Status:done

Pass authored tags through a mixed list|tag:x field with emitTag

resolveSequence's emit branch skips anything that is not a list, then replaces the field with only the emitted nodes (packages/runes/src/lib/resolver.ts:194):

Priority:highComplexity:simpleMilestone:v0.38.0Source:SPEC-003

Criteria completion

Criteria completion: 5 of 5 (100%) checked; tracking started on Sep 24, no incremental history yet0%25%50%75%100%Sep 24Oct 11

Tracking started Sep 24 — check back for trends.

Acceptance Criteria

  • A greedy list|tag:x field with emitTag yields both the emitted and the authored tags, in document order
  • A regression test covers the interleaved case — list, authored tag, list — and asserts document order, not just count
  • A list-only field with emitTag is unchanged (cast, audio, plot)
  • refrakt contracts --check and npm run seo:baseline:check report no drift
  • npm test passes unchanged

Approach

Pass a non-list node through into tagNodes rather than continue-ing past it. It is already a tag and needs no conversion — only to keep its position.

Verified before filing. Given - Song A, - Song B, {% track name="Song C" /%}, - Song D against such a field, the resolver returns ['Song A', 'Song B', 'Song D'].

The result is a homogeneous, document-ordered array, which is better than cast's current shape: cast collects list items and explicit tags in two separate fields and concatenates (const allMembers = [...asNodes(resolved.members), ...asNodes(resolved.items)]), so all list-derived members land before all explicit ones and interleaving is lost. cast is not changed here, but the option becomes available to it.

Edge Cases

  • A non-greedy field whose single match is a tag rather than a list — the listNodes wrapper is [result[field.name]], so the same branch must handle it.
  • A field matching tag:x where x is not the emitTag name: the node still passes through unconverted, which is correct — the content model said it was acceptable.

References

  • BUG-030 — the bug this fixes
  • BUG-028 — the same silent-content-loss class
  • SPEC-003 — the declarative content model

Resolution

Completed: 2026-10-06

Branch: claude/v0-37-post-release-plan-dc3va1

What was done

  • packages/runes/src/lib/resolver.ts: the emitTag branch of resolveSequence passes a non-list node through into tagNodes in place instead of continue-ing past it, so a list|tag:x field resolves to one document-ordered array of tags.
  • packages/runes/test/resolver.test.ts: three tests — the bug report's interleaved input (list / authored {% track %} / list) asserting order, a list-only field unchanged, and a non-greedy field whose single match is a tag. The first and third fail without the fix.
  • site/content/extend/rune-authoring/content-models.md: the emitTag row states the mixed-field behaviour.

Notes

  • Latent: no built-in rune declared this combination, so rendered output is byte-identical (inspect over every rune × variant on both sites).
  • BUG-030 can close with this.