Skip to content

Commit 723073c

Browse files
committed
fix(website): fold JS escapes out of llms corpus prose too
The fenced-sample fold left prose alone, so the corpus still taught `html\`...\`` on 50 lines across 8 pages where the rendered page shows a plain backtick. Prose is copied out of the same template literal as a sample or a hole, so it folds the same way, and the module now has one rule instead of three paths that disagree. The fold runs after the two hole passes because those are what tell a literal `\${x}` from a render-time `${x}`, and before the restore so text they already folded cannot fold twice. The single entity decode still comes last. The two page regex fixes had nothing that would notice a regression, which matters because the fold's whole point is that it now copies an authoring error into the corpus verbatim. A letter after a backslash is never meaningful in a template literal, so a test rejects one across every docs page.
1 parent 9c3b2ec commit 723073c

3 files changed

Lines changed: 70 additions & 9 deletions

File tree

website/AGENTS.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,26 @@ website/
136136
`${x}` is render-time and gets dropped. Treating all
137137
three alike printed `<form action=\>` on 12 corpus
138138
lines, teaching an LLM the one shape invariant 12
139-
exists to rule out. A fenced sample and a kept prose
140-
hole are both copied out of page SOURCE, which is a JS
141-
template literal, so both fold their backslash escapes
142-
BEFORE that single entity decode. That order is the
143-
browser's own: JS cooks the literal first and the HTML
144-
parser only ever sees cooked text. The fenced half was
145-
missing, which is why the corpus printed
146-
`<form action=\${createPost}>` on 5 lines while the
147-
rendered page showed `<form action=${createPost}>`.
139+
exists to rule out. EVERYTHING it emits is copied out
140+
of page SOURCE, which is a JS template literal, so
141+
every path folds its backslash escapes BEFORE that
142+
single entity decode: a fenced sample at capture, a
143+
kept hole as it parks, and ordinary prose in one pass
144+
once the hole passes have run. That order is the
145+
browser's own, since JS cooks the literal first and the
146+
HTML parser only ever sees cooked text. The prose fold
147+
runs AFTER the hole passes because those are what tell
148+
`\${x}` (literal text) from `${x}` (render-time, and
149+
dropped), so folding first would drop the literal.
150+
Only the hole half existed, which is why the corpus
151+
printed `<form action=\${createPost}>` on 5 lines and
152+
``html\`...\` `` on 50 more, where the rendered page
153+
shows `<form action=${createPost}>` and a plain
154+
backtick. A backslash surviving into the corpus is now
155+
one an author WROTE as `\\`, so a page that escapes a
156+
letter (`/\s+/g`, which cooks to `/s+/g` and rendered
157+
that way live on two pages) is a page bug, guarded by
158+
a test rather than by convention.
148159
modules/
149160
ui/components/ GITIGNORED mirror of the @webjsdev/ui registry sources,
150161
written by scripts/copy-registry.mjs. NEVER hand-write

website/lib/docs-llms.server.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,20 @@ export function bodyToMarkdown(raw: string): string {
283283
// behind a nested hole.
284284
.replace(/\$\{(?:[^{}]|\{[^}]*\})*\}/g, '');
285285

286+
// Prose is copied out of the same JS template literal as a hole or a
287+
// sample, so it folds its escapes too. Without this the corpus taught
288+
// ``export a function returning html\`...\` `` on 50 lines across 8 pages,
289+
// where the rendered page shows a plain backtick, which is the same
290+
// disagreement the two hole passes above and the sample capture were
291+
// written to remove.
292+
//
293+
// AFTER those hole passes, never before: the escaped-hole pass is what
294+
// tells `\${x}` (literal text a reader sees) apart from `${x}` (render-time
295+
// and dropped), so folding first would erase the distinction and drop the
296+
// literal. BEFORE the restore below, so the text those passes already
297+
// folded cannot fold a second time. The single decode still comes last.
298+
body = unescapeJs(body);
299+
286300
// Restore kept holes. A kept hole can itself contain a parked sentinel (an
287301
// escaped hole nested inside a string-literal one), so this repeats until
288302
// none is left. Replacing once emitted the inner sentinel verbatim, which

website/test/ssr/docs-llms.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,42 @@ test('a fenced sample folds its escapes before it decodes its entities', () => {
245245
assert.equal(bodyToMarkdown('html`<code-block>a &am\\p; b</code-block>`'), '```\na & b\n```');
246246
});
247247

248+
test('prose is unescaped too, since it comes out of the same template literal', () => {
249+
// A hole and a sample were folded before prose was, so the corpus still
250+
// taught ``returning html\`...\` `` on 50 lines across 8 pages where the
251+
// rendered page shows a plain backtick.
252+
assert.equal(bodyToMarkdown('html`<p>returning <code>html\\`...\\`</code></p>`'), 'returning html`...`');
253+
254+
// The fold runs AFTER the hole passes, never before. Folding first would
255+
// turn `\${x}` into `${x}`, which the dynamic-hole pass then drops, losing
256+
// the literal text a reader actually sees.
257+
assert.equal(bodyToMarkdown('html`<p>a \\${x} b ${y} c</p>`'), 'a ${x} b c');
258+
});
259+
260+
test('no docs page escapes a letter, which the extractor would fold away', async () => {
261+
// A page body is a JS template literal, so `\s` cooks to a bare `s`: the
262+
// LIVE page rendered `replace(/s+/g, '-')` on /docs/backend-only and
263+
// `/;s*/` on /docs/websockets until those two were corrected to `\\s`.
264+
// The extractor is faithful, so it copies that damage into the corpus as a
265+
// teaching sample, and the corpus's only reader is an LLM.
266+
//
267+
// Escaping a letter or a digit is never meaningful here. `\``, `\$` and
268+
// `\\` are the escapes a template literal genuinely needs, and punctuation
269+
// escapes are at worst redundant. So a letter or digit after a backslash
270+
// is always the mistake above, and this is the only thing that would
271+
// notice the next one.
272+
const offenders: string[] = [];
273+
for (const page of await getDocPages()) {
274+
const src = await readFile(new URL(`../../app${page.path}/page.ts`, import.meta.url), 'utf8');
275+
// Pair-consuming, so the `\\` in a correctly authored `\\s` is eaten as
276+
// one escape and its `s` is never read as escaped.
277+
for (const m of src.matchAll(/\\([\s\S])/g)) {
278+
if (/[A-Za-z0-9]/.test(m[1])) offenders.push(`${page.path}: \\${m[1]} in ${JSON.stringify(src.slice(Math.max(0, m.index - 30), m.index + 30))}`);
279+
}
280+
}
281+
assert.deepEqual(offenders.slice(0, 5), [], `${offenders.length} docs-page escapes fold to a bare letter`);
282+
});
283+
248284
test('a kept hole nested inside another leaves no sentinel in the output', () => {
249285
// The two keep passes run in sequence, so a string-literal hole can park
250286
// text that already contains an escaped hole's sentinel. Restoring once

0 commit comments

Comments
 (0)