<select> fails to build when one of its children contributes no variants — <setup>, <sort> or <collect> alongside an <option> throws Cannot read properties of undefined (reading 'map'), so the whole document fails to render.
Reproduce
<select>
<setup><number name="n">5</number></setup>
<option><math>1</math></option>
</select>
Also reproduces with <sort> or <collect> in place of <setup>, in either order, with or without numToSelect. It does not reproduce with <conditionalContent>, <group>, <repeat>, <repeatForSequence>, <select>, <module> or <shuffle> in the same position — those build (some with an Invalid children for <select> warning, which is the expected complaint about a non-<option> child).
Cause
Select.determineNumberOfUniqueVariants builds numVariantsByChild from gatherVariantComponents(serializedComponent.children) — one entry per variant-bearing child. <setup>, <sort> and <collect> contribute none, so that array is shorter than the child list.
Select.getUniqueVariant then counts the children instead:
let numChildren = serializedComponent.children.length; // Select.js:897
let childrenToSelect = serializedComponent.children; // Select.js:898
With one <option> and one <setup>, numChildren is 2 while numVariantsByChild has length 1. The combination for index 1 reads numVariantsByChild[1] — undefined — so numberOfPossibilities becomes NaN. Every comparison against NaN is false, so the variantIndexLeft < chunksize * nCombinationsLeft guard falls to the else branch and the numberOfPossibilities > possibilitiesUsed filter drops every combination. The while loop exits with combinationIndexSelected still undefined, and combinations[undefined].map(...) throws at Select.js:986.
Candidate fix
Counting the variant-bearing children rather than all children removes all three failures:
let childrenToSelect =
serializedComponent.variants.descendantVariantComponents ??
serializedComponent.children;
let numChildren = numVariantsByChild.length;
With that change all three shapes build, and <select> with a <setup> sibling is clean (no warnings at all). src/test/variants/ and src/test/tagSpecific/select.test.ts — 103 tests — still pass. It has not been verified that variant selection is unchanged for existing documents, which is the part that needs care: the indices this function returns drive which option a seeded activity shows. A narrower alternative that provably cannot change any document that works today is to bail out instead of crashing:
if (numVariantsByChild.length < numChildren) {
return { success: false };
}
Any document in that state throws today, so nothing that currently renders takes the new branch; the select falls back to non-unique variants.
Why now
#1872 marks <setup>, <sort> and <collect> allowInSchemaAnywhere, which makes them legal children of <select> in the schema. The runtime failure is older than that PR — an author could always write the markup — but the editor used to flag it and now does not. Swept over every (container, composite) pair that PR makes newly legal, with a legal sibling child present (1520 shapes), these three are the only ones that fail to build.
🤖 Generated with Claude Code
https://claude.ai/code/session_01P3ayJn1UrbSMKdAwQYkDGw
<select>fails to build when one of its children contributes no variants —<setup>,<sort>or<collect>alongside an<option>throwsCannot read properties of undefined (reading 'map'), so the whole document fails to render.Reproduce
Also reproduces with
<sort>or<collect>in place of<setup>, in either order, with or withoutnumToSelect. It does not reproduce with<conditionalContent>,<group>,<repeat>,<repeatForSequence>,<select>,<module>or<shuffle>in the same position — those build (some with anInvalid children for <select>warning, which is the expected complaint about a non-<option>child).Cause
Select.determineNumberOfUniqueVariantsbuildsnumVariantsByChildfromgatherVariantComponents(serializedComponent.children)— one entry per variant-bearing child.<setup>,<sort>and<collect>contribute none, so that array is shorter than the child list.Select.getUniqueVariantthen counts the children instead:With one
<option>and one<setup>,numChildrenis 2 whilenumVariantsByChildhas length 1. The combination for index 1 readsnumVariantsByChild[1]—undefined— sonumberOfPossibilitiesbecomesNaN. Every comparison againstNaNis false, so thevariantIndexLeft < chunksize * nCombinationsLeftguard falls to theelsebranch and thenumberOfPossibilities > possibilitiesUsedfilter drops every combination. Thewhileloop exits withcombinationIndexSelectedstillundefined, andcombinations[undefined].map(...)throws atSelect.js:986.Candidate fix
Counting the variant-bearing children rather than all children removes all three failures:
With that change all three shapes build, and
<select>with a<setup>sibling is clean (no warnings at all).src/test/variants/andsrc/test/tagSpecific/select.test.ts— 103 tests — still pass. It has not been verified that variant selection is unchanged for existing documents, which is the part that needs care: the indices this function returns drive which option a seeded activity shows. A narrower alternative that provably cannot change any document that works today is to bail out instead of crashing:Any document in that state throws today, so nothing that currently renders takes the new branch; the select falls back to non-unique variants.
Why now
#1872 marks
<setup>,<sort>and<collect>allowInSchemaAnywhere, which makes them legal children of<select>in the schema. The runtime failure is older than that PR — an author could always write the markup — but the editor used to flag it and now does not. Swept over every (container, composite) pair that PR makes newly legal, with a legal sibling child present (1520 shapes), these three are the only ones that fail to build.🤖 Generated with Claude Code
https://claude.ai/code/session_01P3ayJn1UrbSMKdAwQYkDGw