Implement Tefter per SPEC.md: NV-style notes with server, sync, PWA, desktop
- frontend/: Svelte 4 + TS + Vite app — omnibar (search-and-create), ranked diacritic-insensitive filtering, CodeMirror 6 markdown editor with 400ms autosave, marked+DOMPurify preview with [[wiki-links]], tag filter, undo toast, light/dark, narrow-screen stacked layout, IndexedDB store, pull-then- push sync engine with conflict handling, versioned cache-first service worker + manifest (installable PWA) - server/: tefterd — Go stdlib HTTP + modernc.org/sqlite, /api/v1 sync API (changes/batch/health/import), SHA-256 hashed bearer token, LWW-with- conflict-copies push rules, subcommands: init, token rotate, import simplenote, compact, backup (VACUUM INTO); embeds the frontend bundle - desktop/: Wails v2 shell — single instance, hide-to-tray (fyne systray), global Ctrl+Shift+Space hotkey, quit-on-close flag - Simplenote import (CLI + web upload), idempotent via source_id dedupe; verified against a real 242-note export - Makefile (frontend/server/server-all/desktop/test), GitHub release workflow, README with systemd/Caddy/backup docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
110
README.md
Normal file
110
README.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 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 |
|
||||
|
||||
## 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.
|
||||
|
||||
## 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)
|
||||
```
|
||||
|
||||
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).
|
||||
Reference in New Issue
Block a user