No description
Find a file
2026-09-08 17:57:03 +02:00
android feat(sync): stabilize bidirectional sync and equipment reconciliation 2026-09-08 17:57:03 +02:00
catalog fix(tui): use shared header on history screen 2026-09-07 23:05:10 +02:00
docs feat(sync): stabilize bidirectional sync and equipment reconciliation 2026-09-08 17:57:03 +02:00
examples Define Trainlog v1 freeze candidate 2026-09-05 19:22:37 +02:00
format Freeze Trainlog exchange format v1 2026-09-05 19:28:26 +02:00
tests feat(sync): stabilize bidirectional sync and equipment reconciliation 2026-09-08 17:57:03 +02:00
tools feat(sync): stabilize bidirectional sync and equipment reconciliation 2026-09-08 17:57:03 +02:00
tui feat(sync): stabilize bidirectional sync and equipment reconciliation 2026-09-08 17:57:03 +02:00
.gitignore feat(sync): stabilize bidirectional sync and equipment reconciliation 2026-09-08 17:57:03 +02:00
AGENTS.md Modernize TUI and harden Android workout capture 2026-09-07 16:14:01 +02:00
CHANGELOG.md feat(sync): stabilize bidirectional sync and equipment reconciliation 2026-09-08 17:57:03 +02:00
LICENSE Bootstrap Trainlog project contract 2026-09-05 19:05:07 +02:00
meson.build Add first usable Trainlog TUI 2026-09-05 19:59:55 +02:00
README.md feat(sync): stabilize bidirectional sync and equipment reconciliation 2026-09-08 17:57:03 +02:00

Trainlog

Trainlog is a local-first workout and body-tracking system with two user interfaces:

  • a native Android application optimized for fast data entry during training;
  • a C17/Notcurses TUI used for durable history, editing, visualization, statistics, and synchronization.

The desktop SQLite database is the canonical long-term history. Android keeps its own local SQLite database so recording remains usable independently from the desktop.

Current status

TRAINLOG_FORMAT_V1=FROZEN

DESKTOP_SCHEMA_V8=PASS
ANDROID_LOCAL_WORKFLOWS=PASS
ANDROID_LOCAL_DATABASE_V8=PASS
ANDROID_SESSION_DRAFT_V1=PASS
EXERCISE_EDIT_V1=PASS
ANDROID_BANNER_PARITY_V1=PASS

VARIABLE_REPETITION_SETS=PASS
CONTINUOUS_ACTIVITY_TRACKING=PASS

DIRECT_MTP_TRANSPORT=PASS
COMMON_SYNC_ENGINE=PASS
TRAINLOG_SYNCD=PASS
ANDROID_TRIGGERED_SYNC=PASS
ANDROID_SYNC_RECEIPT=PASS
BIDIRECTIONAL_SYNC_V1=PASS
MULTI_OCCURRENCE_SESSION_V2=PASS
EQUIPMENT_ASSOCIATIONS_V2=PASS
EQUIPMENT_DEFINITIONS_V1=PASS
EXERCISE_RECONCILIATION_V2=PASS

DESKTOP_TESTS=32/32 PASS
ANDROID_BUILD=PASS

Architecture

                Android application
                local SQLite store
                       |
             automatic mobile snapshot
                       |
                       v
        Download/Trainlog on Android storage
                       |
                       | direct MTP / libmtp
                       v
                trainlog_sync_run()
                  /             \
                 /               \
        Android -> PC         PC -> Android
        snapshot import       catalog publish
                 \               /
                  \             /
                   sync receipt
                       |
                       v
                    Android

Desktop TUI --------------------+
    |                           |
    +-- same sync engine -------+
    |
    v
desktop SQLite
canonical long-term history

No SQLite database file is copied between devices. The desktop does not require a GVFS/FUSE mount of the phone.

Exercise model

Trainlog does not infer behavior from exercise names.

recording_mode = SETS | CONTINUOUS
tracking_mode  = REPS | DURATION
data_fields    = SPEED_KMH | DISTANCE_KM

Valid model-v1 combinations are:

SETS + REPS
SETS + DURATION
CONTINUOUS + DURATION

Actual repetition sets are stored independently. Compact input supports:

5x10
4,5,6,7,8,9,10,9,8,7,6,5,4
4..10..4

A session may contain several ordered occurrences of the same catalogue exercise. Each occurrence has a stable entry_id, distinct from the stable exercise_id of the catalogue item. Equipment selection belongs to that occurrence, as do its actual per-set loads. external records an applied or machine-displayed load; assistance records assistance and is not interpreted as increasing strength.

During V2 synchronization, a different-ID normalized-name collision is merged only when recording/tracking modes match, every other known invariant is compatible, and one data_fields mask contains the other. The desktop identity is retained as canonical, the bit-mask union preserves the richer capability, and historical occurrence snapshots remain unchanged; absent optional values stay absent. Incomparable profiles remain explicit conflicts. Name equality alone is never sufficient.

Repository layout

android/        native Kotlin/Compose Android client
tui/            C17 Notcurses desktop application and core
docs/           canonical project documentation
format/         frozen Trainlog JSON v1 schema material
examples/       valid frozen-format examples
tests/          fixtures and cross-component tests
tools/          validators, import/export helpers, sync daemon tooling

Desktop build and validation

meson setup --reconfigure build
meson compile -C build
meson test -C build --print-errorlogs

python tools/validate_json.py
python tools/validate_import_contract.py

git diff --check

Android build

Android keeps one durable in-progress workout in its local SQLite database. Home offers Reprendre la séance en cours after navigation, app switching, Activity recreation, process death or force-stop/relaunch. Added exercises and raw unfinished form text are retained. Removing an exercise affects only the draft; abandoning the draft requires confirmation. Final save atomically creates completed history and clears the draft. Drafts never enter mobile export or desktop synchronization as completed sessions.

Schema v4 migrates additively from v3, preserving existing capture data. See Android behavior and validation.

The current Android schema is v8. Its additive v4 -> v8 chain adds the shared equipment catalogue, per-occurrence equipment links, durable occurrence identities, and custom-equipment definition support without recreating completed history or the active draft.

Exercises can be renamed in place from Android. The ex_<uuid-v4> identity is unchanged; completed history, an active draft, and synchronization therefore continue to resolve the same logical exercise. Referenced profiles are locked; only unreferenced catalog exercises may change their recording/tracking profile.

The local Android SDK is intentionally not committed. Configure it with either ANDROID_HOME or android/local.properties.

Example:

cd android

printf 'sdk.dir=%s\n' "$HOME/Android/Sdk" > local.properties

JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
./gradlew assembleDebug

Android-triggered synchronization

Launch the desktop TUI from a built checkout with:

trainlog

The usual user command resolves to build/tui/trainlog in this checkout. The desktop database is $XDG_DATA_HOME/trainlog/trainlog.db, or ~/.local/share/trainlog/trainlog.db when XDG_DATA_HOME is unset.

Build the desktop first, then install the user service:

bash tools/install_syncd_user.sh

Check it with:

systemctl --user is-active trainlog-syncd.service
tail -f ~/.local/state/trainlog/syncd.log

On Android:

Sync
-> Synchroniser maintenant

The request is consumed by trainlog-syncd, the shared bidirectional engine runs, a receipt is returned to Android, and the PC catalog is applied locally. The active completed-session exchange is V2 and preserves occurrence entry_id, per-set weights and equipment associations. Frozen V1 artifacts remain readable as legacy artifacts; they are not silently redefined as V2.

Documentation

  • docs/current_state.md: compact canonical implementation snapshot;
  • docs/architecture.md: component and ownership boundaries;
  • docs/exercise_data_model.md: exercise semantics;
  • docs/database.md: desktop SQLite schema and migrations;
  • docs/android.md: Android behavior;
  • docs/tui.md: desktop TUI behavior;
  • docs/sync_exchange.md: MTP synchronization artifacts and protocol;
  • docs/exchange_format.md: frozen Trainlog JSON v1 contract;
  • docs/tests.md: validation strategy;
  • docs/roadmap.md: completed gates and future cursor;
  • AGENTS.md: development contract.

Development principles

  • local-first;
  • no mandatory cloud account;
  • user-owned data;
  • versioned persistent and exchange formats;
  • stable identities;
  • idempotent synchronization;
  • no fake data representation to force incompatible models together;
  • strict compiler warnings;
  • documentation and tests are part of feature completion.

Measured max

Explicit max_test sessions are the only source of measured maxima.

Ordinary training best sets remain ordinary performance even when they exceed a previous max-test result.

The desktop exercise catalog exposes a separate measured-max view with current result, same-mode record, test history, a dedicated graph, and 60/70/80/90% working loads for external resistance. Working loads are rounded to a selectable practical increment and are not calculated for assistance.

Android can explicitly save a session as Entraînement or Test max.

MEASURED_MAX_V1=PASS
WORKING_LOAD_PERCENTAGES=PASS
ANDROID_MAX_TEST_SESSION=PASS

Body analytics

Body analytics are desktop-only. Android remains a capture client.

The TUI derives descriptive ratios, left/right asymmetry, and an optional circumference-based body-fat estimate from real body observations.

The estimate requires a local desktop-only analytics profile containing the formula branch and height. Estimated fat mass and lean mass are calculated only when a real body weight is present.

Estimated values are never persisted as direct measurements.

BODY_ANALYTICS_V1=PASS
BODY_COMPOSITION_ESTIMATE=PASS
BODY_PROPORTION_RATIOS=PASS
BODY_SYMMETRY_ANALYTICS=PASS
DESKTOP_TESTS=32/32 PASS