Skip to content

Search and navigation

lukataylor-pixel edited this page May 3, 2026 · 1 revision

Search and navigation

Soffit indexes every markdown file in your workspace as soon as you open it. The index is what powers fast search, wiki-links, backlinks, tags, and the outline. It rebuilds incrementally on FSEvents — there's nothing to refresh manually.

Quick palette — ⌘P

Fuzzy file jumper. Types are matched against:

  1. File name (highest weight)
  2. Title (the first H1 in the file, falls back to the file stem)
  3. Headings (any ##, ###… inside the file)
⌘P  → "ember"  → opens prds/ember-retro-v1.md
⌘P  → "vote"   → opens stories/us-003-vote.md
⌘P  → "what"   → opens spec.md (matched on the "What to look at" heading)

to navigate, to open, esc to close. Click anywhere outside also closes.

Full-text search — ⌘⇧F

Same palette UI, different mode. Searches the content of every indexed markdown file. The first matching line shows as a snippet so you can disambiguate.

⌘⇧F → "regression"   → finds three files mentioning the term
⌘⇧F → "TODO"         → every TODO across the workspace, ranked
⌘⇧F → "$E = mc^2$"   → matches verbatim including the math

The index keeps a lowercase copy of every file's content in memory. For workspaces with thousands of large files this could grow noticeable; v0.4's heuristic is to skip .git/, .build/, and node_modules/ directories during the walk.

Find / Replace — ⌘⌥F

When you actually need to change something across files. Type the find string, optionally a replacement, click Find → see the per-file matches. Click any row to open that file at the line. Click Replace All to rewrite every matched file atomically and refresh the index.

Case sensitivity is a per-search toggle (the Aa button).

Wiki-links — [[Note Name]]

Type [[ and write a note name. In Preview, the link is clickable. In Source, -click opens it. Aliasing is supported: [[real-note|displayed text]].

If the target doesn't exist yet, Soffit creates <active-folder>/<Name>.md with a # <Name>\n stub and opens it. This is the fastest way to grow a knowledge graph: just write what you mean and let the file get created.

Resolution rules:

  • Case-insensitive match on the file stem (no extension)
  • Searches the entire workspace, not just the current folder
  • First match wins (sorted by file path)

Backlinks panel

In any markdown panel's status bar, click the chain-link icon. The side panel lists every other markdown file whose wiki-links resolve to the active file. Click any entry to open it.

Backlinks are read directly from the index, so they update as the workspace updates — no manual refresh.

Tags

Tags can live in the body (#projectx) or in YAML frontmatter (tags: [a, b, c]). Both end up in the index.

In the sidebar, expand the Tags section to see every tag with its count. Click a tag to open the first matching file. Right-click for the full list.

Outline

In any markdown panel's status bar, click the bulleted-list icon. The side panel shows the heading hierarchy of the active file, indented by level. Click any heading → editor scrolls to that line. (If you're in Preview mode, Soffit auto-flips to Split so the scroll has somewhere to land.)

Daily notes — ⌘⇧D

Opens (or creates) daily/YYYY-MM-DD.md in the workspace. If .soffit/templates/daily.md exists, the new file is seeded from it with {{date}} and {{long_date}} substituted.

A daily note is just a regular markdown file, so wiki-links, backlinks, tags, and the outline all work normally.

Recent files

Sidebar → Recent expands to the last 20 files you've opened. MRU-ordered, deduped, persisted to UserDefaults. Right-click for Open or Reveal in Finder. Clear at the bottom wipes the list.

How fast is it?

The index walks the workspace concurrently with Task.detached. On a 500-file workspace it takes ~150ms; on a 5,000-file workspace it takes ~2s and runs entirely off the main thread, so the UI stays responsive.

Per-keystroke search is sub-millisecond up to ~5,000 files because:

  • Quick palette uses fuzzy scoring on cached lowercased() strings
  • Full-text uses String.range(of:) against pre-lowercased content
  • Both queries are bounded to ~50 result rows

For very large workspaces, consider opening a sub-folder as the workspace root rather than your ~/Documents.

Clone this wiki locally