Developer reference for wikilink and term: link internals. For user-facing syntax see WIKILINKS.
_resolveWikilinkSelector(sel) — called on click and for data-autolabel spans:
- If
selcontains:or is a bare integer → used as-is (full nb selector, e.g.preciousfinds.ca:2,42) - Otherwise → search via
/api/list?q=<text>— resolved with title first, filename stem fallback:- Exact title match (case-insensitive)
- Filename stem match —
[[1b]]resolves to1b.mdeven if itstitle:is"1-1b — Wide shot"
_resolveWikilinks(container) — called after rendering. For data-autolabel spans, resolves the selector then fetches the note title via /api/note to set as display text. Falls back to leaving the text as-is on failure.
Results are cached in _wikilinkCache with a \x00 prefix key (separate from selector-keyed display-title entries). Cache is session-scoped — persists across note switches, cleared on page reload.
Wikilinks are converted to <span> placeholders before marked parses the body, so the markdown parser never sees them as plain text:
.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, (_, target, label) =>
`<span class="nb-wiki-link" data-selector="${target}" ...>${label || target}</span>`)Fenced code blocks and inline code are split out first — links inside backticks are left untouched.
When a wikilink has no pipe label ([[filename-stem]] not [[target|label]]),
the span gets data-autolabel="1". After rendering, _resolveWikilinks fetches
the note via /api/note and sets the display text as:
title = d.meta?.alias || d.title || d.filename || selPriority: alias: frontmatter first. This means [[WH-captive-cu-4f]]
displays as 4f (from alias: 4f) with no pipe label needed. The title:
frontmatter appears as a tooltip via the title attribute on the rendered link.
Convention (NbWeb-cine): embed the stable filename stem in the script;
let alias: supply the compact inline label automatically. Never embed bare
aliases ([[4f]]) as wikilinks — they resolve by title or filename stem only,
not by alias: field value.
Config dotfiles (.shots.md, .Takeout.md, .nb.md) are not indexed by nb,
so nb search never finds them. Two mechanisms make wikilinks to dotfiles work
transparently:
_search_notes() in app.py checks whether the query starts with .. If so,
it rglobs the notebook for matching filenames after the main nb search:
[[.shots]]→ globs for.shots.md→ findsshots/.shots.md→ selectorTakeout:shots/.shots.md[[.Takeout]]→ globs for.Takeout.md→ finds.Takeout.mdat notebook root
Accepts bare stem (.shots) or explicit name (.shots.md). The JS resolver
matches on filename stem as normal — [[.shots]] matches a result whose
filename is .shots.md because .shots.md.replace last extension = .shots.
GET /api/note falls back to direct filesystem lookup when nb show fails.
For any notebook:rel-path selector, it tries NB_DIR/notebook/rel-path
directly (must resolve within NB_DIR — path traversal safe):
[[Takeout:.Takeout.md]]→ skips search (has:),/api/notehits fallback[[Takeout:shots/.shots.md]]→ same path
Recommended form: [[.shots]] (bare stem) for same-notebook links — shorter,
and the search supplement handles it. Use [[Notebook:.file.md]] when linking
cross-notebook or when the stem is ambiguous.
Display label: data-autolabel fetches the note via /api/note and uses
meta.alias || title || filename. Config dotfiles typically have config: shots
in frontmatter; title: is usually absent. The config: field is not used as
autolabel — include a pipe label for a clean display: [[.shots|shots config]].
term: links execute arbitrary shell commands in the built-in terminal pane. The click handler:
- Extracts the URL from
href="term:..." - Decodes percent-encoding via
decodeURIComponent - Substitutes
{variable}placeholders with current-note context values - Sends the command to the terminal pane (opens pane if not already open)
Placeholder substitution happens at click time — not at render time. Values reflect the note that is open when the link is clicked, not when the page was rendered. This matters for templates: a term: link in a template is resolved fresh each time it's used.
Percent-encoding requirement: CommonMark disallows spaces in unquoted link URLs. Spaces and quotes in the href must be percent-encoded; the click handler decodes them before passing to the shell.
[hledger register "Bank:"](term:hledger%20register%20%22Bank:%22)Use urllib.parse.quote (Python) or encodeURIComponent (JS) when generating term: links programmatically. The org-to-nb-notes.py tool handles this for $ hledger lines in org tutorial files.
Safety note: term: links execute arbitrary shell commands. They are nb-web-only — Quartz does not evaluate the term: scheme and the raw link appears as literal text in static builds.
Inline {{...}} patterns are detected in _resolveInlineQueries during _enrichRendered. Each span gets:
data-provider— the provider name (hledger,tw,nb,fm,date,inline)data-query— the query string
Non-inline providers (hledger, tw, nb, fm, date) fire in parallel — they're cheap single-value lookups. inline includes are sequential (see RENDER_PIPELINE). fm (2026-08-04) is a thin wrapper — count-only, reuses the exact same _run_front_query/_parse_fm_scope the fm codeblock itself is built on; implementation details live with the codeblock's own notes: codeblocks.
_iq_strip — strips hledger report formatting (separators, commodity padding, column headers) down to a plain value suitable for inline text. Multiple result rows are joined with ·. This is why multi-row queries collapse into unreadable strings — use codeblocks for reports with more than one or two rows.
Patterns inside `code` or fenced blocks are never evaluated — code split happens before _resolveInlineQueries.
Depth guard for {{inline:}}: included notes never nest. {{inline:}} patterns in included content are silently dropped to prevent infinite recursion.