SVG source in frontend/public/icon.svg, rendered to 192/512/maskable PNGs and desktop/build/appicon.png; SVG favicon with PNG fallback; icons added to the service-worker precache. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tefter
Self-hosted, single-user notes with the classic Notational Velocity interaction model: one omnibar that searches and creates, instant filtering on every keystroke, no save buttons, no dialogs, everything on the keyboard. Markdown editing with preview, offline-first sync across Linux/macOS/Windows/Android/web.
One Go binary (tefterd) + one SQLite file. The web app (PWA) is embedded in the binary;
the desktop app is the same frontend in a Wails shell.
Quick start (server)
make server # builds frontend + tefterd into build/tefterd
./build/tefterd init # creates tefter.db, prints your auth token — save it
./build/tefterd # serves on :8420
Open http://localhost:8420, click ⋯ → set server URL + token → Save.
Keyboard model
| Key | Action |
|---|---|
| type in omnibar | filter notes instantly (title > prefix > contains > body; diacritic-insensitive) |
Enter |
open selected note / create note titled with the query |
↑ ↓ |
move list selection (editor live-previews) |
Esc |
editor → omnibar; omnibar → clear |
Ctrl/Cmd+L |
focus omnibar, select text |
Ctrl/Cmd+Delete |
delete note (undo toast, no confirm) |
Ctrl/Cmd+Shift+P |
toggle markdown preview ([[wiki-links]] open/create notes) |
Ctrl/Cmd+K |
cycle tag filter |
Ctrl/Cmd+J / +Shift |
next / previous note while editing |
CLI
tefterd # serve (default), flags: --listen :8420 --db tefter.db
tefterd init # create db + print token
tefterd token rotate # new token (old one stops working)
tefterd import simplenote export.zip # idempotent Simplenote import (also in the web UI)
tefterd compact --days 90 # purge old tombstones
tefterd backup /backups/tefter.db # consistent snapshot (VACUUM INTO)
tefterd version
Environment: TEFTER_DB, TEFTER_LISTEN override the flag defaults. No config file.
Deployment
tefterd listens on plain HTTP; put TLS in front of it.
systemd — /etc/systemd/system/tefterd.service:
[Unit]
Description=Tefter notes server
After=network.target
[Service]
User=tefter
ExecStart=/usr/local/bin/tefterd --db /var/lib/tefter/tefter.db --listen 127.0.0.1:8420
Restart=on-failure
[Install]
WantedBy=multi-user.target
Caddy (two lines):
notes.example.com {
reverse_proxy 127.0.0.1:8420
}
Backup = one file:
tefterd backup /backups/tefter-$(date +%F).db --db /var/lib/tefter/tefter.db
Clients
- Web / Android: open the server URL, install as PWA (Chrome: “Add to home screen”). Fully offline-capable; edits sync on reconnect.
- Desktop:
tefter-desktop(Wails). Single instance, closes to tray, global shortcutCtrl+Shift+Space(change with--hotkey/TEFTER_HOTKEY;--quit-on-closedisables the tray behavior). Works with no server at all — configure sync later under⋯.
Sync model
Pull-then-push, last-write-wins with conflict copies (Simplenote-style). Concurrent
edits of the same note never silently overwrite: the loser comes back as a new note
tagged conflict with “(conflicted copy …)” in its title. Delete-vs-edit: the edit wins.
Every client keeps a full offline copy in IndexedDB; a crash mid-sync loses nothing.
Development
make dev # vite dev server (proxies /api to :8420)
make test # go vet + go test (uses notes.zip for a real-import test when present)
make server # build/tefterd for this machine
make server-all # linux/amd64, linux/arm64, darwin/arm64, windows/amd64
make desktop # Wails build (needs wails CLI + GTK/WebKit dev packages on Linux)
Repo layout: frontend/ (Svelte + TS, all app logic incl. offline store + sync engine),
server/ (Go, stdlib HTTP + modernc.org/sqlite, no CGO), desktop/ (Wails shell).