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
claude/v0-37-post-release-plan-dc3va1 View source

Criteria completion

Criteria completion: 5 of 5 (100%) checked; history from Oct 6 to Oct 60%25%50%75%100%Oct 6Oct 6
Branches 3
claude/v0-37-post-release-plan-dc3va1 current done
main doneclaude/nifty-ptolemy-80epot readyclaude/v0-37-0-review-vqpl41 ready
History 2
  1. f68c5ce
    • ☑ 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
    by bjornolofandersson
  2. a798a2c
    Created (in-progress)by bjornolofandersson

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.