This PR fixes a high-severity CodeQL alert on a playground that #3640 put on main. The interesting part is not the XSS, which is a one-line habit error. It is how it reached main: auto-merge was armed on #3640 while its author was still pushing to it, so the branch merged the instant its required checks went green, leaving the follow-up fix stranded on a branch whose PR had already closed.
10:58 push playground to #3640, arm auto-merge
11:00 CodeQL flags the playground: DOM text reinterpreted as HTML
11:01 write the fix, commit locally
11:02 #3640 MERGES (required checks were green; CodeQL is not required)
11:03 push the fix -> its PR already closed, branch now conflicts after the squash
result: a HIGH alert live on main, fix orphaned on a dead branch
Two gate facts made this possible and neither is a bug: CodeQL is advisory here, not a required check, so it cannot hold a merge; and auto-merge fires on required checks alone. Arming it early is a bet that nothing you learn in the next three minutes matters.
| Before | After |
|---|---|
out.innerHTML = '<span>FAIL</span> ' + '<code>/' + name + '</code>' |
show(
el("span","bad","FAIL"),
el("code", null, "/" + name)
) // textContent, never parsed |
Under the old code the second button's value would have been parsed as an element.
Here it renders as literal characters, because it is set with textContent and never reaches an HTML parser.
docs/fix--playground-xss/, derived from the branch, not the one the change happens to touch.