Build Napkin
A local idea collection tool whose hard problems are capturing a thought without ever losing a keystroke, keeping a search index that can be thrown away and rebuilt from the files, and links that survive a rename.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Tauri 2 needs both, and the global shortcut and file watcher come from the native side.
Get it nodejs.org and rustup.rs, then the Tauri prerequisites for your platform. open ↗
Verify
node --version prints v22 or higher and cargo --version succeeds - installfree
Why The whole search design rests on FTS5 and on external content tables, and a build without the extension fails in a confusing way.
Get it Most distributions ship it enabled; confirm rather than assume. open ↗
Verify
Creating a virtual table using fts5 succeeds, and so does creating one with the trigram tokeniser - have readyfree
Why Every destructive test in this build runs against a real folder, and the copy is what lets you run them without fear.
Get it Point the application at a folder you own, and keep a copy outside it before the first run.
Verify
A full scan indexes the copy and the counts match the file count - have readyfree
Why Search timing and rebuild duration only become real above a few thousand files, and that is exactly where a tokeniser choice starts to hurt.
Get it A script that writes several thousand Markdown files with plausible text and links.
Verify
A full rebuild and a search both complete within your stated times - decidefree
Why Each is asserted by a test, and the tokeniser in particular is a trade-off that should be written down rather than inherited.
Get it Write down the tokeniser per column with its reason, the capture write debounce, and how many folder snapshots are kept.
Verify
All three are in the configuration or the schema, and named in tests
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- notes(id, path, title, created_at, updated_at, mtime, size, content_hash) · a mirror of the folder, reproducible by a scan - notes_fts(title, body) · FTS5 over the notes content table, with triggers keeping it in step - links(from_note_id, to_note_id, raw_target, resolved, occurrences) · unresolved links are kept and shown, never dropped - tags(note_id, tag) · parsed out of the text, which stays the only authority - scan_state(last_full_scan_at, last_watch_event_at, files_seen, files_indexed, rebuild_ms) - journal(seq, note_id, at, fragment) · appended as you type, replayed only after a crash - Front matter carries the note id and its creation time. Everything else is derived, so deleting the database costs you a rebuild and nothing more.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
NOTES_DIR | required | ~/Documents/notes | The notes themselves. This is the thing to back up. |
INDEX_PATH | required | ~/.local/share/napkin/index.db | The index. Safe to delete; it rebuilds from the folder. |
CAPTURE_SHORTCUT | required | CommandOrControl+Shift+Space | The global shortcut. Capture speed is the product. |
WRITE_DEBOUNCE_MS | required | 400 | How long after a keystroke the file is written atomically. |
SNAPSHOT_DIR | required | ~/.local/share/napkin/snapshots | Folder snapshots. The restore is exercised in the suite. |
SNAPSHOT_KEEP | required | 14 | How many snapshots to retain before the oldest is deleted. |
The build, in order
Capture that cannot lose the thought
A global shortcut gives you a text box in under a second, and a crash mid-sentence loses nothing.
The whole product lives or dies on this taking under a second.
Files
src-tauri/src/shortcut.rssrc/capture/Capture.tsxThe journal is the thing that survives the crash.
Files
src/store/journal.tstests/journal.test.tsTemporary file, flush, rename. Never write in place.
Files
src/store/atomic.tstests/crash-replay.test.ts
done when · tick each as it passeswatch out- Writing the note only when the window closes loses the whole thought to a crash or a battery, and writing the real file on every keystroke leaves a half-written file when the process dies mid-write, which is worse because it looks like a note. Append to a journal, write the file atomically on a debounce, and replay the journal on start.
The database is an index you can delete
Deleting the database and restarting reproduces every note, tag and backlink from the folder.
The folder is the truth and it can be edited elsewhere.
Files
src/index/scan.tssrc/index/watch.tsExternal content without triggers is an index that lies.
Files
src/index/schema.sqltests/fts-sync.test.tsAnything recoverable should be recoverable from a button.
Files
src/index/rebuild.ts
done when · tick each as it passeswatch out- An FTS5 external content table that is updated without the matching triggers goes quietly out of step with the table it indexes, and a search then returns rows whose content is NULL, which reads as an empty note rather than as a broken index (checked against the SQLite FTS5 documentation, September 2026). Write the three triggers, and make the documented rebuild command a button in the application rather than something you remember at three in the morning.
Finding the note you half remember
Search behaviour for short and partial words is a decision you made, not a surprise you discovered.
Both options are defensible; the undocumented one is not.
Files
src/index/schema.sqldocs/search.mdThe shortest query is the one that surprises people.
Files
tests/short-query.test.tsFive thousand notes, generated, before you trust the timing.
Files
scripts/generate-notes.tstests/search-perf.test.ts
done when · tick each as it passeswatch out- Indexing with the default tokeniser means a search for a word buried inside a longer one never finds it, and switching everything to the trigram tokeniser fixes that while silently returning nothing for any query shorter than three characters and inflating the index (both checked against the SQLite FTS5 documentation, September 2026). Decide per column, document the choice, and put a test on the short query so the behaviour is visible.
Links that survive a rename
Renaming a note leaves every link to it working and every backlink intact.
The id is the link; the title is the label.
Files
src/links/parse.tstests/rename.test.tsA broken link that silently disappears is a lost thought.
Files
src/links/unresolved.tsOne source, so the two views cannot disagree.
Files
src/links/backlinks.ts
done when · tick each as it passeswatch out- Linking by filename or title means renaming one note breaks every link into it, and because the links still look like text nothing errors: you only find out months later when a backlinks panel is mysteriously empty. Link by id, resolve the title at render, and list unresolved links so a broken one is visible the same day.
Already portable, and provably soproduct builder
Everything is readable without this application, and a folder copied elsewhere still works.
Add front matter; change nothing else.
Files
src/import/folder.tstests/import-idempotent.test.tsA backup nobody has restored is a hope.
Files
src/backup/snapshot.tstests/restore.test.tsShow both. Never merge, never quietly adopt one.
Files
src/index/conflicts.ts
done when · tick each as it passeswatch out- Keeping the notes in a folder that a file sync tool watches feels like free sync until two devices edit the same note offline and the tool drops a second file beside it with 'conflicted copy' in the name, which your scanner then imports as a separate note. Detect the pattern, surface both versions side by side, and never merge them automatically.
That is the whole plan for Napkin. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Multi-device sync.
- Collaboration and sharing.
- A mobile client.
- Importers for every other notes product.
- conflict-safe multi-device sync
- mature importers and exporters
- collaborative editing and sharing
- native mobile clients
- A daily note that is just a note with a predictable name, once capture has been reliable for a month
- Saved searches stored as files in the folder, so they travel with the notes
- A graph view built from the links table, which already holds everything it needs
Need the files? The project pack on the verdict page hands your agent the whole brief · more notes & knowledge.