Glossa

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

That trailing comment must NOT cause the walker to mark "sourceMappingURL" — it's inside a fenced code block. The same term sourceMappingURL appears once in this paragraph and should be marked there.

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.