Repository navigation
Speculative Lazy Notation
Speculative design. Open, not settled — thinking and proposals, not shipped behavior. See Roadmap for what's in flight.
Status: speculative (2026-10-09). Part of BL-68 (compute-engine's live-engine APIs). Today every host
collects every package's LaTeX entries before it builds an engine, because LatexSyntax took its
dictionary at construction. compute-engine 0.145 added ce.latexSyntax.addEntries(entries), so a library's
notation can join a running engine. See Speculative Lazy Engine for the resolver
this hangs off.
-
frontend/src/engine.ts:configureLatex(entries)pushes onto a list read once when the shared engine is built; it throws once the engine exists. - The site (
web/.vitepress/theme/index.mts) builds that list fromvirtual:notation-entries, which imports every package's notation entry, and runs it from__notatioEngineSetup. - Kernels, the CLI and prerender each build their own engine from the same combined list; MathLive macros come from the same combination (
configureMacros). - Libraries themselves are already lazy: the resolver declares a package when an expression names one of its heads.
Measured: all fifteen notation entry modules together are about 16 KB. Laziness buys structure, not bytes:
a library that arrives from node_modules (installed, #688) brings its own notation; nothing in the host
lists packages; no host has to be rebuilt to learn a new trigger.
Step 1: a live configureLatex. Entries given after the engine exists go to addEntries at once, as
configureEngine applies at once. Same merge rule as now (a named entry replaces an earlier one; a new
trigger wins over the default). Removes the "dictionary is fixed" assumption and the throw. No behavior change.
Step 2: notation loads with its library. The resolver, on declaring a package, also registers that
package's notation (addEntries). virtual:notation-entries and the up-front combination go. Constraints:
-
A package-only trigger parses before its package is loaded.
parseForalready parses, declares what the result names and parses again. A trigger that parses to an error names nothing, so the resolver needs a trigger→package index (the manifest already publishes triggers, #563), consulted on the text before the first parse. A parse error naming an unknown command is the fallback. - Load order must not change answers. Two packages with one trigger: today the combination refuses a collision; keep it a hard, build-time error over the whole catalogue (the index is built from all packages), never decided by which loaded first.
-
Prerender equals the site. The build's engine (
makeEngine) loads the notation of what the page names, the way the site does, so prerendered LaTeX prints as the browser does. - Macros stay eager. Pages with no engine typeset kernel answers with KaTeX, using MathLive macros from the combined notation. They are light; the combination stays for macros only.
-
A head's declaration and its notation load together, so
.latexof an answer never runs before the entries for its head exist. - Replay does not depend on the dictionary: the session journal stores parsed MathJSON.
-
Rule strings (LaTeX-string rules) are parsed with the default dictionary, not the engine's (a compute-engine ROADMAP item), so they don't see
addEntriesnotation.
Tests first: a package-only trigger parses and serializes through a lazily loaded engine; two loading orders
give the same text; prerender equals site for a page with a package-only trigger. Then step 1, then step 2
behind the same tests. prerender.ts / makeEngine last, after the open layout-leaf prerender work lands.
- Does the trigger index live in the manifest build (per library
declares.json/notation.json) or is it derived from the entries at resolve time? - Kernels (workers) never parse LaTeX; do they need the entries at all, or only the main-thread engine and prerender?