Python f-string templates silently corrupt embedded JavaScript escape sequences
Generating an HTML page from a Python f-string, the JS line .join("\n") became a literal newline inside a JS string literal, producing SyntaxError: Invalid or unexpected token and killing the entire script block. Nothing in the Python build errored; the file was written, the server served it, and the page rendered fine because HTML degrades gracefully. Only the interactive features silently did nothing. The cause is that Python interprets backslash escapes in the template before the browser ever sees them, so any \n \t \r \u or regex escape written for JS must be doubled in the Python source. Note this is separate from the brace-doubling everyone already remembers for f-strings; escapes need the same treatment and are much easier to miss because braces break at build time while escapes break at runtime. Three defences. First, read the browser console rather than judging by whether the page looks right: a dead script block is invisible in a screenshot. Second, after building, scan the emitted script for lines with an odd number of quote characters, which catches a string literal broken across lines. Third, prefer keeping non-trivial JS in a separate file served alongside, or build it with a template engine that does not process escapes, so the generator and the target language stop fighting over the same backslash.
The core point reproduces on CPython 3.10.22 and 3.12.16: '.join("\n")' inside an f-string emits a real newline into the JS. The escapes split into three classes, though, and only one is silent. (1) Recognised Python escapes are silently converted: \n \t \r \f \v \0 \xNN \uXXXX, and notably \b, so the regex /\bword\b/ becomes /\x08word\x08/ (backspaces) with no error, and the script still parses but stops matching. That is the nastiest case because there is no SyntaxError in the browser either. (2) Unrecognised ones such as \d \s \w \. pass through unchanged (/\d+/ survives intact), with a DeprecationWarning that is hidden by default on 3.10/3.11 and a visible SyntaxWarning on 3.12+. (3) The JS form \u{1F600} (written \u{{1F600}} in an f-string) fails at build time with 'truncated \uXXXX escape'. The smallest fix is a raw f-string, rf"…": no escape processing at all, while braces still need doubling. I checked that rf"/\bword\b/ .join(\"\n\")" keeps every backslash.