Zum Inhalt

Produkt, Architektur und Datenmodell

Dokumentierter Stand: main bei Commit f7fe401caa7891ad8aa64ae57dfbf678bde1781f vom 21. Juli 2026. Einzelne Details können durch spätere Änderungen überholt sein.

Produktbild

Wofür Steady gedacht ist

Steady soll Veränderungen sichtbar machen, ohne aus dem Alltag einen Pflichtfragebogen zu machen. Alles ist freiwillig. Nicht ausgefüllte Werte bleiben unbekannt und werden nicht als Null, Fehler oder nicht erledigte Gewohnheit interpretiert.

Hauptbereiche der Oberfläche

flowchart TD
    T[Heute] --> J[Journal]
    T --> R[Recovery]
    T --> P[Muster]
    T --> E[Explorer]
    T --> X[Toolbox / Mehr]

    J:::read -->|chronologischer Rückblick| U[Gespeicherte Daten]
    R:::read -->|Counter, Urges, Trigger| U
    P:::read -->|Zusammenhänge| U
    E:::read -->|Messreihen| U

    classDef read fill:#eaf6f1,stroke:#5b9b82,color:#173d31
Bereich Leitfrage Rolle
Heute Was möchte ich heute festhalten? Eingabe und Autosave
Journal Was ist wann passiert? Chronologischer Rückblick
Recovery Wie entwickelt sich mein Veränderungsbereich? Counter, Urges, Trigger, Strategien
Muster Was bewegt sich möglicherweise gemeinsam? Lebensrad, Personen, Sport, Korrelationen
Explorer Wie entwickelt sich ein einzelner Messwert? Frei konfigurierbare Graphen
Toolbox / Mehr Was brauche ich zusätzlich? Aufgaben, Inventar, Export, Einstellungen

Produktgrenzen

  • keine Diagnose und kein Medizinprodukt
  • keine automatische Rückfallentscheidung
  • kein verpflichtendes Konto
  • keine Telemetrie und kein Steady-Backend
  • Korrelationen werden nicht als Ursache bezeichnet
  • der Companion besitzt keinen Schreibzugriff auf Trackingdaten

Quellen im Repository


Systemarchitektur

Laufzeitmodell

Steady ist eine React-Anwendung, wird mit Vite gebaut und über Capacitor als Android-App verpackt. Der Browser dient als Entwicklungs- und Testumgebung; Android ist das eigentliche Produktziel.

flowchart TB
    UI[React Views und Dialoge]
    APP[App-Zustand und Anwendungslogik]
    SVC[Services: Tracking, Export, Companion]
    DB[db.js – gemeinsame Datenabstraktion]
    IDX[(IndexedDB<br/>Browser/Test)]
    NATIVE[native-database.js]
    CAP[Capacitor-Plugin]
    SQL[(verschlüsselte SQLite-Datenbank)]

    UI --> APP --> SVC --> DB
    DB -->|Browser| IDX
    DB -->|Android| NATIVE --> CAP --> SQL

Schichten und Verantwortlichkeiten

Schicht Verantwortung Beispiele
Views Darstellung und Interaktion TodayView, RecoveryView, AnalysisViews
App-Orchestrierung Navigation, Zustand, Dialoge, Datenübergabe App.jsx
Services atomare Fachoperationen tracking-service.js, Export-Service
Datenabstraktion Lesen, Schreiben, Migration, Browser-/Android-Umschaltung db.js
Native Grenze SQLite, Keystore, Datenschutz, lokale KI Capacitor-Plugins

Warum ein generischer Record-Store?

Die native SQLite-Datenbank verwendet logisch getrennte Stores, speichert die eigentlichen Objekte aber als verschlüsselte JSON-Payloads. Das passt zur konfigurierbaren Feldstruktur: Neue Felder erfordern nicht automatisch eine neue SQL-Spalte.

Atomare Operationen

Ein Urge mit Rückfall kann gleichzeitig ein Ereignis und mehrere Counter-Resets erzeugen. Diese Änderungen werden mit applyBatch in einer Transaktion geschrieben. Dadurch gibt es keinen Zwischenzustand, in dem der Rückfall gespeichert, der Counter aber noch nicht zurückgesetzt wurde.

sequenceDiagram
    participant UI as Urge-Dialog
    participant S as tracking-service.js
    participant DB as db.js / applyBatch
    participant Store as events + resets

    UI->>S: payload speichern
    S->>S: Ereignis und Reset-Datensätze vorbereiten
    S->>DB: eine Batch-Operation
    DB->>Store: alles committen
    Store-->>UI: Erfolg oder vollständiger Fehler

Quellen


Datenmodell

Logische Stores

erDiagram
    SECTIONS ||--o{ FIELDS : enthaelt
    ENTRIES ||--o{ ENTRY_VALUES : besitzt
    FIELDS ||--o{ ENTRY_VALUES : definiert
    CATALOG_ITEMS ||--o{ EVENTS : klassifiziert
    EVENTS ||--o{ RESETS : kann_ausloesen
    PROFILES ||--o{ COUNTERS : besitzt
    PROFILES }o--o{ EVENTS : betrifft
Store Inhalt Schlüsselidee
meta Laufzeitkonfiguration, Sprache, UI-Zustand, Datenschutz Schlüssel/Wert-Metadaten
sections sichtbare Bereiche des Tages-Check-ins Reihenfolge, Label, Radar-Achse
fields versionierte Felddefinitionen stabile IDs und logicalId
entries ein Tagesdatensatz pro Datum sparse values-Map
catalogItems Personen, Sportarten, Mahlzeiten, Trigger wiederverwendbare Katalogobjekte
events wiederholbare Ereignisse sozial, Sport, Selbstfürsorge, Mahlzeit, Urge, Trigger
resets Counter-Startpunkte und Rückfälle Historie statt Überschreiben
todos Aufgaben Status, Priorität, Deadline
inventoryEntries reflektierende Inventar-Einträge bewusst nicht analytisch
chats, chatMessages Companion-Konversationen getrennt von Trackingdaten

Tagesdatensatz

{
  "id": "2026-07-21",
  "date": "2026-07-21",
  "values": {
    "sleep_quality": 7,
    "stress": 4,
    "work_end_git": "yes"
  },
  "createdAt": "...",
  "updatedAt": "...",
  "source": "app"
}

Die values-Map ist sparse. Ein nicht vorhandener Schlüssel bedeutet unbekannt. Das ist semantisch etwas anderes als 0, false oder "no".

Dynamische Ereignisse

Wiederholbare Dinge werden nicht in den Tagesdatensatz gepresst. Drei soziale Kontakte an einem Tag sind drei events, können aber dieselbe Person aus catalogItems referenzieren.

{
  "id": "social-...",
  "kind": "social",
  "date": "2026-07-21",
  "catalogItemId": "person-anna",
  "connection": 4,
  "note": "kurzer Austausch"
}

Laufzeitkonfiguration

loadAppData() verbindet eingebaute Standardkonfiguration mit lokal gespeicherten Änderungen. Die UI erhält dadurch ein konsistentes Objekt mit Daten, Konfiguration und Präferenzen.

Quellen


Bereich → Feld → Typ → Wert

Die Ebenen

flowchart LR
    S[Bereich / section] --> F[Felddefinition]
    F --> T[Feldtyp]
    T --> V[Rohwert im Tagesdatensatz]
    F --> G[Zielrichtung]
    S --> R[Lebensrad-Achse]
    V --> A[Analyse]
    G --> A
    R --> W[Lebensrad]

1. Bereich

Ein Bereich strukturiert die Oberfläche. Beispiele sind sleep, work, mental oder reflection. Er bestimmt:

  • wo ein Feld angezeigt wird,
  • die Reihenfolge im Check-in,
  • optionale Wochentagsfilter,
  • die Zuordnung zu einer Lebensrad-Achse.

Standardzuordnung zum Lebensrad

Bereich Standardachse
reflection Zustand
sleep Schlaf
social Verbindung
work Arbeit
sport Bewegung
mental Regulation
nutrition Ernährung
evening Recovery
spaa Recovery
morning keine Achse

Die Zuordnung wird im Gruppenmanager bearbeitet. Eine Gruppe kann bewusst keiner Achse zugeordnet sein.

2. Felddefinition

Ein Feld beschreibt die Bedeutung des Rohwerts.

{
  "id": "stress",
  "section": "reflection",
  "type": "scale",
  "min": 0,
  "max": 10,
  "goal": "low",
  "stats": true
}

Wichtige Eigenschaften:

Eigenschaft Bedeutung
id physische ID dieser Feldversion
logicalId gemeinsame Identität über Versionen hinweg
section Bereich der Oberfläche
type Eingabe- und Konvertierungslogik
min, max, step Zahlenraum
goal high, low, target oder neutral
targetMin, targetMax günstiger Zielbereich
stats darf das Feld analysiert werden?
active aktuell sichtbar?

3. Feldtypen

Typ gespeicherter Wert Beispiel Analytics
boolean yes, no oder fehlend Zähne geputzt Statusqualität
status yes, partial, no, na oder fehlend To-dos vorbereitet abgestufte Qualität
scale Zahl Stress 0–10 Normalisierung nach Zielrichtung
duration Zahl plus Einheit Meditation in Minuten Wert + sättigende Evidenz
number Zahl Recovery-Kontakte Aggregation nach Konfiguration
choice Optionswert Arbeitsort Explorer/Breakdown, nicht automatisch Radar
text Text Reflexionsnotiz normalerweise keine numerische Analyse

4. Wert und Zielrichtung

Die gleiche Zahl kann je nach Zielrichtung etwas anderes bedeuten:

  • hoch ist günstig: Stimmung, Energie, Schlafqualität
  • niedrig ist günstig: Stress, Prokrastination, Urge-Stärke
  • Zielbereich ist günstig: Schlafdauer, Essensmenge
  • neutral: nur beobachten; keine automatische Wertung im Lebensrad

5. Gewicht

Aktuell gibt es keine frei konfigurierbaren Feldgewichte. Die Lebensrad-Engine verwendet:

  • feste Evidenzwerte je Feldtyp,
  • spezielle Kurven für Dauer und dynamische Ereignisse,
  • sättigende Zusammenführung mehrerer Signale eines Tages.

Die Zuordnung ist gegenwärtig Bereich → Lebensrad-Achse, nicht Feld → mehrere Achsen. Das ist bewusst als aktuelle Systemgrenze dokumentiert.

Quellen


Feldversionierung und Historie

Warum Versionierung nötig ist

Ein Feld kann heute „Stress 0–10“ und später „Stress 1–5“ heißen. Würde die bestehende Definition einfach überschrieben, wären alte Werte nicht mehr eindeutig interpretierbar.

Steady trennt deshalb:

  • physische Feld-ID: konkrete Version,
  • logicalId: fachlich derselbe Messwert über mehrere Versionen.
flowchart LR
    F1[stress<br/>Version 1<br/>id: stress] -->|Typ oder Skala geändert| F2[stress<br/>Version 2<br/>id: stress-xyz]
    F1 -. logicalId: stress .- F2
    F1 -->|retired / supersededBy| F2

Wann eine neue Version entsteht

Wenn historische Werte existieren und sich eine versionierte Eigenschaft ändert, wird das alte Feld stillgelegt und ein Ersatz angelegt. Dazu gehören unter anderem:

  • Labels und Bereich
  • Typ und Optionen
  • Minimum, Maximum, Schrittweite und Einheit
  • Zielrichtung und Zielbereich
  • Standardwert und Statistikfreigabe

Wie Analytics damit umgeht

Explorer und Statistik suchen alle Felder mit derselben logicalId. Beim Lesen eines Tages wird die Feldversion verwendet, deren ID tatsächlich im Tagesdatensatz vorhanden ist.

flowchart TD
    E[Tagesdatensatz] --> K{welche Feld-ID ist vorhanden?}
    K --> V1[alte Version]
    K --> V2[neue Version]
    V1 --> L[gemeinsamer logischer Messwert]
    V2 --> L
    L --> A[Aggregation]

Wichtig für zukünftige Änderungen

Eine neue analytische Eigenschaft muss in die Liste der versionierten Eigenschaften aufgenommen werden, wenn ihre Änderung die historische Bedeutung verändert. Andernfalls könnte eine neue Konfiguration rückwirkend alte Daten anders bewerten.

Quellen