# Desktop database ## 1. Status ```text TRAINLOG_DATABASE_SCHEMA_VERSION=5 DATABASE_SCHEMA_V5=PASS TRAINLOG_FORMAT_V1=FROZEN ``` The desktop SQLite database is the canonical long-term Trainlog history. Its schema evolves independently from all JSON exchange-format versions. ## 2. Versioning Schema version uses: ```sql PRAGMA user_version; ``` Current value: ```text 5 ``` Supported historical databases are migrated explicitly through the implemented migration chain. A database newer than the running binary understands is rejected. A schema fixture must represent the real historical structure. Rewriting only `user_version` is not an acceptable migration test. ## 3. Connection invariants Every connection enables: ```sql PRAGMA foreign_keys = ON; ``` A bounded SQLite busy timeout is configured by the core. ## 4. Tables ### `exercises` Canonical desktop exercise catalog. ```text id exercise_id UNIQUE stable identity name normalized_name UNIQUE normalized display form tracking_mode reps | duration recording_mode sets | continuous data_fields bounded bit mask ``` Rules include: - continuous implies duration tracking; - unknown supplemental field bits are rejected; - normalized names remain unique. ### `sessions` ```text id session_id UNIQUE stable identity started_at ended_at nullable session_type training | max_test notes nullable ``` ### `session_exercises` Ordered exercise occurrence inside one session. ```text session_row_id exercise_row_id recording_mode data_fields position load_mode rest_seconds target_sets target_reps target_duration_seconds target_weight_kg notes ``` Current desktop history snapshots `recording_mode` and `data_fields` in the session row. `tracking_mode` remains associated with the referenced exercise catalog identity. `SETS` rows support two target shapes in schema v5: ```text explicit planned target target_sets + exactly one target metric actual-only mobile observation target_sets = NULL target_reps = NULL target_duration_seconds = NULL ``` This v5 rule is what permits heterogeneous mobile performed sets without inventing a fake uniform target. `CONTINUOUS` rows are targetless and require: ```text load_mode = none rest_seconds = 0 target_weight_kg = NULL ``` ### `performed_sets` Ordered actual set records. ```text session_exercise_row_id position reps nullable duration_seconds nullable weight_kg nullable ``` Exactly one primary actual metric is present: ```text reps or duration_seconds ``` Actual repetitions may be zero. Each row is independent; heterogeneous repetition sequences are first-class data. ### `continuous_activity` One-to-one actual record for a continuous session exercise. ```text session_exercise_row_id UNIQUE duration_seconds speed_kmh nullable distance_km nullable ``` Continuous activity never creates a fake performed set. ### `body_observations` ```text observation_id UNIQUE observed_at session_row_id optional UNIQUE link body_weight_kg neck_cm shoulders_cm chest_cm waist_cm hips_cm left_arm_cm right_arm_cm left_forearm_cm right_forearm_cm left_thigh_cm right_thigh_cm left_calf_cm right_calf_cm notes ``` At least one body metric must be present. ## 5. Identifier generation Official desktop creator prefixes: ```text ex_ exercise se_ session bo_ body observation sy_ synchronization run ``` All use random UUIDv4 values. Exchange parsers may accept other schema-valid opaque identities where their contract explicitly permits it. ## 6. Transactions Multi-row user operations are atomic. Persisted session correction replaces session child rows transactionally while preserving the parent: ```text session_id started_at ended_at session_type session notes linked body observation ``` Removing an exercise from a persisted session is therefore a transactional replacement of the remaining child set. A failed replacement rolls back to the previously persisted session. Body-observation editing preserves its stable identity, timestamp, and optional session link. ## 7. Mobile import semantics `tools/import_mobile_export.py` validates the complete mobile snapshot before committing database changes. Properties: ```text schema-v5 aware transactional idempotent by stable IDs profile-aware catalog reconciliation heterogeneous performed sets preserved no fake target generated continuous activity kept separate ``` ## 8. Units Canonical desktop persistence: ```text weight/load kg body circumference cm duration/rest seconds speed km/h distance km ``` ## 9. Android database The Android SQLite database is independent. Desktop and Android schema versions are not required to match. Do not synchronize SQLite database files. ## 10. Validation ```bash meson compile -C build meson test -C build --print-errorlogs ``` Migration-specific regression coverage includes: ```text schema_v5_migration ``` The current normal suite contains 19 tests. ## 11. Measured-max derivation Measured maxima require no desktop schema v6. The existing `sessions.session_type = max_test` classification plus actual `performed_sets` are sufficient. Exercise performance points carry the originating session type so the measured-max layer can distinguish explicit tests from ordinary training. Rules: ```text training session never becomes measured max implicitly max_test + external greatest successful actual load tie -> greatest reps/duration max_test + assistance lowest successful assistance tie -> greatest reps/duration max_test + no load greatest successful reps/duration ``` A zero-repetition failed attempt is not a successful measurement. The current measured result is the newest successful max-test point. The record is the best max-test point using the same load mode. No extra maximum row is persisted; results are derived from canonical history.