Thank you! That set me off on the right track. I modified your partial and came up with this, which avoids using any JavaScript:
{{ $headers := findRE "<h[2].*?>(.|\n])+?</h[2]>" .Content }}
{{ if and (ge (len $headers) 1) (ne $.Params.toc "none") }}
<div class="docs-right-nav">
<div class="docs-right-nav-inner">
<ul>
{{ range $headers }}
<!-- for each h2 element, do the following:
- apply Hugo's plainify function to strip the html and get the contents
- apply lower to turn it lowercase
- replace whitespace with hyphens
- strip punctuation -->
<li><a href='#{{ replaceRE "\\s" "-" (. | plainify | lower) | replaceRE "[,.!?;:]" "" }}'>{{ . | plainify }}</a></li>
{{ end }}
</ul>
<a class="js-download" href="index.pdf">Download page as PDF</a>
</div>
</div>
{{ end }}
However, this is really just a workaround. I would love to know why Hugo does this - in my case, the shortcode did nothing to the headings, it was just around them.