DocumentationDecision recordsADR-0053
Math blocks and the dollar trigger
Notes need inline and display math. The portable Markdown spelling that Obsidian, GitHub, Pandoc, and most static site generators read is $...$ for inline math…
accepted
Context
Notes need inline and display math. The portable Markdown spelling that
Obsidian, GitHub, Pandoc, and most static site generators read is $...$ for
inline math and a $$ fence for display math. In the editor, $ already opens
the people menu, and person mentions export as $name. Math therefore has to
reuse the dollar in Markdown without taking the dollar away from people while
typing.
Rendering has to respect the performance contract: no startup or navigation cost, no parsing in the typing path, and nothing loaded from the network under the desktop CSP.
Decision
Two canonical nodes carry math:
math_block, a code-like textblock whose text is the TeX source. It exports as a$$line, the source, and a closing$$line.math_inline, an atom with atexattribute. It exports as$tex$.
Typing $ keeps opening the people menu exactly as before. There is no $...$
input rule. Math is created by:
/math(block) and/inline(inline) in the slash menu;mod+alt+efor inline math andmod+alt+shift+efor a math block, both rebindable, and both turning selected text into the TeX;- a line holding only
$$followed by Enter, which becomes a math block. The second$already closes the people menu, because a$right after another$is not a mention trigger, so Enter reaches the editor.
The Markdown reader only accepts strict inline math: no whitespace after the
opening $, no whitespace before the closing $, the closing $ not followed
by a digit, and both on one line. A lone $ada, $ada met $bob, and prices such
as $5 and $10 stay text. The serializer escapes a text $ as \$ only when
it would otherwise pair up into math on the way back in, so ordinary dollars and
person mentions export unchanged. The full-text index reads the same pattern
and indexes the TeX without its delimiters.
Rendering uses KaTeX, pinned exactly and wrapped by
apps/workspace/src/features/editor/math/render.ts. KaTeX and its stylesheet
are a dynamic import on the first math node a note view meets; its fonts are
bundled assets, so nothing leaves the device. Renders are memoized by source and
mode, so a revisited note paints from the cache in the same frame. First renders
run in an idle callback, and re-renders while typing are debounced. Output is
htmlAndMathml: the visual HTML is hidden from assistive technology and the
MathML is exposed. trust is off, so \href and similar commands render as
errors instead of links.
A math block shows its source only while the caret is in it, and arrow keys at a neighbouring textblock's edge move into that source explicitly, because the collapsed source would otherwise be skipped by the browser. Escape returns to the text after the block. Inline math opens a small popover field on Enter or click; Enter, Escape, and Tab apply and return to the text, and arrow keys at either end of the field step out of the node. TeX errors render as a quiet message and never throw.
Rejected alternatives
- A
$...$input rule. It collides with the people trigger while typing and would turn prices into math. - A different Markdown spelling, such as
\(...\)or amathcode fence. Other tools would not render it, and imported notes already use dollars. - MathJax. Larger, slower to typeset, and heavier to load lazily. KaTeX renders synchronously to a string with MathML included.
- Escaping every
$on export. Lossless, but it rewrites the Markdown of every note that mentions a price or a person.
Consequences
- Imported Markdown whose text happens to match the strict pattern, such as
a$b$c, now reads as math. That is the same rule other Markdown tools apply. - Inline math immediately followed by a digit (
$x$2) cannot be written in Markdown and reads back as text. TeX ending in a lone backslash has the same limit. SEARCH_INDEX_VERSIONmoved to 3, so existing workspaces rebuild their full-text index once.- The KaTeX chunk and fonts load on first use only; the main bundle does not grow beyond the node views and commands.
Follow-up: copied math, workspace macros, and chemistry
Plain text paste accepts \(...\) and \[...\], normalizing them to
inline dollar math and display fences. It also accepts strict dollar spans and
$$...$$ within prose. Code spans, fenced and indented code, escaped delimiters,
and paste into code blocks stay literal. Rich HTML keeps the existing clipboard
precedence.
Workspace macro definitions use a bounded mathMacros settings extension: at
most 64 backslash commands consisting of ASCII letters, each name at most 64
bytes and each nonempty definition at most 4096 bytes. The settings field uses
one \name = definition per line and supports #1 through #9 arguments.
Submitting the field lazily loads KaTeX and expands every command with sample
arguments before saving, surfacing malformed definitions and expansion loops
in settings.
set_math_macros is a distinct replicated workspace operation. It persists the
settings extension in canonical SQLite and applies optimistically in the
renderer. Existing update_settings operations remain device-local and preserve
the canonical macros, so a stale appearance-settings document cannot overwrite
workspace content. Workspace archives carry the definitions with settings.
The render cache includes the macro definition version. Its version is computed once per immutable macro set; cached note navigation does not serialize the settings again. Each math view subscribes only to the macro extension, refreshes through its idle render pipeline when it changes, and unsubscribes on destruction. KaTeX receives a fresh macro object so TeX expansion cannot mutate workspace settings.
Markdown exports retain the user's macro commands without expansion. Definitions are Skriuw workspace data and are absent from individual Markdown files. Other editors need equivalent macro definitions configured to render those files. This preserves editable TeX and avoids running a typesetter during export.
katex/contrib/mhchem loads alongside KaTeX in the same first-use lazy boundary.
Both inline and display nodes accept \ce{} and \pu{}. All extension code,
styles, and fonts are bundled locally. Equation numbering and cross-block
references remain outside this change.
On this page