release: prepare Trainlog 0.1.0 baseline

This commit is contained in:
fy59 2026-09-15 08:08:36 +02:00
parent 89abf531a3
commit 09f120566d
152 changed files with 1239 additions and 1454 deletions

View file

@ -164,7 +164,7 @@ It is not the canonical analytics store.
The Android UI is driven by exercise metadata, never by exercise-name
heuristics.
Android local SQLite schema v12 retains the exactly-one durable active-session
Android local SQLite schema v17 retains the exactly-one durable active-session
draft introduced in v4.
Every meaningful draft/form mutation is persisted by the repository. Back,
backgrounding and process death never delete the draft. Home offers explicit
@ -177,7 +177,9 @@ Android schema v11 additionally stores optional planning metadata separately
from actual occurrence data after the additive v10 -> v11 migration. Existing
rows retain `load_mode=none`, zero rest and NULL targets. Android v12 adds the
durable flattened exercise-alias mapping used to resolve retired creator IDs.
Desktop schema v12 owns the same alias compatibility boundary. The active
Desktop schema v12 owns the same alias compatibility boundary. Later additive
desktop migrations reach v18 and Android migrations reach v17 without changing
that identity contract. The active
completed-session exchange is the separate strict
`trainlog-mobile-export` V3; V1/V2 remain readable and `TRAINLOG_FORMAT_V1`
remains unchanged.
@ -230,9 +232,11 @@ Desktop SQLite schema is versioned with:
PRAGMA user_version;
```
The current desktop schema is v12. Schema v9 added an occurrence-owned explicit
maximum result; v10/v11 added body-zone and planning metadata, and v12 adds
durable flattened exercise aliases. None changes `TRAINLOG_FORMAT_V1`.
The current desktop schema is v18. Schema v9 added an occurrence-owned explicit
maximum result; v10/v11 added body-zone and planning metadata; v12 added durable
flattened exercise aliases; v13-v18 add machine metadata, feedback and
revisions, occurrence tracking snapshots, causal profile state, and AI proposal
storage/publication state. None changes `TRAINLOG_FORMAT_V1`.
Every incompatible schema evolution requires an explicit migration and
regression coverage.
@ -276,6 +280,11 @@ Canonical documents:
- `docs/roadmap.md`;
- `CHANGELOG.md`.
`README.md` is the concise repository entry point. `docs/README.md` is the
canonical documentation index. `docs/current_state.md` owns current implemented
status; `docs/roadmap.md` owns future work. Design and review records are
historical evidence and must not override those current owners.
Checkpoint history belongs in Git history and `docs/reviews`; canonical
documents describe the current state rather than accumulating obsolete
`NEXT` sections.
@ -302,6 +311,10 @@ cd android
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug
```
Run Android unit and instrumented automation on the host/emulator. The primary
personal phone is reserved for manual, non-destructive install and sync smoke
tests; do not run `connectedDebugAndroidTest` on it.
Run ASan/UBSan at meaningful C implementation checkpoints.
Hardware-dependent MTP tests remain explicit manual validations and are not

View file

@ -1,5 +1,18 @@
# Changelog
- Documented stable-release distribution through GitHub Releases, including
prebuilt Android and architecture-specific dynamically linked Linux assets,
SHA-256 verification, runtime/build/optional dependencies, and the release
signing and exact-tag requirements. No v0.1.0 release is claimed until a
signed APK and authenticated GitHub publication path are available.
- Completed `TRAINLOG_FULL_REPOSITORY_DOCUMENTATION_AUDIT_V2`: added the
canonical documentation index, made README a concise entry point, separated
current state from future roadmap ownership, corrected current schema and
validation descriptions, and added concise English ownership headers across
previously undocumented C, Kotlin, Python, and shell sources. The audit
changes documentation comments only and introduces no functional behavior.
- Fixed fresh Android database body-zone reconstruction by applying the
PC-published body-zone companion immediately after catalog, flattened aliases
and strict profile-state, before fallible downstream session, equipment, AI

572
README.md
View file

@ -1,405 +1,249 @@
# Trainlog
Trainlog is a local-first workout and body-tracking system with two user
interfaces:
Trainlog is a local-first workout and body-data system. Its native Android app
is the lightweight field companion for fast capture and glanceable summaries;
its C17/Notcurses desktop TUI is the detailed surface for correction,
catalogue management, analytics, graphs, and long-term follow-up.
- a native Android application optimized for fast data entry during training;
- a C17/Notcurses TUI used for durable history, editing, visualization,
statistics, and synchronization.
Android and desktop each own a local SQLite database. The desktop database is
the canonical long-term history. Trainlog synchronizes versioned JSON artifacts
over direct MTP; it never copies SQLite database files between devices.
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.
## Product split
## Current status
| Surface | Primary responsibility |
|---|---|
| Android | Capture sets, repetitions, loads, durations, continuous activities, feedback, J+1 follow-ups, body measurements, active drafts, and AI proposals; trigger sync; show quick summaries. |
| Desktop TUI | Consult and correct canonical history; manage exercises and equipment; analyze body data, MAX results, training statistics, body zones, calendar periods, and longitudinal graphs. |
```text
TRAINLOG_FORMAT_V1=FROZEN
TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING
In short: **Android captures and summarizes. The TUI analyzes and tracks over
time.** Android intentionally remains lightweight for analytics.
DESKTOP_SCHEMA_V11=PASS
ANDROID_LOCAL_WORKFLOWS=PASS
ANDROID_LOCAL_DATABASE_V10=PASS
ANDROID_LOCAL_DATABASE_V11=PASS
ANDROID_SESSION_DRAFT_V1=PASS
EXERCISE_EDIT_V1=PASS
ANDROID_BANNER_PARITY_V1=PASS
## Release links
VARIABLE_REPETITION_SETS=PASS
CONTINUOUS_ACTIVITY_TRACKING=PASS
- [Download the latest release](https://github.com/labfytools/trainlog/releases/latest)
- [Documentation](docs/README.md)
- [Changelog](CHANGELOG.md)
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
EXPLICIT_MAX_RESULTS_V1=PASS
BODY_ZONES_V1=PASS
BODY_ZONE_SYNC_V1=PASS
BODY_ZONES_DESKTOP_REAL_MIGRATION=PASS
BODY_ZONES_ANDROID_DEVICE_VALIDATION=PASS
## Installation
DESKTOP_TESTS=58/58 PASS (latest validated checkpoint)
ANDROID_BUILD=PASS
### Download a prebuilt release
TRAINING_KNOWLEDGE_V1=PASS
SESSION_GENERATOR_V1=PASS
APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2
```
Stable releases publish an Android APK, an x86-64 Linux TUI executable, and a
`SHA256SUMS` file on [GitHub Releases](https://github.com/labfytools/trainlog/releases).
Verify the downloaded binary against `SHA256SUMS` before installing it.
`APP_SHELL_V1` provides the shared seven-section application shell on both
platforms: **Accueil**, **Séances**, **Exercices**,
**Statistiques**, **Synchronisation**, and **Paramètres**. Completed history
and the existing generator now live under **Séances**; existing body and MAX
views live under **Statistiques**. It records no new statistics, schema, or
synchronization protocol. Automated review repairs are recorded, but the
status remains awaiting human visual/accessibility review; see
[the APP_SHELL_V1 review record](docs/reviews/app_shell_v1.md).
`TRAINING_KNOWLEDGE_V1` has passed its bounded scientific review, independent
temporal delta review, final engineering audit, repair verification, and final
executable validation. Its C and
Android occurrence pages and latest-MAX context share the settled exact-instant
ordering and source-text cursor contract. The independent temporal review
returned PASS with no findings. The initial full-tranche audit's four findings
were resolved by one bounded repair chain and independently verified. Fresh
validation passed: strict build, 56 Meson tests, Python knowledge/temporal
tests, validators, headers, sanitizers, 12-form temporal probes, Android 56
tests with one known fixture skip, and Java 17 debug assembly.
The [temporal correction record](docs/reviews/training_knowledge_v1_temporal_contract.md)
defines the accepted grammar, regression evidence and remaining review boundary.
## Architecture
```text
Android application
local SQLite store
|
automatic mobile snapshot
|
v
Documents/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.
```text
recording_mode = SETS | CONTINUOUS
tracking_mode = REPS | DURATION
data_fields = SPEED_KMH | DISTANCE_KM
```
Valid model-v1 combinations are:
```text
SETS + REPS
SETS + DURATION
CONTINUOUS + DURATION
```
Actual repetition sets are stored independently. The Notcurses desktop flow
collects planning only, then creates actual work in an ordered table: the user
explicitly adds every row and enters its actual repetitions (or duration) and
optional load. It does not accept compact performed-repetition input; normal
set sessions cannot finish with zero actual rows.
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. An actual load is either absent or finite and
non-negative, so an explicit zero remains distinct from no recorded load;
planned targets remain strictly positive and are never substituted for actuals.
In a `max_test` session, an occurrence may instead own one explicit positive
`max_weight_kg`. This result has no performed set, repetitions, or target-set
count. Its identity remains the movement's `exercise_id` plus the occurrence's
`entry_id`; optional equipment is context and never owns a shared maximum.
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.
Exercise body zones are independent metadata sourced from the single canonical
[`catalog/body-zones-v1.json`](catalog/body-zones-v1.json) manifest. An exercise
may have one primary assignable zone and several distinct secondary zones.
Secondary relations require that primary; the explicit unclassified state has
no relations at all.
`upper_body` and `lower_body` are hierarchy groups used for display and
descendant-aware filtering; they are never duplicated as stored relations.
`full_body` and `core` remain autonomous. Historical exercises without an
objective stable-ID mapping remain visible as **Non renseignés**.
## Repository layout
```text
android/ native Kotlin/Compose Android client
tui/ C17 Notcurses desktop application and core
docs/ canonical project documentation
format/ frozen Trainlog JSON v1 schema material
catalog/ canonical versioned equipment and body-zone manifests
examples/ valid frozen-format examples
tests/ fixtures and cross-component tests
tools/ validators, import/export helpers, sync daemon tooling
```
## Desktop build and validation
The Linux TUI artifact is architecture-specific and dynamically linked. It is
not a universally portable Linux binary: the host must provide compatible
runtime libraries listed under [Dependencies](#dependencies).
```bash
meson setup --reconfigure build
chmod +x trainlog-tui-linux-x86_64-v0.1.0
./trainlog-tui-linux-x86_64-v0.1.0
```
The executable can remain in the download directory. Moving it into a directory
on `PATH`, such as a user-managed `~/.local/bin`, is optional.
For Android, download the release APK, allow installation from the browser or
file manager when Android requests it, and install the package. Android 8.0
(API 26) or later is required. Grant Trainlog all-files access only when direct
`Documents/Trainlog` synchronization is needed. Desktop synchronization also
requires the Linux side and its MTP dependencies to be installed and configured.
Android and the TUI in one GitHub Release share the same Trainlog product
version. Database schemas and JSON protocol versions are independent.
### Build from source
Desktop:
```bash
meson setup 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
```
## Local history export for external analysis
Create the read-only `TRAINLOG_AI_EXPORT_V1` artifact in the current directory:
```bash
python tools/export_ai_history.py --database ~/.local/share/trainlog/trainlog.db
```
The default output is `trainlog_ai_export_v1.json`. It is ignored by Git
because it contains user data. After every completely successful desktop sync,
the shared sync engine regenerates this artifact and asks the external desktop
`rclone` command to copy it to
`TrainLog Gdrive:Trainlog/AI/trainlog_ai_export_v1.json`. Export or Drive
failure is reported without changing the successful Trainlog sync result.
Android never runs `rclone`, and its configuration/OAuth credentials remain in
the user's normal `~/.config/rclone/` location outside this repository and the
APK. See [the exchange documentation](docs/exchange_format.md) for the
versioned raw-data contract and feedback ownership rules.
## AI session-draft exchange
`TRAINLOG_AI_SESSION_DRAFT_V1` is a separate, strict proposal exchange; it
does not alter `TRAINLOG_FORMAT_V1`, the mobile export, or the existing
read-only `TRAINLOG_AI_EXPORT_V1` history export. An external producer places
one source object at
`TrainLog Gdrive:Trainlog/AI/inbox/trainlog_ai_session_draft_v1.json`. The desktop
fetches it during synchronization, validates and imports it transactionally,
then copies those exact fetched bytes after that commit to
`TrainLog Gdrive:Trainlog/AI/archive/` under its stable `aid_<uuid-v4>`
identity. The mutable inbox object is never moved or deleted: its logical
replay is idempotent, and an archive failure is visible and retryable without
undoing the committed import.
The desktop then publishes the distinct PC-to-Android companion
`trainlog-ai-session-drafts-v1.json` in `Documents/Trainlog`. Android keeps its
pending proposals as a collection, separate from its exactly-one active
capture draft. Starting one proposal copies targets only into that singleton
draft and atomically tombstones the proposal; deleting a proposal also leaves
a tombstone, so replay cannot recreate it. No proposal creates performed work,
history, MAX, or feedback.
The source and companion are bounded and strict: UUIDv4 `aid_` identities,
nonempty trimmed text when supplied (title 120 and draft notes 2,000 characters;
entries have no notes), at most 64 ordered entries, target sets 1..99, target
reps 1..999, and at most 256 unpublished drafts per deterministic publication.
Successful MTP publication advances the durable batch cursor; failure retries
the same batch. Only `SETS` plans are
allowed; the selected exercise's REPS-or-DURATION profile determines the one
valid target metric. The final real Google Drive and Android-triggered
bidirectional smoke test remains manual, so this checkpoint is documented as
`VALIDATION_PENDING`, not PASS. See [AI draft exchange](docs/exchange_format.md).
## 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 migrations are additive and preserve existing capture data. See
[Android behavior](docs/android.md) and [validation](docs/tests.md).
The current Android schema is v17. Its additive v4 -> v17 chain adds the shared
equipment catalogue, per-occurrence equipment links, durable occurrence
identities, custom-equipment definition support, explicit MAX results and
stable-source Test max resumption, then direct primary/secondary body-zone
relations and occurrence planning, without recreating completed history or
discarding the active draft. The additive v11 -> v12 migration stores flattened
exercise aliases used to resolve retired IDs during synchronization.
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:
Android:
```bash
cd android
printf 'sdk.dir=%s\n' "$HOME/Android/Sdk" > local.properties
JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
./gradlew testDebugUnitTest assembleDebug
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew test
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleRelease
```
## Android-triggered synchronization
`assembleRelease` produces a distributable stable APK only when release signing
is configured by the release operator. The repository does not contain signing
credentials and does not silently generate them.
Launch the desktop TUI from this built checkout with:
## Dependencies
```bash
cd /home/fy59/Documents/trainlog
./build/tui/trainlog
```
### Linux runtime dependencies
The desktop database is `$XDG_DATA_HOME/trainlog/trainlog.db`, or
`~/.local/share/trainlog/trainlog.db` when `XDG_DATA_HOME` is unset.
The published x86-64 TUI is dynamically linked. The current build directly
requires compatible versions of:
Build the desktop first, then install the user service:
- glibc and the GCC support runtime;
- SQLite 3;
- libuuid;
- utf8proc;
- libudev;
- libmtp;
- Notcurses Core;
- the standard math library.
```bash
bash tools/install_syncd_user.sh
```
Distribution packages may pull additional transitive libraries, including
libusb, ncursesw, unistring, gpm, libgcrypt, libgpg-error, and libdeflate.
Package names and ABI versions vary by distribution; inspect the release's
`ldd` evidence and install packages from the target distribution rather than
assuming one universal package command.
Check it with:
### Optional runtime and integration dependencies
```bash
systemctl --user is-active trainlog-syncd.service
tail -f ~/.local/state/trainlog/syncd.log
```
- Android is optional when using the desktop TUI alone. USB/MTP synchronization
requires the Android companion, libudev, libmtp, and an unlocked connected
device.
- `trainlog-syncd` installation expects a systemd user session. Manual TUI
synchronization does not require the user service.
- `rclone` is required only for automatic AI history upload and Drive
inbox/archive workflows. Core capture, local history, analytics, and direct
MTP synchronization work without `rclone`.
On Android:
### Build dependencies
```text
Sync
-> Synchroniser maintenant
```
Desktop builds require:
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 V3 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.
Exercise-zone metadata travels separately in the sole bidirectional
`trainlog-exercise-body-zones-v1.json` companion.
- Meson and Ninja;
- a C17 compiler toolchain;
- `pkg-config`;
- Notcurses Core development headers;
- SQLite, libuuid, utf8proc, libudev, and libmtp development headers;
- Python 3 for generated sources, validators, import/export helpers, and tests.
Android builds require JDK 17, an Android SDK supporting the configured API
levels, and the Gradle wrapper committed in this repository. People installing
the APK do not need Java, Gradle, or the Android SDK.
## 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;
- `docs/domain/knowledge_system.md`: read-only scientific knowledge system,
runtime-context boundary and documented future planning pipeline;
- `AGENTS.md`: development contract.
| Topic | Document | Purpose |
|---|---|---|
| Documentation map | [docs/README.md](docs/README.md) | Canonical index and ownership map. |
| Current state | [docs/current_state.md](docs/current_state.md) | Current implemented schemas, capabilities, validation, and limitations. |
| Architecture | [docs/architecture.md](docs/architecture.md) | Component, storage, process, and ownership boundaries. |
| Android | [docs/android.md](docs/android.md) | Field-companion behavior and local persistence. |
| Desktop TUI | [docs/tui.md](docs/tui.md) | Notcurses workflows, correction, and analytics. |
| Database | [docs/database.md](docs/database.md) | Desktop persistence schema and migration contracts. |
| Exercise model | [docs/exercise_data_model.md](docs/exercise_data_model.md) | Exercise, occurrence, load, and MAX semantics. |
| Synchronization | [docs/sync_exchange.md](docs/sync_exchange.md) | Active MTP flow and companion artifacts. |
| Exchange formats | [docs/exchange_format.md](docs/exchange_format.md) | Frozen and separately versioned JSON contracts. |
| Training feedback | [docs/training_feedback.md](docs/training_feedback.md) | Immediate feedback, revisions, and J+1 follow-ups. |
| Tests | [docs/tests.md](docs/tests.md) | Durable validation commands and coverage strategy. |
| Roadmap | [docs/roadmap.md](docs/roadmap.md) | Current cursor and future work only. |
| Development contract | [AGENTS.md](AGENTS.md) | Repository invariants and contribution rules. |
| Change history | [CHANGELOG.md](CHANGELOG.md) | Chronological implementation history. |
## Architecture overview
```text
Android local SQLite
|
| versioned JSON snapshots and requests
v
Documents/Trainlog on Android storage
|
| direct MTP / libmtp
v
trainlog_sync_run() <---- trainlog-syncd or desktop TUI
|
+---- import Android snapshot into desktop SQLite
+---- publish desktop catalog companions to Android
+---- write a synchronization receipt
desktop SQLite = canonical long-term history
```
The optional desktop AI flow exports read-only history and exchanges session
proposals through Google Drive via the external `rclone` process. Android does
not contain cloud credentials and does not run `rclone`.
## Current status
As of 2026-09-15:
- `TRAINLOG_FORMAT_V1=PASS/FROZEN`;
- desktop SQLite schema v18 and Android SQLite schema v17;
- Notcurses is the only active desktop terminal backend;
- direct storage is `/storage/emulated/0/Documents/Trainlog` under Android's
all-files access setting;
- mobile export V3 is active; V1/V2 remain readable legacy inputs;
- `STATS_V1=IMPLEMENTED`, `TRAINING_KNOWLEDGE_V1=PASS`, and
`SESSION_GENERATOR_V1=PASS` (the generator is hidden pending V2);
- `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2`;
- `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING` pending a real Drive plus
Android-triggered bidirectional smoke test.
The latest executable result belongs in
[current state](docs/current_state.md), not in multiple README narratives.
## Desktop development quick start
Requirements include Meson, Ninja, SQLite3, utf8proc, libuuid, libudev,
libmtp, and Notcurses.
```bash
meson setup build
meson compile -C build
meson test -C build --print-errorlogs
./build/tui/trainlog
```
The database path is `$XDG_DATA_HOME/trainlog/trainlog.db`, or
`~/.local/share/trainlog/trainlog.db` when `XDG_DATA_HOME` is unset.
## Android development quick start
Use Java 17 and provide the Android SDK through `ANDROID_HOME` or the untracked
`android/local.properties` file.
```bash
cd android
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew test
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug
```
Instrumented tests belong on an emulator. Do not run
`connectedDebugAndroidTest` on the primary personal phone.
## Synchronization quick start
```bash
meson compile -C build
bash tools/install_syncd_user.sh
systemctl --user is-active trainlog-syncd.service
```
On Android, grant all-files access for the fixed `Documents/Trainlog`
directory, then use **Synchronisation → Synchroniser maintenant**. Hardware MTP
validation requires an unlocked connected device and remains an explicit manual
check.
## 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.
- Preserve frozen, versioned compatibility boundaries.
- Use stable IDs; display names are not identities.
- Keep performed work separate from plans and continuous activity separate
from fake sets.
- Keep Android capture, desktop analytics, persistence, transport, and
rendering responsibilities distinct.
- Prefer explicit failure to silent data loss or corruption.
- Treat migrations, idempotence, tests, and documentation as part of feature
completion.
- Keep scientific catalogs separate from runtime user data and preserve stated
uncertainty.
## Measured max
## License and project notes
Explicit `max_test` sessions are the only source of measured maxima. Schema v9
stores each new weight result in a one-to-one `max_results` row; it no longer
encodes a maximum as a synthetic `1 × 1` set.
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 and the TUI expose a dedicated `Test max` form containing exercise,
optional equipment, and `Poids max (kg)`. Android can reopen an existing Test
max and atomically replace its ordered entries while preserving the original
session and occurrence identities.
```text
MEASURED_MAX_V1=PASS
WORKING_LOAD_PERCENTAGES=PASS
ANDROID_MAX_TEST_SESSION=PASS
EXPLICIT_MAX_RESULTS_V1=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.
```text
BODY_ANALYTICS_V1=PASS
BODY_COMPOSITION_ESTIMATE=PASS
BODY_PROPORTION_RATIOS=PASS
BODY_SYMMETRY_ANALYTICS=PASS
DESKTOP_TESTS=39/39 PASS
```
`SESSION_GENERATOR_V1` remains implemented but is hidden from normal Android
and TUI navigation pending V2. `catalog/exercise-names-v1.json` owns mapped
exercise display metadata across sync while preserving stable IDs and history.
See [LICENSE](LICENSE). Forgejo is the primary repository; GitHub is a mirror.
Repository layout, detailed contracts, and historical review records are linked
from [docs/README.md](docs/README.md).

View file

@ -11,8 +11,10 @@ ANDROID_INLINE_EXERCISE_ROUTE=IMPLEMENTED
ANDROID_EXERCISE_SHELL=IMPLEMENTED
ANDROID_BODY_SHELL=IMPLEMENTED
LOCAL_PERSISTENCE=NEXT
MTP_SYNC=AFTER_LOCAL_WORKFLOW
LOCAL_PERSISTENCE=IMPLEMENTED
DIRECT_STORAGE=IMPLEMENTED
MTP_SYNC=IMPLEMENTED
ANDROID_SCHEMA=17
```
## Visual contract
@ -68,10 +70,12 @@ minSdk 26
JDK 17
```
A Gradle wrapper is intentionally not committed by the scaffold script unless
it can be generated locally. From this directory:
The committed Gradle wrapper is the canonical entry point. From this directory:
```bash
gradle wrapper --gradle-version 9.6.0
./gradlew assembleDebug
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew test
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug
```
Instrumented tests run on an emulator. Do not run
`connectedDebugAndroidTest` on the primary personal phone.

View file

@ -1,3 +1,8 @@
/*
* Android TrainlogRepositoryDraftInstrumentedTest.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android SessionDraftUiInstrumentedTest.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Debug-only DraftUiTestActivity.
*
* Supports isolated Android UI validation and does not participate in release behavior.
*/
package com.labfytools.trainlog.ui
import android.os.Bundle

View file

@ -1,3 +1,8 @@
/*
* Android MainActivity.
*
* Connects the application lifecycle to Trainlog UI without owning domain or persistence behavior.
*/
package com.labfytools.trainlog
import android.os.Bundle

View file

@ -1,3 +1,8 @@
/*
* Android BodyZoneCatalog.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android BodyZoneHomeOverview.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
enum class BodyZoneHomeState(val label: String) {

View file

@ -1,3 +1,8 @@
/*
* Android DirectStoragePermission.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android EquipmentCatalog.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android ExchangeSafTree.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android ExerciseNameCatalog.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android PercentMaxCalculator.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
enum class GeneratorLoadChoice { AUTOMATIC, PERCENT_MAX, NONE }

View file

@ -1,3 +1,8 @@
/*
* Android SessionGenerationEngine.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import com.labfytools.trainlog.model.RecordingMode

View file

@ -1,3 +1,8 @@
/*
* Android SessionGenerationPolicy.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android SessionGeneratorRepositoryModels.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import com.labfytools.trainlog.model.SessionExercisePlan

View file

@ -1,3 +1,8 @@
/*
* Android StatisticsModels.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
/** STATS_V1 is a read-only projection: none of these values are persisted as facts. */

View file

@ -1,3 +1,8 @@
/*
* Android SyncCatalogInbox.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android SyncExporter.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android SyncRequestOutbox.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android TrainingKnowledgeCatalog.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android TrainlogRepository.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
import android.content.ContentValues
@ -62,6 +67,13 @@ internal fun exerciseProfileRevision(parent: String, recording: String, tracking
return "pr2_" + UUID(bytes.long, bytes.long).toString()
}
/*
* Advances one exercise profile along its bounded causal revision chain.
*
* The transaction-owning caller supplies the database. This function mutates
* only profile-state ancestry, returns false for missing/exhausted state, and
* never rewrites historical occurrence snapshots.
*/
private fun advanceExerciseProfileRevision(
db: SQLiteDatabase, rowId: Long, recording: String, tracking: String, fields: Int,
): Boolean {
@ -6884,6 +6896,13 @@ private const val MAX_AI_DRAFT_ENTRIES = 64
private const val MAX_AI_TARGET_SETS = 99
private const val MAX_AI_TARGET_REPS = 999
/*
* Owns creation and ordered migration of the Android-local capture database.
*
* SQLiteOpenHelper serializes lifecycle callbacks. Each additive migration
* preserves completed history and the singleton active draft; desktop schema
* numbering and synchronization remain separate contracts.
*/
private class TrainlogDatabaseHelper(
private val appContext: Context,
databaseName: String,

View file

@ -1,3 +1,8 @@
/*
* Android TrainlogTimestamp.
*
* Owns this data-layer boundary while keeping UI state, canonical desktop history, and exchange contracts separate.
*/
package com.labfytools.trainlog.data
internal data class TrainlogTimestampKey(

View file

@ -1,3 +1,8 @@
/*
* Android BodyModels model.
*
* Defines in-memory capture semantics without owning persistence, synchronization, or presentation.
*/
package com.labfytools.trainlog.model
data class BodyObservationDraft(

View file

@ -1,3 +1,8 @@
/*
* Android ExerciseProfile model.
*
* Defines in-memory capture semantics without owning persistence, synchronization, or presentation.
*/
package com.labfytools.trainlog.model
enum class RecordingMode(

View file

@ -1,3 +1,8 @@
/*
* Android SessionModels model.
*
* Defines in-memory capture semantics without owning persistence, synchronization, or presentation.
*/
package com.labfytools.trainlog.model
enum class SessionType(

View file

@ -1,3 +1,8 @@
/*
* Android AndroidAppShell.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.activity.compose.BackHandler

View file

@ -1,3 +1,8 @@
/*
* Android AndroidSpeechRecognitionAdapter.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Android AppNavigation.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.runtime.getValue

View file

@ -1,3 +1,8 @@
/*
* Android BodyScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.foundation.background

View file

@ -1,3 +1,8 @@
/*
* Android CompletedSessionCorrectionScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.runtime.Composable

View file

@ -1,3 +1,8 @@
/*
* Android ExerciseScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.foundation.background

View file

@ -1,3 +1,8 @@
/*
* Android FeedbackDictation.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
/** Explicit, platform-independent dictation state used by Compose and tests. */

View file

@ -1,3 +1,8 @@
/*
* Android FeedbackEditor.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import android.Manifest

View file

@ -1,3 +1,8 @@
/*
* Android HistoryScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.runtime.Composable

View file

@ -1,3 +1,8 @@
/*
* Android HomeScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.foundation.Canvas

View file

@ -1,3 +1,8 @@
/*
* Android SectionScreens.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.activity.compose.rememberLauncherForActivityResult

View file

@ -1,3 +1,8 @@
/*
* Android SessionDetailScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.runtime.Composable

View file

@ -1,3 +1,8 @@
/*
* Android SessionGeneratorScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.runtime.Composable

View file

@ -1,3 +1,8 @@
/*
* Android SessionScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
/* TRAINLOG_ANDROID_MAX_TEST_SESSION_V1 */

View file

@ -1,3 +1,8 @@
/*
* Android StatisticsScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.foundation.Canvas

View file

@ -1,3 +1,8 @@
/*
* Android SyncScreen.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
/* TRAINLOG_ANDROID_TRIGGERED_SYNC_V1 */

View file

@ -1,3 +1,8 @@
/*
* Android TrainlogApp.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
/* TRAINLOG_PC_CATALOG_AUTO_APPLY */

View file

@ -1,3 +1,8 @@
/*
* Android TrainlogComponents.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.foundation.background

View file

@ -1,3 +1,8 @@
/*
* Android TrainlogIcons.
*
* Owns this Compose presentation boundary; durable state and domain rules remain in repository and model layers.
*/
package com.labfytools.trainlog.ui
import androidx.compose.ui.graphics.Color

View file

@ -1,3 +1,8 @@
/*
* Android TrainlogTheme.
*
* Owns shared presentation tokens without changing domain, persistence, or synchronization semantics.
*/
package com.labfytools.trainlog.ui.theme
import androidx.compose.runtime.Composable

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for AiSessionDraftRepositoryTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for AndroidV10PlanningMigrationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for AndroidV11ExerciseAliasMigrationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for AndroidV16AiSessionDraftMigrationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for BodyZoneHomeOverviewRepositoryTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for BodyZonesTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for DirectExchangeStorageTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.Manifest

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for EquipmentCatalogTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import androidx.test.core.app.ApplicationProvider

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for ExerciseProfileStateSyncTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for FeedbackElapsedLabelTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for HistoricalSessionProfileImportTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for PercentMaxCalculatorTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import org.junit.Assert.assertEquals

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for RealAndroidV12MachineExerciseMigrationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for RealAndroidV14FeedbackRevisionMigrationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for RealAndroidV9BodyZonesMigrationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for SessionGenerationEngineTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import androidx.test.core.app.ApplicationProvider

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for SessionGeneratorRepositoryTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for StatisticsRepositoryTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for TrainingExerciseContextTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for TrainingFeedbackRepositoryTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for TrainingKnowledgeCatalogTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for TrainlogRepositoryDraftTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for TrainlogTimestampTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.data
import org.junit.Assert.assertEquals

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for AiSessionDraftUiWiringTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for AndroidSpeechRecognitionIntentTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import android.speech.RecognizerIntent

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for AndroidUiRedesignTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import androidx.compose.ui.test.junit4.createComposeRule

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for AppNavigationStateTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for BodyZoneHomePresentationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import com.labfytools.trainlog.data.BodyZoneHomeState

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for BodyZoneHomeSemanticsTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import androidx.compose.foundation.layout.Box

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for DraftFeedbackUiReadbackTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for FeedbackDictationControllerTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import org.junit.Assert.*

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for FeedbackEditorVoiceUiTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import android.Manifest

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for SessionGeneratorPreviewControllerTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import com.labfytools.trainlog.data.BodyZoneRecentExposure

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for SessionSetRowEditorTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import com.labfytools.trainlog.model.ExerciseProfile

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for StatisticsScreenPresentationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import androidx.compose.foundation.layout.Column

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for SyncBundlePublicationTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import android.content.Context

View file

@ -1,3 +1,8 @@
/*
* Regression coverage for TrainlogAppNavigationCallbackTest.
*
* Exercises production contracts without owning runtime behavior or persistent formats.
*/
package com.labfytools.trainlog.ui
import android.content.Context

73
docs/README.md Normal file
View file

@ -0,0 +1,73 @@
# Trainlog documentation
This file is the canonical documentation index. Canonical documents describe
the current contract or state; `reviews/` and `CHANGELOG.md` retain historical
evidence and chronology.
## Start here
- [Current state](current_state.md) owns present schemas, capabilities,
validation, and active limitations.
- [Architecture](architecture.md) owns component, process, storage, transport,
and responsibility boundaries.
- [Roadmap](roadmap.md) owns the current cursor and future work.
## Interfaces
- [Android](android.md) owns field-companion capture, local persistence,
glanceable summaries, permissions, and Android interaction rules.
- [Desktop TUI](tui.md) owns Notcurses navigation, correction, detailed
consultation, analytics, graphs, and long-term follow-up behavior.
## Data model
- [Exercise data model](exercise_data_model.md) owns exercise profiles,
occurrence identity, planned-versus-actual work, load, and MAX semantics.
- [Database](database.md) owns desktop SQLite tables, constraints, transactions,
and migrations.
- [Training feedback](training_feedback.md) owns immediate feedback, immutable
revisions, and J+1/session follow-up semantics.
- [Domain documentation](domain/) owns cited anatomy, biomechanics, equipment
interpretation, training knowledge, and session-generation science.
## Synchronization and exchange
- [Synchronization exchange](sync_exchange.md) owns the active MTP workflow,
artifact direction, replay, publication, receipts, and daemon behavior.
- [Exchange formats](exchange_format.md) owns frozen Trainlog JSON V1 and
separately versioned contracts, including AI exchange definitions.
## Analytics
- [Desktop TUI](tui.md) owns visible statistics, MAX, body analytics, calendar
buckets, and graph behavior.
- [Current state](current_state.md) records which analytics are implemented.
- [Exercise data model](exercise_data_model.md) owns the distinctions between
measured MAX, performed work, plans, and estimates.
## Development
- [Tests and validation](tests.md) owns durable build, test, sanitizer,
contract, and hardware-validation commands.
- [Coding style](coding_style.md) owns C17/Kotlin style and comment rules.
- [Development contract](../AGENTS.md) owns frozen boundaries, invariants,
validation policy, and safe device-testing policy.
- [Android build notes](../android/README.md) are the Android setup entry point.
- [Format material](../format/README.md) indexes frozen schema assets.
## Design
- [Design records](design/) retain proposals and design-gate evidence. They are
not the authority for current implementation status.
## Reviews and history
- [Review records](reviews/) retain historical audits, incidents, checkpoints,
and validation evidence. Their status and paths are historical.
- [Changelog](../CHANGELOG.md) owns chronological change history.
## Ownership rule
Do not copy long normative explanations between documents. The repository
`README.md` is the entry point, this file is the index,
`current_state.md` is the snapshot, and `roadmap.md` is future-oriented.

View file

@ -1,615 +1,133 @@
# Current implementation state
Canonical snapshot: 2026-09-10.
Snapshot date: **2026-09-15**.
This document is the compact source of truth for the implemented Trainlog
baseline. Detailed behavior belongs in the topic-specific documents.
This document owns the current implemented state. Historical checkpoints and
closed incidents belong in [reviews](reviews/) and the
[changelog](../CHANGELOG.md).
## Status
## Product boundary
```text
GATE_0_PROJECT_CONTRACT=PASS
GATE_1_TRAINLOG_FORMAT_V1=PASS/FROZEN
GATE_2_PERSISTENCE_AND_USABLE_TUI=PASS
Android is the field companion: it captures training and body data, preserves
the active draft, receives AI proposals, triggers synchronization, and shows
quick summaries. The C17/Notcurses TUI is the detailed consultation,
correction, catalogue, analytics, graphing, and long-term tracking surface.
TRAINLOG_FORMAT_V1=FROZEN
TRAINLOG_AI_EXPORT_V1=PASS
TRAINLOG_AI_GDRIVE_SYNC_V1=PASS
TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING
TRAINLOG_ANDROID_BODY_ZONE_REPOPULATION_FIX=PASS
Android captures and summarizes. The TUI analyzes and tracks over time. The
desktop SQLite database remains the canonical long-term history.
DESKTOP_SCHEMA_V11=PASS
ANDROID_LOCAL_DATABASE_V10=PASS
ANDROID_LOCAL_DATABASE_V11=PASS
ANDROID_SESSION_DRAFT_V1=PASS
ANDROID_DRAFT_DURABLE=PASS
ANDROID_DRAFT_BACKGROUND_SURVIVAL=PASS
ANDROID_DRAFT_PROCESS_DEATH_SURVIVAL=PASS
ANDROID_DRAFT_FORCE_STOP_SURVIVAL=PASS
ANDROID_SESSION_RESUME=PASS
ANDROID_DRAFT_FORM_RESTORE=PASS
ANDROID_DRAFT_EXERCISE_REMOVE=PASS
ANDROID_DRAFT_DISCARD=PASS
ANDROID_DRAFT_FINALIZE_ATOMIC=PASS
ANDROID_DRAFT_NOT_EXPORTED_AS_SESSION=PASS
EXERCISE_EDIT_V1=PASS
EXERCISE_RENAME_STABLE_ID=PASS
ANDROID_BANNER_PARITY_V1=PASS
ANDROID_INSTALL_ADB=PASS
ANDROID_USER_DATA_PRESERVED=PASS
ANDROID_MAX_V9_INSTALL_ADB=PASS
ANDROID_MAX_V9_REAL_DATA_MIGRATION=PASS
DESKTOP_MAX_V9_REAL_DATA_MIGRATION=PASS
REAL_DATABASE_APPLICATION=PASS
## Versions and compatibility
PROFILE_AWARE_EXERCISES=PASS
CONTINUOUS_ACTIVITY=PASS
VARIABLE_REPETITION_SETS=PASS
| Boundary | Current state |
|---|---|
| Frozen project exchange | `TRAINLOG_FORMAT_V1=PASS/FROZEN` |
| Desktop SQLite | schema v18 |
| Android SQLite | schema v17 |
| Mobile snapshot | V3 active; V1/V2 readable legacy inputs |
| Desktop terminal backend | Notcurses only |
| AI history export | `TRAINLOG_AI_EXPORT_V1` active |
| AI session proposals | `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING` |
| Training knowledge | `TRAINING_KNOWLEDGE_V1=PASS` |
| Session generator | `SESSION_GENERATOR_V1=PASS`, hidden pending V2 |
| Application shell | `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2` |
| Statistics | `STATS_V1=IMPLEMENTED` |
DIRECT_MTP_TRANSPORT=PASS
ANDROID_TO_PC_IMPORT=PASS
PC_TO_ANDROID_CATALOG=PASS
COMMON_SYNC_ENGINE=PASS
TRAINLOG_SYNCD=PASS
ANDROID_TRIGGERED_SYNC=PASS
ANDROID_SYNC_RECEIPT=PASS
TUI_SYNC_LOG_SHOW=PASS
BIDIRECTIONAL_SYNC_V1=PASS
MULTI_OCCURRENCE_SESSION_V2=PASS
EQUIPMENT_ASSOCIATIONS_V2=PASS
EQUIPMENT_DEFINITIONS_V1=PASS
EXERCISE_RECONCILIATION_V2=PASS
EXPLICIT_MAX_RESULTS_V1=PASS
MAX_TEST_RESUME_STABLE_ID=PASS
BODY_ZONES_V1=PASS
BODY_ZONE_SYNC_V1=PASS
BODY_ZONES_DESKTOP_REAL_MIGRATION=PASS
BODY_ZONES_TUI_REAL_VALIDATION=PASS
BODY_ZONES_ANDROID_DEVICE_VALIDATION=PASS
Desktop and Android schema numbers are independent. Neither changes the frozen
Trainlog JSON V1 contract.
TRAINING_KNOWLEDGE_V1=PASS
SESSION_GENERATOR_V1=PASS
APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2
## Storage and synchronization
DESKTOP_TESTS=52/52 PASS (normal); affected ASan/UBSan tests=6/6 PASS
ANDROID_BUILD=PASS
TRAINLOG_ANDROID_UI_REDESIGN_V1=PASS
HARDWARE_SYNC_VALIDATION=HISTORICAL_PASS
```
## Desktop
Implemented:
- C17/Notcurses true-color TUI with the APP_SHELL_V1 persistent seven-section
shell (72x20 minimum, UTF-8 bounded editors, resize fallback, F6 navigation,
F7 actions, and restored focus after overlays/routes);
- UTF-8 cell-aware scrolling training-knowledge screen, tested at the 72x20
minimum terminal;
- SQLite schema v18, with stable ordered `session_exercises.entry_id`, an
immutable occurrence-owned `tracking_mode` snapshot,
bounded durable current-profile revision ancestry,
occurrence-level equipment identity, and desktop-local custom-equipment
definitions, plus occurrence-owned `max_results`; its v9 -> v10 migration
rebuilds only `performed_sets` to permit explicit zero actual loads while
preserving historic NULL and positive rows;
- canonical generated body-zone taxonomy, direct primary/secondary exercise
relations, stable-ID-only initial migration and descendant-aware filtering;
- direct session entry;
- normal desktop SETS planning followed by the table-only explicit actual-row
editor; zero-row completion is rejected while MAX and continuous entries keep
their separate no-set contracts;
- persisted session detail and editing;
- exercise removal from a session through transactional child replacement;
- exercise catalog;
- supplied-equipment browsing/search/detail and desktop-local custom-equipment
creation/selection;
- profile-aware set and continuous activities;
- heterogeneous repetition sets;
- multiple occurrences of one catalogue exercise in a session;
- per-set actual loads with distinct external/assistance semantics;
- body-observation creation/history/editing;
- `TRAINLOG_TUI_MEASUREMENTS_GRAPH_V1=PASS`: a reusable Notcurses body-history
chart with elapsed-time X spacing, labelled and margined Y scale, Braille 2x4
line rasterization, distinct real-measurement markers, responsive geometry,
and terminal-free calculation/raster regressions;
- `TRAINLOG_TUI_STATISTICS_V1=PASS`: the TUI statistics hub covers recorded
body history, global 7-day/30-day/all-time activity, canonical-exercise
facts and graphs, persisted primary/secondary body-zone activity, and
explicit MAX history. Aggregations are read-only and terminal-independent;
volume, planning and feedback scopes remain conservative and auditable;
- `TRAINLOG_TUI_STATISTICS_REFINEMENT_V1=PASS`: finite graph windows preserve
their selected domain, weekly totals use zero-based discrete bars and local
Monday buckets, wide summaries use semantic panels, zone bars share one
numeric scale, and MAX defaults to explicitly measured exercises;
- `TRAINLOG_TUI_STATISTICS_PERIOD_BUCKETING_V1=PASS`: the 7-day summary compares
up to four consecutive calendar-week buckets and preserves empty periods;
- `TRAINLOG_TUI_STATISTICS_PERIOD_BUCKET_SIZE_FIX=PASS`: historical checkpoint
that separated bucket sizes before the subsequent calendar alignment. Its
duration-based full-history adaptation remains active;
- `TRAINLOG_TUI_STATISTICS_CALENDAR_BUCKETS_V1=PASS`: the current `7j` summary
and bars are Monday–Sunday calendar weeks, while `30j` uses the current and
preceding Gregorian calendar months. Persisted civil dates determine bucket
membership without reinterpretation through the machine timezone; empty
weeks/months and partial current periods remain explicit;
- normalized body overlays;
- exercise performance history;
- direct USB/MTP device access;
- manual Android -> PC, PC -> Android, and bidirectional synchronization;
- structured synchronization history and detail.
`TRAINLOG_AI_SESSION_DRAFT_V1` is implemented but awaits one real external
Drive/Android-triggered bidirectional smoke test. The Drive source is the
single strict `TRAINLOG_AI_SESSION_DRAFT` V1 object at
`TrainLog Gdrive:Trainlog/AI/inbox/trainlog_ai_session_draft_v1.json`; after a
transactional, idempotent desktop import its exact fetched bytes are copied to
the archive by stable `aid_` ID. The inbox object remains available for logical
replay and archive/status convergence.
Desktop exports the separate `trainlog-ai-session-drafts-v1.json` companion to
Android in deterministic batches of at most 256 unpublished proposals. It
marks exactly a batch successfully published over MTP and retries an unchanged
batch after publication failure; source replay identities remain permanent.
The Android pending proposal collection is not the durable singleton
active capture draft: explicit start copies targets only and tombstones the
proposal in one transaction; explicit deletion tombstones it too. The 64-entry,
99-set, 999-rep and 256-proposal limits, profile compatibility and replay rules
are enforced. The always-visible **Brouillons** action reports the pending
proposal count, and a successful catalogue/companion import invalidates the
pending-screen query so newly imported proposals appear without restarting the
application. Inbound `NONE`, `IMPORTED`, `ALREADY_IMPORTED`, `REJECTED`,
`DRIVE_FAIL`, and `ARCHIVE_FAIL` outcomes remain visible in successful sync
history. A transport/archive failure never rolls back a committed import.
The September 2026 connected-device diagnosis found that the installed APK was
the older 2026-09-13 build: its copied local database was still schema v16 and
had no `ai_session_drafts` table. That APK therefore could not have imported the
Android companion. After a current APK is installed, one new synchronization is
required to import the still-pending companion. This diagnosis is not the real
Drive/Android acceptance smoke test, which remains pending.
Primary navigation is now shared with Android:
```text
Accueil | Séances | Exercices | Statistiques | Synchronisation | Paramètres
```
`Séances` contains the current draft, the existing generator, manual entry,
and **Séances effectuées**. `Statistiques` exposes existing mensurations,
body views and explicit MAX/performance views only. Navigation neither writes
data nor launches synchronization. The second automated repair review covered
the TUI root search/action paths, selection/focus restoration and section hubs,
plus Android compact controls, hidden internal IDs and latest-MAX display
semantics. Human visual/accessibility review is still required before this
status can advance.
- Android stores capture data in its private SQLite database.
- Android reads and writes exchange artifacts only in
`/storage/emulated/0/Documents/Trainlog` after the user grants all-files
access through system settings.
- The old SAF/tree-URI path and `Download/Trainlog` are historical recovery
boundaries, not active storage choices.
- Desktop synchronization uses direct libudev/libmtp discovery and object
access. No GVFS/FUSE mount is required.
- `trainlog_sync_run()` is shared by the TUI and `trainlog-syncd`.
- The desktop imports Android snapshots, publishes catalog/profile/feedback/AI
companions, and returns a receipt. Stable IDs and tombstones make defined
replay paths idempotent.
- SQLite database files are never synchronized.
- The desktop AI flow uses external `rclone` for Drive inbox/archive and
read-only history export. Android owns no Drive credentials.
## Android
Implemented:
Android currently provides:
- native Kotlin/Compose application;
- local SQLite database v17, with non-destructive migrations through v17;
- one durable active-session draft, Home resume and raw-form restoration;
- `BODY_FOCUS_HOME_V1`: a compact accessible Home front/back BODY ZONES map,
textual top-three exposure ranking and Catalogue/Statistics drill-through;
- explicit confirmed discard and atomic completed-save/draft-clear;
- schema v15 immutable feedback wording revisions, Android correction UX,
read-only TUI display, and Training Feedback V2 synchronization;
- exercise creation;
- stable-ID exercise rename/editing with referenced-profile protection;
- primary/secondary body-zone selection, display, hierarchy filtering and
prefix-search composition, including visible unclassified exercises;
- inline exercise creation during session entry;
- profile-aware session recording;
- heterogeneous repetition-set entry;
- exercise removal from the current session draft;
- continuous activity recording;
- shared equipment selection, local custom equipment creation and occurrence
equipment persistence;
- local session history/detail;
- body measurements;
- automatic mobile snapshot maintenance;
- PC catalog application through user-enabled direct all-files access to the
fixed `Documents/Trainlog` directory;
- Android-triggered synchronization request;
- synchronization receipt handling.
- metadata-driven SETS+REPS, SETS+DURATION, and CONTINUOUS+DURATION capture;
- actual per-set values, optional occurrence equipment, and explicit MAX rows;
- exactly one durable active session draft with resume, confirmed discard, and
atomic completion;
- completed-session consultation and correction;
- exercise catalogue editing with stable `exercise_id` and referenced-profile
protection;
- supplied/custom equipment and machine-exercise metadata;
- body measurements and recent BODY ZONES summaries;
- immediate exercise feedback, immutable wording revisions, and J+1/session
follow-ups;
- pending AI proposal collection, explicit start/delete, and durable tombstones;
- 7/30/90-day, year, and all-history quick statistics;
- direct-storage snapshot publication, sync triggering, receipt display, and
companion application.
The Home BODY ZONES overview is exposure-only and read-only. It uses only
actual performed sets, continuous activities and explicit MAX results from
completed history, with canonical parsed-instant 7/30-day windows. Plans,
targets and drafts never qualify. Exactly the eight child anatomical zones are
processed; grouping zones cannot double count. Primary exposure dominates the
stable ranking, while secondary exposure is a weaker tie modifier. Alias
history resolves once to the canonical exercise. Unsupported zones are shown
as **Aucun exercice résolu disponible** and excluded from suggestions; empty
history is stated explicitly and suggests supported zones only. The existing
session generator remains hidden from normal UI, and the overview makes no
recovery, readiness, fatigue or prescription claim.
Android does not own canonical long-term analytics. The V1 session generator is
implemented but intentionally hidden from normal navigation pending V2.
The Android catalog exposes **Modifier** for every existing exercise. A rename
updates `name` and `normalized_name` in the original row, never creates an ID,
and remains valid for completed session and active-draft references. A profile
change is only accepted while the row has neither completed-session nor draft
references. Same-ID catalog reconciliation updates display metadata in place.
A different-ID normalized-name collision is reconciled only when recording and
tracking modes match, other known invariants are compatible, and one
`data_fields` mask contains the other. PC identity is canonical on Android;
the existing desktop identity is canonical on desktop. The richer bit-mask
union is retained without rewriting historical occurrence snapshots.
## Desktop TUI
All Android screens are hosted by the fixed `AndroidAppShell` Material 3
`Scaffold` and `TopAppBar`: root routes show `TRAINLOG`, non-root routes show
their route title, and page content is rendered beneath that shell. The drawer,
top bar and content host keep navigation separate from page ownership.
The desktop is a strict C17 application using Notcurses. It currently provides:
## Training knowledge V1
- a keyboard-first seven-section shell, semantic true-color roles, UTF-8 input,
resize recovery, and a 72x20 minimum-terminal fallback;
- session creation, history, detail, and transactional correction;
- exercise and equipment catalogue management;
- explicit measured-MAX consultation and working-load calculations;
- body observation history, correction, derived analytics, and longitudinal
graphs;
- statistics derived from canonical occurrence-owned actual work, including
exact calendar-week/month buckets, performance, body, frequency, exercise,
and BODY ZONES views;
- detailed synchronization status and history through the shared engine.
The implemented read-only training-knowledge layer loads shared versioned JSON
catalogs as the sole authored scientific source, generates the immutable C
catalog representation, and loads the same assets on Android. It has no
database migration, no auto-seeding, and no synchronization artifact. The
desktop database is schema v18 and Android is schema v17. Schema v13 is additive: exercise
rows own machine-facing load semantics, optional legacy equipment provenance,
an optional scientific profile, and an explicit resolved/unresolved science
state. Legacy equipment tables and occurrence columns remain intact.
Rendering does not own SQL or business rules. Statistics are read-only
projections; they are not persisted as facts.
Desktop `training_knowledge.h` and Android `TrainingKnowledgeCatalog` expose
source-linked science lookups and resolved-candidate filters. The additive
`equipment-exercise-relations-v2.json` catalog is the canonical
equipment-to-exercise compatibility source. Equipment owns physical identity,
load semantics, configuration labels, confidence, and source references only;
exercise knowledge exclusively owns muscles, movements, and BODY ZONES. Desktop
`training_context.h` and Android `TrainlogRepository` compose one real runtime
exercise with its persisted zones, compatible equipment, latest explicit MAX,
and bounded chronological occurrence/set history. Persisted zones remain
separate from scientific mappings; missing scientific knowledge is valid.
## Data semantics
The scientific review passed and the reviewed catalog bytes remain unchanged.
The independent temporal review returned `TEMPORAL_DELTA_REVIEW=PASS`, with no
temporal defects or repairs. It reviewed the grammar, calendar and offset
bounds, fraction precision, bytewise ties, cursor aliasing and exclusivity,
source capacity, snapshots, and Android/Python parity; it ran the targeted
Meson and four Python temporal tests. The initial full-tranche engineering
audit initially failed with stale temporal-defect documentation (BLOCKER),
Android loader parity gaps (BLOCKER), omitted Meson generator inputs (BLOCKER),
and a C role-only query mismatch (HIGH). One bounded repair chain resolved all
four findings; independent repair verification returned
`FINAL_REVIEW_REPAIR_VERIFICATION=PASS` and
`TRAINING_KNOWLEDGE_V1_ENGINEERING_REVIEW=PASS`. Fresh final validation passed:
strict build, 56 Meson tests, eight knowledge and four temporal Python tests,
knowledge/JSON/import validators, three C17 headers, affected C knowledge and
context tests under ASan/UBSan plus Python timestamp validation, normal and
sanitized 12-form temporal probes, and Android 56 tests with zero failures or
errors and one known missing-real-v9-fixture skip; Java 17 `assembleDebug` also
passed. Generated C is byte-identical with SHA-256
`e8c099f67eb111d61621b5d76592c049823af5508d43e73ec646f22e4c377fca`; all six
Android assets are byte-identical. Preservation before and after repair confirms
historical desktop/Android schemas v11/v10, unchanged catalog/science/temporal bytes, and unchanged real
database logical SHA-256 `26139cafeffbde3ec08f6ef23c5069e75afb9cd69ffffb40be5c099006fedc4d`,
counts, integrity, and foreign keys. `TRAINING_KNOWLEDGE_V1=PASS`. The
[temporal contract](reviews/training_knowledge_v1_temporal_contract.md)
defines the settled reader behavior. No manual Android install, manual TUI
visual validation or manual MTP validation is claimed for this tranche.
- `exercise_id` identifies a catalogue exercise; `entry_id` identifies one
ordered occurrence in a session.
- Plans and performed work are distinct. A continuous activity is not a fake
performed set, and actual repetitions may be heterogeneous.
- Explicit MAX results are occurrence-owned and distinct from ordinary best
sets. Equipment/resistance/execution context remains relevant.
- BODY ZONES are a UX/business projection over explicit scientific knowledge,
not a complete anatomical taxonomy.
- Feedback roots retain identity while append-only revisions preserve wording
history. J+1/session follow-ups are session-owned.
- AI proposals contain target-only plans and never create performed work,
history, MAX, or feedback until the user explicitly starts and completes a
normal capture draft.
## Synchronization
## Validation status
Canonical exchange directory:
The durable commands are owned by [tests.md](tests.md). On 2026-09-15 this audit
passed desktop compilation, **58/58 Meson tests**, Android unit tests, Android
debug assembly, the JSON validator, and the import-contract validator. Link and
diff safety checks also pass in the final audit evidence.
```text
Documents/Trainlog
```
Hardware-dependent MTP and the final real Drive plus Android-triggered AI-draft
smoke test are not automated. The latter is why
`TRAINLOG_AI_SESSION_DRAFT_V1` remains `VALIDATION_PENDING`.
Artifacts:
## Active limitations
```text
Android -> PC
trainlog-mobile-equipment-definitions-v1.json
trainlog-mobile-export-v3.json
trainlog-equipment-associations-v2.json
trainlog-exercise-body-zones-v1.json
PC -> Android
trainlog-pc-equipment-definitions-v1.json
trainlog-pc-catalog-v1.json
trainlog-pc-mobile-export-v3.json
trainlog-equipment-associations-v2.json
trainlog-exercise-body-zones-v1.json
Android -> PC agent
trainlog-sync-request-v1.json
PC agent -> Android
trainlog-sync-receipt-v1.json
```
The desktop TUI and `trainlog-syncd` share `trainlog_sync_run()`. Its explicit
`a`, `p`, and `b` modes perform Android -> PC only, PC -> Android only, and
inbound-then-outbound respectively. Each direction reconciles equipment
definitions V1 before V2 artifacts that reference them.
After the shared engine has reached a definitive successful result (including
publication of an Android-request receipt when applicable), the desktop runs
`TRAINLOG_AI_EXPORT_V1` and then the external `rclone copyto` publication to
`TrainLog Gdrive:Trainlog/AI/trainlog_ai_export_v1.json`. The TUI summary and
`trainlog-syncd` log expose `SYNC`, `AI_EXPORT`, and `GDRIVE_UPLOAD` separately.
An export or Drive failure never retroactively changes the successful Trainlog
sync result. Android does not execute `rclone`; all rclone/OAuth configuration
remains external under the user's normal `~/.config/rclone/` directory.
Each structured local sync-history run persists its selected `a`, `p`, or `b`
direction. `direction inconnue` is reserved for legacy history rows whose
direction was not recorded; it is never used for a current run.
On the TUI Sync page, `a`, `p`, and `b` directly open one confirmation for the
selected direction; `Enter` runs that confirmed action once, while `Esc`
cancels it without an operation (or returns when idle). `r` refreshes device
status only, and the former `s` shortcut is inert. Full and compact footers use
the same three directional actions, with compact labels only abbreviating the
endpoints.
V2 resolves equipment by `(session_id, entry_id)`, never by display name or
catalogue identity alone. V1 files remain legacy-compatible and do not gain
multi-occurrence semantics retroactively.
Body-zone relations have one direction-neutral companion and are keyed only by
canonical zone IDs and `exercise_id`. Identical state is an idempotent skip; a
successful publication records that exact state as the publisher baseline, so
a later peer-only edit of a new custom exercise is accepted. Simultaneous
divergence is an explicit conflict and secondary lists are never unioned. The
unclassified state has no relations; orphan secondary rows are invalid. Parent
groups are derived from the manifest and never serialized as exercise
relations.
Supplied equipment remains generated from `catalog/equipment-v1.json`; its IDs
are reserved. User-created definitions synchronize additively through the
directional definitions V1 artifacts. Equal same-ID definitions are idempotent;
divergent definitions, unknown references, and association conflicts are
reported explicitly without silent overwrite.
Conflict summaries identify the stable session, body-observation, association,
or definition identity and the source artifact/direction. Existing persisted
content is preserved when a conflict is reported.
Synchronization addition counters describe durable inserts only. Exercise
rekeys or profile enrichments remain available through the separate
reconciliation counter; a compatible different-ID lookup that requires no
database write is an idempotent skip. This prevents a repeated Android
snapshot carrying a retired creator ID from being displayed as `+1 exercice`
when the canonical desktop catalog row and all business tables are unchanged.
The current Marche collision is safely reconciled to desktop identity
`ex_b1e6ffc6-75b5-45ff-a3c0-e7433c58013d`. Its catalog profile is the compatible
union `continuous/duration/data_fields=3`; prior `data_fields=1` occurrences
retain their own snapshots and absent distance values. The Android occurrence
`sxe_f25142c8-455e-4346-9bfc-31d0989e275d` retains its duration, speed, distance
and equipment. No reference to the retired Android exercise identity remains
in the validated merged database copy.
No SQLite file is copied.
No mounted Android filesystem is required.
## Session generator V1
`SESSION_GENERATOR_V1=PASS`.
The frozen separate policy is loaded identically by C and Android. It generates a
read-only, editable proposal for `full_body`, `upper_body`, `lower_body`, or
one of the eight remaining leaf zones, with four goals and policy-owned duration
bounds. It uses complete completed-history evidence, deterministic candidate
selection, explicit shortages, and an observed repeated-dose load only when the
exact exercise and compatible external equipment have a qualifying 28-day
anchor. MAX is context only and never produces a numeric target.
The Android v10 -> v11 migration adds nullable planning metadata to normal and
draft occurrences. Existing rows receive `load_mode=none`, `rest_seconds=0`,
and null targets without reconstruction. Android acceptance atomically creates
the existing normal singleton draft with no actual sets; an existing draft is a
non-mutating conflict. TUI `g` opens its preview and then the normal editor;
completion still needs actual work. Empty proposals cannot be accepted.
Nonempty partial proposals retain explicit shortages and may enter normal draft
editing. The separate active V3 mobile export preserves plan and actual fields
atomically; V1/V2 remain readable legacy formats.
The initial [independent engineering delta review](reviews/session_generator_v1_engineering_review.md)
found no findings, and the one deep final audit then found three repairable
blockers. Its bounded repairs and bounded final repair review passed. The final
matrix passed 45/45 Meson tests, named ASan/UBSan 4/4, and Android 75 tests with
zero failures/errors and one known unavailable external real-v9 fixture skip;
the structural v10 migration test executed and passed. Validators, strict C17
headers, deterministic generation and APK asset byte comparisons passed. Real
desktop preservation is baseline-equal with logical SHA-256
`5bff76850581cc3abafd583377a5de2c16d6313607a4cacb812376cfebd36cce`, ten
protected catalog/format files unchanged, and an empty index. No real app
upgrade/install or hardware MTP exercise is claimed.
## Validation checkpoint
Desktop:
```text
Historical checkpoint: 39/39 Meson tests PASS for desktop schema v11
JSON valid/invalid checks PASS
import-contract validator 6/6 PASS
Historical checkpoint: ASan/UBSan 39/39 Meson tests PASS with leak detection
standalone public-header C17 syntax PASS
real Notcurses binary zone workflows PASS in Kitty
git diff --check PASS
```
Android:
```text
Android unit tests 44/44 PASS with a real v9 copy enabled, including body-zone
v9 -> v10 migration and sync
assembleDebug PASS
APK Signature Scheme v2 verification PASS
APK certificate SHA-256 matches the local debug keystore
APK SHA-256 5355f54e83f00ef31dc0e2f1db866c43ea3e9d880875f67878b1ffe169b5dc50
```
Current real-data/environment boundary:
```text
BODY_ZONES_DESKTOP_BACKUP=PASS
BODY_ZONES_DESKTOP_REAL_V10_TO_V11=PASS
BODY_ZONES_DESKTOP_HISTORY_ROW_EQUALITY=PASS
BODY_ZONES_DESKTOP_RELATIONS=32/20_EXERCISES
BODY_ZONES_DESKTOP_UNCLASSIFIED=3
BODY_ZONES_ANDROID_INSTALL=PASS
BODY_ZONES_ANDROID_REAL_V9_COPY_TO_V10=PASS
BODY_ZONES_ANDROID_INSTALLED_DB_V10=PASS
BODY_ZONES_LIVE_MTP_ROUNDTRIP=PASS
BODY_ZONES_LIVE_MTP_SECOND_PASS_IDEMPOTENT=PASS
REAL_ANDROID_ARTIFACT_COPY_ROUNDTRIP=PASS
PC_EXPORT_ANDROID_IMPORT_THREE_PASS_COPY=PASS
ANDROID_INSTALL_R_DATABASE_HASH_PRESERVED=PASS
```
The real desktop v10 database was backed up with SQLite to
`backups/body-zones-v1-20260909T101310Z/trainlog-v10-before.db` under the Trainlog
user-data directory (SHA-256
`9d5dcacd8be941be17bd3b98bbbc1815944fec9dc1b18133111ca481f50684e9`).
The production binary migrated it to v11; integrity/FK checks pass and every
row of all eight historical tables compares equal in both directions with the
backup. The migration added 32 direct relations for 20 of 23 exercises and
left Gym échauffement, Gym/Échauffement and Marche unclassified.
The Samsung SM-G990B application data was freshly backed up before installation
under `backups/body-zones-v1-android-20260909TVFprJ8/`. The coherent v9 SQLite
copy has SHA-256
`c402b69cdbfa4912eec1553a84754af1e892fa8da0cae6c3cbd14c4f10198401`;
its integrity check passed and its foreign-key check was empty. The installed,
built and established-keystore certificate fingerprints all matched
`aa56c97f2781a0d01f007f4444c3970deb58ad8327ca3da7b0b90f37dbe2ad25`
before `adb install -r`. The normal installed migration reached v10 with every
row of the 16 existing application tables plus `android_metadata` equal in both
directions to the v9 backup. Integrity/FK checks pass, 32 direct relations cover
20 of 23 exercises, and Gym échauffement, Gym/Échauffement and Marche remain
unclassified.
The real Android UI displayed primary, secondary and derived-group metadata;
the chest, upper-body descendant, lower-body descendant and unclassified
filters; and the `Dos` + `lat` prefix intersection. Editing `Lat pull`
preloaded `Dos` and secondary `Bras`; cancellation left every application table
unchanged. Live sync runs `sy_0b00dc46-d899-4865-8718-c95085a380b2` and
`sy_fb0630a8-1938-4d62-9836-b0281955f625` both completed successfully. The
second run reported zero additions/reconciliations, both stores retained the
same 32-relation stable-ID hash, and Android application tables plus companion
exercise states compared equal to the first pass. Android now resolves the
exact request and every outbound artifact through direct filesystem access to
`Documents/Trainlog`. Historical numbered copies remain untouched. One
transaction-local exact-name snapshot is shared
by the complete export and request on the I/O dispatcher; each later sync takes
a fresh snapshot. The redundant application-start export was removed, and
request-ID replay protection remains authoritative.
`TRAINLOG_ANDROID_DIRECT_STORAGE_V1=PASS`. Android no longer uses SAF for
exchange artifacts. User-enabled all-files access gates the fixed
`/storage/emulated/0/Documents/Trainlog` directory; direct temporary-file
replacement preserves canonical names. A physical Samsung Android 16 one-sync
and its identical second run completed successfully without suffixes, and the
recovered `Dos + biceps` proposal remained visible after replay.
## Current implementation cursor
Body Zones V1, exercise reconciliation, definition-first V2 synchronization
and the current real-data importer/exporter copy validation are complete. The
body-zone taxonomy now supplies the read boundary required by a future
zone-driven planner; no session generator or proposed-load calculation is part
of this tranche.
```text
MEASURED_MAX_V1=PASS
WORKING_LOAD_PERCENTAGES=PASS
ANDROID_MAX_TEST_SESSION=PASS
EXPLICIT_MAX_RESULTS_V1=PASS
MAX_TEST_RESUME_STABLE_ID=PASS
CURRENT_OPERATIONAL_CURSOR=REAL_DATA_BASELINE_V1
NEXT_FEATURE=GYM_CATALOG_V1
```
Future work must start from this validated baseline rather than from obsolete
historical `NEXT` notes.
## Measured max v1
```text
MEASURED_MAX_V1=PASS
MEASURED_MAX_ONLY_FROM_MAX_TEST=PASS
WORKING_LOAD_PERCENTAGES=PASS
ASSISTANCE_DIRECTION_AWARE=PASS
ANDROID_MAX_TEST_SESSION=PASS
EXPLICIT_MAX_RESULTS_V1=PASS
MAX_TEST_RESUME_STABLE_ID=PASS
DESKTOP_TESTS=58/58 PASS
```
A measured maximum belongs to an exercise occurrence in an explicit `max_test`
session. Schema v9 introduced the positive `max_weight_kg` separately from sets;
equipment is optional context, so one physical machine may carry independent
Pec Fly and Rear Delt Fly results. Ordinary training is never promoted
implicitly.
The Android and TUI max forms request no set or repetition count. Android loads
a completed Test max without changing `session_id`, existing `entry_id` values,
order, exercise identity, or equipment context; finalization replaces that
session's children atomically, permits explicit value/equipment corrections,
and may append new entries.
The current measured result is the newest successful max test. The historical
record compares max tests using the same load mode.
External-load working percentages are pure calculations from the current
measured load; they are not persisted and no estimated 1RM is introduced.
`PERCENT_MAX_INPUT_V1` additionally provides a user-directed 1..100 integer
calculator in desktop planning, Android ordinary set-based manual planning, and
both generator previews. It requires the exact exercise and
external-resistance equipment identity; assistance is unavailable. It uses
`MAX × percentage / 100`, makes no recommendation, and persists only the
resulting `target_weight_kg`. Automatic generation retains the frozen V1
observed-load policy. Latest MAX means the chronologically latest explicit
result, not an aggregate record.
## Body analytics v1
```text
BODY_ANALYTICS_V1=PASS
BODY_ANALYTICS_TUI_ONLY=PASS
BODY_COMPOSITION_ESTIMATE=PASS
BODY_PROPORTION_RATIOS=PASS
BODY_SYMMETRY_ANALYTICS=PASS
NO_ESTIMATE_PERSISTENCE=PASS
DESKTOP_TESTS=58/58 PASS
```
Android remains capture-only for this feature.
The TUI derives analytics from canonical body observations. A local
desktop-only profile provides height and the circumference-formula branch
needed for the optional body-fat estimate.
No estimated body-fat, fat-mass, lean-mass, ratio, or asymmetry value is stored
as if it were a real measurement.
## Shell and naming closeout
`SESSION_GENERATOR_V1` is technically retained but hidden from normal UI pending
V2. Manual `%MAX` remains available. `exercise-names-v1.json` supplies
movement-oriented canonical metadata for mapped stable IDs; stale peer labels
cannot overwrite those names, while custom exercise naming remains editable.
`STATS_V1=IMPLEMENTED`: Android exposes a Material 3 Catppuccin Mocha/Lavender
statistics landing dashboard with selected-period fact counts, weekly
conservative working/MAX improvement-event counts, bounded measurements, weekly
session frequency, and context-owned drill-down series. The Notcurses Statistics
route loads a bounded, transactionally consistent controller snapshot outside
rendering and presents the same global facts at wide and compact widths. Working
events compare every set only within exact exercise, resolved external equipment
and performed-dose context; equal canonical instants never create an event. A
session contributes once when it owns a
performed set, continuous activity, or explicit MAX; `ended_at` does not gate
persisted history, and plans/empty occurrences contribute nothing. Exact parsed
instants, rather than SQLite text ordering, choose and order points. Its >=100-column multi-region and 72x20
compact layouts retain the same three detail actions.
Malformed legacy timestamps are skipped before sorting with a nonfatal dashboard
warning on both platforms.
No statistics schema or exchange-format field was added.
- `APP_SHELL_V1` still awaits the recorded human visual/accessibility review.
- AI proposal exchange still awaits one real Drive plus Android-triggered
bidirectional smoke test.
- Session Generator V1 is hidden while V2 planning semantics are developed.
- Hardware MTP validation requires a connected unlocked Android device.
- Scientific knowledge is bounded to reviewed catalog entries; unknown custom
exercises remain unclassified rather than inferred from names.

View file

@ -17,7 +17,7 @@ is a later, separately versioned migration.
```text
TRAINLOG_DATABASE_SCHEMA_VERSION=18
DATABASE_SCHEMA_V18=IMPLEMENTED_VALIDATION_PENDING
DATABASE_SCHEMA_V18=IMPLEMENTED
TRAINLOG_FORMAT_V1=FROZEN
```

View file

@ -1,523 +1,103 @@
# Roadmap
## MACHINE_EXERCISE_MODEL_V1 — implementation awaiting human review
Phase 1 introduces additive schema v13, machine-facing exercise identities,
exercise-owned load semantics, reusable scientific profile references, exact
manifest splits, and exercise-centric Android/TUI navigation. Real database
migration remains gated on copy validation and human review.
`EQUIPMENT_SCHEMA_CLEANUP_V1` remains a later phase after real-world
validation; it may remove obsolete legacy tables, fields, companions, and UI
only with a new explicit migration and compatibility decision.
This file is the canonical product roadmap for Trainlog.
Historical implementation detail belongs in Git history and `docs/reviews`.
The published Trainlog JSON v1 compatibility boundary remains frozen unless a
future feature explicitly introduces a new version.
## TRAINLOG_AI_SESSION_DRAFT_V1 — implementation validation pending
The implemented proposal path is deliberately separate from completed-session
exchange and the singleton capture draft. A strict single Drive source,
`TRAINLOG_AI_SESSION_DRAFT` V1, imports target-only SETS proposals into desktop
schema v18, archive-copies the exact fetched snapshot only after commit while
retaining the inbox object for logical replay, then produces the bounded
`trainlog-ai-session-drafts` V1 Android companion in deterministic batches of
at most 256 unpublished proposals. Only successful MTP publication advances
the desktop cursor; replay identities remain permanent. Android v17 retains a
pending collection, while explicit start/delete creates durable tombstones so
replay is idempotent and cannot resurrect proposals. The remaining gate is one
real Drive plus Android-triggered bidirectional synchronization smoke test;
therefore this item is `VALIDATION_PENDING` and is not PASS.
## Completed foundation
```text
GATE_0=PASS
GATE_1=PASS
GATE_2=PASS
TRAINLOG_FORMAT_V1=FROZEN
DESKTOP_SCHEMA_V11=PASS
ANDROID_LOCAL_DATABASE_V10=PASS
ANDROID_LOCAL_DATABASE_V11=PASS
SESSION_GENERATOR_V1=PASS
DIRECT_MTP_TRANSPORT=PASS
BIDIRECTIONAL_SYNC_V1=PASS
VARIABLE_REPETITION_SETS=PASS
MEASURED_MAX_V1=PASS
EXPLICIT_MAX_RESULTS_V1=PASS
MAX_TEST_RESUME_STABLE_ID=PASS
BODY_ANALYTICS_V1=PASS
EXERCISE_EDIT_V1=PASS
ANDROID_BANNER_PARITY_V1=PASS
BODY_ZONES_V1=PASS
BODY_ZONE_SYNC_V1=PASS
BODY_ZONES_ANDROID_DEVICE_VALIDATION=PASS
DESKTOP_TESTS=58/58 PASS (latest validated checkpoint)
TUI_NOTCURSES_V1=PASS
TRAINLOG_TUI_MEASUREMENTS_GRAPH_V1=PASS
TRAINLOG_TUI_STATISTICS_V1=PASS
TRAINLOG_TUI_STATISTICS_REFINEMENT_V1=PASS
TRAINLOG_TUI_STATISTICS_PERIOD_BUCKETING_V1=PASS
TRAINLOG_TUI_STATISTICS_PERIOD_BUCKET_SIZE_FIX=PASS
TRAINLOG_TUI_STATISTICS_CALENDAR_BUCKETS_V1=PASS
NCURSESW_REMOVED_FROM_ACTIVE_TUI=PASS
NOTCURSES_TRUECOLOR_THEME=PASS
```
The current product baseline includes:
- SQLite persistence and explicit migrations;
- usable Notcurses desktop TUI;
- native Android capture client;
- exercise catalog;
- canonical hierarchical body zones, primary/secondary relations and filters;
- Android `BODY_FOCUS_HOME_V1` recent-exposure map and deterministic supported-zone ranking;
- profile-aware set and continuous activity;
- heterogeneous repetition sets;
- session history and editing;
- body measurements and body history;
- measured-max sessions and measured-max analysis;
- explicit max-weight capture without synthetic sets, with stable-ID continuation;
- desktop body analytics;
- direct USB/MTP transport;
- bidirectional Android/PC synchronization;
- safe V2 exercise-identity reconciliation with richer compatible profiles;
- supplied and custom equipment definitions with occurrence-level links;
- shared synchronization engine and `trainlog-syncd`.
`BODY_ZONES_V1` is complete infrastructure for session generation: one shared
manifest, desktop v11/Android v10 relations, Android/TUI edit and display,
descendant-aware filters, unclassified history and one explicit-conflict sync
companion. The implemented generator uses this infrastructure; custom zones are
still outside this checkpoint and proposed loads remain editable plans rather
than actual work.
`TRAINING_KNOWLEDGE_V1=PASS` is read-only infrastructure. Scientific review,
independent temporal review, final engineering review, repair verification, and
final executable validation passed. The temporal contract preserves source text
and exact C/Android chronological pagination; one bounded repair chain closed
the audit's stale-documentation, Android-loader, Meson-input, and role-only C
query findings.
It provides catalog-backed scientific lookup and runtime context composition
without prescriptions, schema changes or catalog seeding. The implemented
separate session generator consumes that boundary; multi-session programming is
still not a roadmap gate. See
[Training knowledge system V1](domain/knowledge_system.md).
`EXERCISE_EDIT_V1` is a completed capture correction: Android permits
stable-ID renames, protects referenced profiles, and reconciles same-ID display
metadata without duplicates. `ANDROID_BANNER_PARITY_V1` is a presentation-only
completed checkpoint: all Android screens share the Notcurses-derived compact
header; it does not reorder the roadmap below.
`BODY_FOCUS_HOME_V1` is a completed Android Home presentation/query tranche.
It adds no schema, sync or catalog semantics. The overview uses completed
actual work only, canonical exact-instant windows and exactly eight anatomical
child zones. Its stable order is: absent primary, oldest primary, lower primary
7d then 30d, weaker lower secondary 7d then 30d, oldest secondary, then stable
zone ID. Only zones backed by a current canonical exercise with a resolved
direct relation are eligible; unsupported zones stay visible but cannot be
recommended. It is descriptive exposure guidance, not medical recovery,
fatigue/readiness analysis or a session prescription. Session Generator V1
remains hidden.
This document owns future work. The implemented baseline is summarized in
[current_state.md](current_state.md); completed narratives and evidence belong
in [reviews](reviews/) and [CHANGELOG.md](../CHANGELOG.md).
## Product boundary
`ANDROID_SESSION_DRAFT_V1` is an implemented P0 capture-reliability correction:
one durable active draft, Home resume, explicit discard and atomic completion.
Host and device validation and the final tranche review pass. This repair
does not introduce planning/templates or reorder the product roadmap below.
The intended split remains:
```text
ANDROID
= fast capture during real use
DESKTOP TUI
= planning
+ catalog management
+ history
+ analytics
+ long-term decision support
Android captures and summarizes.
The TUI analyzes and tracks over time.
Desktop SQLite is canonical long-term history.
```
Android must not become the main analytics surface unless this boundary is
explicitly revised later.
Future work must preserve the frozen Trainlog JSON V1 contract, stable
identities, actual-versus-planned separation, direct MTP transport, and the
distinction between measured values and estimates.
## Operational cursor — real data baseline
## Current baseline
The immediate priority is not another implementation block.
Desktop schema v18, Android schema v17, Notcurses, direct
`Documents/Trainlog` storage, mobile export V3, Training Knowledge V1, Body
Zones V1, Training Feedback V1/V2, STATS V1, and Session Generator V1 are
implemented. Session Generator V1 is hidden pending V2. AI session-draft
exchange and APP_SHELL_V1 retain their explicit manual validation/review gates.
Trainlog now needs real measurements and real training sessions so later
analytics are based on useful data rather than fixtures.
## Current cursor
```text
CURRENT_OPERATIONAL_CURSOR=REAL_DATA_BASELINE_V1
```
Expected baseline:
Collect real body observations and training sessions through Android, sync them
to desktop, and record MAX only through explicit `max_test` results. Do not seed
fictitious user history into canonical databases.
- first complete real body observation;
- first real training sessions;
- Android used for capture;
- Android -> PC synchronization after training;
- measured max recorded only through explicit `max_test` results;
- no fictitious user data in the canonical databases.
## Next
Gate:
### Gym catalog V1
```text
REAL_DATA_BASELINE_V1=PASS
```
Build an inventory from the equipment actually available in the user's gym.
Keep physical equipment, exercises, movement patterns, muscles, and BODY ZONES
distinct. One photographed machine may support several exercises. Record model
or manufacturer only when evidence identifies it.
## Next feature — gym catalog v1
### Exercise metadata V1
```text
NEXT_FEATURE=GYM_CATALOG_V1
```
After the gym inventory is normalized, design any remaining structured metadata
such as movement family and laterality. Reuse existing equipment and BODY ZONES
identities; do not encode semantics in display names or notes.
The catalog must be based on the equipment actually available in the user's
gym rather than on a generic Internet exercise list.
### Session Generator V2
The initial inventory will be built from a complete photo survey of the gym.
Replace the hidden V1 product surface with duration-aware construction that can
represent warm-up, ramp-up, main work, cool-down, transitions, setup time, and
candidate scarcity without claiming unsupported physiological precision.
For each useful equipment item, record where applicable:
### Session templates V1
```text
gym zone
equipment family
machine name
manufacturer/model if identifiable
useful duplicate count
supported exercise(s)
Trainlog recording profile
load semantics
primary body zone (BODY_ZONES_V1)
secondary body zones (BODY_ZONES_V1)
```
Offer editable conveniences built on the real catalogue and planning model.
Templates are not rigid prescriptions and never become performed work before
normal capture.
Important rule:
## Later
```text
one photographed machine != one exercise
```
- evolve training analytics only where sufficient real history supports the
metric, without aggregating incomparable loads;
- add body baseline and trend comparisons while keeping direct measurements
distinct from estimates;
- add conservative, editable progression suggestions with correct external-load
and assistance interpretation;
- support explicit user objectives without mandatory gamification;
- add user-controlled dated backup and complete/period-limited exports.
A single piece of equipment may support multiple exercises. Equipment and
exercise identity must remain distinct concepts.
The primary/secondary body-zone dimension is already implemented by Body Zones
V1; gym inventory should link stable exercise identities to that model rather
than inventing another free-text body-region field.
Gate:
```text
GYM_CATALOG_V1=PASS
```
## Exercise metadata v1
After the real gym inventory is normalized, enrich the exercise catalog with
structured metadata needed by planning and analytics.
Remaining candidate metadata:
```text
equipment
movement_family
laterality
```
The final schema must be designed before implementation. Do not encode these
concepts into names or free-form notes as a substitute for a real model.
Body-zone metadata is no longer future scope here; its frozen V1 taxonomy and
direct relations must be reused.
Gate:
```text
EXERCISE_METADATA_V1=PASS
```
## Session generator v1
`SESSION_GENERATOR_V1=PASS`.
It provides
a bounded policy-driven generator in both UIs, then hands accepted nonempty
proposals to normal draft/editor flows where actual work is captured separately.
It does not create reusable templates, a planned-session sync product surface,
or a multi-session program. The one deep final audit found repairable gaps;
its bounded repairs, review, and final validation matrix passed.
`PERCENT_MAX_INPUT_V1` is an explicit calculator layered on editable V1 targets;
it does not change automatic selection policy. Any broader multi-session,
periodized, or program-producing work is deferred to a future
`SESSION_GENERATOR_V2` and is not present today.
## Session templates v1
Templates are built from the real exercise catalog and planning model.
Candidate examples:
```text
glutes + arms
back + shoulders
chest + arms
legs
full body
cardio
```
Templates are conveniences, not rigid training prescriptions.
Gate:
```text
SESSION_TEMPLATES_V1=PASS
```
## Training analytics v1
Implement only after enough real sessions exist to make the output meaningful.
Candidate views:
```text
7 days
30 days
90 days
since baseline
```
Candidate metrics:
- training frequency;
- sets;
- repetitions;
- duration;
- external load;
- assistance;
- measured-max progression;
- useful volume metrics where semantically valid;
- progression by exercise.
Use the existing Body Zones V1 read surface to aggregate by canonical body zone
and hierarchy when evaluating training balance. A separate movement-family
taxonomy, if ever needed, must be versioned rather than inferred from names.
Gate:
```text
TRAINING_ANALYTICS_V1=PASS
```
## Body analytics v2
Implement after multiple real body observations exist.
Possible additions:
- baseline comparison;
- previous-observation comparison;
- 7/30/90-day trends;
- moving-average weight trend;
- waist trend;
- anthropometric estimate trend;
- left/right asymmetry trend;
- obvious measurement-outlier warnings.
Permanent semantic rule:
```text
DIRECT_MEASUREMENT != ESTIMATE
```
Derived body-composition values remain explicitly labelled estimates.
Gate:
```text
BODY_ANALYTICS_V2=PASS
```
## Progression assist v1
Use accumulated history to produce conservative training suggestions.
Examples:
```text
external load:
stable completed work
-> suggest a small load increase
assistance:
stable completed work
-> suggest a small assistance decrease
```
The system proposes. It does not silently change planned training.
Measured max and estimated performance must remain distinct concepts.
Gate:
```text
PROGRESSION_ASSIST_V1=PASS
```
## Objectives v1
Support explicit user-defined objectives such as:
```text
body weight
waist circumference
plank duration
exercise load
repetitions
measured max
weekly training frequency
```
Display current value, target, remaining difference, and trend where the data
supports it.
Avoid mandatory gamification.
Gate:
```text
OBJECTIVES_V1=PASS
```
## Backup and export v1
SQLite remains canonical.
Provide durable user-controlled export and backup:
```text
SQLite
-> dated backup
-> complete JSON export
-> CSV export where useful
-> period-limited export
```
Gate:
```text
BACKUP_EXPORT_V1=PASS
```
## Canonical implementation order
## Canonical order
```text
REAL_DATA_BASELINE_V1
|
v
GYM_CATALOG_V1
|
v
EXERCISE_METADATA_V1
|
v
SESSION_PLANNER_V1
|
v
SESSION_TEMPLATES_V1
|
v
TRAINING_ANALYTICS_V1
|
v
BODY_ANALYTICS_V2
|
v
PROGRESSION_ASSIST_V1
|
v
OBJECTIVES_V1
|
v
BACKUP_EXPORT_V1
-> GYM_CATALOG_V1
-> EXERCISE_METADATA_V1
-> SESSION_GENERATOR_V2
-> SESSION_TEMPLATES_V1
-> ANALYTICS_EVOLUTION
-> BODY_ANALYTICS_V2
-> PROGRESSION_ASSIST_V1
-> OBJECTIVES_V1
-> BACKUP_EXPORT_V1
```
This order is canonical until explicitly revised.
## Outstanding validation gates
## APP_SHELL_V1 visual-review boundary
`APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2`. The implemented shell does
not open STATS_V1, EXERCISE_NAMING_V1, a schema migration, or a new
synchronization feature. The remaining checkpoint is a human visual and
accessibility review of the existing seven-root shell: TUI at 72x20, 80x24,
100x25, 100x30, 120x31 and 120x35; Android at 320, 360, 393 and 412 dp, with
large system font, IME form interaction, durable-draft leave guards and
TalkBack. The `_2` suffix records the completed automated repair review only;
its human-review outcome is not recorded here in advance.
- `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2`: complete the recorded
human visual/accessibility matrix without redefining product semantics.
- `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING`: complete one real Drive
plus Android-triggered bidirectional synchronization smoke test.
## Permanent constraints
Do not regress to:
```text
SQLite file synchronization
mandatory mounted Android filesystem
exercise-name identity heuristics
fake performed sets for continuous activity
fake uniform targets for heterogeneous actual sets
ordinary training promoted to measured max
assistance interpreted as external load
body-composition estimates presented as direct measurements
incompatible changes to Trainlog JSON v1 without a new format version
```
## Session generator V2
V1 remains technically implemented but hidden from normal UI. V2 must construct
the requested duration with warm-up, appropriate ramp-up, coherent main work,
cool-down, transition/setup time, and explicit candidate-scarcity handling.
## STATS_V1
Implemented as a derived-query tranche with no summary schema: the Android
landing dashboard provides 7/30/90-day, year and all-history filters;
the performance landing graph counts weekly improvements from every performed
set under strict canonical exercise/nonempty resolved external-equipment/
performed-dose comparison, with explicit MAX classified separately. Only a
strictly later canonical instant can improve; equal-instant IDs order display
only. Raw values remain drill-down data and are never globally aggregated
as kilograms. Body charts show only actually recorded measures; weekly frequency
uses MAX as a marker on the once-counted session. The Notcurses
landing route gives the same visual dashboard hierarchy and routes to the
existing detailed persisted views. Its controller-owned snapshot is loaded
outside rendering; wide terminals show performance, body and frequency regions
while 80x24 and 72x20 retain all three factual summaries. The selected-period
summary reports sessions, performed sets, distinct actual exercises and explicit
MAX records; body selects the latest actually observed metric, and weekly frequency counts each stable session
ID exactly once when it owns at least one persisted performed set, continuous
activity, or explicit MAX result. Planned targets and empty occurrences do not
count; `ended_at` is presentation/lifecycle metadata, not the history gate.
Exact parsed instants govern boundaries and ordering. Assistance, missing values
and incomparable equipment contexts are never coerced into progress values.
Malformed legacy timestamps are skipped by the projection with a visible warning
while valid statistics remain available.
Future equipment identification may collect manufacturer, model, name, photo,
pictogram, and notes to form a proposal which the user must verify before
persistence. OCR/image recognition and a custom-equipment relation schema are
not part of EQUIPMENT_KNOWLEDGE_V2.
## TRAINING_FEEDBACK_V1
Implemented for human review: schema v14, Android voice/manual append-only
capture and readback, desktop read-only consultation, and bidirectional strict
companion convergence. Automatic interpretation and recommendations remain
future scope.
Do not regress to SQLite-file synchronization, a mandatory mounted Android
filesystem, exercise-name identity heuristics, fake sets for continuous work,
synthetic uniform actuals, ordinary training promoted to measured MAX,
assistance interpreted as external load, or estimates presented as direct
measurements. Incompatible exchange semantics require a new version.

View file

@ -120,8 +120,9 @@ tui_workflows
app_shell
```
The current desktop suite, including APP_SHELL_V1 production-transition
coverage and the AI-draft exchange regressions, is 56/56. The following generator-specific checkpoint counts remain
The desktop suite includes APP_SHELL_V1 production-transition coverage and the
AI-draft exchange regressions. The configured suite size and latest result are
recorded once in `current_state.md`; the following generator-specific checkpoint counts remain
historical evidence:
```text
@ -234,6 +235,28 @@ Notable regression coverage:
## 5. Build
Stable-release validation additionally builds the signed release variant and
checks the packaged product versions and binary linkage:
```bash
meson compile -C build
meson test -C build --print-errorlogs
cd android
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew test
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleRelease
file trainlog-tui-linux-x86_64-v0.1.0
ldd trainlog-tui-linux-x86_64-v0.1.0
sha256sum trainlog-android-v0.1.0.apk \
trainlog-tui-linux-x86_64-v0.1.0
```
An unsigned release APK is build evidence only and must not be published as the
canonical stable Android artifact. Release assets must come from the exact
tagged source state, and the tag, Android `versionName`, Meson project version,
TUI product version, asset names, and release title must agree.
```bash
meson setup --reconfigure build
meson compile -C build
@ -533,7 +556,7 @@ Coverage proves:
and footer F6/F7 single-occurrence output at 120x35, 100x30, 80x24, and
72x20.
Historical validation checkpoint (the current desktop suite is 56/56):
Historical validation checkpoint (not the current suite size):
```text
39/39 Meson tests PASS

View file

@ -548,7 +548,7 @@ meson test -C build --print-errorlogs
Validated current normal suite:
```text
39/39 Meson tests PASS
39/39 Meson tests PASS (historical checkpoint)
```
## 16. Measured max view

View file

@ -1,4 +1,7 @@
#!/usr/bin/env python3
"""Trainlog export pc catalog helper.
Owns this command-line workflow while preserving the versioned database and exchange contracts it consumes."""
import argparse
import json
import os

View file

@ -1,4 +1,7 @@
#!/usr/bin/env python3
"""Trainlog import mobile export helper.
Owns this command-line workflow while preserving the versioned database and exchange contracts it consumes."""
import argparse
import json
import math

View file

@ -1,4 +1,6 @@
#!/usr/bin/env bash
# Trainlog install syncd user helper.
# Owns this command-line workflow while preserving the versioned database and exchange contracts it consumes.
set -euo pipefail
ROOT="$(

View file

@ -1,3 +1,8 @@
/*
* Trainlog app shell interface.
*
* Declares the module boundary and ownership contract; implementation and persistence remain in their owning modules.
*/
#ifndef TRAINLOG_APP_SHELL_H
#define TRAINLOG_APP_SHELL_H

View file

@ -1,3 +1,8 @@
/*
* Trainlog body analytics interface.
*
* Declares the module boundary and ownership contract; implementation and persistence remain in their owning modules.
*/
#ifndef TRAINLOG_BODY_ANALYTICS_H
#define TRAINLOG_BODY_ANALYTICS_H

View file

@ -1,3 +1,8 @@
/*
* Trainlog body zone catalog interface.
*
* Declares the module boundary and ownership contract; implementation and persistence remain in their owning modules.
*/
#ifndef TRAINLOG_BODY_ZONE_CATALOG_H
#define TRAINLOG_BODY_ZONE_CATALOG_H

View file

@ -1,3 +1,8 @@
/*
* Trainlog bodyviz interface.
*
* Declares the module boundary and ownership contract; implementation and persistence remain in their owning modules.
*/
#ifndef TRAINLOG_BODYVIZ_H
#define TRAINLOG_BODYVIZ_H

View file

@ -1,3 +1,8 @@
/*
* Trainlog catalog interface.
*
* Declares the module boundary and ownership contract; implementation and persistence remain in their owning modules.
*/
#ifndef TRAINLOG_CATALOG_H
#define TRAINLOG_CATALOG_H

View file

@ -1,3 +1,8 @@
/*
* Trainlog chart interface.
*
* Declares the module boundary and ownership contract; implementation and persistence remain in their owning modules.
*/
#ifndef TRAINLOG_CHART_H
#define TRAINLOG_CHART_H

Some files were not shown because too many files have changed in this diff Show more