Trainingstagebuch · selbst gehostet · offline-fähig

Trainieren statt Tippen.

SATZIO protokolliert Krafttraining so knapp wie eine Notiz: 60 kg × 12, abhaken, weiter. Werte vom letzten Mal sind vorausgefüllt, das Training läuft auch ohne Netz, und alle Daten liegen auf deinem eigenen Server.

Lizenzkosten
keine Cloud-Abos, keine bezahlten APIs
Deployment
docker compose up -d --build
Plattformen
Web (PWA), Android, iOS via Capacitor
PUSH DAY Mo · 3/5

Bankdrücken

  • 40 kg×10W
  • 60 kg×12
  • 70 kg×10
  • 72,5 kg×8zuletzt

Schrägbankdrücken KH

  • 26 kg×10zuletzt
  • — kg×8Plan
lokal · 2 Ops in Outbox
SATZIO-App, Workout in der Listenansicht: Bankdrücken mit drei abgehakten Sätzen (40 kg × 10, 60 kg × 12, 70 kg × 10), darunter offene Sätze und ein laufender Pausentimer 2:31.

01 Notizen-Import

Alte Trainingsnotizen übernehmen, ohne KI-Cloud.

Text aus Apple Notizen, Google Keep oder einer beliebigen Textdatei einfügen. Ein lokaler, regelbasierter Parser erkennt Übungen, Sätze, Wiederholungen und Gewichte. Der Text verlässt dafür nicht das Gerät. Gewichte werden nie erfunden, und Mehrdeutiges wird markiert statt geraten.

notizen.txtEingabe
1Montag Push
2Bankdrücken 3x10 80kg
3Latziehen 12x55, 10x60, 8x65
4Bizeps 3 Sätze je 12 mit 14kg
5Dips +10kg 3x8
6Schrägbank 24x12, 26x10, 26x8
parseWorkoutNotes()Ergebnis
Vom Parser erkannte Übungen und Sätze
ZeileÜbung (Bibliothek)SätzeStatus
2Bankdrücken3 × 10 @ 80 kgerkannt
3Latziehen55×12 · 60×10 · 65×8erkannt
4Bizepscurls Kurzhantel aus „Bizeps“3 × 12 @ 14 kgerkannt
5Dips3 × 8 @ +10 kgerkannt
6Schrägbankdrücken Kurzhanteln aus „Schrägbank“12×24 · 10×26 · 8×26prüfen

Zeile 6: „Gewicht und Wiederholungen sind nicht eindeutig – bitte die Reihenfolge prüfen.“ In der Vorschau tauscht ein Tipp auf Gewicht/Wdh. tauschen die Werte, bevor etwas gespeichert wird.

Ausgabe des tatsächlichen Parsers aus packages/shared für die gezeigte Eingabe. Ebenfalls unterstützt: Blockformat mit einer Übung pro Zeile und Sätzen darunter, Häkchen ✓/☐, Wiederholungsbereiche wie 4x8-10.

02 Funktionen

Was die App konkret macht.

Notiz-Editor fürs Workout
Jeder Satz ist eine Zeile 60 kg × 12 ✓. Gewicht und Wiederholungen sind direkt in der Zeile editierbar, abgehakt wird mit einem Tipp. Sätze hinzufügen oder duplizieren, Aufwärmsätze markieren. Gespeichert wird automatisch, einen Speichern-Knopf gibt es nicht.
Smart Defaults
Vorausgefüllt werden die echten Werte vom letzten Training, je Satzposition und Satztyp, sonst Planvorgabe oder dein Standardwert. Für jeden Wert wird die Quelle gespeichert (history, plan, user_default, none). Fehlt eine Quelle, bleibt das Feld leer.
Geführter Modus & Pausentimer
Ein Satz pro Bildschirm mit großen Bedienelementen. Der Pausentimer rechnet mit einer absoluten Endzeit und läuft deshalb nach Reload oder App-Wechsel korrekt weiter. Einschränkung: Hintergrund-Timer und Benachrichtigungen sind auf iOS nur begrenzt verfügbar.
Trainingspläne
Plan-Editor mit Autosave und Versionsverlauf. 11 Vorlagen (u. a. Ganzkörper, Push/Pull/Legs, Oberkörper/Unterkörper, Kurzhanteltraining) mit Satz- und Wiederholungszielen, ohne erfundene Gewichte. Flexible Rotation oder feste Wochentage; verpasste Einheiten lassen sich verschieben.
Übungsbibliothek
184 Übungen mit deutschen Ausführungshinweisen, Muskelgruppen und Equipment. Eigene Übungen lassen sich ergänzen und werden beim Import mit berücksichtigt.
Rekorde & Progression
Persönliche Bestwerte pro Übung. Das 1RM wird nach Epley geschätzt und als Schätzung gekennzeichnet. Progressionsvorschläge erscheinen als Vorschlag. Übernommen wird erst, wenn du bestätigst.
Auswertung
Verlauf, Kalender, Volumen und Rekorde je Übung und Muskelgruppe, ausschließlich aus deinen erfassten Trainings. Körpergewicht und Maße separat. Ohne Daten bleibt ein Diagramm leer, statt Beispielwerte zu zeigen.
Erfolge & Serien
Trainingsserien und Erfolge als leise Rückmeldung. Wer das nicht möchte, schaltet Erfolge im Profil komplett ab.

03 Offline & Sync

Funkloch im Keller? Das Training läuft weiter.

Jede Änderung wird zuerst lokal geschrieben und als Operation in eine Outbox gelegt. Sobald eine Verbindung besteht, werden die Operationen in Reihenfolge an den Server gesendet.

  1. Client App React, Service Worker cacht die App-Shell
  2. IndexedDB Outbox (Dexie) Operation mit eindeutiger opId und baseVersion
  3. HTTP POST /api/v1/sync idempotent, bis zu 100 Operationen je Request
  4. Server PostgreSQL 16 Versionsnummer je Entität (Workout, Übung, Satz)
Antwort je Operationstatus
applied
übernommen, neue Version zurück
duplicate
opId schon verarbeitet, kein Doppeleintrag
conflict
anderes Gerät war schneller, Serverstand wird mitgeliefert
not_found
Entität existiert nicht (mehr)
rejected
Validierung fehlgeschlagen, mit Fehlertext
  • Keine stillen Überschreibungen. Hat ein anderes Gerät dieselbe Entität seit deiner Basisversion geändert, zeigt die App beide Stände, und du entscheidest.
  • Wiederholbar. Bricht die Verbindung mitten im Request ab, wird einfach erneut gesendet. Doppelte opIds werden serverseitig erkannt.
  • Reload-fest. Ein offline begonnenes Workout übersteht das Neuladen der Seite. Das ist durch einen End-to-End-Test abgedeckt.

04 Self-Hosting

Drei Container, ein Server, deine Daten.

Docker Compose startet PostgreSQL, die API und den Web-Client hinter nginx. Kein Firebase, kein Supabase, keine kostenpflichtigen Drittanbieter-APIs.

  1. 1 Repository holen

    git clone <repo-url> satzio && cd satzio
  2. 2 Secrets erzeugen

    ./scripts/setup.sh

    Schreibt eine .env mit zufälligen Datenbank- und Token-Secrets.

  3. 3 Starten

    docker compose up -d --build

    Für den Betrieb im Internet einen Reverse Proxy mit TLS davorschalten.

Sicherheit & Datenschutz

Passwörter
Argon2id
Sitzungen
Rotierende Refresh-Tokens im HttpOnly-Cookie
CSRF
Double-Submit-Token
Missbrauch
Rate Limiting auf Auth-Endpunkten
Isolation
Konten-Trennung durch API-Tests geprüft
Datenexport
Export und Import als JSON und CSV
Löschen
Konto inklusive aller Daten in der App löschbar

05 Screens

So sieht es aus.

Unbearbeitete Screenshots der Web-App, 390 px Viewport.

Startseite „Heute“ mit dem nächsten Training PUSH aus dem Plan Push/Pull/Legs und der Übungsliste.
heuteNächstes Training aus dem aktiven Plan
Workout in der Listenansicht mit abgehakten Sätzen und Gewichts-Schnellwahl.
workout/listeSätze inline bearbeiten und abhaken
Geführter Modus: ein Satz pro Bildschirm mit großen Plus-/Minus-Tasten und Pausentimer.
workout/geführtEin Satz pro Bildschirm
Zusammenfassung nach dem Training mit Dauer, Sätzen, Wiederholungen und Trainingsvolumen.
zusammenfassungVolumen, Sätze, neue Rekorde
Vorschau des Notizen-Imports mit erkannten Übungen; unsichere Sätze sind farbig hinterlegt.
plaene/importImport-Vorschau, Unsicheres markiert
Fortschrittsansicht mit Verlaufsdiagramm für Bankdrücken: Höchstgewicht und geschätztes 1RM.
fortschrittVerlauf je Übung, 1RM als Schätzung
Übungsdetail Bankdrücken mit anatomischer Muskelkarte (Vorder- und Rückseite) und Ausführungsschritten.
übungen184 Übungen mit Muskelkarte und Ausführung
Onboarding, Schritt 1 von 4: Auswahl des Trainingsziels.
onboardingSchritt 1 von 4: Trainingsziel

06 Technik

Stack und Qualitätssicherung.

Technische Daten
Web-ClientReact 19, Vite, Tailwind CSS, TanStack Query, Zustand
Offline-SpeicherIndexedDB über Dexie, Outbox-Muster, Service Worker für die App-Shell
MobileCapacitor für Android und iOS (gleiche Codebasis wie Web)
APINestJS 11, Validierung mit Zod, OpenAPI-Dokumentation
DatenbankPostgreSQL 16, Prisma 6
Geteilte LogikParser, Smart Defaults, Progression, Serien und Rekorde als eigenes Paket, von Client und Server genutzt
BetriebDocker Compose: postgres, api, web (nginx)
TestsAutomatisierte Tests decken ab: die geteilte Logik (Unit-Tests), die API mit 36 Integrationstests inklusive Konten-Isolation sowie den kompletten Trainingsablauf und den Offline-Betrieb per Playwright-E2E.

Trainieren. Abhaken. Stärker werden.