You got me to thinking how can one create a template that guards against what happened (i.e. missing attribute). Basically you need a default filter, for state_attr(), that produces a value that can be consumed by the balance of the template
It’s a big insurance policy, but it’s better than relying on an if statement, which is an even bigger insurance policy.
I’ve learned to include this stuff EVERYWHERE because of the difficulty of debugging errors like this when something breaks that isn’t due to an immediately recent change to the code.
In this instance, I was complacent, and the seismic change was caused not by a code change but by an upgrade.