created
2026-07-26
updated
2026-10-30

Searching notes

Searching notes

track search looks through the vault three ways, chosen with --scope:

ScopeWhat it matches
titleNote titles and #tag filters, ranked by how closely the title matches.
bodyThe full text of every note body, ranked by relevance.
pathThe note whose file the query names.
all (default)Title hits, then body hits not already found, then the named file.

Back to track. The CLI page lists the surrounding commands.

Readable editor filenames

The opt-in editor view keeps the real note at note/<id>.md and creates a local symlink named <safe-title>--<id>.md for editing. The suffix is intentional: titles can collide after filename sanitization or truncation. Track's Telescope labels can still show just the title while opening the full alias path.

Choose an absolute, machine-local directory outside the vault and outside OneDrive or other synchronized folders. Set TRACK_EDITOR_VIEW in the environment used to start Track and Neovim, then build the view:

export TRACK_EDITOR_VIEW="$HOME/.local/state/track-editor-view"
track editor-view
track editor-path --path /absolute/vault/note/1785024000000.md
track editor-path --path /absolute/vault/note/1785024000000.md --check

The ordinary JSON path remains the canonical file; editor_path is the view path. --check validates without creating an alias. Use the returned view root when configuring a generic Telescope file picker, and ensure its finder includes file symlinks. Searching the original vault still lists ID filenames. LSP reference pickers get their filenames from the server's returned URIs, not from Telescope's working directory. An already open canonical buffer keeps its existing URI to avoid opening a second editing owner; close it before reopening through the view if you want the readable filename.

Names are pinned after creation, including across editor restarts: changing a title does not retitle or delete an existing alias. The filename may therefore show an older title. This initial mode has no automatic cleanup or retitling; do not delete the view while an editor holds its paths open. Keep using ordinary canonical paths if this tradeoff does not suit your workflow.

The Neovim integration checks alias identity around saves and requires link-preserving save settings. If an external formatter replaces a symlink with a regular file, Track reports a conflict and preserves that file rather than copying it over the master or deleting it. Do not use arbitrary atomic-replacement formatters against alias paths. Missing or changed targets must be resolved before saving; a stale alias is not a way to recreate a deleted note. The view is local generated state, never a second copy to sync.

This mode is available on Linux and macOS; Windows is rejected. Linux is the tested platform for this initial implementation, not a promise about every filesystem. The guard detects duplicate buffers within one Neovim process, not concurrent edits from other processes. Use one editing session per note. This is a Track/Neovim integration, not a guarantee for other language servers or editors. Each vault has a separate LSP server: cross-vault heading/block positions are read from disk, not another server’s unsaved text. Keep canonical editing as the fallback on untested filesystems. Body links are independent of filenames: see Linking notes for opt-in ID targets with title labels.

A plain query matches titles with implicit-AND terms, an uppercase OR between alternatives, and a #tag prefix to filter by tag:

track search --query "graph OR web"
track search --query "#zettel workspace"

Finding a note by its file

A note's file is named from its id, so naming the file names the note:

track search --query "note/1785024006000.md"
track search --query "1785024006000.md"
track search --query "1785024006000"

All three are the same lookup. The rule is exact — a whole id, with or without a directory and a .md suffix — so a query that merely contains digits is still an ordinary text search rather than a shower of near-misses.

This is here for the times a note arrives named as a file: a coding agent that has been reading the vault refers to notes the way the filesystem does, and pasting what it said should find the note rather than nothing.

--scope body runs against a full-text index kept in the SQLite cache, so it does not re-read every file on each query. Terms combine like they do for titles: space-separated terms are an implicit AND — a note matches only when its body contains all of them — and an uppercase OR separates alternatives, so containerd OR podman matches either. Results are ranked by relevance (denser, more focused matches first). Each hit still reports the first matching line and a snippet, so a picker can jump straight to it:

track search --scope body --query "containerd runtime"
track search --scope body --query "containerd OR podman"
{"results":[{"note_id":900,"title":"Deploy notes","line":3,"snippet":"We run containerd as the runtime."}]}

The index stores the body verbatim, so text inside fenced code blocks is searchable too:

track search --scope body --query "nginx"   # finds a note that only mentions nginx in a YAML block

Because the body index is part of the same rebuildable cache as titles and links, an edit made outside track — a save from your editor, or a change pulled in by file sync — is reconciled automatically the next time you search. You never run a separate "rebuild search" step.

CJK and short queries

The body index tokenizes on character trigrams, so it matches substrings and works for scripts without spaces, such as Japanese or Chinese:

track search --scope body --query "テスト"

A query term shorter than three characters — a two-letter word, or a two-character word like 世界 — cannot form a trigram. Those queries fall back to a direct file scan, so they keep working; only very short queries pay that cost, and the common case stays on the fast index.

In the workspace

The Web workspace's search box is the same search, unscoped: it shows the title matches first, then, under Full text, the notes whose body matches, each with the line it matched on, and last, under File name, the note the query named by its file. Typing is debounced, so a query runs when you pause rather than on every keystroke. The group captions only appear once there is more than one group — a search that only matches titles looks exactly as it always has.

A published static site searches bodies too. It has no server to ask, so it runs the search in the browser over the bodies its bundle carries: the same grammar, the same #tag filter, the same substring matching, the same snippet — it finds the same notes. What it cannot do is rank by relevance, which lives in the index, so its full-text group is ordered most recently updated first instead. That is what the search above falls back to whenever a query cannot use the index either. The bodies are fetched the first time you search, not when the page opens.

It has no File name group at all, and cannot: a published bundle carries no source paths, because a note's file name is derived from its id and publishing it would give back the id that a slug: is there to keep out of the URL.