Phase 2 of the collaboration plan: - frontend/src/lib/docSync: TypeScript port of the block tree, operations, effects and order keys (kept identical to Go via shared test vectors), diff from the editor's flat list to operations, IndexedDB persistence, HTTP transport and DocSyncEngine (one request in flight, idempotent resends, backoff, 3 s/30 s polling, keepalive flush and leave on exit, block leases for focus, 20 s grace and new blocks). - Text changes carry the version the user saw when typing (first change wins when coalescing; a stale focused field keeps its old base), so a concurrent edit becomes a conflict copy instead of a silent overwrite. - DocumentEditor uses the engine instead of the 800 ms whole-document PUT; editors (not only owners) can edit, remote changes appear live. - Server: task IDs of linked blocks are delivered again, linked tasks follow ticked/deleted blocks after sync, note tags are derived, title changes notify other users; block IDs already used in another note are renamed (starter content with fixed IDs was empty for every further user). - Fix: unlinking tasks from blocks wrote to non-existent columns and never worked (error was swallowed). - Tests: Go/TS order key vectors, diff round trips, engine races (lost responses, offline + reload, edits arriving between typing and sending, stale focused field, duplicate first block), plus a Playwright test with two browsers against the real image (e2e/run.sh). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.9 KiB
Architektur
Überblick
Browser (React SPA, PWA-Manifest)
│ REST /api/v1/* Authorization: Bearer <JWT | mindsift_sec_…>
│ WebSocket /api/v1/ws Fallback: SSE /api/v1/events
▼
Go-Server (chi) ── internal/api Handler, Middleware, Alarm-Ticker
│ internal/ws Hub (Räume, Benutzer-Kanäle, SSE)
│ internal/auth JWT, bcrypt, API-Key-Hash
▼ internal/config
SQLite (modernc.org/sqlite, WAL) ── internal/storage
Das Frontend liegt per go:embed im Binary (cmd/server/main.go). Unbekannte Pfade liefern index.html (SPA-Routing); index.html, sw.js und manifest.json werden mit no-store ausgeliefert.
Stack
| Schicht | Technik |
|---|---|
| Backend | Go 1.23, go-chi/chi v5, go-chi/cors, gorilla/websocket, golang-jwt/jwt v5, x/crypto/bcrypt |
| Datenbank | SQLite über modernc.org/sqlite (reines Go, kein CGO), WAL, foreign_keys=ON |
| Frontend | React 18, TypeScript, Vite 5, Tailwind 3, framer-motion (Triage-Karten), lucide-react, prismjs (Code-Blöcke) |
| Auslieferung | Mehrstufiges Dockerfile (Node 22 → Go 1.23 → Alpine 3.21), Image git.dsit.me/sandrodach/mindsift |
Verzeichnisse
| Pfad | Inhalt |
|---|---|
cmd/server/ |
main.go, eingebettetes dist/ |
internal/api/ |
server.go (Routen, Handler, Alarm-Ticker, SMTP/Discord/Webhook-Versand) |
internal/storage/ |
sqlite.go (Schema, Migrationen, gesamte Datenlogik) |
internal/domain/ |
Strukturen (models.go), ID-Erzeugung, Tag-Extraktion |
internal/ws/ |
hub.go |
internal/collab/ |
Kern der Notiz-Synchronisation ohne Datenbank: Blockbaum, Operationen und Konfliktregeln, Sortierschlüssel, Leases, Long-Poll-Benachrichtigung |
internal/auth/ |
Tokens und Passwörter |
internal/config/ |
Umgebungsvariablen |
frontend/src/features/ |
triage, workspace, lists, teams, settings, capture, auth, help |
frontend/src/lib/ |
api.ts (REST-Client, Transport-Umschaltung WS/SSE), types.ts, markdown.tsx, theme.ts |
frontend/src/lib/docSync/ |
Notiz-Synchronisation im Browser: Portierung von internal/collab (model.ts, orderKey.ts), Vergleich Editor-Liste → Operationen (diff.ts), engine.ts, IndexedDB (persist.ts), HTTP (transport.ts) |
e2e/ |
Browser-Tests mit Playwright gegen einen echten Server (e2e/run.sh) |
frontend/src/themes/ |
CSS pro Theme |
Start und Laufzeit
config.Load()liest die Umgebung (siehe unten).storage.NewStore()öffnet SQLite (busy_timeout, WAL, max. 8 Verbindungen) und führtmigrate()aus.- SMTP-Werte aus der Umgebung werden in
system_settingsübernommen, sofern dort noch nichts steht. - Mit Demo-Flag legt
EnsureDemoAccountsdie Kontenadmin/adminunduser/useran. api.NewServerstartet den Alarm-Ticker (jede Minute) und baut den Router. Server-Timeouts: Lesen 15 s, Schreiben 30 s, Leerlauf 60 s. Beim Beenden wird sauber heruntergefahren.
Konfiguration
| Variable | Standard | Bedeutung |
|---|---|---|
PORT |
8080 |
HTTP-Port |
DATA_DIR |
./data (Container: /data) |
Verzeichnis der Datenbank |
DATABASE_URL |
leer | sqlite:///pfad/datei.db oder Pfad; ohne Angabe DATA_DIR/mindsift.db. Postgres-URLs werden nicht unterstützt (siehe Hinweis) |
JWT_SECRET |
festes Fallback (unsicher!) | Signaturschlüssel der Tokens; unbedingt setzen, siehe FLAW-04 |
BASE_URL |
http://localhost:<PORT> |
Basis-URL der Instanz |
ALLOW_REGISTRATION |
false |
Startwert; die Einstellung im Admin-UI (system_settings) hat Vorrang |
LOG_LEVEL / DEBUG |
info / false |
debug, info, warn, error; DEBUG=true erzwingt debug |
MINDA_DEMO (auch MINDSIFT_DEMO, DEMO_MODE) |
false |
Demo-Konten anlegen |
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM, SMTP_TLS |
leer / 587 / true |
Mailversand, auch im Admin-UI einstellbar |
Hinweis: DATABASE_URL mit postgres… führt dazu, dass der Standardpfad in DATA_DIR genutzt wird; Postgres ist nicht implementiert.
Authentifizierung
- Login mit E-Mail, Benutzername (aus dem E-Mail-Präfix) oder
login. Passwörter: bcrypt (DefaultCost). - JWT: HS256, Laufzeit 30 Tage, Claims
userId,email,displayName,tokenVersion,jti. Gültig nur, wenn Token nicht inrevoked_tokenssteht, das Konto aktiv ist undtokenVersionmitusers.token_versionübereinstimmt.token_versionwird bei Passwortänderung, Rollenwechsel, Deaktivierung und „Alle Sitzungen beenden“ erhöht. - Logout schreibt den Token-Hash in
revoked_tokens. - API-Keys:
mindsift_sec_<hex>, gespeichert als SHA-256, nur das Präfix bleibt sichtbar. Der Key gilt als Bearer-Token oder inX-API-Keyund authentifiziert als Besitzer (FLAW-07). - Registrierung: nur mit Einladungscode oder wenn
allow_registrationaktiv ist. Das erste Konto entsteht über/auth/setupund wird Administrator.
Hintergrundjob: Alarme
startAlarmTicker prüft jede Minute alle offenen, noch nicht gemeldeten Aufgaben mit Fälligkeit. Ist due_date <= heute (Serverzeit), sendet er über die Kanäle des Benutzers (email, discord, webhook) und setzt alarm_sent, sobald mindestens ein Kanal erfolgreich war. Details und Einschränkungen: modules/tasks.md, FLAW-12.
Build
Stage 1 node:22-alpine tsc, vitest, vite build → frontend/dist
Stage 2 golang:1.23 go vet, go test, go build → /app/mindsift (dist wird eingebettet)
Stage 3 alpine:3.21 nur Binary, ca-certificates, tzdata
Die Tests laufen beim Image-Build (Frontend: npm run test:run, Backend: go test -v ./...); ein Fehler bricht den Build ab. Frontend-Tests liegen unter frontend/src/**/__tests__/ (Vitest + jsdom + Testing Library, Setup in frontend/src/test/setup.ts).