# 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) ```sh 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 | | `Ctrl/Cmd+I` | note info (created / last synced) & version history | ## CLI ```sh 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`: ```ini [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: ```sh 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 shortcut `Ctrl+Shift+Space` (change with `--hotkey` / `TEFTER_HOTKEY`; `--quit-on-close` disables 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. **Version history:** every time an accepted sync overwrites a note, the server keeps the previous state (last 50 revisions per note). `Ctrl/Cmd+I` (or the “History” button under the editor) shows a note’s revisions; restoring one applies the old text as a new edit, so the replaced text itself stays in history — rollback is never destructive. `tefterd compact` drops history along with the purged notes. ## Development ```sh 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) ``` ## CI / Releases (Gitea Actions) `.gitea/workflows/ci.yml` runs tests on every push and, on a `v*` tag, builds `tefterd` for all four platforms plus the Linux desktop app and attaches them to a Gitea release. Requirements: Actions enabled on the repo and one Linux runner whose `ubuntu-latest` label maps to a Debian/Ubuntu-based image (the default `catthehacker/ubuntu:act-latest` works). ```sh git tag v0.1.0 && git push origin v0.1.0 # cut a release ``` macOS/Windows desktop shells need a native machine: install Go + Node + the Wails CLI there and run `make desktop` (the result lands in `desktop/build/bin/`). The server binaries for those platforms come out of the Linux runner via cross-compilation. 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).