I have an item P25_VALUES that contains the delimited string “Jupiter:Saturn:Uranus:Neptune”, and a button that will execute a dynamic action on click to execute JavaScript that will use the apex.util.applyTemplate() function to transform the data using the LOOP template directive.

If I paste the JS my my browser console, I get the data I would expect
console.log(apex.util.applyTemplate('<ul>{loop ":" P25_VALUES/} <li>Value(&APEX$I.): &APEX$ITEM.</li> {endloop/}</ul>'));
<ul> <li>Value(1): Jupiter</li> <li>Value(2): Saturn</li> <li>Value(3): Uranus</li> <li>Value(4): Neptune</li> </ul>
However, when I press the button to execute the JS action, the data is not substituted
<ul> <li>Value(): </li> <li>Value(): </li> <li>Value(): </li> <li>Value(): </li> </ul>
It's not quite the same as the item being interpreted as empty, as running that manually returns
<ul></ul>
I've tried using comma as the delimiter instead - same effect.
I do not have this problem when using the IF directive
console.log(apex.util.applyTemplate('{if P25_VALUES/}&P25_VALUES.{else/}No values present.{endif/}'))
Which is reminiscent of this post, but that's apparently fixed in 21.1 - I'm on 24.2.8. And the suggested workaround had no affect.
What am I missing?