Code-heavy fixture
Prose is interleaved with fenced code blocks and inline code. The walker's policy (per plan/01_renderer.md) is that text inside <code> and <pre> is not a mark target — keeps syntax-highlighter overlays clean. This fixture stresses that policy: the same word appears inside and outside code, and only the outside occurrence should get marked.
Closures and the event loop
A closure captures variables from its enclosing scope. Outside a closure, hoisting still applies: variable declarations using var are hoisted; declarations using let and const are subject to the TDZ.
function makeCounter() {
// closure example — the inner function captures `count`
let count = 0;
return () => ++count;
}
// the word closure appears in this comment too — but it's inside <code>
// so the walker should NOT mark it.
After the fence, the term closure in prose is a mark target again.
A Promise wraps an eventual value. Anything with a .then method is a thenable. Resolved Promises queue microtasks; setTimeout queues macrotasks; the event loop alternates between them.
// `Promise` inline — should NOT mark
const p = new Promise(resolve => resolve(1));
p.then(v => console.log(v)); // microtask runs after current task
setTimeout(() => {}, 0); // macrotask
Inline code in prose: the Promise constructor is on the platform. Inside the backticks, "Promise" should NOT be marked. In the surrounding prose, Promise should be.
Iterators and generators
A generator function returns an iterator. An async iterator returns Promises. WeakMap and WeakRef are GC-friendly references.
function* range(n) {
for (let i = 0; i < n; i++) yield i;
}
const it = range(3); // iterator inside the fence — no mark
it.next();
// async iterator and generator both appear here
async function* gen() {
for await (const chunk of stream) yield chunk;
}
Workers and shared memory
Web Workers are background threads. Service Workers proxy network. SharedArrayBuffer plus Atomics enables lock-free shared state. structured clone is the default cross-realm copy; transferable objects skip the clone.
const w = new Worker('worker.js');
const buf = new SharedArrayBuffer(1024);
w.postMessage(buf); // shared
// the words SharedArrayBuffer, Atomics, transferable inside <code> — no marks
Modules
ESM files use the .mjs extension or "type": "module". CJS uses require(). An import map lets a bare specifier resolve to a URL. dynamic import is a runtime expression. top-level await waits during module evaluation.
// import map example, inside <script type="importmap">
{
"imports": {
"lodash": "https://cdn.skypack.dev/lodash"
}
}
const m = await import('./lazy.js'); // dynamic import
// top-level await is fine in ESM
// CJS example
const x = require('./x');
module.exports = { x };
Bundling
tree-shaking removes unused exports. code-splitting emits lazy chunks. source map files map minified code to source via the sourceMappingURL comment:
// sourceMappingURL=app.js.map
The term sourceMappingURL above is inside a fenced code block and must NOT be marked by the walker. The same term sourceMappingURL appears once in this paragraph and should be marked there. The hash is intentionally omitted from the code-block comment so Vite's source-map directive detector does not false-warn during the astro dev build.
Late syntax highlighter caveat
Some hosts run Prism / Shiki / highlight.js *after* the page renders. The widget mounts on DOMContentLoaded and may have already injected marks before the highlighter walks the <pre> content. Since the walker skips <code> content, no marks landed inside fences in the first place — the highlighter is free to do its thing. This fixture is the proof.
If a host wraps inline <code> in additional spans during highlighting (e.g. <code><span class="token">closure</span></code>), the walker still sees a <code> ancestor and skips. No marks inside, regardless of how the host rewrites the inner DOM.