Produkt, Architektur und Datenmodell¶
Dokumentierter Stand:
mainbei Commitf7fe401caa7891ad8aa64ae57dfbf678bde1781fvom 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¶
src/config/app-config.jsonsrc/features/settings/GroupManager.jsxsrc/features/settings/RadarAxisManager.jsxsrc/features/analysis/radar-score.js
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.