Clean up canonical project documentation
This commit is contained in:
parent
18958c5001
commit
e68b6cdc14
12 changed files with 1874 additions and 3946 deletions
357
AGENTS.md
357
AGENTS.md
|
|
@ -2,186 +2,257 @@
|
||||||
|
|
||||||
## 1. Scope
|
## 1. Scope
|
||||||
|
|
||||||
Trainlog is composed of two applications sharing a versioned exchange format:
|
Trainlog consists of:
|
||||||
|
|
||||||
- a lightweight Android application for fast workout data entry;
|
- a native Android application for fast workout and body-data capture;
|
||||||
- a Unix/Linux TUI for storage, review, analysis, and visualization.
|
- a Unix/Linux C17 ncursesw TUI for durable history, correction, analysis,
|
||||||
|
visualization, and manual synchronization;
|
||||||
|
- a small user-session PC agent, `trainlog-syncd`, for Android-triggered
|
||||||
|
synchronization;
|
||||||
|
- versioned JSON synchronization artifacts exchanged over direct MTP.
|
||||||
|
|
||||||
The TUI SQLite database is the canonical long-term history.
|
The desktop SQLite database is the canonical long-term history.
|
||||||
|
|
||||||
JSON files are the exchange contract between Android and the TUI.
|
Android has an independent local SQLite store for offline capture. SQLite
|
||||||
|
database files are never synchronized directly.
|
||||||
|
|
||||||
## 2. General development rules
|
## 2. Frozen compatibility boundary
|
||||||
|
|
||||||
Every change must respect the following rules:
|
`TRAINLOG_FORMAT_V1` is frozen.
|
||||||
|
|
||||||
1. behavior is defined before implementation;
|
|
||||||
2. code must be readable and deterministic;
|
|
||||||
3. errors must be handled explicitly;
|
|
||||||
4. user data must never be silently discarded;
|
|
||||||
5. persistent formats must be versioned;
|
|
||||||
6. importing the same data repeatedly must not create duplicates;
|
|
||||||
7. every new feature must be documented;
|
|
||||||
8. affected tests must be added or updated;
|
|
||||||
9. compiler warnings are treated as defects unless explicitly justified;
|
|
||||||
10. an undocumented or untested feature is not considered complete.
|
|
||||||
|
|
||||||
## 3. TUI
|
|
||||||
|
|
||||||
The TUI is implemented in C17.
|
|
||||||
|
|
||||||
Planned dependencies:
|
|
||||||
|
|
||||||
- ncursesw;
|
|
||||||
- SQLite3;
|
|
||||||
- a deliberately selected JSON library;
|
|
||||||
- Meson;
|
|
||||||
- Ninja.
|
|
||||||
|
|
||||||
The business logic, persistence layer, and ncurses rendering layer must remain separated.
|
|
||||||
|
|
||||||
SQLite calls must not be scattered through rendering code.
|
|
||||||
|
|
||||||
Important business rules must not depend directly on ncurses.
|
|
||||||
|
|
||||||
## 4. Android
|
|
||||||
|
|
||||||
The Android application is a data-entry client.
|
|
||||||
|
|
||||||
It must remain intentionally simple and must not become the primary analytics or historical store.
|
|
||||||
|
|
||||||
It must support:
|
|
||||||
|
|
||||||
- starting a workout session;
|
|
||||||
- automatic recording of the start time;
|
|
||||||
- selecting or creating an exercise;
|
|
||||||
- entering planned sets and repetitions;
|
|
||||||
- entering actual sets and repetitions;
|
|
||||||
- entering load;
|
|
||||||
- entering planned rest time;
|
|
||||||
- entering body weight and supported measurements;
|
|
||||||
- automatic recording of the end time;
|
|
||||||
- exporting a valid Trainlog JSON file.
|
|
||||||
|
|
||||||
## 5. Documentation
|
|
||||||
|
|
||||||
Documentation is mandatory.
|
|
||||||
|
|
||||||
Primary documents:
|
|
||||||
|
|
||||||
- `README.md`: user-facing project overview;
|
|
||||||
- `docs/architecture.md`: architecture and component boundaries;
|
|
||||||
- `docs/coding_style.md`: coding and commenting conventions;
|
|
||||||
- `docs/exchange_format.md`: JSON exchange contract;
|
|
||||||
- `docs/database.md`: SQLite schema and migration policy;
|
|
||||||
- `docs/tui.md`: TUI behavior and visual rules;
|
|
||||||
- `docs/android.md`: Android behavior and scope;
|
|
||||||
- `docs/tests.md`: validation strategy and commands;
|
|
||||||
- `docs/roadmap.md`: implementation order and gates.
|
|
||||||
|
|
||||||
A behavior change must update the relevant documentation in the same change.
|
|
||||||
|
|
||||||
## 6. Code comments
|
|
||||||
|
|
||||||
Comments are mandatory when code expresses:
|
|
||||||
|
|
||||||
- an invariant;
|
|
||||||
- a format constraint;
|
|
||||||
- an architectural decision;
|
|
||||||
- non-obvious logic;
|
|
||||||
- special error handling;
|
|
||||||
- a public API;
|
|
||||||
- an important data structure;
|
|
||||||
- an assumption required for correctness.
|
|
||||||
|
|
||||||
Comments must not merely restate obvious code.
|
|
||||||
|
|
||||||
Prefer explaining why a decision exists when the reason is not obvious from the code.
|
|
||||||
|
|
||||||
## 7. Exchange format
|
|
||||||
|
|
||||||
The Trainlog format is versioned.
|
|
||||||
|
|
||||||
Each workout export must contain:
|
|
||||||
|
|
||||||
- a format identifier;
|
|
||||||
- a schema version;
|
|
||||||
- a unique session identifier;
|
|
||||||
- ISO 8601 timestamps including an explicit UTC offset.
|
|
||||||
|
|
||||||
Exercise identifiers are stable and permanent.
|
|
||||||
|
|
||||||
An exercise display name may change without changing its identifier.
|
|
||||||
|
|
||||||
Imports must be idempotent.
|
|
||||||
|
|
||||||
A published format version must never receive an incompatible semantic change.
|
A published format version must never receive an incompatible semantic change.
|
||||||
|
|
||||||
## 8. TUI visual rules
|
New synchronization or domain needs use separate, explicitly versioned
|
||||||
|
artifacts. Do not overload frozen v1 through notes, fake sets, or silent data
|
||||||
|
loss.
|
||||||
|
|
||||||
The TUI uses color when it improves understanding.
|
## 3. Exercise model
|
||||||
|
|
||||||
Color must never be the sole carrier of information.
|
Exercise behavior is metadata-driven:
|
||||||
|
|
||||||
Important states must remain understandable in monochrome terminals.
|
```text
|
||||||
|
recording_mode = SETS | CONTINUOUS
|
||||||
|
tracking_mode = REPS | DURATION
|
||||||
|
data_fields = bounded supplemental field mask
|
||||||
|
```
|
||||||
|
|
||||||
Colors must be centralized in a dedicated theme module.
|
Valid model-v1 combinations are:
|
||||||
|
|
||||||
The TUI must use `ncursesw` and handle UTF-8 correctly.
|
```text
|
||||||
|
SETS + REPS
|
||||||
|
SETS + DURATION
|
||||||
|
CONTINUOUS + DURATION
|
||||||
|
```
|
||||||
|
|
||||||
Raw ANSI escape sequences are forbidden in ncurses rendering code unless explicitly documented and justified.
|
`CONTINUOUS + REPS` is invalid.
|
||||||
|
|
||||||
## 9. Database
|
Continuous activity must not be represented as a fake performed set.
|
||||||
|
|
||||||
SQLite is the canonical TUI store.
|
Actual set values are independent records. Heterogeneous repetitions are valid.
|
||||||
|
|
||||||
The database schema must be versioned.
|
## 4. Identity
|
||||||
|
|
||||||
Incompatible schema evolution requires an explicit migration.
|
Stable identities use UUIDv4-based creator IDs:
|
||||||
|
|
||||||
Integrity constraints must be used where appropriate, including:
|
```text
|
||||||
|
ex_<uuid-v4> exercise
|
||||||
|
se_<uuid-v4> session
|
||||||
|
bo_<uuid-v4> body observation
|
||||||
|
sy_<uuid-v4> synchronization run
|
||||||
|
```
|
||||||
|
|
||||||
- foreign keys;
|
Display names are not identities.
|
||||||
- unique identifiers;
|
|
||||||
- anti-duplication constraints.
|
|
||||||
|
|
||||||
## 10. Validation
|
Import and synchronization paths must remain idempotent by stable IDs.
|
||||||
|
|
||||||
|
## 5. Desktop implementation
|
||||||
|
|
||||||
|
The desktop core is C17.
|
||||||
|
|
||||||
|
Current primary dependencies:
|
||||||
|
|
||||||
|
- ncursesw;
|
||||||
|
- SQLite3;
|
||||||
|
- utf8proc;
|
||||||
|
- libuuid;
|
||||||
|
- libudev;
|
||||||
|
- libmtp;
|
||||||
|
- Meson;
|
||||||
|
- Ninja.
|
||||||
|
|
||||||
|
Business logic, persistence, transport, and rendering remain separated.
|
||||||
|
|
||||||
|
SQLite operations must not be scattered through rendering code.
|
||||||
|
|
||||||
|
Important business rules must not depend directly on ncurses.
|
||||||
|
|
||||||
|
Strict warning policy must not be weakened to make a change compile.
|
||||||
|
|
||||||
|
## 6. Android implementation
|
||||||
|
|
||||||
|
Android is a Kotlin/Jetpack Compose capture client.
|
||||||
|
|
||||||
|
It owns local data entry and local persistence for:
|
||||||
|
|
||||||
|
- exercise catalog entries;
|
||||||
|
- sessions;
|
||||||
|
- performed sets;
|
||||||
|
- continuous activities;
|
||||||
|
- body observations.
|
||||||
|
|
||||||
|
It is not the canonical analytics store.
|
||||||
|
|
||||||
|
The Android UI is driven by exercise metadata, never by exercise-name
|
||||||
|
heuristics.
|
||||||
|
|
||||||
|
## 7. Synchronization architecture
|
||||||
|
|
||||||
|
Desktop access to Android uses physical-device discovery with `libudev` and
|
||||||
|
direct object access with `libmtp`.
|
||||||
|
|
||||||
|
Do not introduce a mandatory GVFS/FUSE mount.
|
||||||
|
|
||||||
|
Canonical exchange folder:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Download/Trainlog
|
||||||
|
```
|
||||||
|
|
||||||
|
The shared desktop synchronization engine is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trainlog_sync_run()
|
||||||
|
```
|
||||||
|
|
||||||
|
Both the TUI and `trainlog-syncd` use this engine.
|
||||||
|
|
||||||
|
Android-triggered synchronization uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trainlog-sync-request-v1.json
|
||||||
|
trainlog-sync-receipt-v1.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Android -> PC data uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trainlog-mobile-export-v1.json
|
||||||
|
```
|
||||||
|
|
||||||
|
PC -> Android catalog data uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trainlog-pc-catalog-v1.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Persistence
|
||||||
|
|
||||||
|
Desktop SQLite schema is versioned with:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
PRAGMA user_version;
|
||||||
|
```
|
||||||
|
|
||||||
|
The current desktop schema is v5.
|
||||||
|
|
||||||
|
Every incompatible schema evolution requires an explicit migration and
|
||||||
|
regression coverage.
|
||||||
|
|
||||||
|
Foreign keys must be enabled.
|
||||||
|
|
||||||
|
Multi-row mutations that represent one user operation must be transactional.
|
||||||
|
|
||||||
|
## 9. Error handling
|
||||||
|
|
||||||
|
Trainlog prefers explicit failure over silent corruption.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- malformed exchange JSON -> reject;
|
||||||
|
- unsupported version -> reject;
|
||||||
|
- duplicate stable ID -> idempotent skip or explicit conflict as defined;
|
||||||
|
- incompatible exercise profile -> reject;
|
||||||
|
- incomplete session -> preserve explicitly;
|
||||||
|
- failed persisted-session replacement -> rollback;
|
||||||
|
- synchronization failure -> record a meaningful diagnostic.
|
||||||
|
|
||||||
|
User-facing code must not intentionally return placeholders such as
|
||||||
|
`error=unknown` when a specific failure can be reported.
|
||||||
|
|
||||||
|
## 10. Documentation
|
||||||
|
|
||||||
|
Canonical documents:
|
||||||
|
|
||||||
|
- `README.md`;
|
||||||
|
- `docs/current_state.md`;
|
||||||
|
- `docs/architecture.md`;
|
||||||
|
- `docs/coding_style.md`;
|
||||||
|
- `docs/exchange_format.md`;
|
||||||
|
- `docs/exercise_data_model.md`;
|
||||||
|
- `docs/database.md`;
|
||||||
|
- `docs/tui.md`;
|
||||||
|
- `docs/android.md`;
|
||||||
|
- `docs/sync_exchange.md`;
|
||||||
|
- `docs/tests.md`;
|
||||||
|
- `docs/roadmap.md`;
|
||||||
|
- `CHANGELOG.md`.
|
||||||
|
|
||||||
|
Checkpoint history belongs in Git history and `docs/reviews`; canonical
|
||||||
|
documents describe the current state rather than accumulating obsolete
|
||||||
|
`NEXT` sections.
|
||||||
|
|
||||||
|
## 11. Validation
|
||||||
|
|
||||||
Before every meaningful push:
|
Before every meaningful push:
|
||||||
|
|
||||||
- build;
|
```bash
|
||||||
- run tests;
|
meson compile -C build
|
||||||
- verify formatting;
|
meson test -C build --print-errorlogs
|
||||||
- verify compiler warnings;
|
|
||||||
- validate JSON examples against the schema;
|
|
||||||
- verify documentation impacted by the change.
|
|
||||||
|
|
||||||
The repository must not knowingly be pushed in a broken state.
|
python tools/validate_json.py
|
||||||
|
python tools/validate_import_contract.py
|
||||||
|
|
||||||
## 11. Git workflow
|
git diff --check
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
Forgejo is the primary repository.
|
When Android code changes:
|
||||||
|
|
||||||
Primary remote:
|
```bash
|
||||||
|
cd android
|
||||||
|
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
`ssh://git@git.labfytools.com:2223/fy59/trainlog.git`
|
Run ASan/UBSan at meaningful C implementation checkpoints.
|
||||||
|
|
||||||
|
Hardware-dependent MTP tests remain explicit manual validations and are not
|
||||||
|
required to run in CI without a connected unlocked Android device.
|
||||||
|
|
||||||
|
## 12. Git workflow
|
||||||
|
|
||||||
|
Forgejo is primary:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ssh://git@git.labfytools.com:2223/fy59/trainlog.git
|
||||||
|
```
|
||||||
|
|
||||||
GitHub is a mirror:
|
GitHub is a mirror:
|
||||||
|
|
||||||
`git@github.com:labfytools/trainlog.git`
|
```text
|
||||||
|
git@github.com:labfytools/trainlog.git
|
||||||
Normal development must push to Forgejo.
|
```
|
||||||
|
|
||||||
Do not develop directly against the GitHub mirror.
|
Do not develop directly against the GitHub mirror.
|
||||||
|
|
||||||
## 12. Definition of Done
|
## 13. Definition of Done
|
||||||
|
|
||||||
A task is complete only when:
|
A task is complete only when:
|
||||||
|
|
||||||
- the expected behavior is implemented;
|
- behavior is implemented;
|
||||||
- the code builds without accepted warnings;
|
- the affected code builds without accepted warnings;
|
||||||
- relevant tests pass;
|
- relevant tests pass;
|
||||||
- new error paths are handled;
|
- new error paths are explicit;
|
||||||
- documentation is current;
|
- persistent-format changes have migrations;
|
||||||
- examples and schemas are updated when required;
|
- synchronization remains idempotent where applicable;
|
||||||
|
- documentation describes the resulting state;
|
||||||
- no known regression is intentionally left behind.
|
- no known regression is intentionally left behind.
|
||||||
|
|
|
||||||
427
CHANGELOG.md
427
CHANGELOG.md
|
|
@ -1,395 +1,86 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to Trainlog will be documented in this file.
|
All notable changes to Trainlog are documented here.
|
||||||
|
|
||||||
|
Detailed implementation chronology remains available in Git history and
|
||||||
|
`docs/reviews`. This file summarizes the current unreleased product baseline.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Frozen Trainlog JSON v1 contract.
|
- native Kotlin/Compose Android capture client;
|
||||||
- SQLite persistence foundation.
|
- Android local exercise, session, continuous-activity, and body persistence;
|
||||||
- Direct workout entry.
|
- C17/ncursesw desktop TUI with direct session entry and durable SQLite history;
|
||||||
- Exercise catalog.
|
- profile-aware exercise model using recording mode, tracking mode, and
|
||||||
- Body tracking.
|
supplemental fields;
|
||||||
- Colored ncursesw dashboard.
|
- continuous activity persistence without synthetic sets;
|
||||||
- Body-weight graph.
|
- variable repetition-set input including `5x10`, explicit lists, and pyramid
|
||||||
- Arrow-key and F1-F4 navigation.
|
shorthand such as `4..10..4`;
|
||||||
- Highlighted list selections.
|
- persisted desktop session editing and exercise removal;
|
||||||
- Navigable session history.
|
- Android current-session draft exercise removal;
|
||||||
|
- body-observation history, editing, graphs, and normalized overlays;
|
||||||
|
- exercise performance history;
|
||||||
|
- direct physical Android USB/MTP discovery with libudev/libmtp;
|
||||||
|
- direct MTP read/write/list/delete support without filesystem mounts;
|
||||||
|
- Android full mobile snapshot export;
|
||||||
|
- strict idempotent desktop mobile importer;
|
||||||
|
- PC canonical exercise-catalog publication to Android;
|
||||||
|
- automatic Android snapshot maintenance;
|
||||||
|
- Android `Synchroniser maintenant` request flow;
|
||||||
|
- shared C bidirectional synchronization engine;
|
||||||
|
- `trainlog-syncd` user-session synchronization agent;
|
||||||
|
- request/receipt synchronization protocol;
|
||||||
|
- stable `sy_<uuid-v4>` synchronization identities;
|
||||||
|
- structured synchronization history with selectable TUI list/detail views.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Exercise selection now uses interactive keyboard navigation.
|
- desktop SQLite schema evolved to v5;
|
||||||
- Body tracking is now a persistent history screen instead of entry-only.
|
- schema v5 permits targetless set-session rows for actual-only mobile data;
|
||||||
- TUI polish is prioritized before Android development.
|
- heterogeneous performed sets are preserved without inventing a uniform target;
|
||||||
|
- Android/desktop synchronization uses dedicated versioned artifacts instead of
|
||||||
|
modifying frozen Trainlog JSON v1;
|
||||||
|
- PC and TUI synchronization paths now share one engine;
|
||||||
|
- Android PC-created artifact access uses a persistent SAF grant for
|
||||||
|
`Download/Trainlog`;
|
||||||
|
- the Android data package is no longer accidentally hidden by a broad
|
||||||
|
repository `data/` ignore rule;
|
||||||
|
- user-facing session history timestamps use `DD/MM/YYYY HH:MM` while canonical
|
||||||
|
storage remains RFC3339.
|
||||||
|
|
||||||
<!-- TRAINLOG_TUI_V02_CHANGELOG -->
|
### Fixed
|
||||||
### TUI v0.2 checkpoint
|
|
||||||
|
|
||||||
Added:
|
- stale schema-v4 importer call after desktop schema v5 migration;
|
||||||
|
- stale schema-v4 guard in the PC catalog exporter;
|
||||||
|
- missing `sy` prefix support in the UUID creator;
|
||||||
|
- synchronization failures that previously surfaced only as `error=unknown`;
|
||||||
|
- Android folder-selection UX so a wrong SAF folder can be changed without
|
||||||
|
clearing application data;
|
||||||
|
- libmtp terminal output leaking into ncurses rendering.
|
||||||
|
|
||||||
- bordered colorful dashboard;
|
### Validation
|
||||||
- arrow-key and F-key navigation;
|
|
||||||
- weight history visualization;
|
|
||||||
- flat-series weight graph handling;
|
|
||||||
- navigable session history;
|
|
||||||
- complete read-only workout details.
|
|
||||||
|
|
||||||
Planned next:
|
Current validated baseline:
|
||||||
|
|
||||||
- human duration input such as `1:30`, `1m30`, `2m`, and `45s` (implemented);
|
|
||||||
- automatic minute/second display formatting (implemented);
|
|
||||||
- history graphs for all body measurements (implemented);
|
|
||||||
- left/right asymmetry summaries (implemented).
|
|
||||||
<!-- TRAINLOG_TUI_V02_CHANGELOG _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_CHANGELOG -->
|
|
||||||
### Current TUI checkpoint
|
|
||||||
|
|
||||||
Implemented:
|
|
||||||
- human duration input;
|
|
||||||
- human minute/second display;
|
|
||||||
- graphs for every persisted body metric;
|
|
||||||
- recent values for every body metric;
|
|
||||||
- left/right asymmetry summaries.
|
|
||||||
|
|
||||||
Next:
|
|
||||||
- normalized global body overlay graph (implemented);
|
|
||||||
- metric legend using both colors and symbols;
|
|
||||||
- percentage evolution summary;
|
|
||||||
- richer dashboard weight graph (implemented);
|
|
||||||
- dashboard previous-weight delta and min/max (implemented);
|
|
||||||
- compact body/asymmetry dashboard summary (implemented).
|
|
||||||
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_CHANGELOG _END -->
|
|
||||||
|
|
||||||
### Dashboard graph-only refinement
|
|
||||||
|
|
||||||
- removed redundant weight/min/max/asymmetry summary lines;
|
|
||||||
- dashboard now uses a global body evolution graph;
|
|
||||||
- dates are shown on X;
|
|
||||||
- Y is percentage evolution from each metric baseline;
|
|
||||||
- legend shows metric name, unit, and latest percentage change.
|
|
||||||
|
|
||||||
### Dashboard rolling 12 months
|
|
||||||
|
|
||||||
- added a fixed 12-month rolling X axis;
|
|
||||||
- empty months remain visible and empty;
|
|
||||||
- no zero fill or interpolation across missing months;
|
|
||||||
- multiple readings in one month use the last monthly value.
|
|
||||||
|
|
||||||
### Exercise performance history
|
|
||||||
|
|
||||||
- added Enter-to-open exercise performance detail;
|
|
||||||
- added mode-aware representative best-set history;
|
|
||||||
- added terminal performance graph;
|
|
||||||
- assistance explicitly treats lower assistance as better;
|
|
||||||
- best recorded set remains distinct from measured max.
|
|
||||||
|
|
||||||
### Session type schema v2
|
|
||||||
|
|
||||||
- added local `training` / `max_test` session classification;
|
|
||||||
- added transactional SQLite schema migration v1 -> v2;
|
|
||||||
- preserved migrated sessions as `training`;
|
|
||||||
- kept Trainlog JSON v1 frozen and unchanged.
|
|
||||||
|
|
||||||
### Transaction-safe session editing foundation
|
|
||||||
|
|
||||||
- added exact bounded loading of persisted exercise/set values;
|
|
||||||
- added atomic replacement of recorded session exercise/set rows;
|
|
||||||
- preserved the parent session row and linked body observations;
|
|
||||||
- added rollback coverage for failed replacements.
|
|
||||||
|
|
||||||
### Body observation history and correction
|
|
||||||
|
|
||||||
- redesigned `F4 Corps` around recorded measurement dates;
|
|
||||||
- added a framed trend graph above the newest-first record list;
|
|
||||||
- added PageUp/PageDown and a visual ncurses scrollbar;
|
|
||||||
- added two-page detail views with `e Modifier` on every page;
|
|
||||||
- added correction of existing observations without changing their timestamp or
|
|
||||||
session link;
|
|
||||||
- added `-` to clear one erroneous measurement and Escape to cancel the edit.
|
|
||||||
|
|
||||||
<!-- TRAINLOG_EDITABILITY_NAV_CHANGELOG -->
|
|
||||||
### TUI editability and navigation checkpoint
|
|
||||||
|
|
||||||
- added local `training` / `max_test` selection to the session workflow;
|
|
||||||
- added safe persisted-session editing while preserving parent session identity;
|
|
||||||
- added newest-first body-observation history, detail pages, and editing;
|
|
||||||
- added immediate Escape cancellation to text/numeric entry paths;
|
|
||||||
- added a shared top navigation bar with `0 Accueil`, `1-4`, and `F1-F4`;
|
|
||||||
- added Tab/Shift+Tab focus on multi-zone screens;
|
|
||||||
- focused frames use a yellow border/title without recoloring content;
|
|
||||||
- added consistent ASCII banners and ncurses frames to secondary detail views;
|
|
||||||
- added direct exercise creation from the in-session exercise chooser;
|
|
||||||
- kept the final rolling 12-month `MM/YY` dashboard label inside its frame.
|
|
||||||
<!-- TRAINLOG_EDITABILITY_NAV_CHANGELOG _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_MTP_TRANSPORT_CHANGELOG -->
|
|
||||||
### Direct Android USB/MTP transport foundation
|
|
||||||
|
|
||||||
- added `libudev` discovery of physical MTP devices;
|
|
||||||
- filtered MTP USB interface children to avoid duplicate phones;
|
|
||||||
- added exact bus/device matching into `libmtp`;
|
|
||||||
- added MTP storage enumeration;
|
|
||||||
- added root-folder discovery/creation;
|
|
||||||
- added direct MTP text-file upload;
|
|
||||||
- added direct folder-child listing;
|
|
||||||
- added direct MTP file download;
|
|
||||||
- split cached and uncached libmtp open paths where required;
|
|
||||||
- validated a real Android internal-storage write/list/read roundtrip;
|
|
||||||
- kept Android access mount-free: no GVFS/FUSE mount lifecycle is required.
|
|
||||||
<!-- TRAINLOG_MTP_TRANSPORT_CHANGELOG _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SYNC_FINAL_CHANGELOG -->
|
|
||||||
### Sync TUI page
|
|
||||||
|
|
||||||
- added `5 Sync / F5` to primary navigation;
|
|
||||||
- added dedicated Sync ASCII banner and framed page;
|
|
||||||
- added live direct-MTP device and storage status;
|
|
||||||
- suppressed libmtp terminal output during ncurses rendering;
|
|
||||||
- added focused-frame navigation with Tab/Shift+Tab;
|
|
||||||
- added clean list navigation and scrollbar behavior;
|
|
||||||
- exposed Android -> PC session/exercise/body synchronization directions;
|
|
||||||
- exposed PC -> Android canonical exercise-catalog direction;
|
|
||||||
- kept frozen Trainlog session JSON v1 unchanged.
|
|
||||||
<!-- TRAINLOG_SYNC_FINAL_CHANGELOG _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_PROFILE_AWARE_CHANGELOG -->
|
|
||||||
### Profile-aware and continuous exercise tracking
|
|
||||||
|
|
||||||
- added set-based versus continuous recording organization;
|
|
||||||
- added speed and distance supplemental-field metadata;
|
|
||||||
- added profiled catalog creation API;
|
|
||||||
- added profile-aware exercise creation in the TUI;
|
|
||||||
- added inline profiled exercise creation while recording a session;
|
|
||||||
- migrated SQLite persistence to schema v4;
|
|
||||||
- added one-to-one continuous activity persistence;
|
|
||||||
- kept continuous activities out of `performed_sets`;
|
|
||||||
- added profile-aware session detail loading;
|
|
||||||
- added continuous duration/speed/distance history display;
|
|
||||||
- made continuous TUI duration entry explicitly minute-based;
|
|
||||||
- preserved frozen session JSON v1 unchanged.
|
|
||||||
<!-- TRAINLOG_PROFILE_AWARE_CHANGELOG _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_LOCAL_CHANGELOG -->
|
|
||||||
### Android local client
|
|
||||||
|
|
||||||
- added native Kotlin/Jetpack Compose Android client;
|
|
||||||
- matched Trainlog TUI visual language;
|
|
||||||
- added minimal themed `T` launcher icon;
|
|
||||||
- added exercise catalog and profile-aware exercise creation;
|
|
||||||
- added inline exercise creation from session recording;
|
|
||||||
- added persistent local session recording;
|
|
||||||
- preserved SETS versus CONTINUOUS persistence semantics;
|
|
||||||
- added local session history and detail views;
|
|
||||||
- added persistent body measurement recording;
|
|
||||||
- validated the application on a real Samsung device through ADB.
|
|
||||||
<!-- TRAINLOG_ANDROID_LOCAL_CHANGELOG _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SYNC_FOUNDATION_CHANGELOG -->
|
|
||||||
### Bidirectional synchronization foundation
|
|
||||||
|
|
||||||
- validated Android-to-PC domain snapshot transfer through direct MTP;
|
|
||||||
- added strict transactional and idempotent desktop mobile import;
|
|
||||||
- added canonical PC exercise-catalog export;
|
|
||||||
- validated PC-to-Android catalog publication through direct MTP;
|
|
||||||
- added Android Storage Access Framework access for PC-created catalog files;
|
|
||||||
- made the Android synchronization folder selection recoverable/changeable;
|
|
||||||
- kept synchronization artifacts separate from frozen Trainlog session JSON v1;
|
|
||||||
- documented the next synchronization architecture: structured history,
|
|
||||||
detailed sync inspection, common sync engine and PC-side `trainlog-syncd`.
|
|
||||||
<!-- TRAINLOG_SYNC_FOUNDATION_CHANGELOG _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_VARIABLE_SET_REPS_V1 -->
|
|
||||||
## Variable repetition sets
|
|
||||||
|
|
||||||
Trainlog preserves each performed set independently.
|
|
||||||
|
|
||||||
Accepted repetition input:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5x10
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
|
||||||
4..10..4
|
|
||||||
```
|
|
||||||
|
|
||||||
`4..10..4` expands to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
|
||||||
```
|
|
||||||
|
|
||||||
Desktop schema v5 permits targetless `SETS` rows for actual-only mobile
|
|
||||||
observations. Synchronization therefore does not invent a uniform target when
|
|
||||||
performed sets are heterogeneous.
|
|
||||||
|
|
||||||
`performed_sets` remains the source of truth for actual per-set values.
|
|
||||||
|
|
||||||
Existing planned desktop sessions may still carry explicit target sets/reps or
|
|
||||||
target durations.
|
|
||||||
|
|
||||||
`trainlog-mobile-export` v1 keeps ordered heterogeneous `sets[]`.
|
|
||||||
|
|
||||||
Frozen `TRAINLOG_FORMAT_V1` is unchanged.
|
|
||||||
<!-- TRAINLOG_VARIABLE_SET_REPS_V1 _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL -->
|
|
||||||
## Variable sets and session exercise removal checkpoint
|
|
||||||
|
|
||||||
Validated functionality in this checkpoint:
|
|
||||||
|
|
||||||
```text
|
|
||||||
VARIABLE_REPETITION_SETS=PASS
|
|
||||||
REPETITION_SHORTHAND_5x10=PASS
|
|
||||||
REPETITION_EXPLICIT_LIST=PASS
|
|
||||||
REPETITION_PYRAMID=PASS
|
|
||||||
|
|
||||||
DESKTOP_SCHEMA_V5=PASS
|
DESKTOP_SCHEMA_V5=PASS
|
||||||
V4_TO_V5_MIGRATION_REGRESSION=PASS
|
DESKTOP_TESTS=19/19 PASS
|
||||||
MOBILE_HETEROGENEOUS_SET_IMPORT=PASS
|
|
||||||
MOBILE_IMPORT_IDEMPOTENCE=PASS
|
|
||||||
NO_FAKE_UNIFORM_TARGET=PASS
|
|
||||||
|
|
||||||
ANDROID_SESSION_DRAFT_EXERCISE_REMOVE=PASS
|
ANDROID_BUILD=PASS
|
||||||
DESKTOP_SESSION_EXERCISE_REMOVE=PASS
|
ANDROID_LOCAL_WORKFLOWS=PASS
|
||||||
```
|
|
||||||
|
|
||||||
Accepted repetition examples:
|
USB_MTP_DETECTION=PASS
|
||||||
|
MTP_ROUNDTRIP=PASS
|
||||||
|
|
||||||
```text
|
ANDROID_TO_PC_MTP=PASS
|
||||||
5x10
|
PC_TO_ANDROID_MTP_PUBLISH=PASS
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
|
||||||
4..10..4
|
|
||||||
```
|
|
||||||
|
|
||||||
A heterogeneous mobile session is persisted as ordered `performed_sets`.
|
|
||||||
The desktop does not invent `target_sets`, `target_reps` or
|
|
||||||
`target_duration_seconds` for actual-only mobile observations.
|
|
||||||
|
|
||||||
On Android, an exercise already added to the current session can be removed
|
|
||||||
before saving the session.
|
|
||||||
|
|
||||||
On the desktop TUI, session editing already supports:
|
|
||||||
|
|
||||||
```text
|
|
||||||
d supprimer
|
|
||||||
```
|
|
||||||
|
|
||||||
for removing the selected exercise from a current or persisted session draft.
|
|
||||||
The database replacement remains transactional.
|
|
||||||
|
|
||||||
`TRAINLOG_FORMAT_V1` remains frozen and unchanged.
|
|
||||||
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 -->
|
|
||||||
## Shared bidirectional synchronization v1
|
|
||||||
|
|
||||||
Validated architecture:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Android local write
|
|
||||||
-> automatic mobile snapshot
|
|
||||||
|
|
||||||
Android "Synchroniser maintenant"
|
|
||||||
-> trainlog-sync-request-v1.json
|
|
||||||
|
|
||||||
trainlog-syncd
|
|
||||||
-> shared C synchronization engine
|
|
||||||
-> Android → PC mobile import
|
|
||||||
-> PC → Android catalog publish
|
|
||||||
-> trainlog-sync-receipt-v1.json
|
|
||||||
|
|
||||||
Android
|
|
||||||
-> receipt matched by request_id
|
|
||||||
-> PC catalog applied locally
|
|
||||||
-> final result displayed
|
|
||||||
```
|
|
||||||
|
|
||||||
The ncurses TUI and `trainlog-syncd` call the same
|
|
||||||
`trainlog_sync_run()` implementation.
|
|
||||||
|
|
||||||
Direct libmtp remains mandatory. No filesystem mount and no SQLite-file
|
|
||||||
synchronization are introduced.
|
|
||||||
|
|
||||||
### Concurrency
|
|
||||||
|
|
||||||
The shared engine owns:
|
|
||||||
|
|
||||||
```text
|
|
||||||
$XDG_DATA_HOME/trainlog/sync.lock
|
|
||||||
```
|
|
||||||
|
|
||||||
A TUI-triggered transaction waits for the lock. Daemon request polling is
|
|
||||||
non-blocking and retries later.
|
|
||||||
|
|
||||||
### Sync history
|
|
||||||
|
|
||||||
Every actual synchronization transaction creates:
|
|
||||||
|
|
||||||
```text
|
|
||||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json
|
|
||||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
and appends a compact entry to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
$XDG_DATA_HOME/trainlog/sync_history.log
|
|
||||||
```
|
|
||||||
|
|
||||||
The TUI behaves like:
|
|
||||||
|
|
||||||
```text
|
|
||||||
git log
|
|
||||||
↑/↓ select synchronization
|
|
||||||
|
|
||||||
git show
|
|
||||||
Enter opens structured detail
|
|
||||||
```
|
|
||||||
|
|
||||||
Legacy three-field history entries remain readable but have no structured
|
|
||||||
detail file.
|
|
||||||
|
|
||||||
### Android request and receipt
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format = trainlog-sync-request
|
|
||||||
version = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Receipt:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format = trainlog-sync-receipt
|
|
||||||
version = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
The receipt carries the originating `request_id`, a generated `sync_id`,
|
|
||||||
status, summary and synchronization counts. Android ignores a receipt for a
|
|
||||||
different request ID.
|
|
||||||
|
|
||||||
### User service
|
|
||||||
|
|
||||||
Install/refresh the user service with:
|
|
||||||
|
|
||||||
```text
|
|
||||||
bash tools/install_syncd_user.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
No root privilege is required.
|
|
||||||
|
|
||||||
### Status
|
|
||||||
|
|
||||||
```text
|
|
||||||
COMMON_SYNC_ENGINE=PASS
|
COMMON_SYNC_ENGINE=PASS
|
||||||
TUI_SYNC_LOG_SHOW=PASS
|
|
||||||
TRAINLOG_SYNCD=PASS
|
TRAINLOG_SYNCD=PASS
|
||||||
ANDROID_TRIGGERED_SYNC=PASS
|
ANDROID_TRIGGERED_SYNC=PASS
|
||||||
ANDROID_SYNC_RECEIPT=PASS
|
ANDROID_SYNC_RECEIPT=PASS
|
||||||
|
TUI_SYNC_LOG_SHOW=PASS
|
||||||
BIDIRECTIONAL_SYNC_V1=PASS
|
BIDIRECTIONAL_SYNC_V1=PASS
|
||||||
```
|
```
|
||||||
|
|
||||||
Frozen `TRAINLOG_FORMAT_V1` remains unchanged.
|
|
||||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 _END -->
|
|
||||||
|
|
|
||||||
206
README.md
206
README.md
|
|
@ -1,75 +1,189 @@
|
||||||
# Trainlog
|
# Trainlog
|
||||||
|
|
||||||
Trainlog is a local-first workout log composed of:
|
Trainlog is a local-first workout and body-tracking system with two user
|
||||||
|
interfaces:
|
||||||
|
|
||||||
- a lightweight Android data-entry application;
|
- a native Android application optimized for fast data entry during training;
|
||||||
- a colorful Unix/Linux TUI for history, statistics, graphs, and progress tracking.
|
- a C17/ncursesw TUI used for durable history, editing, visualization,
|
||||||
|
statistics, and synchronization.
|
||||||
|
|
||||||
## Project goals
|
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.
|
||||||
|
|
||||||
Android is optimized for fast use during a workout:
|
## Current status
|
||||||
|
|
||||||
- start and end time;
|
```text
|
||||||
- exercise selection;
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
- new exercise creation;
|
|
||||||
- planned sets and repetitions;
|
|
||||||
- actual sets and repetitions;
|
|
||||||
- load;
|
|
||||||
- rest time;
|
|
||||||
- body weight;
|
|
||||||
- body measurements;
|
|
||||||
- JSON export.
|
|
||||||
|
|
||||||
The TUI is the main application:
|
DESKTOP_SCHEMA_V5=PASS
|
||||||
|
ANDROID_LOCAL_WORKFLOWS=PASS
|
||||||
|
|
||||||
- import Android exports;
|
VARIABLE_REPETITION_SETS=PASS
|
||||||
- create sessions directly from the terminal;
|
CONTINUOUS_ACTIVITY_TRACKING=PASS
|
||||||
- maintain the canonical exercise catalog;
|
|
||||||
- maintain SQLite history;
|
DIRECT_MTP_TRANSPORT=PASS
|
||||||
- display workout history;
|
COMMON_SYNC_ENGINE=PASS
|
||||||
- track body weight;
|
TRAINLOG_SYNCD=PASS
|
||||||
- track body measurements;
|
ANDROID_TRIGGERED_SYNC=PASS
|
||||||
- track performance;
|
ANDROID_SYNC_RECEIPT=PASS
|
||||||
- display colorful terminal graphs;
|
BIDIRECTIONAL_SYNC_V1=PASS
|
||||||
- export data for external use.
|
|
||||||
|
DESKTOP_TESTS=19/19 PASS
|
||||||
|
ANDROID_BUILD=PASS
|
||||||
|
```
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Android
|
Android application
|
||||||
|
local SQLite store
|
||||||
|
|
|
|
||||||
| Trainlog JSON
|
automatic mobile snapshot
|
||||||
v
|
|
||||||
Trainlog TUI
|
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
SQLite
|
Download/Trainlog on Android storage
|
||||||
|
|
|
||||||
|
| direct MTP / libmtp
|
||||||
|
v
|
||||||
|
trainlog_sync_run()
|
||||||
|
/ \
|
||||||
|
/ \
|
||||||
|
Android -> PC PC -> Android
|
||||||
|
snapshot import catalog publish
|
||||||
|
\ /
|
||||||
|
\ /
|
||||||
|
sync receipt
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Android
|
||||||
|
|
||||||
|
Desktop TUI --------------------+
|
||||||
|
| |
|
||||||
|
+-- same sync engine -------+
|
||||||
|
|
|
||||||
|
v
|
||||||
|
desktop SQLite
|
||||||
|
canonical long-term history
|
||||||
```
|
```
|
||||||
|
|
||||||
The TUI SQLite database is the canonical long-term store.
|
No SQLite database file is copied between devices. The desktop does not require
|
||||||
|
a GVFS/FUSE mount of the phone.
|
||||||
|
|
||||||
The JSON exchange format is versioned and designed for idempotent imports.
|
## 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. Compact input supports:
|
||||||
|
|
||||||
|
```text
|
||||||
|
5x10
|
||||||
|
4,5,6,7,8,9,10,9,8,7,6,5,4
|
||||||
|
4..10..4
|
||||||
|
```
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
||||||
```text
|
```text
|
||||||
android/ Android application
|
android/ native Kotlin/Compose Android client
|
||||||
tui/ C17 ncursesw application
|
tui/ C17 ncursesw desktop application and core
|
||||||
docs/ Canonical project documentation
|
docs/ canonical project documentation
|
||||||
format/ JSON schema and exchange-format material
|
format/ frozen Trainlog JSON v1 schema material
|
||||||
examples/ Valid exchange examples
|
examples/ valid frozen-format examples
|
||||||
tools/ Development and validation tools
|
tests/ fixtures and cross-component tests
|
||||||
|
tools/ validators, import/export helpers, sync daemon tooling
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Desktop build and validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
meson setup --reconfigure build
|
||||||
|
meson compile -C build
|
||||||
|
meson test -C build --print-errorlogs
|
||||||
|
|
||||||
|
python tools/validate_json.py
|
||||||
|
python tools/validate_import_contract.py
|
||||||
|
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Android build
|
||||||
|
|
||||||
|
The local Android SDK is intentionally not committed. Configure it with either
|
||||||
|
`ANDROID_HOME` or `android/local.properties`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd android
|
||||||
|
|
||||||
|
printf 'sdk.dir=%s\n' "$HOME/Android/Sdk" > local.properties
|
||||||
|
|
||||||
|
JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
|
||||||
|
./gradlew assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Android-triggered synchronization
|
||||||
|
|
||||||
|
Build the desktop first, then install the user service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash tools/install_syncd_user.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Check it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user is-active trainlog-syncd.service
|
||||||
|
tail -f ~/.local/state/trainlog/syncd.log
|
||||||
|
```
|
||||||
|
|
||||||
|
On Android:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Sync
|
||||||
|
-> Synchroniser maintenant
|
||||||
|
```
|
||||||
|
|
||||||
|
The request is consumed by `trainlog-syncd`, the shared bidirectional engine
|
||||||
|
runs, a receipt is returned to Android, and the PC catalog is applied locally.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- `docs/current_state.md`: compact canonical implementation snapshot;
|
||||||
|
- `docs/architecture.md`: component and ownership boundaries;
|
||||||
|
- `docs/exercise_data_model.md`: exercise semantics;
|
||||||
|
- `docs/database.md`: desktop SQLite schema and migrations;
|
||||||
|
- `docs/android.md`: Android behavior;
|
||||||
|
- `docs/tui.md`: desktop TUI behavior;
|
||||||
|
- `docs/sync_exchange.md`: MTP synchronization artifacts and protocol;
|
||||||
|
- `docs/exchange_format.md`: frozen Trainlog JSON v1 contract;
|
||||||
|
- `docs/tests.md`: validation strategy;
|
||||||
|
- `docs/roadmap.md`: completed gates and future cursor;
|
||||||
|
- `AGENTS.md`: development contract.
|
||||||
|
|
||||||
## Development principles
|
## Development principles
|
||||||
|
|
||||||
- local-first;
|
- local-first;
|
||||||
- no mandatory cloud account;
|
- no mandatory cloud account;
|
||||||
- user-owned data;
|
- user-owned data;
|
||||||
- versioned persistent formats;
|
- versioned persistent and exchange formats;
|
||||||
- stable exercise identifiers;
|
- stable identities;
|
||||||
- idempotent imports;
|
- idempotent synchronization;
|
||||||
- documentation and tests are part of every feature;
|
- no fake data representation to force incompatible models together;
|
||||||
- clear separation between UI, business logic, and persistence.
|
- strict compiler warnings;
|
||||||
|
- documentation and tests are part of feature completion.
|
||||||
See `AGENTS.md` for the development contract.
|
|
||||||
|
|
|
||||||
885
docs/android.md
885
docs/android.md
|
|
@ -1,556 +1,119 @@
|
||||||
# Android Application
|
# Android application
|
||||||
|
|
||||||
## 1. Purpose
|
## 1. Purpose
|
||||||
|
|
||||||
The Android application is a lightweight training-session recorder.
|
The Android application is Trainlog's low-friction capture client.
|
||||||
|
|
||||||
Its design priority is low-friction data entry during a workout.
|
It is a native Kotlin/Jetpack Compose application with local SQLite persistence.
|
||||||
|
|
||||||
It is not the canonical history or analytics application.
|
The desktop remains the canonical long-term history and analytics store.
|
||||||
|
|
||||||
## 2. Session flow
|
## 2. Implemented navigation
|
||||||
|
|
||||||
```text
|
|
||||||
Start session
|
|
||||||
|
|
|
||||||
v
|
|
||||||
record started_at
|
|
||||||
|
|
|
||||||
v
|
|
||||||
select/create exercise
|
|
||||||
|
|
|
||||||
v
|
|
||||||
enter target + planned rest
|
|
||||||
|
|
|
||||||
v
|
|
||||||
record actual sets
|
|
||||||
|
|
|
||||||
v
|
|
||||||
optional body data / notes
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Finish session
|
|
||||||
|
|
|
||||||
v
|
|
||||||
record ended_at
|
|
||||||
|
|
|
||||||
v
|
|
||||||
validate + export Trainlog JSON
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. Exercise catalog
|
|
||||||
|
|
||||||
The Android application keeps a local exercise catalog so names are not retyped every session.
|
|
||||||
|
|
||||||
Creating an exercise requires:
|
|
||||||
|
|
||||||
- display name;
|
|
||||||
- tracking mode: repetitions or duration.
|
|
||||||
|
|
||||||
The application generates a stable `exercise_id`.
|
|
||||||
|
|
||||||
A new exercise used in an exported session is included in the top-level session export metadata and is therefore importable by the TUI.
|
|
||||||
|
|
||||||
The Android application must prevent accidental duplicate normalized names according to the Trainlog v1 contract.
|
|
||||||
|
|
||||||
## 4. Fast exercise form
|
|
||||||
|
|
||||||
For a repetition exercise, the basic form is conceptually:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Exercise Presse à cuisses
|
|
||||||
Load mode External
|
|
||||||
Load 80 kg
|
|
||||||
Sets 4
|
|
||||||
Repetitions 5
|
|
||||||
Rest 60 s
|
|
||||||
```
|
|
||||||
|
|
||||||
For a timed exercise:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Exercise Gainage ventral
|
|
||||||
Load mode None
|
|
||||||
Sets 3
|
|
||||||
Duration 45 s
|
|
||||||
Rest 60 s
|
|
||||||
```
|
|
||||||
|
|
||||||
The application should remember practical defaults from the previous use of an exercise when that reduces typing, but remembered UI defaults are not part of the exchange-format contract.
|
|
||||||
|
|
||||||
## 5. Load modes
|
|
||||||
|
|
||||||
The user chooses only when relevant:
|
|
||||||
|
|
||||||
- none;
|
|
||||||
- external;
|
|
||||||
- assistance.
|
|
||||||
|
|
||||||
`external` covers free weights and machine-displayed load.
|
|
||||||
|
|
||||||
`assistance` stores a positive assistance value.
|
|
||||||
|
|
||||||
The UI should label assistance explicitly so it cannot be confused with added resistance.
|
|
||||||
|
|
||||||
## 6. Actual work
|
|
||||||
|
|
||||||
The application should pre-populate actual sets from the target.
|
|
||||||
|
|
||||||
The user edits only what differs.
|
|
||||||
|
|
||||||
Example target:
|
|
||||||
|
|
||||||
```text
|
|
||||||
5 / 5 / 5 / 5
|
|
||||||
```
|
|
||||||
|
|
||||||
Actual:
|
|
||||||
|
|
||||||
```text
|
|
||||||
5 / 5 / 5 / 3
|
|
||||||
```
|
|
||||||
|
|
||||||
The export preserves both target and actual values.
|
|
||||||
|
|
||||||
Zero actual repetitions are valid for a real failed attempt.
|
|
||||||
|
|
||||||
A planned exercise may also have zero actual sets if it was never started.
|
|
||||||
|
|
||||||
## 7. Rest
|
|
||||||
|
|
||||||
`rest_seconds` is the planned rest duration for the exercise.
|
|
||||||
|
|
||||||
v1 does not require a running rest timer and does not serialize measured per-set rest.
|
|
||||||
|
|
||||||
A timer can be added later as UI behavior without changing the v1 format.
|
|
||||||
|
|
||||||
## 8. Timestamps
|
|
||||||
|
|
||||||
`started_at` is recorded automatically when the session starts.
|
|
||||||
|
|
||||||
`ended_at` is recorded automatically when the user finishes the session.
|
|
||||||
|
|
||||||
An active/interrupted local session may exist without `ended_at`.
|
|
||||||
|
|
||||||
The application must never invent an end timestamp merely to make export validation pass.
|
|
||||||
|
|
||||||
## 9. Body data
|
|
||||||
|
|
||||||
Optional session-associated data:
|
|
||||||
|
|
||||||
- body weight;
|
|
||||||
- neck;
|
|
||||||
- shoulders;
|
|
||||||
- chest;
|
|
||||||
- waist;
|
|
||||||
- hips;
|
|
||||||
- left/right arm;
|
|
||||||
- left/right forearm;
|
|
||||||
- left/right thigh;
|
|
||||||
- left/right calf.
|
|
||||||
|
|
||||||
The Android UI does not need to force these fields during every workout.
|
|
||||||
|
|
||||||
## 10. Notes
|
|
||||||
|
|
||||||
Session and exercise notes are optional.
|
|
||||||
|
|
||||||
The initial Android UI may omit note controls without violating v1, because the fields are optional.
|
|
||||||
|
|
||||||
## 11. Export
|
|
||||||
|
|
||||||
Before export, Android must enforce both:
|
|
||||||
|
|
||||||
- JSON structural validity;
|
|
||||||
- Trainlog v1 semantic validity.
|
|
||||||
|
|
||||||
A malformed or semantically inconsistent file must not be exported as a completed Trainlog document.
|
|
||||||
|
|
||||||
## 12. Non-goals
|
|
||||||
|
|
||||||
Initial Android versions do not need:
|
|
||||||
|
|
||||||
- analytics;
|
|
||||||
- complex graphs;
|
|
||||||
- cloud accounts;
|
|
||||||
- remote databases;
|
|
||||||
- social features;
|
|
||||||
- muscle classification;
|
|
||||||
- distance/cardio metrics;
|
|
||||||
- per-set rest measurement.
|
|
||||||
## 13. Identifier generation
|
|
||||||
|
|
||||||
When Android creates a new exercise, it generates:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ex_<random UUID v4>
|
|
||||||
```
|
|
||||||
|
|
||||||
When Android creates a new session, it generates:
|
|
||||||
|
|
||||||
```text
|
|
||||||
se_<random UUID v4>
|
|
||||||
```
|
|
||||||
|
|
||||||
Display-name slugs must not be used as persistent identifiers.
|
|
||||||
|
|
||||||
The visible exercise name remains independent from identity.
|
|
||||||
|
|
||||||
## 14. Catalog conflict behavior
|
|
||||||
|
|
||||||
Android must prevent duplicate normalized names inside its own local catalog.
|
|
||||||
|
|
||||||
A valid Android export can still conflict with an independently edited TUI catalog.
|
|
||||||
|
|
||||||
The TUI owns final reconciliation.
|
|
||||||
|
|
||||||
Android must not assume that a matching display name means two different IDs may be silently merged.
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_MTP_TRANSPORT -->
|
|
||||||
## 15. USB file-transfer transport
|
|
||||||
|
|
||||||
The initial Android/Linux integration uses standard Android **file transfer
|
|
||||||
(MTP)** mode.
|
|
||||||
|
|
||||||
The Linux TUI/core accesses the device directly with `libudev` + `libmtp`.
|
|
||||||
Trainlog does not require a mounted Android filesystem.
|
|
||||||
|
|
||||||
Validated Linux-side capabilities:
|
|
||||||
|
|
||||||
```text
|
|
||||||
USB_MTP_DETECTION=PASS
|
|
||||||
MTP_STORAGE_ACCESS=PASS
|
|
||||||
MTP_WRITE=PASS
|
|
||||||
MTP_READ=PASS
|
|
||||||
MTP_ROUNDTRIP=PASS
|
|
||||||
```
|
|
||||||
|
|
||||||
The transport foundation currently uses a root `Trainlog` folder for physical
|
|
||||||
validation.
|
|
||||||
|
|
||||||
The final JSON exchange subdirectory/naming convention is defined in the next
|
|
||||||
slice before the Android recorder depends on it.
|
|
||||||
|
|
||||||
The Android application itself will only need to produce a valid frozen
|
|
||||||
Trainlog JSON v1 document and place it in the agreed exchange area.
|
|
||||||
<!-- TRAINLOG_ANDROID_MTP_TRANSPORT _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_NEXT_SLICE -->
|
|
||||||
## 16. Current Android implementation cursor
|
|
||||||
|
|
||||||
The Linux transport and Sync TUI foundations are complete enough to begin the
|
|
||||||
Android client.
|
|
||||||
|
|
||||||
Initial Android development uses fictitious data.
|
|
||||||
|
|
||||||
First Android slice:
|
|
||||||
|
|
||||||
```text
|
|
||||||
1. application scaffold
|
|
||||||
2. local exercise catalog
|
|
||||||
3. create/select exercise
|
|
||||||
4. session form
|
|
||||||
5. actual set entry
|
|
||||||
6. optional body measurements
|
|
||||||
7. fictitious completed session
|
|
||||||
```
|
|
||||||
|
|
||||||
Transport/export is added only after the local recorder workflow is comfortable.
|
|
||||||
|
|
||||||
The Android client must preserve stable exercise IDs so a catalog snapshot from
|
|
||||||
the PC can normalize exercise selection on both sides.
|
|
||||||
<!-- TRAINLOG_ANDROID_NEXT_SLICE _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_PROFILE_AWARE_ENTRY -->
|
|
||||||
## Profile-aware Android forms
|
|
||||||
|
|
||||||
Android uses the same exercise metadata as the TUI:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recording_mode
|
|
||||||
tracking_mode
|
|
||||||
data_fields
|
|
||||||
```
|
|
||||||
|
|
||||||
A continuous `Marche` configured with speed displays only:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Durée
|
|
||||||
Vitesse
|
|
||||||
```
|
|
||||||
|
|
||||||
and no set count.
|
|
||||||
|
|
||||||
Creating an exercise directly inside session entry must configure this metadata
|
|
||||||
before adding it to the catalog/session.
|
|
||||||
<!-- TRAINLOG_ANDROID_PROFILE_AWARE_ENTRY _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_PROFILE_CURSOR -->
|
|
||||||
## Android implementation cursor
|
|
||||||
|
|
||||||
Android is the next implementation area.
|
|
||||||
|
|
||||||
The application must share Trainlog's visual language with the TUI.
|
|
||||||
|
|
||||||
Theme direction:
|
|
||||||
|
|
||||||
```text
|
|
||||||
dark background
|
|
||||||
cyan/teal Trainlog accent
|
|
||||||
yellow active/focus role
|
|
||||||
green success
|
|
||||||
red error
|
|
||||||
```
|
|
||||||
|
|
||||||
Launcher icon:
|
|
||||||
|
|
||||||
```text
|
|
||||||
T
|
|
||||||
```
|
|
||||||
|
|
||||||
Only the letter `T`, using Trainlog theme colors.
|
|
||||||
|
|
||||||
Required recording sections:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Séance
|
|
||||||
Exercice
|
|
||||||
Mensurations
|
|
||||||
```
|
|
||||||
|
|
||||||
Session recording must allow creating a new exercise inline without leaving the
|
|
||||||
session flow.
|
|
||||||
|
|
||||||
Android forms are driven by the same profile metadata as desktop:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recording_mode
|
|
||||||
tracking_mode
|
|
||||||
data_fields
|
|
||||||
```
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```text
|
|
||||||
SETS + REPS
|
|
||||||
sets / reps / load / rest
|
|
||||||
|
|
||||||
SETS + DURATION
|
|
||||||
sets / duration / optional load / rest
|
|
||||||
|
|
||||||
CONTINUOUS + DURATION + SPEED_KMH
|
|
||||||
duration / speed
|
|
||||||
```
|
|
||||||
|
|
||||||
The app must never infer an input form from an exercise display name.
|
|
||||||
|
|
||||||
Initial development uses fictitious records. Test/development data is removed
|
|
||||||
before normal production use starts.
|
|
||||||
<!-- TRAINLOG_ANDROID_PROFILE_CURSOR _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_SCAFFOLD_IMPLEMENTED -->
|
|
||||||
## Android scaffold implementation
|
|
||||||
|
|
||||||
The Android project now lives in:
|
|
||||||
|
|
||||||
```text
|
|
||||||
android/
|
|
||||||
```
|
|
||||||
|
|
||||||
It is a native Kotlin + Jetpack Compose application with a custom Trainlog
|
|
||||||
visual layer rather than default Material presentation.
|
|
||||||
|
|
||||||
Implemented scaffold navigation:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Accueil
|
Accueil
|
||||||
├── Enregistrer une séance
|
├── Enregistrer une séance
|
||||||
│ ├── Ajouter depuis le catalogue
|
|
||||||
│ └── Créer un nouvel exercice
|
|
||||||
│ └── returns to session flow
|
|
||||||
├── Enregistrer un exercice
|
├── Enregistrer un exercice
|
||||||
└── Enregistrer des mensurations
|
├── Enregistrer des mensurations
|
||||||
|
├── Historique des séances
|
||||||
|
└── Synchroniser avec le PC
|
||||||
```
|
```
|
||||||
|
|
||||||
The launcher icon is a minimal themed `T`.
|
## 3. Local persistence
|
||||||
|
|
||||||
The UI reuses the Trainlog visual roles from the TUI:
|
Android local database version:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
background
|
3
|
||||||
accent/cyan
|
|
||||||
success/green
|
|
||||||
warning/yellow
|
|
||||||
error/red
|
|
||||||
muted/blue
|
|
||||||
graph/magenta
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Android forms will be driven by:
|
Domain tables cover:
|
||||||
|
|
||||||
```text
|
|
||||||
recording_mode
|
|
||||||
tracking_mode
|
|
||||||
data_fields
|
|
||||||
```
|
|
||||||
|
|
||||||
The scaffold intentionally does not implement persistence or MTP yet.
|
|
||||||
|
|
||||||
Next:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_LOCAL_MODEL_AND_PERSISTENCE=NEXT
|
|
||||||
ANDROID_SESSION_FORM=AFTER
|
|
||||||
ANDROID_MTP_SYNC=AFTER_LOCAL_WORKFLOW
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_ANDROID_SCAFFOLD_IMPLEMENTED _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_LOCAL_CATALOG -->
|
|
||||||
## Android local catalog checkpoint
|
|
||||||
|
|
||||||
The Android application now has a persistent local exercise catalog.
|
|
||||||
|
|
||||||
Implemented:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Android SQLite exercise database
|
|
||||||
profile-aware exercise model
|
|
||||||
standalone exercise creation
|
|
||||||
inline exercise creation from session flow
|
|
||||||
catalog survives application restart
|
|
||||||
session screen refreshes after inline creation
|
|
||||||
```
|
|
||||||
|
|
||||||
Android uses the same semantic axes as desktop:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recording_mode
|
|
||||||
tracking_mode
|
|
||||||
data_fields
|
|
||||||
```
|
|
||||||
|
|
||||||
Known supplemental fields remain:
|
|
||||||
|
|
||||||
```text
|
|
||||||
SPEED_KMH
|
|
||||||
DISTANCE_KM
|
|
||||||
```
|
|
||||||
|
|
||||||
Continuous creation forces duration tracking. Set-based creation keeps
|
|
||||||
supplemental continuous fields disabled.
|
|
||||||
|
|
||||||
The local Android schema is intentionally independent from the desktop SQLite
|
|
||||||
schema. Synchronization later exchanges versioned domain data rather than
|
|
||||||
copying SQLite database files.
|
|
||||||
|
|
||||||
Next:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_SESSION_RECORDING=NEXT
|
|
||||||
ANDROID_BODY_PERSISTENCE=AFTER
|
|
||||||
MTP_SYNC=AFTER_LOCAL_WORKFLOWS
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_ANDROID_LOCAL_CATALOG _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_SESSION_RECORDING -->
|
|
||||||
## Android session recording checkpoint
|
|
||||||
|
|
||||||
Android can now build and persist real local sessions.
|
|
||||||
|
|
||||||
Flow:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Session
|
|
||||||
→ choose catalog exercise
|
|
||||||
→ profile-aware entry form
|
|
||||||
→ add exercise to session draft
|
|
||||||
→ repeat for additional exercises
|
|
||||||
→ save session
|
|
||||||
```
|
|
||||||
|
|
||||||
Profile-aware forms:
|
|
||||||
|
|
||||||
```text
|
|
||||||
SETS + REPS
|
|
||||||
set count
|
|
||||||
repetitions per set
|
|
||||||
|
|
||||||
SETS + DURATION
|
|
||||||
set count
|
|
||||||
duration per set
|
|
||||||
|
|
||||||
CONTINUOUS + DURATION
|
|
||||||
duration minutes
|
|
||||||
configured speed/distance fields
|
|
||||||
```
|
|
||||||
|
|
||||||
Persistence mirrors the domain split:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
exercises
|
||||||
sessions
|
sessions
|
||||||
session_exercises
|
session_exercises
|
||||||
performed_sets
|
performed_sets
|
||||||
continuous_activity
|
continuous_activity
|
||||||
|
body_observations
|
||||||
```
|
```
|
||||||
|
|
||||||
Continuous exercises do not create fake performed sets.
|
This database is Android-local. It is not copied to the PC.
|
||||||
|
|
||||||
The Android local database version is now 2.
|
## 4. Exercise catalog
|
||||||
|
|
||||||
Next:
|
Exercise creation records:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ANDROID_SESSION_HISTORY=NEXT
|
name
|
||||||
ANDROID_BODY_PERSISTENCE=AFTER
|
recording_mode
|
||||||
MTP_SYNC=AFTER_LOCAL_WORKFLOWS
|
tracking_mode
|
||||||
|
data_fields
|
||||||
```
|
```
|
||||||
<!-- TRAINLOG_ANDROID_SESSION_RECORDING _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_SESSION_HISTORY -->
|
Stable identity:
|
||||||
## Android session history checkpoint
|
|
||||||
|
|
||||||
Android now exposes persisted local sessions through:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Accueil
|
ex_<uuid-v4>
|
||||||
→ Consultation
|
|
||||||
→ Historique des séances
|
|
||||||
→ Détail séance
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Detail rendering remains profile-aware:
|
The UI rejects invalid profile combinations and local normalized-name
|
||||||
|
collisions.
|
||||||
|
|
||||||
|
An exercise may be created standalone or inline while building a session.
|
||||||
|
|
||||||
|
## 5. Session recording
|
||||||
|
|
||||||
|
Stable session identity:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SETS + REPS
|
se_<uuid-v4>
|
||||||
one line per performed set with reps
|
|
||||||
|
|
||||||
SETS + DURATION
|
|
||||||
one line per performed set with duration
|
|
||||||
|
|
||||||
CONTINUOUS
|
|
||||||
duration
|
|
||||||
configured speed
|
|
||||||
configured distance
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The history reader uses the persisted session snapshot metadata rather than
|
Session entry is profile-aware.
|
||||||
inferring behavior from exercise names.
|
|
||||||
|
|
||||||
Next:
|
### Sets + repetitions
|
||||||
|
|
||||||
|
Actual set values may be heterogeneous.
|
||||||
|
|
||||||
|
Compact entry supports:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ANDROID_BODY_PERSISTENCE=NEXT
|
5x10
|
||||||
ANDROID_LOCAL_WORKFLOWS_THEN_MTP
|
4,5,6,7,8,9,10,9,8,7,6,5,4
|
||||||
|
4..10..4
|
||||||
```
|
```
|
||||||
<!-- TRAINLOG_ANDROID_SESSION_HISTORY _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_BODY_PERSISTENCE -->
|
### Sets + duration
|
||||||
## Android body measurement checkpoint
|
|
||||||
|
|
||||||
The Android body workflow is now persistent and uses the same measurement set
|
Each performed set stores its own duration.
|
||||||
as the TUI.
|
|
||||||
|
|
||||||
Fields:
|
### Continuous + duration
|
||||||
|
|
||||||
|
The form asks for duration and only the configured supplemental fields such as
|
||||||
|
speed or distance.
|
||||||
|
|
||||||
|
Continuous work does not create fake sets.
|
||||||
|
|
||||||
|
## 6. Session draft editing
|
||||||
|
|
||||||
|
Before a session is saved, an exercise already added to the draft can be
|
||||||
|
removed.
|
||||||
|
|
||||||
|
Removing one exercise does not alter the exercise catalog entry itself.
|
||||||
|
|
||||||
|
## 7. Session history
|
||||||
|
|
||||||
|
Android exposes persisted local session history and profile-aware detail.
|
||||||
|
|
||||||
|
Set-based history renders ordered performed sets.
|
||||||
|
|
||||||
|
Continuous history renders its one activity record with configured supplemental
|
||||||
|
values.
|
||||||
|
|
||||||
|
## 8. Body measurements
|
||||||
|
|
||||||
|
Supported metrics:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
weight
|
weight
|
||||||
|
|
@ -568,304 +131,114 @@ left/right calf
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
empty field = measurement not taken
|
empty field = not measured
|
||||||
at least one positive metric required
|
at least one positive metric required
|
||||||
comma or dot accepted for decimal entry
|
comma or dot accepted for decimal entry
|
||||||
```
|
```
|
||||||
|
|
||||||
Android SQLite schema version:
|
Stable identity:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
3
|
bo_<uuid-v4>
|
||||||
```
|
```
|
||||||
|
|
||||||
The body screen also shows the five most recent observations.
|
## 9. Automatic mobile snapshot
|
||||||
|
|
||||||
At this point the three primary Android recording workflows are locally
|
Android maintains:
|
||||||
functional:
|
|
||||||
|
|
||||||
```text
|
|
||||||
session recording
|
|
||||||
exercise creation
|
|
||||||
body measurement recording
|
|
||||||
```
|
|
||||||
|
|
||||||
Next:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_LOCAL_POLISH_AND_VALIDATION=NEXT
|
|
||||||
MTP_SYNC=AFTER_LOCAL_CHECKPOINT
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_ANDROID_BODY_PERSISTENCE _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_LOCAL_WORKFLOWS_PASS -->
|
|
||||||
## Local Android workflows — validated
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_SCAFFOLD=PASS
|
|
||||||
ANDROID_THEME_PARITY=PASS
|
|
||||||
ANDROID_EXERCISE_CREATE=PASS
|
|
||||||
ANDROID_INLINE_EXERCISE_CREATE=PASS
|
|
||||||
ANDROID_SESSION_RECORDING=PASS
|
|
||||||
ANDROID_SESSION_HISTORY=PASS
|
|
||||||
ANDROID_BODY_RECORDING=PASS
|
|
||||||
ANDROID_LOCAL_WORKFLOWS=PASS
|
|
||||||
```
|
|
||||||
|
|
||||||
The application is now locally usable for its three primary recording flows:
|
|
||||||
|
|
||||||
```text
|
|
||||||
session
|
|
||||||
exercise
|
|
||||||
body measurements
|
|
||||||
```
|
|
||||||
|
|
||||||
Session and history rendering are profile-aware.
|
|
||||||
|
|
||||||
The Android-local SQLite database is not a synchronization format.
|
|
||||||
|
|
||||||
Next:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_MTP_SYNC=NEXT
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_ANDROID_LOCAL_WORKFLOWS_PASS _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_MOBILE_EXPORT_V1 -->
|
|
||||||
## MTP mobile export v1
|
|
||||||
|
|
||||||
Android now prepares a versioned full mobile snapshot at:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Download/Trainlog/trainlog-mobile-export-v1.json
|
Download/Trainlog/trainlog-mobile-export-v1.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The file contains:
|
The snapshot is refreshed after relevant local changes, including exercise,
|
||||||
|
session, body-observation, and PC-catalog updates.
|
||||||
|
|
||||||
```text
|
The user does not need a separate manual export step before synchronization.
|
||||||
exercise profiles
|
|
||||||
sessions
|
|
||||||
body observations
|
|
||||||
```
|
|
||||||
|
|
||||||
It is explicitly separate from frozen `TRAINLOG_FORMAT_V1`.
|
## 10. PC catalog access
|
||||||
|
|
||||||
Desktop direct-MTP validation is available through:
|
PC-created files are accessed through a persistent Storage Access Framework
|
||||||
|
grant.
|
||||||
|
|
||||||
```text
|
The selected folder must be:
|
||||||
./build/tui/trainlog-mtp-mobile-export-probe
|
|
||||||
```
|
|
||||||
|
|
||||||
The probe traverses:
|
|
||||||
|
|
||||||
```text
|
|
||||||
internal storage
|
|
||||||
→ Download
|
|
||||||
→ Trainlog
|
|
||||||
→ trainlog-mobile-export-v1.json
|
|
||||||
```
|
|
||||||
|
|
||||||
and downloads it directly through libmtp without a mount.
|
|
||||||
|
|
||||||
Next after hardware PASS:
|
|
||||||
|
|
||||||
```text
|
|
||||||
DESKTOP_MOBILE_EXPORT_IMPORT=NEXT
|
|
||||||
PC_TO_ANDROID_CATALOG=AFTER
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_MOBILE_EXPORT_V1 _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_SYNC_FOLDER_CHECKPOINT -->
|
|
||||||
## Android synchronization folder
|
|
||||||
|
|
||||||
PC-created synchronization artifacts are consumed through a persistent Storage
|
|
||||||
Access Framework grant.
|
|
||||||
|
|
||||||
Canonical selected folder:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Download/Trainlog
|
Download/Trainlog
|
||||||
```
|
```
|
||||||
|
|
||||||
The Sync screen always exposes the folder-selection action.
|
The Sync screen always permits changing the stored folder selection.
|
||||||
|
|
||||||
When a folder is already authorized, the action becomes:
|
No application-data reset is required to fix a wrong folder choice.
|
||||||
|
|
||||||
|
## 11. Android-triggered synchronization
|
||||||
|
|
||||||
|
The Sync screen exposes:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Changer le dossier Trainlog
|
Synchroniser maintenant
|
||||||
```
|
```
|
||||||
|
|
||||||
This is required so a wrong persisted folder selection can be corrected without
|
Android writes:
|
||||||
clearing the Android application database.
|
|
||||||
|
|
||||||
Validated PC catalog publication:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
trainlog-pc-catalog-v1.json
|
trainlog-sync-request-v1.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The final Android synchronization workflow must evolve toward a single
|
and waits for a matching:
|
||||||
`Synchroniser maintenant` action backed by a PC-side synchronization agent,
|
|
||||||
rather than manual export/import steps.
|
|
||||||
<!-- TRAINLOG_ANDROID_SYNC_FOLDER_CHECKPOINT _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL -->
|
|
||||||
## Variable sets and session exercise removal checkpoint
|
|
||||||
|
|
||||||
Validated functionality in this checkpoint:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
VARIABLE_REPETITION_SETS=PASS
|
trainlog-sync-receipt-v1.json
|
||||||
REPETITION_SHORTHAND_5x10=PASS
|
|
||||||
REPETITION_EXPLICIT_LIST=PASS
|
|
||||||
REPETITION_PYRAMID=PASS
|
|
||||||
|
|
||||||
DESKTOP_SCHEMA_V5=PASS
|
|
||||||
V4_TO_V5_MIGRATION_REGRESSION=PASS
|
|
||||||
MOBILE_HETEROGENEOUS_SET_IMPORT=PASS
|
|
||||||
MOBILE_IMPORT_IDEMPOTENCE=PASS
|
|
||||||
NO_FAKE_UNIFORM_TARGET=PASS
|
|
||||||
|
|
||||||
ANDROID_SESSION_DRAFT_EXERCISE_REMOVE=PASS
|
|
||||||
DESKTOP_SESSION_EXERCISE_REMOVE=PASS
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Accepted repetition examples:
|
The receipt is matched by `request_id`.
|
||||||
|
|
||||||
|
On success Android then applies the latest PC catalog and displays the final
|
||||||
|
result.
|
||||||
|
|
||||||
|
A receipt belonging to another request is ignored as pending rather than
|
||||||
|
misreported as the current result.
|
||||||
|
|
||||||
|
## 12. Synchronization ownership
|
||||||
|
|
||||||
|
Android does not initiate raw MTP operations itself.
|
||||||
|
|
||||||
|
MTP is host-initiated:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5x10
|
Android request
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
-> PC trainlog-syncd
|
||||||
4..10..4
|
-> shared desktop sync engine
|
||||||
|
-> receipt
|
||||||
```
|
```
|
||||||
|
|
||||||
A heterogeneous mobile session is persisted as ordered `performed_sets`.
|
## 13. Build
|
||||||
The desktop does not invent `target_sets`, `target_reps` or
|
|
||||||
`target_duration_seconds` for actual-only mobile observations.
|
|
||||||
|
|
||||||
On Android, an exercise already added to the current session can be removed
|
Example local configuration:
|
||||||
before saving the session.
|
|
||||||
|
|
||||||
On the desktop TUI, session editing already supports:
|
```bash
|
||||||
|
cd android
|
||||||
|
|
||||||
```text
|
printf 'sdk.dir=%s\n' "$HOME/Android/Sdk" > local.properties
|
||||||
d supprimer
|
|
||||||
|
JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
|
||||||
|
./gradlew assembleDebug
|
||||||
```
|
```
|
||||||
|
|
||||||
for removing the selected exercise from a current or persisted session draft.
|
Install to a connected test device:
|
||||||
The database replacement remains transactional.
|
|
||||||
|
|
||||||
`TRAINLOG_FORMAT_V1` remains frozen and unchanged.
|
```bash
|
||||||
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL _END -->
|
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
|
||||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 -->
|
|
||||||
## Shared bidirectional synchronization v1
|
|
||||||
|
|
||||||
Validated architecture:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Android local write
|
|
||||||
-> automatic mobile snapshot
|
|
||||||
|
|
||||||
Android "Synchroniser maintenant"
|
|
||||||
-> trainlog-sync-request-v1.json
|
|
||||||
|
|
||||||
trainlog-syncd
|
|
||||||
-> shared C synchronization engine
|
|
||||||
-> Android → PC mobile import
|
|
||||||
-> PC → Android catalog publish
|
|
||||||
-> trainlog-sync-receipt-v1.json
|
|
||||||
|
|
||||||
Android
|
|
||||||
-> receipt matched by request_id
|
|
||||||
-> PC catalog applied locally
|
|
||||||
-> final result displayed
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The ncurses TUI and `trainlog-syncd` call the same
|
`local.properties` is local machine configuration and must not be committed.
|
||||||
`trainlog_sync_run()` implementation.
|
|
||||||
|
|
||||||
Direct libmtp remains mandatory. No filesystem mount and no SQLite-file
|
## 14. Non-goals
|
||||||
synchronization are introduced.
|
|
||||||
|
|
||||||
### Concurrency
|
Android is not intended to own:
|
||||||
|
|
||||||
The shared engine owns:
|
- canonical long-term analytics;
|
||||||
|
- complex body/performance graphs;
|
||||||
```text
|
- cloud accounts;
|
||||||
$XDG_DATA_HOME/trainlog/sync.lock
|
- direct SQLite-file synchronization;
|
||||||
```
|
- exercise-name heuristics;
|
||||||
|
- a mounted-filesystem dependency.
|
||||||
A TUI-triggered transaction waits for the lock. Daemon request polling is
|
|
||||||
non-blocking and retries later.
|
|
||||||
|
|
||||||
### Sync history
|
|
||||||
|
|
||||||
Every actual synchronization transaction creates:
|
|
||||||
|
|
||||||
```text
|
|
||||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json
|
|
||||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
and appends a compact entry to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
$XDG_DATA_HOME/trainlog/sync_history.log
|
|
||||||
```
|
|
||||||
|
|
||||||
The TUI behaves like:
|
|
||||||
|
|
||||||
```text
|
|
||||||
git log
|
|
||||||
↑/↓ select synchronization
|
|
||||||
|
|
||||||
git show
|
|
||||||
Enter opens structured detail
|
|
||||||
```
|
|
||||||
|
|
||||||
Legacy three-field history entries remain readable but have no structured
|
|
||||||
detail file.
|
|
||||||
|
|
||||||
### Android request and receipt
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format = trainlog-sync-request
|
|
||||||
version = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Receipt:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format = trainlog-sync-receipt
|
|
||||||
version = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
The receipt carries the originating `request_id`, a generated `sync_id`,
|
|
||||||
status, summary and synchronization counts. Android ignores a receipt for a
|
|
||||||
different request ID.
|
|
||||||
|
|
||||||
### User service
|
|
||||||
|
|
||||||
Install/refresh the user service with:
|
|
||||||
|
|
||||||
```text
|
|
||||||
bash tools/install_syncd_user.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
No root privilege is required.
|
|
||||||
|
|
||||||
### Status
|
|
||||||
|
|
||||||
```text
|
|
||||||
COMMON_SYNC_ENGINE=PASS
|
|
||||||
TUI_SYNC_LOG_SHOW=PASS
|
|
||||||
TRAINLOG_SYNCD=PASS
|
|
||||||
ANDROID_TRIGGERED_SYNC=PASS
|
|
||||||
ANDROID_SYNC_RECEIPT=PASS
|
|
||||||
BIDIRECTIONAL_SYNC_V1=PASS
|
|
||||||
```
|
|
||||||
|
|
||||||
Frozen `TRAINLOG_FORMAT_V1` remains unchanged.
|
|
||||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 _END -->
|
|
||||||
|
|
|
||||||
|
|
@ -1,199 +1,287 @@
|
||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
## 1. Purpose
|
## 1. System boundary
|
||||||
|
|
||||||
Trainlog separates capture from analysis.
|
Trainlog separates capture, durable history, transport, and presentation.
|
||||||
|
|
||||||
The Android application is optimized for fast data entry during training.
|
```text
|
||||||
|
Android capture client
|
||||||
|
|
|
||||||
|
| local SQLite
|
||||||
|
|
|
||||||
|
+-- automatic mobile snapshot
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Android shared storage
|
||||||
|
Download/Trainlog
|
||||||
|
|
|
||||||
|
| direct MTP
|
||||||
|
v
|
||||||
|
shared desktop sync engine
|
||||||
|
/ \
|
||||||
|
/ \
|
||||||
|
desktop SQLite PC catalog artifact
|
||||||
|
| |
|
||||||
|
v v
|
||||||
|
desktop TUI Android
|
||||||
|
```
|
||||||
|
|
||||||
The TUI is optimized for durable storage, inspection, statistics, and visualization.
|
The desktop SQLite database is the canonical long-term history.
|
||||||
|
|
||||||
|
Android local SQLite is a capture store, not a synchronization format.
|
||||||
|
|
||||||
## 2. Components
|
## 2. Components
|
||||||
|
|
||||||
### Android client
|
### Android application
|
||||||
|
|
||||||
Responsibilities:
|
Responsibilities:
|
||||||
|
|
||||||
- start a session;
|
- exercise catalog entry;
|
||||||
- record the session start timestamp;
|
- workout-session recording;
|
||||||
- select an existing exercise;
|
- performed set entry;
|
||||||
- create a new exercise;
|
- continuous-activity entry;
|
||||||
- record workout targets;
|
- body measurement entry;
|
||||||
- record actual performed sets;
|
- local history/detail;
|
||||||
- record rest duration;
|
- automatic mobile snapshot generation;
|
||||||
- record body data;
|
- PC catalog application;
|
||||||
- record the session end timestamp;
|
- synchronization request creation;
|
||||||
- export one valid Trainlog JSON document.
|
- synchronization receipt display.
|
||||||
|
|
||||||
Non-responsibilities:
|
Android is not responsible for canonical long-term analytics.
|
||||||
|
|
||||||
- long-term analytics;
|
### Desktop core
|
||||||
- canonical history;
|
|
||||||
- complex graphing;
|
|
||||||
- cloud synchronization.
|
|
||||||
|
|
||||||
### Exchange format
|
The C17 core owns:
|
||||||
|
|
||||||
The exchange format is the compatibility boundary between Android and the TUI.
|
- desktop SQLite persistence;
|
||||||
|
- exercise/catalog rules;
|
||||||
It is:
|
- profile-aware session data;
|
||||||
|
- body data;
|
||||||
- JSON;
|
- ID and time helpers;
|
||||||
- UTF-8;
|
- USB discovery;
|
||||||
- versioned;
|
- direct MTP operations;
|
||||||
- self-contained enough to import newly created exercises;
|
- the shared bidirectional synchronization engine.
|
||||||
- designed for idempotent import.
|
|
||||||
|
|
||||||
### TUI
|
### TUI
|
||||||
|
|
||||||
Responsibilities:
|
The ncursesw layer owns interaction and rendering.
|
||||||
|
|
||||||
- import Trainlog JSON;
|
It consumes core services for:
|
||||||
- reject malformed or incompatible input cleanly;
|
|
||||||
- deduplicate sessions;
|
|
||||||
- maintain the canonical exercise catalog;
|
|
||||||
- create workouts directly from the terminal;
|
|
||||||
- maintain SQLite history;
|
|
||||||
- calculate progress metrics;
|
|
||||||
- render graphs and summaries;
|
|
||||||
- export data when needed.
|
|
||||||
|
|
||||||
### SQLite store
|
- session entry/editing;
|
||||||
|
- history;
|
||||||
|
- exercise performance;
|
||||||
|
- body tracking;
|
||||||
|
- graphs;
|
||||||
|
- manual synchronization;
|
||||||
|
- synchronization log/detail display.
|
||||||
|
|
||||||
SQLite is the canonical local history.
|
### `trainlog-syncd`
|
||||||
|
|
||||||
The database must use:
|
`trainlog-syncd` is a small user-session agent.
|
||||||
|
|
||||||
- foreign keys;
|
It polls for a new Android request and invokes the same shared C synchronization
|
||||||
- uniqueness constraints;
|
engine used by the TUI.
|
||||||
- schema versioning;
|
|
||||||
- explicit migration rules.
|
|
||||||
|
|
||||||
## 3. Data flow
|
It does not implement a second synchronization algorithm.
|
||||||
|
|
||||||
|
## 3. Exercise model
|
||||||
|
|
||||||
|
Trainlog is metadata-driven:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Android
|
recording_mode = SETS | CONTINUOUS
|
||||||
|
|
tracking_mode = REPS | DURATION
|
||||||
| export
|
data_fields = SPEED_KMH | DISTANCE_KM
|
||||||
v
|
|
||||||
Trainlog JSON
|
|
||||||
|
|
|
||||||
| import + validation
|
|
||||||
v
|
|
||||||
TUI application
|
|
||||||
|
|
|
||||||
| persistence
|
|
||||||
v
|
|
||||||
SQLite
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. Identity rules
|
Valid model-v1 combinations:
|
||||||
|
|
||||||
Exercises have:
|
|
||||||
|
|
||||||
- a stable machine identifier: `exercise_id`;
|
|
||||||
- a mutable display name: `name`.
|
|
||||||
|
|
||||||
The display name is not the identity.
|
|
||||||
|
|
||||||
Sessions have:
|
|
||||||
|
|
||||||
- a globally unique `session_id`.
|
|
||||||
|
|
||||||
A second import of the same `session_id` must not duplicate the session.
|
|
||||||
|
|
||||||
## 5. Separation rules for the TUI
|
|
||||||
|
|
||||||
The C17 TUI will be split into layers:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ncursesw rendering
|
SETS + REPS
|
||||||
|
|
SETS + DURATION
|
||||||
v
|
CONTINUOUS + DURATION
|
||||||
TUI state / navigation
|
|
||||||
|
|
|
||||||
v
|
|
||||||
application services
|
|
||||||
|
|
|
||||||
+---- exchange-format parser
|
|
||||||
|
|
|
||||||
+---- analytics
|
|
||||||
|
|
|
||||||
v
|
|
||||||
SQLite persistence
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The rendering layer must not own business rules.
|
Load mode is session-specific:
|
||||||
|
|
||||||
The persistence layer must not depend on ncurses.
|
|
||||||
|
|
||||||
## 6. Error philosophy
|
|
||||||
|
|
||||||
Trainlog must prefer explicit failure over silent corruption.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- malformed JSON: reject import with a precise error;
|
|
||||||
- unsupported format version: reject import;
|
|
||||||
- duplicate session: report already imported, do not duplicate;
|
|
||||||
- unknown exercise: import it when valid catalog data is present;
|
|
||||||
- incomplete active session: preserve it explicitly rather than silently inventing an end time.
|
|
||||||
|
|
||||||
<!-- TRAINLOG_DIRECT_MTP_ARCHITECTURE -->
|
|
||||||
## 7. Direct Android USB/MTP transport
|
|
||||||
|
|
||||||
The Linux side does not require the Android device to be mounted as a normal
|
|
||||||
filesystem.
|
|
||||||
|
|
||||||
Transport layering is:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Android USB file-transfer mode
|
none
|
||||||
|
|
external
|
||||||
v
|
assistance
|
||||||
libudev physical-device discovery
|
|
||||||
|
|
|
||||||
| bus number + device number
|
|
||||||
v
|
|
||||||
libmtp exact raw-device open
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Android internal MTP storage
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This avoids GVFS/FUSE mount state and manual mount/unmount lifecycle management.
|
Continuous work is persisted separately from performed sets.
|
||||||
|
|
||||||
`libudev` owns physical-device discovery. `libmtp` owns storage and object
|
## 4. Persistence ownership
|
||||||
operations. The JSON exchange layer remains above both and stays independent
|
|
||||||
from USB/MTP backend details.
|
|
||||||
|
|
||||||
Current transport foundation supports folder creation, file upload, folder
|
### Desktop
|
||||||
listing, file download, and verified byte-for-byte roundtrip.
|
|
||||||
<!-- TRAINLOG_DIRECT_MTP_ARCHITECTURE _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_PROFILE_AWARE_ARCHITECTURE -->
|
Desktop SQLite schema v5 is canonical long-term history.
|
||||||
## Profile-aware activity architecture
|
|
||||||
|
|
||||||
Trainlog has two distinct actual-work persistence paths:
|
Main tables:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SET-based exercise
|
exercises
|
||||||
session_exercises
|
sessions
|
||||||
|
|
session_exercises
|
||||||
+--> performed_sets [0..N]
|
performed_sets
|
||||||
|
continuous_activity
|
||||||
CONTINUOUS exercise
|
body_observations
|
||||||
session_exercises
|
|
||||||
|
|
|
||||||
+--> continuous_activity [exactly 1]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The two paths must remain semantically distinct.
|
### Android
|
||||||
|
|
||||||
Catalog metadata determines future entry forms.
|
Android has an independent local SQLite schema.
|
||||||
|
|
||||||
Session-exercise snapshot metadata determines historical rendering/editing.
|
It mirrors domain concepts needed for capture, but its schema version is not
|
||||||
|
coupled to the desktop schema.
|
||||||
|
|
||||||
The Android client must consume the same catalog profile metadata rather than
|
Synchronization exchanges domain artifacts rather than database files.
|
||||||
maintaining an independent exercise-type system.
|
|
||||||
<!-- TRAINLOG_PROFILE_AWARE_ARCHITECTURE _END -->
|
## 5. Compatibility boundaries
|
||||||
|
|
||||||
|
### Frozen Trainlog JSON v1
|
||||||
|
|
||||||
|
`TRAINLOG_FORMAT_V1` is frozen and remains a compatibility boundary for its
|
||||||
|
existing set-based session contract.
|
||||||
|
|
||||||
|
### Synchronization artifacts
|
||||||
|
|
||||||
|
Synchronization uses separate formats:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trainlog-mobile-export v1
|
||||||
|
trainlog-pc-catalog v1
|
||||||
|
trainlog-sync-request v1
|
||||||
|
trainlog-sync-receipt v1
|
||||||
|
```
|
||||||
|
|
||||||
|
A new domain requirement must not be forced into frozen v1 by using notes,
|
||||||
|
synthetic sets, or data loss.
|
||||||
|
|
||||||
|
## 6. Direct MTP transport
|
||||||
|
|
||||||
|
Linux transport:
|
||||||
|
|
||||||
|
```text
|
||||||
|
physical Android USB device
|
||||||
|
|
|
||||||
|
v
|
||||||
|
libudev discovery
|
||||||
|
|
|
||||||
|
| bus + device number
|
||||||
|
v
|
||||||
|
libmtp exact raw-device access
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Android internal storage
|
||||||
|
```
|
||||||
|
|
||||||
|
No GVFS/FUSE mount is required.
|
||||||
|
|
||||||
|
Canonical exchange directory:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Download/Trainlog
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Shared synchronization engine
|
||||||
|
|
||||||
|
Both user-trigger paths call:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trainlog_sync_run()
|
||||||
|
```
|
||||||
|
|
||||||
|
Manual path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
TUI -> trainlog_sync_run()
|
||||||
|
```
|
||||||
|
|
||||||
|
Android-triggered path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android request
|
||||||
|
-> trainlog-syncd
|
||||||
|
-> trainlog_sync_run()
|
||||||
|
-> receipt
|
||||||
|
```
|
||||||
|
|
||||||
|
The engine performs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. direct-MTP device/storage discovery
|
||||||
|
2. exchange-folder resolution
|
||||||
|
3. mobile snapshot download
|
||||||
|
4. strict transactional Android -> PC import
|
||||||
|
5. PC catalog export
|
||||||
|
6. direct-MTP PC catalog publication
|
||||||
|
7. optional request receipt publication
|
||||||
|
8. structured run-history recording
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Synchronization concurrency
|
||||||
|
|
||||||
|
The shared engine serializes synchronization with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$XDG_DATA_HOME/trainlog/sync.lock
|
||||||
|
```
|
||||||
|
|
||||||
|
The TUI waits for an active transaction.
|
||||||
|
|
||||||
|
Daemon polling uses non-blocking acquisition and retries later.
|
||||||
|
|
||||||
|
A request ID already successfully consumed is not processed as a new request.
|
||||||
|
|
||||||
|
## 9. Synchronization history
|
||||||
|
|
||||||
|
Every actual run gets a stable:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sy_<uuid-v4>
|
||||||
|
```
|
||||||
|
|
||||||
|
Structured history is stored under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$XDG_DATA_HOME/trainlog/sync_runs/
|
||||||
|
```
|
||||||
|
|
||||||
|
The TUI exposes list/detail semantics comparable to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
git log
|
||||||
|
git show
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Error philosophy
|
||||||
|
|
||||||
|
Trainlog prefers explicit failure over silent corruption.
|
||||||
|
|
||||||
|
Hard validation or persistence failure aborts the relevant transaction.
|
||||||
|
|
||||||
|
The synchronization layer records useful failure detail rather than masking
|
||||||
|
known errors with generic placeholders.
|
||||||
|
|
||||||
|
## 11. Layering rule
|
||||||
|
|
||||||
|
```text
|
||||||
|
ncurses / Compose rendering
|
||||||
|
|
|
||||||
|
v
|
||||||
|
application workflow
|
||||||
|
|
|
||||||
|
+-- domain model
|
||||||
|
+-- synchronization
|
||||||
|
+-- validation
|
||||||
|
|
|
||||||
|
v
|
||||||
|
persistence / MTP transport
|
||||||
|
```
|
||||||
|
|
||||||
|
Rendering does not own persistence rules.
|
||||||
|
|
||||||
|
Persistence and MTP code do not depend on ncurses rendering.
|
||||||
|
|
|
||||||
146
docs/current_state.md
Normal file
146
docs/current_state.md
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
# Current implementation state
|
||||||
|
|
||||||
|
Canonical snapshot: 2026-09-06.
|
||||||
|
|
||||||
|
This document is the compact source of truth for the implemented Trainlog
|
||||||
|
baseline. Detailed behavior belongs in the topic-specific documents.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
```text
|
||||||
|
GATE_0_PROJECT_CONTRACT=PASS
|
||||||
|
GATE_1_TRAINLOG_FORMAT_V1=PASS/FROZEN
|
||||||
|
GATE_2_PERSISTENCE_AND_USABLE_TUI=PASS
|
||||||
|
|
||||||
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
|
|
||||||
|
DESKTOP_SCHEMA_V5=PASS
|
||||||
|
ANDROID_LOCAL_DATABASE_V3=PASS
|
||||||
|
|
||||||
|
PROFILE_AWARE_EXERCISES=PASS
|
||||||
|
CONTINUOUS_ACTIVITY=PASS
|
||||||
|
VARIABLE_REPETITION_SETS=PASS
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
DESKTOP_TESTS=19/19 PASS
|
||||||
|
ANDROID_BUILD=PASS
|
||||||
|
HARDWARE_SYNC_VALIDATION=PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Desktop
|
||||||
|
|
||||||
|
Implemented:
|
||||||
|
|
||||||
|
- C17/ncursesw TUI;
|
||||||
|
- SQLite schema v5;
|
||||||
|
- direct session entry;
|
||||||
|
- persisted session detail and editing;
|
||||||
|
- exercise removal from a session through transactional child replacement;
|
||||||
|
- exercise catalog;
|
||||||
|
- profile-aware set and continuous activities;
|
||||||
|
- heterogeneous repetition sets;
|
||||||
|
- body-observation creation/history/editing;
|
||||||
|
- body graphs and normalized overlays;
|
||||||
|
- exercise performance history;
|
||||||
|
- direct USB/MTP device access;
|
||||||
|
- manual bidirectional synchronization;
|
||||||
|
- structured synchronization history and detail.
|
||||||
|
|
||||||
|
Primary navigation:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 Accueil
|
||||||
|
1 Séance
|
||||||
|
2 Historique
|
||||||
|
3 Exercices
|
||||||
|
4 Corps
|
||||||
|
5 Sync
|
||||||
|
```
|
||||||
|
|
||||||
|
## Android
|
||||||
|
|
||||||
|
Implemented:
|
||||||
|
|
||||||
|
- native Kotlin/Compose application;
|
||||||
|
- local SQLite database v3;
|
||||||
|
- exercise creation;
|
||||||
|
- inline exercise creation during session entry;
|
||||||
|
- profile-aware session recording;
|
||||||
|
- heterogeneous repetition-set entry;
|
||||||
|
- exercise removal from the current session draft;
|
||||||
|
- continuous activity recording;
|
||||||
|
- local session history/detail;
|
||||||
|
- body measurements;
|
||||||
|
- automatic mobile snapshot maintenance;
|
||||||
|
- PC catalog application through a persistent SAF folder grant;
|
||||||
|
- Android-triggered synchronization request;
|
||||||
|
- synchronization receipt handling.
|
||||||
|
|
||||||
|
## Synchronization
|
||||||
|
|
||||||
|
Canonical exchange directory:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Download/Trainlog
|
||||||
|
```
|
||||||
|
|
||||||
|
Artifacts:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android -> PC
|
||||||
|
trainlog-mobile-export-v1.json
|
||||||
|
|
||||||
|
PC -> Android
|
||||||
|
trainlog-pc-catalog-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()`.
|
||||||
|
|
||||||
|
No SQLite file is copied.
|
||||||
|
|
||||||
|
No mounted Android filesystem is required.
|
||||||
|
|
||||||
|
## Validation checkpoint
|
||||||
|
|
||||||
|
Desktop:
|
||||||
|
|
||||||
|
```text
|
||||||
|
19/19 Meson tests PASS
|
||||||
|
frozen JSON validator PASS
|
||||||
|
import-contract validator PASS
|
||||||
|
git diff --check PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Android:
|
||||||
|
|
||||||
|
```text
|
||||||
|
assembleDebug PASS
|
||||||
|
real Samsung request -> daemon -> bidirectional sync -> receipt PASS
|
||||||
|
multiple distinct request IDs consumed once each PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current implementation cursor
|
||||||
|
|
||||||
|
No new feature is frozen by this documentation cleanup.
|
||||||
|
|
||||||
|
```text
|
||||||
|
NEXT_FEATURE=UNFROZEN
|
||||||
|
```
|
||||||
|
|
||||||
|
Future work must start from this validated baseline rather than from obsolete
|
||||||
|
historical `NEXT` notes.
|
||||||
392
docs/database.md
392
docs/database.md
|
|
@ -1,122 +1,90 @@
|
||||||
# Database
|
# Desktop database
|
||||||
|
|
||||||
## 1. Status
|
## 1. Status
|
||||||
|
|
||||||
```text
|
```text
|
||||||
GATE_2=IN_PROGRESS
|
TRAINLOG_DATABASE_SCHEMA_VERSION=5
|
||||||
DATABASE_SCHEMA_V2=IMPLEMENTED
|
DATABASE_SCHEMA_V5=PASS
|
||||||
SESSION_TYPE_PERSISTENCE=IMPLEMENTED
|
|
||||||
SESSION_EDIT_PERSISTENCE=IMPLEMENTED
|
|
||||||
BODY_OBSERVATION_EDIT=IMPLEMENTED
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
```
|
```
|
||||||
|
|
||||||
Gate 2 review #1 establishes the persistence foundation.
|
The desktop SQLite database is the canonical long-term Trainlog history.
|
||||||
|
|
||||||
The Trainlog exchange format v1 is already frozen and is not modified by this gate.
|
Its schema evolves independently from all JSON exchange-format versions.
|
||||||
|
|
||||||
## 2. Purpose
|
## 2. Versioning
|
||||||
|
|
||||||
SQLite is the canonical long-term store used by the TUI.
|
Schema version uses:
|
||||||
|
|
||||||
The SQLite database is an internal persistence format and is versioned independently from the Trainlog JSON exchange format.
|
|
||||||
|
|
||||||
## 3. Schema versioning
|
|
||||||
|
|
||||||
Trainlog database schema version uses SQLite:
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
PRAGMA user_version;
|
PRAGMA user_version;
|
||||||
```
|
```
|
||||||
|
|
||||||
Current schema:
|
Current value:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
DATABASE_SCHEMA_V2=2
|
5
|
||||||
```
|
```
|
||||||
|
|
||||||
A new database starts with `user_version = 0` and is initialized atomically to
|
Supported historical databases are migrated explicitly through the implemented
|
||||||
the current schema.
|
migration chain. A database newer than the running binary understands is
|
||||||
|
rejected.
|
||||||
|
|
||||||
The implemented historical path is:
|
A schema fixture must represent the real historical structure. Rewriting only
|
||||||
|
`user_version` is not an acceptable migration test.
|
||||||
|
|
||||||
```text
|
## 3. Connection invariants
|
||||||
0 -> 2 fresh initialization
|
|
||||||
1 -> 2 transactional migration
|
|
||||||
```
|
|
||||||
|
|
||||||
Schema v2 adds local session classification while leaving the frozen Trainlog
|
Every connection enables:
|
||||||
JSON v1 exchange contract unchanged.
|
|
||||||
|
|
||||||
A database newer than the running binary understands is rejected.
|
|
||||||
|
|
||||||
Every future schema change requires an explicit migration and dedicated
|
|
||||||
coverage; metadata-only version rewriting is not an accepted migration.
|
|
||||||
|
|
||||||
## 4. Connection rules
|
|
||||||
|
|
||||||
Every Trainlog SQLite connection must enable:
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
PRAGMA foreign_keys = ON;
|
PRAGMA foreign_keys = ON;
|
||||||
```
|
```
|
||||||
|
|
||||||
The core also configures a bounded SQLite busy timeout.
|
A bounded SQLite busy timeout is configured by the core.
|
||||||
|
|
||||||
Foreign-key activation is verified by tests.
|
## 4. Tables
|
||||||
|
|
||||||
## 5. Tables
|
### `exercises`
|
||||||
|
|
||||||
### 5.1 `exercises`
|
Canonical desktop exercise catalog.
|
||||||
|
|
||||||
Canonical exercise catalog.
|
|
||||||
|
|
||||||
Fields:
|
|
||||||
|
|
||||||
```text
|
|
||||||
id internal INTEGER primary key
|
|
||||||
exercise_id stable Trainlog identity, UNIQUE
|
|
||||||
name display name
|
|
||||||
normalized_name v1 comparison form, UNIQUE
|
|
||||||
tracking_mode reps | duration
|
|
||||||
```
|
|
||||||
|
|
||||||
The database does not compute Unicode normalization in review #1.
|
|
||||||
|
|
||||||
The application/catalog layer will compute the frozen v1 normalized name in Gate 2 review #2.
|
|
||||||
|
|
||||||
SQLite owns the final uniqueness barrier.
|
|
||||||
|
|
||||||
### 5.2 `sessions`
|
|
||||||
|
|
||||||
Canonical workout session header.
|
|
||||||
|
|
||||||
Fields:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
id
|
id
|
||||||
session_id UNIQUE
|
exercise_id UNIQUE stable identity
|
||||||
|
name
|
||||||
|
normalized_name UNIQUE normalized display form
|
||||||
|
tracking_mode reps | duration
|
||||||
|
recording_mode sets | continuous
|
||||||
|
data_fields bounded bit mask
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules include:
|
||||||
|
|
||||||
|
- continuous implies duration tracking;
|
||||||
|
- unknown supplemental field bits are rejected;
|
||||||
|
- normalized names remain unique.
|
||||||
|
|
||||||
|
### `sessions`
|
||||||
|
|
||||||
|
```text
|
||||||
|
id
|
||||||
|
session_id UNIQUE stable identity
|
||||||
started_at
|
started_at
|
||||||
ended_at nullable
|
ended_at nullable
|
||||||
session_type training | max_test
|
session_type training | max_test
|
||||||
notes nullable
|
notes nullable
|
||||||
```
|
```
|
||||||
|
|
||||||
`session_type` is a local SQLite concern in schema v2. Existing schema-v1 rows
|
### `session_exercises`
|
||||||
migrate to `training`; no historical workout is retroactively inferred to be a
|
|
||||||
max test.
|
|
||||||
|
|
||||||
Body data is stored separately so standalone body observations can use the same representation.
|
Ordered exercise occurrence inside one session.
|
||||||
|
|
||||||
### 5.3 `session_exercises`
|
|
||||||
|
|
||||||
One ordered exercise within a session.
|
|
||||||
|
|
||||||
Fields include:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
session_row_id
|
session_row_id
|
||||||
exercise_row_id
|
exercise_row_id
|
||||||
|
recording_mode
|
||||||
|
data_fields
|
||||||
position
|
position
|
||||||
load_mode
|
load_mode
|
||||||
rest_seconds
|
rest_seconds
|
||||||
|
|
@ -127,41 +95,77 @@ target_weight_kg
|
||||||
notes
|
notes
|
||||||
```
|
```
|
||||||
|
|
||||||
Constraints enforce:
|
Current desktop history snapshots `recording_mode` and `data_fields` in the
|
||||||
|
session row. `tracking_mode` remains associated with the referenced exercise
|
||||||
|
catalog identity.
|
||||||
|
|
||||||
- one exercise identity at most once per session;
|
`SETS` rows support two target shapes in schema v5:
|
||||||
- one row at each session position;
|
|
||||||
- exactly one target metric: repetitions or duration;
|
|
||||||
- target load presence consistent with `load_mode`.
|
|
||||||
|
|
||||||
### 5.4 `performed_sets`
|
```text
|
||||||
|
explicit planned target
|
||||||
|
target_sets + exactly one target metric
|
||||||
|
|
||||||
Ordered actual sets.
|
actual-only mobile observation
|
||||||
|
target_sets = NULL
|
||||||
|
target_reps = NULL
|
||||||
|
target_duration_seconds = NULL
|
||||||
|
```
|
||||||
|
|
||||||
Fields:
|
This v5 rule is what permits heterogeneous mobile performed sets without
|
||||||
|
inventing a fake uniform target.
|
||||||
|
|
||||||
|
`CONTINUOUS` rows are targetless and require:
|
||||||
|
|
||||||
|
```text
|
||||||
|
load_mode = none
|
||||||
|
rest_seconds = 0
|
||||||
|
target_weight_kg = NULL
|
||||||
|
```
|
||||||
|
|
||||||
|
### `performed_sets`
|
||||||
|
|
||||||
|
Ordered actual set records.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
session_exercise_row_id
|
session_exercise_row_id
|
||||||
position
|
position
|
||||||
reps
|
reps nullable
|
||||||
duration_seconds
|
duration_seconds nullable
|
||||||
weight_kg
|
weight_kg nullable
|
||||||
```
|
```
|
||||||
|
|
||||||
Exactly one of repetitions or duration is present.
|
Exactly one primary actual metric is present:
|
||||||
|
|
||||||
Cross-table rules such as actual-set load consistency with the owning session exercise remain application/import invariants and will be tested at the import layer.
|
|
||||||
|
|
||||||
### 5.5 `body_observations`
|
|
||||||
|
|
||||||
Body history is a first-class database concept and may exist with or without a workout session.
|
|
||||||
|
|
||||||
Fields include:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
observation_id
|
reps
|
||||||
|
or
|
||||||
|
duration_seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
Actual repetitions may be zero.
|
||||||
|
|
||||||
|
Each row is independent; heterogeneous repetition sequences are first-class
|
||||||
|
data.
|
||||||
|
|
||||||
|
### `continuous_activity`
|
||||||
|
|
||||||
|
One-to-one actual record for a continuous session exercise.
|
||||||
|
|
||||||
|
```text
|
||||||
|
session_exercise_row_id UNIQUE
|
||||||
|
duration_seconds
|
||||||
|
speed_kmh nullable
|
||||||
|
distance_km nullable
|
||||||
|
```
|
||||||
|
|
||||||
|
Continuous activity never creates a fake performed set.
|
||||||
|
|
||||||
|
### `body_observations`
|
||||||
|
|
||||||
|
```text
|
||||||
|
observation_id UNIQUE
|
||||||
observed_at
|
observed_at
|
||||||
session_row_id optional and UNIQUE
|
session_row_id optional UNIQUE link
|
||||||
body_weight_kg
|
body_weight_kg
|
||||||
neck_cm
|
neck_cm
|
||||||
shoulders_cm
|
shoulders_cm
|
||||||
|
|
@ -181,180 +185,94 @@ notes
|
||||||
|
|
||||||
At least one body metric must be present.
|
At least one body metric must be present.
|
||||||
|
|
||||||
Imported session-associated body data will create one linked observation.
|
## 5. Identifier generation
|
||||||
|
|
||||||
Standalone TUI measurements use the same table without `session_row_id`.
|
Official desktop creator prefixes:
|
||||||
|
|
||||||
## 6. UUID generation
|
|
||||||
|
|
||||||
Official Trainlog creators generate UUID version 4 identifiers.
|
|
||||||
|
|
||||||
The C core provides generated IDs for:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ex_<uuid-v4>
|
ex_ exercise
|
||||||
se_<uuid-v4>
|
se_ session
|
||||||
bo_<uuid-v4>
|
bo_ body observation
|
||||||
|
sy_ synchronization run
|
||||||
```
|
```
|
||||||
|
|
||||||
This is creation policy.
|
All use random UUIDv4 values.
|
||||||
|
|
||||||
The frozen exchange parser remains able to accept other schema-valid opaque v1 identifiers.
|
Exchange parsers may accept other schema-valid opaque identities where their
|
||||||
|
contract explicitly permits it.
|
||||||
|
|
||||||
## 7. Transactions
|
## 6. Transactions
|
||||||
|
|
||||||
Multi-row operations are atomic.
|
Multi-row user operations are atomic.
|
||||||
|
|
||||||
Gate 2 provides explicit:
|
Persisted session correction replaces session child rows transactionally while
|
||||||
|
preserving the parent:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
BEGIN IMMEDIATE
|
session_id
|
||||||
COMMIT
|
started_at
|
||||||
ROLLBACK
|
ended_at
|
||||||
|
session_type
|
||||||
|
session notes
|
||||||
|
linked body observation
|
||||||
```
|
```
|
||||||
|
|
||||||
primitives.
|
Removing an exercise from a persisted session is therefore a transactional
|
||||||
|
replacement of the remaining child set.
|
||||||
|
|
||||||
The JSON import service must perform catalog reconciliation and all session inserts inside one transaction.
|
A failed replacement rolls back to the previously persisted session.
|
||||||
|
|
||||||
Persisted session correction also uses an explicit transaction. Editing a
|
Body-observation editing preserves its stable identity, timestamp, and optional
|
||||||
session replaces only its `session_exercises` / `performed_sets` children and
|
session link.
|
||||||
preserves the parent session row, stable `session_id`, timestamps,
|
|
||||||
`session_type`, session notes, and any linked body observation.
|
|
||||||
|
|
||||||
Body-observation correction preserves observation identity, timestamp, and
|
## 7. Mobile import semantics
|
||||||
optional session link.
|
|
||||||
|
|
||||||
A hard conflict or validation failure leaves the database unchanged.
|
`tools/import_mobile_export.py` validates the complete mobile snapshot before
|
||||||
|
committing database changes.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
|
||||||
|
```text
|
||||||
|
schema-v5 aware
|
||||||
|
transactional
|
||||||
|
idempotent by stable IDs
|
||||||
|
profile-aware catalog reconciliation
|
||||||
|
heterogeneous performed sets preserved
|
||||||
|
no fake target generated
|
||||||
|
continuous activity kept separate
|
||||||
|
```
|
||||||
|
|
||||||
## 8. Units
|
## 8. Units
|
||||||
|
|
||||||
Canonical persistent units remain:
|
Canonical desktop persistence:
|
||||||
|
|
||||||
- weight/load: kilograms;
|
```text
|
||||||
- body circumference: centimeters;
|
weight/load kg
|
||||||
- duration/rest: seconds.
|
body circumference cm
|
||||||
|
duration/rest seconds
|
||||||
|
speed km/h
|
||||||
|
distance km
|
||||||
|
```
|
||||||
|
|
||||||
## 9. Current Gate 2 persistence boundary
|
## 9. Android database
|
||||||
|
|
||||||
Implemented persistence includes:
|
The Android SQLite database is independent.
|
||||||
|
|
||||||
- SQLite schema v2;
|
Desktop and Android schema versions are not required to match.
|
||||||
- transactional schema migration v1 -> v2;
|
|
||||||
- Unicode-aware canonical exercise catalog support;
|
|
||||||
- complete session insertion;
|
|
||||||
- session detail loading;
|
|
||||||
- exact bounded editable-session loading;
|
|
||||||
- transactional replacement of session exercise/set children;
|
|
||||||
- body-observation creation, listing, exact lookup, and update;
|
|
||||||
- stable local identifiers for exercises, sessions, and body observations.
|
|
||||||
|
|
||||||
The database remains independent from ncurses rendering.
|
Do not synchronize SQLite database files.
|
||||||
|
|
||||||
The frozen Trainlog JSON v1 format remains a separate compatibility boundary
|
|
||||||
and is not version-coupled to SQLite schema v2.
|
|
||||||
|
|
||||||
## 10. Validation
|
## 10. Validation
|
||||||
|
|
||||||
Normal build:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CC=clang meson setup build
|
|
||||||
meson compile -C build
|
meson compile -C build
|
||||||
meson test -C build --print-errorlogs
|
meson test -C build --print-errorlogs
|
||||||
```
|
```
|
||||||
|
|
||||||
Sanitizer build:
|
Migration-specific regression coverage includes:
|
||||||
|
|
||||||
```bash
|
|
||||||
CC=clang meson setup build-asan \
|
|
||||||
-Db_sanitize=address,undefined \
|
|
||||||
-Db_lundef=false
|
|
||||||
|
|
||||||
meson compile -C build-asan
|
|
||||||
meson test -C build-asan --print-errorlogs
|
|
||||||
```
|
|
||||||
|
|
||||||
Repository-level format validators remain mandatory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python tools/validate_json.py
|
|
||||||
python tools/validate_import_contract.py
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- TRAINLOG_EXERCISE_DATA_MODEL_V1 -->
|
|
||||||
## Exercise recording metadata — schema v3 direction
|
|
||||||
|
|
||||||
The next SQLite migration adds:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
recording_mode = sets | continuous
|
schema_v5_migration
|
||||||
data_fields = bounded bit mask
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Existing `tracking_mode = reps | duration` remains stable.
|
The current normal suite contains 19 tests.
|
||||||
|
|
||||||
Every `session_exercises` row snapshots this metadata so later catalog changes
|
|
||||||
do not reinterpret historical sessions.
|
|
||||||
|
|
||||||
Migration v2 -> v3 defaults all existing rows to `sets` with `data_fields = 0`.
|
|
||||||
No name-based migration is allowed.
|
|
||||||
|
|
||||||
Continuous actual activity data is stored separately from `performed_sets`;
|
|
||||||
Trainlog will not manufacture a fake one-set workout.
|
|
||||||
<!-- TRAINLOG_EXERCISE_DATA_MODEL_V1 _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SCHEMA_V4_CONTINUOUS -->
|
|
||||||
## Schema v4 — continuous exercise persistence
|
|
||||||
|
|
||||||
Current database schema:
|
|
||||||
|
|
||||||
```text
|
|
||||||
TRAINLOG_DATABASE_SCHEMA_VERSION = 4
|
|
||||||
PRAGMA user_version = 4
|
|
||||||
```
|
|
||||||
|
|
||||||
Relevant profile metadata is stored in both:
|
|
||||||
|
|
||||||
```text
|
|
||||||
exercises
|
|
||||||
session_exercises
|
|
||||||
```
|
|
||||||
|
|
||||||
`session_exercises` snapshots:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recording_mode
|
|
||||||
data_fields
|
|
||||||
```
|
|
||||||
|
|
||||||
Continuous actual activity data is stored one-to-one in:
|
|
||||||
|
|
||||||
```text
|
|
||||||
continuous_activity
|
|
||||||
```
|
|
||||||
|
|
||||||
Columns:
|
|
||||||
|
|
||||||
```text
|
|
||||||
session_exercise_row_id
|
|
||||||
duration_seconds
|
|
||||||
speed_kmh nullable
|
|
||||||
distance_km nullable
|
|
||||||
```
|
|
||||||
|
|
||||||
For a valid continuous activity:
|
|
||||||
|
|
||||||
```text
|
|
||||||
target_sets NULL
|
|
||||||
target_reps NULL
|
|
||||||
target_duration_seconds NULL
|
|
||||||
load_mode none
|
|
||||||
rest_seconds 0
|
|
||||||
target_weight_kg NULL
|
|
||||||
performed_sets none
|
|
||||||
```
|
|
||||||
|
|
||||||
Schema migration never infers profile information from exercise names.
|
|
||||||
<!-- TRAINLOG_SCHEMA_V4_CONTINUOUS _END -->
|
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
```text
|
```text
|
||||||
EXERCISE_DATA_MODEL_V1=FROZEN_FOR_IMPLEMENTATION
|
EXERCISE_DATA_MODEL_V1=PASS
|
||||||
DATABASE_SCHEMA_V3=NEXT
|
PROFILE_AWARE_DESKTOP=PASS
|
||||||
TUI_PROFILE_AWARE_ENTRY=AFTER_SCHEMA_V3
|
PROFILE_AWARE_ANDROID=PASS
|
||||||
ANDROID_PROFILE_AWARE_ENTRY=AFTER_TUI
|
CONTINUOUS_ACTIVITY=PASS
|
||||||
|
VARIABLE_REPETITION_SETS=PASS
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
SESSION_EXCHANGE_V2=DESIGN_REQUIRED_LATER
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Trainlog must not use one universal exercise form.
|
## 1. Metadata axes
|
||||||
|
|
||||||
An exercise is defined by three independent pieces of metadata:
|
An exercise is described by independent metadata:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
recording_mode = SETS | CONTINUOUS
|
recording_mode = SETS | CONTINUOUS
|
||||||
|
|
@ -21,196 +21,13 @@ tracking_mode = REPS | DURATION
|
||||||
data_fields = supplemental field bit mask
|
data_fields = supplemental field bit mask
|
||||||
```
|
```
|
||||||
|
|
||||||
Initial valid combinations:
|
Known supplemental fields:
|
||||||
|
|
||||||
```text
|
|
||||||
SETS + REPS
|
|
||||||
SETS + DURATION
|
|
||||||
CONTINUOUS + DURATION
|
|
||||||
```
|
|
||||||
|
|
||||||
`CONTINUOUS + REPS` is invalid in model v1.
|
|
||||||
|
|
||||||
Initial supplemental fields:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SPEED_KMH
|
SPEED_KMH
|
||||||
DISTANCE_KM
|
DISTANCE_KM
|
||||||
```
|
```
|
||||||
|
|
||||||
Unknown field bits are invalid.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Presse à cuisses
|
|
||||||
SETS + REPS
|
|
||||||
|
|
||||||
Gainage
|
|
||||||
SETS + DURATION
|
|
||||||
|
|
||||||
Marche
|
|
||||||
CONTINUOUS + DURATION
|
|
||||||
SPEED_KMH
|
|
||||||
|
|
||||||
Course
|
|
||||||
CONTINUOUS + DURATION
|
|
||||||
SPEED_KMH
|
|
||||||
|
|
||||||
Vélo
|
|
||||||
CONTINUOUS + DURATION
|
|
||||||
SPEED_KMH | DISTANCE_KM
|
|
||||||
|
|
||||||
Rameur
|
|
||||||
CONTINUOUS + DURATION
|
|
||||||
DISTANCE_KM
|
|
||||||
```
|
|
||||||
|
|
||||||
Load semantics remain separate and session-specific:
|
|
||||||
|
|
||||||
```text
|
|
||||||
none
|
|
||||||
external
|
|
||||||
assistance
|
|
||||||
```
|
|
||||||
|
|
||||||
A continuous exercise does not ask for:
|
|
||||||
|
|
||||||
```text
|
|
||||||
number of sets
|
|
||||||
repetitions
|
|
||||||
per-set rest
|
|
||||||
```
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Marche
|
|
||||||
|
|
||||||
Durée 45 min
|
|
||||||
Vitesse 5.8 km/h
|
|
||||||
```
|
|
||||||
|
|
||||||
Creating/editing an exercise asks for:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Name
|
|
||||||
Organization: Sets | Continuous
|
|
||||||
Primary metric: Repetitions | Duration
|
|
||||||
Supplemental fields: Speed | Distance
|
|
||||||
```
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
|
|
||||||
- continuous forces duration in model v1;
|
|
||||||
- sets accepts reps or duration;
|
|
||||||
- UI fields are driven by metadata, never exercise-name heuristics;
|
|
||||||
- changing catalog metadata affects future entry only.
|
|
||||||
|
|
||||||
Historical stability:
|
|
||||||
|
|
||||||
Every session exercise stores a snapshot of:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recording_mode
|
|
||||||
tracking_mode
|
|
||||||
data_fields
|
|
||||||
```
|
|
||||||
|
|
||||||
So changing `Marche` from an old set-based duration exercise to continuous
|
|
||||||
duration + speed does not reinterpret old sessions.
|
|
||||||
|
|
||||||
## SQLite schema v3 direction
|
|
||||||
|
|
||||||
Schema v3 adds to `exercises`:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recording_mode
|
|
||||||
data_fields
|
|
||||||
```
|
|
||||||
|
|
||||||
and snapshots the same values in `session_exercises`.
|
|
||||||
|
|
||||||
Migration v2 -> v3 is conservative:
|
|
||||||
|
|
||||||
```text
|
|
||||||
all existing exercises -> SETS
|
|
||||||
all existing session exercises -> SETS
|
|
||||||
data_fields -> 0
|
|
||||||
```
|
|
||||||
|
|
||||||
No migration guesses by exercise name.
|
|
||||||
|
|
||||||
Continuous actual activity data gets its own one-to-one record:
|
|
||||||
|
|
||||||
```text
|
|
||||||
duration_seconds
|
|
||||||
speed_kmh nullable
|
|
||||||
distance_km nullable
|
|
||||||
```
|
|
||||||
|
|
||||||
Continuous activities have no `performed_sets` rows and no fake one-set
|
|
||||||
representation.
|
|
||||||
|
|
||||||
## Frozen JSON v1
|
|
||||||
|
|
||||||
Trainlog JSON session v1 remains frozen.
|
|
||||||
|
|
||||||
It represents the existing set-based exchange model.
|
|
||||||
|
|
||||||
Continuous data that cannot be represented in v1 must not be:
|
|
||||||
|
|
||||||
- hidden in notes;
|
|
||||||
- converted into a fake set;
|
|
||||||
- silently discarded.
|
|
||||||
|
|
||||||
A future explicit session exchange v2 will carry profile-aware exercise data
|
|
||||||
while v1 import remains supported.
|
|
||||||
|
|
||||||
## Android/TUI parity
|
|
||||||
|
|
||||||
Both interfaces consume identical exercise metadata.
|
|
||||||
|
|
||||||
The future PC -> Android catalog snapshot must contain:
|
|
||||||
|
|
||||||
```text
|
|
||||||
exercise_id
|
|
||||||
name
|
|
||||||
recording_mode
|
|
||||||
tracking_mode
|
|
||||||
data_fields
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation order
|
|
||||||
|
|
||||||
```text
|
|
||||||
1. SQLite schema v3 + migration tests
|
|
||||||
2. C model/API additions
|
|
||||||
3. catalog create/edit support
|
|
||||||
4. TUI profile-aware session entry
|
|
||||||
5. manually convert Marche to CONTINUOUS + SPEED_KMH
|
|
||||||
6. profile-aware detail/history
|
|
||||||
7. Android uses the same model
|
|
||||||
8. design session exchange v2
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- TRAINLOG_PROFILE_AWARE_IMPLEMENTED -->
|
|
||||||
## Implemented checkpoint
|
|
||||||
|
|
||||||
The profile-aware exercise model is now implemented in the C model, SQLite
|
|
||||||
persistence and TUI.
|
|
||||||
|
|
||||||
Current canonical rules:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recording_mode = SETS | CONTINUOUS
|
|
||||||
tracking_mode = REPS | DURATION
|
|
||||||
|
|
||||||
known data_fields:
|
|
||||||
SPEED_KMH
|
|
||||||
DISTANCE_KM
|
|
||||||
```
|
|
||||||
|
|
||||||
Valid model-v1 combinations:
|
Valid model-v1 combinations:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
@ -219,25 +36,166 @@ SETS + DURATION
|
||||||
CONTINUOUS + DURATION
|
CONTINUOUS + DURATION
|
||||||
```
|
```
|
||||||
|
|
||||||
Continuous exercise actual data is persisted as one `continuous_activity`
|
Invalid:
|
||||||
record rather than a performed-set list.
|
|
||||||
|
|
||||||
A continuous activity never manufactures a one-set representation.
|
```text
|
||||||
|
CONTINUOUS + REPS
|
||||||
|
```
|
||||||
|
|
||||||
The TUI asks continuous duration in **minutes**, converts to seconds, and stores
|
UI behavior must never be inferred from an exercise display name.
|
||||||
seconds internally.
|
|
||||||
|
## 2. Examples
|
||||||
|
|
||||||
|
```text
|
||||||
|
Presse à cuisses
|
||||||
|
SETS + REPS
|
||||||
|
|
||||||
|
Gainage
|
||||||
|
SETS + DURATION
|
||||||
|
|
||||||
|
Marche
|
||||||
|
CONTINUOUS + DURATION + SPEED_KMH
|
||||||
|
|
||||||
|
Course
|
||||||
|
CONTINUOUS + DURATION + SPEED_KMH
|
||||||
|
|
||||||
|
Vélo
|
||||||
|
CONTINUOUS + DURATION + SPEED_KMH + DISTANCE_KM
|
||||||
|
|
||||||
|
Rameur
|
||||||
|
CONTINUOUS + DURATION + DISTANCE_KM
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Load semantics
|
||||||
|
|
||||||
|
Load mode is session-specific:
|
||||||
|
|
||||||
|
```text
|
||||||
|
none
|
||||||
|
external
|
||||||
|
assistance
|
||||||
|
```
|
||||||
|
|
||||||
|
`external` is added resistance.
|
||||||
|
|
||||||
|
`assistance` is help. Lower assistance represents less help and is therefore
|
||||||
|
better when comparing otherwise equivalent performance.
|
||||||
|
|
||||||
|
Machine-displayed kilograms are stored faithfully without claiming mechanical
|
||||||
|
equivalence across different machines.
|
||||||
|
|
||||||
|
## 4. Set-based work
|
||||||
|
|
||||||
|
`SETS + REPS` stores one performed-set row per actual set.
|
||||||
|
|
||||||
|
Actual repetitions can differ across sets.
|
||||||
|
|
||||||
|
Accepted compact repetition input includes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
5x10
|
||||||
|
4,5,6,7,8,9,10,9,8,7,6,5,4
|
||||||
|
4..10..4
|
||||||
|
```
|
||||||
|
|
||||||
|
The pyramid shorthand:
|
||||||
|
|
||||||
|
```text
|
||||||
|
4..10..4
|
||||||
|
```
|
||||||
|
|
||||||
|
expands to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
4,5,6,7,8,9,10,9,8,7,6,5,4
|
||||||
|
```
|
||||||
|
|
||||||
|
Each performed row is the source of truth for actual work.
|
||||||
|
|
||||||
|
`SETS + DURATION` likewise stores one actual duration per performed set.
|
||||||
|
|
||||||
|
## 5. Planned versus actual
|
||||||
|
|
||||||
|
Desktop-created set sessions may carry explicit planned targets.
|
||||||
|
|
||||||
|
Android mobile snapshots can represent actual-only heterogeneous work.
|
||||||
|
|
||||||
|
Desktop schema v5 therefore allows an imported set session to have no synthetic
|
||||||
|
uniform target:
|
||||||
|
|
||||||
|
```text
|
||||||
|
target_sets = NULL
|
||||||
|
target_reps = NULL
|
||||||
|
target_duration_seconds = NULL
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not derive a fake target from heterogeneous actual sets.
|
||||||
|
|
||||||
|
## 6. Continuous work
|
||||||
|
|
||||||
|
Continuous activity does not ask for:
|
||||||
|
|
||||||
|
```text
|
||||||
|
set count
|
||||||
|
repetitions
|
||||||
|
per-set rest
|
||||||
|
load
|
||||||
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Marche
|
Marche
|
||||||
CONTINUOUS + DURATION + SPEED_KMH
|
|
||||||
|
|
||||||
TUI entry:
|
Durée 45 min
|
||||||
Durée (minutes)
|
Vitesse 5.8 km/h
|
||||||
Vitesse km/h
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Historical session rows snapshot recording metadata and are not reinterpreted
|
Actual persistence uses one `continuous_activity` record containing duration
|
||||||
when catalog metadata later changes.
|
and configured supplemental values.
|
||||||
<!-- TRAINLOG_PROFILE_AWARE_IMPLEMENTED _END -->
|
|
||||||
|
No fake performed set is created.
|
||||||
|
|
||||||
|
## 7. Historical interpretation
|
||||||
|
|
||||||
|
Desktop `session_exercises` snapshot:
|
||||||
|
|
||||||
|
```text
|
||||||
|
recording_mode
|
||||||
|
data_fields
|
||||||
|
```
|
||||||
|
|
||||||
|
This prevents later catalog changes to those fields from rewriting the meaning
|
||||||
|
of historical rows.
|
||||||
|
|
||||||
|
Current desktop tracking mode remains tied to the referenced exercise identity.
|
||||||
|
Any future change to that historical contract requires an explicit schema
|
||||||
|
decision rather than an implicit name-based migration.
|
||||||
|
|
||||||
|
## 8. Android/TUI parity
|
||||||
|
|
||||||
|
Both interfaces use the same metadata axes.
|
||||||
|
|
||||||
|
The PC -> Android catalog synchronization artifact carries:
|
||||||
|
|
||||||
|
```text
|
||||||
|
exercise_id
|
||||||
|
name
|
||||||
|
recording_mode
|
||||||
|
tracking_mode
|
||||||
|
data_fields
|
||||||
|
```
|
||||||
|
|
||||||
|
Creating an exercise inline on Android or desktop follows the same model rules.
|
||||||
|
|
||||||
|
## 9. Exchange boundaries
|
||||||
|
|
||||||
|
Frozen Trainlog session JSON v1 remains unchanged.
|
||||||
|
|
||||||
|
Synchronization data that exceeds frozen v1 uses separate versioned artifacts.
|
||||||
|
|
||||||
|
Continuous activity must never be:
|
||||||
|
|
||||||
|
- hidden in notes;
|
||||||
|
- converted into a fake set;
|
||||||
|
- silently discarded.
|
||||||
|
|
|
||||||
716
docs/roadmap.md
716
docs/roadmap.md
|
|
@ -1,546 +1,64 @@
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
|
This file describes the current project state and the next implementation
|
||||||
|
cursor. Historical checkpoint detail belongs in Git history and `docs/reviews`.
|
||||||
|
|
||||||
## Gate 0 — Project contract
|
## Gate 0 — Project contract
|
||||||
|
|
||||||
Status: PASS
|
```text
|
||||||
|
GATE_0=PASS
|
||||||
|
```
|
||||||
|
|
||||||
## Gate 1 — Exchange format v1 freeze
|
Development rules, ownership boundaries, identity rules, validation discipline,
|
||||||
|
and Git workflow are established.
|
||||||
|
|
||||||
Status: PASS
|
## Gate 1 — Trainlog exchange format v1
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
GATE_1=PASS
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gate 2 — Persistence + usable TUI
|
The published Trainlog JSON v1 compatibility boundary remains unchanged.
|
||||||
|
|
||||||
Status: IN PROGRESS
|
## Gate 2 — Persistence and usable desktop TUI
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
GATE_2=PASS
|
||||||
|
DESKTOP_SCHEMA_V5=PASS
|
||||||
FIRST_USABLE_TUI=PASS
|
FIRST_USABLE_TUI=PASS
|
||||||
TUI_V0_2_POLISH=IMPLEMENTED
|
|
||||||
GATE_2=IN_PROGRESS
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Current TUI capabilities:
|
Completed baseline includes:
|
||||||
|
|
||||||
- direct workout entry;
|
- SQLite persistence and explicit migrations;
|
||||||
|
- direct session entry;
|
||||||
|
- session history/detail/editing;
|
||||||
- exercise catalog;
|
- exercise catalog;
|
||||||
- body tracking;
|
- profile-aware set and continuous work;
|
||||||
- weight graph;
|
- body history/editing/visualization;
|
||||||
- colored dashboard;
|
- exercise performance history;
|
||||||
- arrow/F-key navigation;
|
- strict validation and rollback behavior.
|
||||||
- navigable history;
|
|
||||||
- Unicode anti-duplicate exercise names.
|
|
||||||
|
|
||||||
Current remaining Gate 2 direction:
|
## Android local client
|
||||||
|
|
||||||
1. detect Android over USB/ADB;
|
|
||||||
2. build the minimal Android recorder;
|
|
||||||
3. transfer/export one frozen Trainlog JSON v1 document over USB;
|
|
||||||
4. import it transactionally into the canonical SQLite store;
|
|
||||||
5. validate the complete Android -> JSON -> TUI -> SQLite path.
|
|
||||||
|
|
||||||
Measured-max semantics remain a later independent analytics contract.
|
|
||||||
|
|
||||||
<!-- TRAINLOG_TUI_V02_ROADMAP -->
|
|
||||||
## TUI v0.2 checkpoint
|
|
||||||
|
|
||||||
Current state:
|
|
||||||
|
|
||||||
```text
|
|
||||||
FIRST_USABLE_TUI=PASS
|
|
||||||
TUI_V0_2_POLISH=IMPLEMENTED
|
|
||||||
TUI_SESSION_DETAILS=IMPLEMENTED
|
|
||||||
TUI_DURATION_HUMAN_INPUT=IMPLEMENTED
|
|
||||||
TUI_BODY_METRIC_GRAPHS=IMPLEMENTED
|
|
||||||
GATE_2=IN_PROGRESS
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
|
||||||
```
|
|
||||||
|
|
||||||
Completed before this checkpoint:
|
|
||||||
|
|
||||||
- C17/Meson persistence core;
|
|
||||||
- SQLite schema v1 foundation;
|
|
||||||
- UUIDv4 generation;
|
|
||||||
- Unicode catalog normalization;
|
|
||||||
- direct workout recording;
|
|
||||||
- direct body observation recording;
|
|
||||||
- colored ncursesw dashboard;
|
|
||||||
- keyboard navigation;
|
|
||||||
- body-weight graph;
|
|
||||||
- flat-series graph rendering;
|
|
||||||
- navigable workout history;
|
|
||||||
- full read-only session detail.
|
|
||||||
|
|
||||||
Next implementation slice:
|
|
||||||
|
|
||||||
1. shared duration parser accepting seconds and minute-oriented syntax;
|
|
||||||
2. shared human duration formatter;
|
|
||||||
3. generic body-metric history query;
|
|
||||||
4. F4 metric selector;
|
|
||||||
5. graphs for weight and every body measurement;
|
|
||||||
6. left/right asymmetry presentation.
|
|
||||||
|
|
||||||
No incompatible change to frozen Trainlog JSON v1 is required.
|
|
||||||
<!-- TRAINLOG_TUI_V02_ROADMAP _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_ROADMAP -->
|
|
||||||
## Next TUI visualization slice
|
|
||||||
|
|
||||||
Canonical state after the current checkpoint:
|
|
||||||
|
|
||||||
```text
|
|
||||||
FIRST_USABLE_TUI=PASS
|
|
||||||
TUI_V0_2_POLISH=IMPLEMENTED
|
|
||||||
TUI_SESSION_DETAILS=IMPLEMENTED
|
|
||||||
TUI_DURATION_HUMAN_INPUT=IMPLEMENTED
|
|
||||||
TUI_BODY_METRIC_GRAPHS=IMPLEMENTED
|
|
||||||
TUI_GLOBAL_BODY_OVERLAY=IMPLEMENTED
|
|
||||||
DASHBOARD_GRAPH_ONLY=IMPLEMENTED
|
|
||||||
GATE_2=IN_PROGRESS
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
|
||||||
```
|
|
||||||
|
|
||||||
Next deliverables:
|
|
||||||
1. normalized global body graph in `F4`;
|
|
||||||
2. color + symbol identity for every overlaid metric;
|
|
||||||
3. global percent-change summary;
|
|
||||||
4. richer home weight graph;
|
|
||||||
5. previous-measurement delta on dashboard;
|
|
||||||
6. dashboard min/max weight;
|
|
||||||
7. latest waist summary when available;
|
|
||||||
8. compact asymmetry warning when relevant.
|
|
||||||
|
|
||||||
No database schema migration is expected.
|
|
||||||
No Trainlog JSON v1 change is expected.
|
|
||||||
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_ROADMAP _END -->
|
|
||||||
|
|
||||||
```text
|
|
||||||
DASHBOARD_12_MONTHS=IMPLEMENTED
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
TUI_EXERCISE_PERFORMANCE=IMPLEMENTED
|
|
||||||
TUI_SESSION_EDIT=IMPLEMENTED
|
|
||||||
TUI_BODY_OBSERVATION_EDIT=IMPLEMENTED
|
|
||||||
ANDROID_USB_DETECTION=NEXT
|
|
||||||
MEASURED_MAX_TRACKING=LATER
|
|
||||||
```
|
|
||||||
|
|
||||||
## Session type / measured max foundation
|
|
||||||
|
|
||||||
```text
|
|
||||||
DATABASE_SCHEMA_V2=IMPLEMENTED
|
|
||||||
SESSION_TYPE_PERSISTENCE=IMPLEMENTED
|
|
||||||
SESSION_TYPE_TUI=IMPLEMENTED
|
|
||||||
TUI_SESSION_EDIT=IMPLEMENTED
|
|
||||||
TUI_BODY_OBSERVATION_EDIT=IMPLEMENTED
|
|
||||||
TUI_PRIMARY_NAVIGATION=IMPLEMENTED
|
|
||||||
TUI_SECONDARY_VIEW_POLISH=IMPLEMENTED
|
|
||||||
ANDROID_USB_DETECTION=NEXT
|
|
||||||
MEASURED_MAX_TRACKING=LATER
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
|
||||||
```
|
|
||||||
|
|
||||||
SQLite schema v2 adds `sessions.session_type` with `training` and `max_test`.
|
|
||||||
Existing v1 rows migrate to `training`. No existing session is retroactively
|
|
||||||
classified as a max test.
|
|
||||||
|
|
||||||
## Editable session data
|
|
||||||
|
|
||||||
```text
|
|
||||||
SESSION_EDIT_PERSISTENCE=IMPLEMENTED
|
|
||||||
SESSION_EDIT_TUI=IMPLEMENTED
|
|
||||||
BODY_OBSERVATION_EDIT=IMPLEMENTED
|
|
||||||
USB_PHONE_DETECTION=NEXT
|
|
||||||
```
|
|
||||||
|
|
||||||
Recorded session exercise/set correction uses atomic replacement of child rows
|
|
||||||
while preserving the parent session row and linked body observations.
|
|
||||||
|
|
||||||
The TUI exposes correction both while reviewing an in-progress draft and after
|
|
||||||
persistence. Escape cancels without committing partial edits.
|
|
||||||
|
|
||||||
## Body observation record workflow
|
|
||||||
|
|
||||||
```text
|
|
||||||
BODY_OBSERVATION_HISTORY_UI=IMPLEMENTED
|
|
||||||
BODY_OBSERVATION_EDIT=IMPLEMENTED
|
|
||||||
BODY_OBSERVATION_SCROLLBAR=IMPLEMENTED
|
|
||||||
USB_PHONE_DETECTION=NEXT
|
|
||||||
```
|
|
||||||
|
|
||||||
`F4 Corps` now uses newest-first observation records with detail/edit views
|
|
||||||
instead of making individual metrics the primary navigation model.
|
|
||||||
|
|
||||||
<!-- TRAINLOG_EDITABILITY_ROADMAP -->
|
|
||||||
## Editability/navigation checkpoint
|
|
||||||
|
|
||||||
Completed:
|
|
||||||
|
|
||||||
- local schema v2 migration with `training` / `max_test`;
|
|
||||||
- session-type selection and persistence;
|
|
||||||
- persisted session editing with stable parent identity;
|
|
||||||
- body-observation history, detail, and editing;
|
|
||||||
- Escape-safe prompt cancellation;
|
|
||||||
- framed ASCII-banner primary and secondary views;
|
|
||||||
- top `Accueil / Séance / Historique / Exercices / Corps` navigation;
|
|
||||||
- Tab focus with yellow border-only focus indication;
|
|
||||||
- direct exercise creation while building a session;
|
|
||||||
- 12-month dashboard axis kept inside its frame.
|
|
||||||
|
|
||||||
Next implementation cursor:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_USB_DETECTION=NEXT
|
|
||||||
ANDROID_MINIMAL_RECORDER=AFTER
|
|
||||||
JSON_V1_USB_IMPORT_EXPORT=AFTER
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_EDITABILITY_ROADMAP _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_MTP_TRANSPORT_ROADMAP -->
|
|
||||||
## Direct MTP transport checkpoint
|
|
||||||
|
|
||||||
```text
|
|
||||||
USB_MTP_DETECTION=PASS
|
|
||||||
MTP_STORAGE_ACCESS=PASS
|
|
||||||
MTP_ROOT_FOLDER_ACCESS=PASS
|
|
||||||
MTP_WRITE=PASS
|
|
||||||
MTP_LIST_FOLDER=PASS
|
|
||||||
MTP_READ=PASS
|
|
||||||
MTP_ROUNDTRIP=PASS
|
|
||||||
MTP_TRANSPORT_FOUNDATION=PASS
|
|
||||||
JSON_V1_MTP_TRANSFER=NEXT
|
|
||||||
ANDROID_MINIMAL_RECORDER=AFTER
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
|
||||||
```
|
|
||||||
|
|
||||||
The Linux side now detects one physical MTP phone without counting USB
|
|
||||||
interface children, opens the exact device with libmtp, accesses internal
|
|
||||||
storage, and performs a verified write/list/read roundtrip without mounting the
|
|
||||||
phone.
|
|
||||||
|
|
||||||
Next implementation slice:
|
|
||||||
|
|
||||||
```text
|
|
||||||
1. freeze the MTP exchange directory/file convention
|
|
||||||
2. roundtrip a real examples/session-v1.json
|
|
||||||
3. validate/import the downloaded JSON
|
|
||||||
4. begin the minimal Android recorder
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_MTP_TRANSPORT_ROADMAP _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SYNC_PAGE_ROADMAP -->
|
|
||||||
## Sync TUI integration
|
|
||||||
|
|
||||||
```text
|
|
||||||
MTP_TRANSPORT_FOUNDATION=PASS
|
|
||||||
TUI_SYNC_PAGE=IMPLEMENTED
|
|
||||||
TUI_SYNC_DEVICE_STATUS=IMPLEMENTED
|
|
||||||
TUI_SYNC_REMOTE_JSON_CANDIDATES=IMPLEMENTED
|
|
||||||
TUI_SYNC_LOCAL_CATALOG_COUNT=IMPLEMENTED
|
|
||||||
JSON_V1_MTP_CLASSIFICATION_IMPORT=NEXT
|
|
||||||
CATALOG_SNAPSHOT_SYNC=AFTER
|
|
||||||
ANDROID_MINIMAL_RECORDER=AFTER
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
|
||||||
```
|
|
||||||
|
|
||||||
Sync direction:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Android -> PC
|
|
||||||
sessions
|
|
||||||
new exercises embedded in sessions -> automatic reconciliation
|
|
||||||
body data embedded in sessions -> automatic import
|
|
||||||
|
|
||||||
PC -> Android
|
|
||||||
canonical exercise catalog snapshot
|
|
||||||
```
|
|
||||||
|
|
||||||
Standalone body-observation exchange, if required by the Android recorder, gets
|
|
||||||
its own explicit versioned contract rather than changing session JSON v1.
|
|
||||||
<!-- TRAINLOG_SYNC_PAGE_ROADMAP _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SYNC_FINAL_ROADMAP -->
|
|
||||||
## Sync UI checkpoint complete
|
|
||||||
|
|
||||||
```text
|
|
||||||
MTP_TRANSPORT_FOUNDATION=PASS
|
|
||||||
TUI_SYNC_PAGE=IMPLEMENTED
|
|
||||||
TUI_SYNC_DEVICE_STATUS=IMPLEMENTED
|
|
||||||
TUI_SYNC_FOCUS_NAVIGATION=IMPLEMENTED
|
|
||||||
TUI_SYNC_REMOTE_JSON_CANDIDATES=IMPLEMENTED
|
|
||||||
TUI_SYNC_LOCAL_CATALOG_COUNT=IMPLEMENTED
|
|
||||||
ANDROID_APP_SCAFFOLD=NEXT
|
|
||||||
ANDROID_FAKE_DATA_FLOW=AFTER
|
|
||||||
JSON_V1_ANDROID_EXPORT=AFTER
|
|
||||||
CATALOG_SNAPSHOT_SYNC=AFTER
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
|
||||||
```
|
|
||||||
|
|
||||||
Development now moves to the Android client.
|
|
||||||
|
|
||||||
Fictitious sessions, exercises, and body observations are used during Android
|
|
||||||
development and synchronization testing. The development database will be
|
|
||||||
purged before normal production use begins.
|
|
||||||
<!-- TRAINLOG_SYNC_FINAL_ROADMAP _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_EXERCISE_DATA_MODEL_ROADMAP -->
|
|
||||||
## Exercise data-model checkpoint
|
|
||||||
|
|
||||||
```text
|
|
||||||
EXERCISE_DATA_MODEL_V1=FROZEN_FOR_IMPLEMENTATION
|
|
||||||
DATABASE_SCHEMA_V3=NEXT
|
|
||||||
PROFILE_AWARE_C_MODEL=AFTER_SCHEMA
|
|
||||||
PROFILE_AWARE_TUI=AFTER
|
|
||||||
ANDROID_PROFILE_AWARE_UI=AFTER
|
|
||||||
SESSION_EXCHANGE_V2=DESIGN_LATER
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
|
||||||
```
|
|
||||||
|
|
||||||
The model separates:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recording organization: SETS | CONTINUOUS
|
|
||||||
primary metric: REPS | DURATION
|
|
||||||
supplemental fields: SPEED_KMH | DISTANCE_KM
|
|
||||||
```
|
|
||||||
|
|
||||||
Existing schema-v2 data migrates conservatively to `SETS`.
|
|
||||||
<!-- TRAINLOG_EXERCISE_DATA_MODEL_ROADMAP _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_PROFILE_AWARE_ROADMAP_FINAL -->
|
|
||||||
## Current implementation cursor
|
|
||||||
|
|
||||||
```text
|
|
||||||
MTP_TRANSPORT_FOUNDATION=PASS
|
|
||||||
TUI_SYNC_PAGE=PASS
|
|
||||||
|
|
||||||
EXERCISE_DATA_MODEL_V1=PASS
|
|
||||||
DATABASE_SCHEMA_V4=PASS
|
|
||||||
PROFILED_CATALOG_API=PASS
|
|
||||||
PROFILE_AWARE_EXERCISE_CREATION=PASS
|
|
||||||
CONTINUOUS_ACTIVITY_PERSISTENCE=PASS
|
|
||||||
CONTINUOUS_ACTIVITY_DETAIL_DISPLAY=PASS
|
|
||||||
CONTINUOUS_DURATION_MINUTES_UI=PASS
|
|
||||||
|
|
||||||
ANDROID_APP=NEXT
|
|
||||||
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN
|
|
||||||
PROFILE_AWARE_SESSION_EXCHANGE=DESIGN_LATER
|
|
||||||
```
|
|
||||||
|
|
||||||
Next Android slice:
|
|
||||||
|
|
||||||
```text
|
|
||||||
1. Android project scaffold
|
|
||||||
2. shared Trainlog visual identity
|
|
||||||
3. launcher icon = themed T
|
|
||||||
4. home/navigation
|
|
||||||
5. session recording
|
|
||||||
6. inline exercise creation
|
|
||||||
7. standalone exercise creation
|
|
||||||
8. standalone body measurement recording
|
|
||||||
9. fictitious local records
|
|
||||||
10. only then connect exchange/sync
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not revert continuous activities to performed sets.
|
|
||||||
Do not modify JSON v1 to accommodate continuous metrics.
|
|
||||||
<!-- TRAINLOG_PROFILE_AWARE_ROADMAP_FINAL _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_LOCAL_CHECKPOINT -->
|
|
||||||
## Android local checkpoint
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_PROJECT=PASS
|
|
||||||
ANDROID_THEME=PASS
|
|
||||||
ANDROID_EXERCISE_CATALOG=PASS
|
|
||||||
ANDROID_SESSION_RECORDING=PASS
|
|
||||||
ANDROID_SESSION_HISTORY=PASS
|
|
||||||
ANDROID_BODY_RECORDING=PASS
|
|
||||||
|
|
||||||
ANDROID_MTP_SYNC=NEXT
|
|
||||||
```
|
|
||||||
|
|
||||||
The next implementation cursor is synchronization between the Android client
|
|
||||||
and the desktop TUI over the existing direct-MTP transport architecture.
|
|
||||||
|
|
||||||
Constraints remain:
|
|
||||||
|
|
||||||
```text
|
|
||||||
no GVFS/FUSE dependency
|
|
||||||
no SQLite-file synchronization
|
|
||||||
TRAINLOG_FORMAT_V1 remains frozen
|
|
||||||
profile-aware data must not be forced into v1
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_ANDROID_LOCAL_CHECKPOINT _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_MOBILE_EXPORT_V1 -->
|
|
||||||
## MTP mobile export v1
|
|
||||||
|
|
||||||
Android now prepares a versioned full mobile snapshot at:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Download/Trainlog/trainlog-mobile-export-v1.json
|
|
||||||
```
|
|
||||||
|
|
||||||
The file contains:
|
|
||||||
|
|
||||||
```text
|
|
||||||
exercise profiles
|
|
||||||
sessions
|
|
||||||
body observations
|
|
||||||
```
|
|
||||||
|
|
||||||
It is explicitly separate from frozen `TRAINLOG_FORMAT_V1`.
|
|
||||||
|
|
||||||
Desktop direct-MTP validation is available through:
|
|
||||||
|
|
||||||
```text
|
|
||||||
./build/tui/trainlog-mtp-mobile-export-probe
|
|
||||||
```
|
|
||||||
|
|
||||||
The probe traverses:
|
|
||||||
|
|
||||||
```text
|
|
||||||
internal storage
|
|
||||||
→ Download
|
|
||||||
→ Trainlog
|
|
||||||
→ trainlog-mobile-export-v1.json
|
|
||||||
```
|
|
||||||
|
|
||||||
and downloads it directly through libmtp without a mount.
|
|
||||||
|
|
||||||
Next after hardware PASS:
|
|
||||||
|
|
||||||
```text
|
|
||||||
DESKTOP_MOBILE_EXPORT_IMPORT=NEXT
|
|
||||||
PC_TO_ANDROID_CATALOG=AFTER
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_MOBILE_EXPORT_V1 _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_DESKTOP_MOBILE_IMPORT_CURSOR -->
|
|
||||||
## Mobile import cursor
|
|
||||||
|
|
||||||
```text
|
|
||||||
MOBILE_EXPORT_MTP=PASS
|
|
||||||
DESKTOP_MOBILE_IMPORT_V1=IMPLEMENTED
|
|
||||||
TUI_SYNC_ACTION=NEXT
|
|
||||||
PC_TO_ANDROID_CATALOG=AFTER
|
|
||||||
```
|
|
||||||
|
|
||||||
The CLI importer is the reference import engine for the next TUI Sync action.
|
|
||||||
<!-- TRAINLOG_DESKTOP_MOBILE_IMPORT_CURSOR _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_TUI_MOBILE_SYNC_CURSOR -->
|
|
||||||
## Sync implementation cursor
|
|
||||||
|
|
||||||
```text
|
|
||||||
MOBILE_EXPORT_MTP=PASS
|
|
||||||
DESKTOP_MOBILE_IMPORT_V1=PASS
|
|
||||||
TUI_ANDROID_TO_PC_SYNC_ACTION=IMPLEMENTED
|
|
||||||
TUI_ANDROID_TO_PC_SYNC_HARDWARE_VALIDATION=NEXT
|
|
||||||
PC_TO_ANDROID_CATALOG=AFTER
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_TUI_MOBILE_SYNC_CURSOR _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_BIDIRECTIONAL_SYNC_CURSOR -->
|
|
||||||
## Bidirectional sync cursor
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_TO_PC_MTP=PASS
|
|
||||||
DESKTOP_MOBILE_IMPORT=PASS
|
|
||||||
PC_TO_ANDROID_CATALOG=IMPLEMENTED
|
|
||||||
SYNC_HISTORY_UI=IMPLEMENTED
|
|
||||||
BIDIRECTIONAL_HARDWARE_VALIDATION=NEXT
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_BIDIRECTIONAL_SYNC_CURSOR _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SYNC_AGENT_CURSOR -->
|
|
||||||
## Sync agent cursor
|
|
||||||
|
|
||||||
```text
|
|
||||||
ANDROID_AUTO_OUTBOX=IMPLEMENTED
|
|
||||||
ANDROID_SYNC_REQUEST=IMPLEMENTED
|
|
||||||
|
|
||||||
TRAINLOG_SYNCD=NEXT
|
|
||||||
ANDROID_SYNC_RECEIPT=AFTER
|
|
||||||
TUI_SYNC_LOG_SHOW=AFTER_AGENT_FOUNDATION
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_SYNC_AGENT_CURSOR _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SYNC_VALIDATED_ROADMAP -->
|
|
||||||
## Synchronization cursor
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ANDROID_LOCAL_WORKFLOWS=PASS
|
ANDROID_LOCAL_WORKFLOWS=PASS
|
||||||
|
ANDROID_LOCAL_DATABASE_V3=PASS
|
||||||
ANDROID_TO_PC_MTP=PASS
|
|
||||||
DESKTOP_MOBILE_IMPORT_V1=PASS
|
|
||||||
DESKTOP_MOBILE_IMPORT_IDEMPOTENT=PASS
|
|
||||||
|
|
||||||
PC_CATALOG_EXPORT_V1=PASS
|
|
||||||
PC_TO_ANDROID_MTP_PUBLISH=PASS
|
|
||||||
|
|
||||||
ANDROID_SAF_FOLDER_CHANGE=PASS
|
|
||||||
|
|
||||||
SYNC_HISTORY_GIT_LIKE=NEXT
|
|
||||||
COMMON_SYNC_ENGINE=NEXT
|
|
||||||
TRAINLOG_SYNCD=NEXT
|
|
||||||
ANDROID_REQUEST_RECEIPT=AFTER
|
|
||||||
AUTO_OUTBOX=AFTER_AGENT_FOUNDATION
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not regress to:
|
Completed:
|
||||||
|
|
||||||
```text
|
- exercise creation;
|
||||||
SQLite file synchronization
|
- inline exercise creation;
|
||||||
filesystem mounts
|
- profile-aware session entry;
|
||||||
exercise-name heuristics
|
- heterogeneous repetition sets;
|
||||||
manual fake sets for continuous activities
|
- continuous activity;
|
||||||
overloading frozen Trainlog JSON v1
|
- session history/detail;
|
||||||
```
|
- body measurements;
|
||||||
<!-- TRAINLOG_SYNC_VALIDATED_ROADMAP _END -->
|
- exercise removal from the current session draft.
|
||||||
|
|
||||||
<!-- TRAINLOG_VARIABLE_SET_REPS_V1 -->
|
## Variable set checkpoint
|
||||||
## Variable repetition sets
|
|
||||||
|
|
||||||
Trainlog preserves each performed set independently.
|
|
||||||
|
|
||||||
Accepted repetition input:
|
|
||||||
|
|
||||||
```text
|
|
||||||
5x10
|
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
|
||||||
4..10..4
|
|
||||||
```
|
|
||||||
|
|
||||||
`4..10..4` expands to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
|
||||||
```
|
|
||||||
|
|
||||||
Desktop schema v5 permits targetless `SETS` rows for actual-only mobile
|
|
||||||
observations. Synchronization therefore does not invent a uniform target when
|
|
||||||
performed sets are heterogeneous.
|
|
||||||
|
|
||||||
`performed_sets` remains the source of truth for actual per-set values.
|
|
||||||
|
|
||||||
Existing planned desktop sessions may still carry explicit target sets/reps or
|
|
||||||
target durations.
|
|
||||||
|
|
||||||
`trainlog-mobile-export` v1 keeps ordered heterogeneous `sets[]`.
|
|
||||||
|
|
||||||
Frozen `TRAINLOG_FORMAT_V1` is unchanged.
|
|
||||||
<!-- TRAINLOG_VARIABLE_SET_REPS_V1 _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL -->
|
|
||||||
## Variable sets and session exercise removal checkpoint
|
|
||||||
|
|
||||||
Validated functionality in this checkpoint:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
VARIABLE_REPETITION_SETS=PASS
|
VARIABLE_REPETITION_SETS=PASS
|
||||||
|
|
@ -551,149 +69,95 @@ REPETITION_PYRAMID=PASS
|
||||||
DESKTOP_SCHEMA_V5=PASS
|
DESKTOP_SCHEMA_V5=PASS
|
||||||
V4_TO_V5_MIGRATION_REGRESSION=PASS
|
V4_TO_V5_MIGRATION_REGRESSION=PASS
|
||||||
MOBILE_HETEROGENEOUS_SET_IMPORT=PASS
|
MOBILE_HETEROGENEOUS_SET_IMPORT=PASS
|
||||||
MOBILE_IMPORT_IDEMPOTENCE=PASS
|
|
||||||
NO_FAKE_UNIFORM_TARGET=PASS
|
NO_FAKE_UNIFORM_TARGET=PASS
|
||||||
|
|
||||||
ANDROID_SESSION_DRAFT_EXERCISE_REMOVE=PASS
|
|
||||||
DESKTOP_SESSION_EXERCISE_REMOVE=PASS
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Accepted repetition examples:
|
## Direct MTP transport
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5x10
|
USB_MTP_DETECTION=PASS
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
MTP_STORAGE_ACCESS=PASS
|
||||||
4..10..4
|
MTP_WRITE=PASS
|
||||||
|
MTP_LIST_FOLDER=PASS
|
||||||
|
MTP_READ=PASS
|
||||||
|
MTP_ROUNDTRIP=PASS
|
||||||
|
DIRECT_MTP_TRANSPORT=PASS
|
||||||
```
|
```
|
||||||
|
|
||||||
A heterogeneous mobile session is persisted as ordered `performed_sets`.
|
No mounted-filesystem dependency is required.
|
||||||
The desktop does not invent `target_sets`, `target_reps` or
|
|
||||||
`target_duration_seconds` for actual-only mobile observations.
|
|
||||||
|
|
||||||
On Android, an exercise already added to the current session can be removed
|
## Bidirectional synchronization v1
|
||||||
before saving the session.
|
|
||||||
|
|
||||||
On the desktop TUI, session editing already supports:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
d supprimer
|
ANDROID_TO_PC_MTP=PASS
|
||||||
|
DESKTOP_MOBILE_IMPORT_V1=PASS
|
||||||
|
DESKTOP_MOBILE_IMPORT_IDEMPOTENT=PASS
|
||||||
|
|
||||||
|
PC_CATALOG_EXPORT_V1=PASS
|
||||||
|
PC_TO_ANDROID_MTP_PUBLISH=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
|
||||||
```
|
```
|
||||||
|
|
||||||
for removing the selected exercise from a current or persisted session draft.
|
|
||||||
The database replacement remains transactional.
|
|
||||||
|
|
||||||
`TRAINLOG_FORMAT_V1` remains frozen and unchanged.
|
|
||||||
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 -->
|
|
||||||
## Shared bidirectional synchronization v1
|
|
||||||
|
|
||||||
Validated architecture:
|
Validated architecture:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Android local write
|
Android local change
|
||||||
-> automatic mobile snapshot
|
-> automatic mobile snapshot
|
||||||
|
|
||||||
Android "Synchroniser maintenant"
|
Android "Synchroniser maintenant"
|
||||||
-> trainlog-sync-request-v1.json
|
-> request
|
||||||
|
|
||||||
trainlog-syncd
|
trainlog-syncd
|
||||||
-> shared C synchronization engine
|
-> shared C sync engine
|
||||||
-> Android → PC mobile import
|
-> Android -> PC import
|
||||||
-> PC → Android catalog publish
|
-> PC -> Android catalog
|
||||||
-> trainlog-sync-receipt-v1.json
|
-> receipt
|
||||||
|
|
||||||
Android
|
Android
|
||||||
-> receipt matched by request_id
|
-> matching receipt
|
||||||
-> PC catalog applied locally
|
-> catalog apply
|
||||||
-> final result displayed
|
-> final result
|
||||||
```
|
```
|
||||||
|
|
||||||
The ncurses TUI and `trainlog-syncd` call the same
|
The TUI invokes the same engine manually.
|
||||||
`trainlog_sync_run()` implementation.
|
|
||||||
|
|
||||||
Direct libmtp remains mandatory. No filesystem mount and no SQLite-file
|
## Current quality baseline
|
||||||
synchronization are introduced.
|
|
||||||
|
|
||||||
### Concurrency
|
|
||||||
|
|
||||||
The shared engine owns:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$XDG_DATA_HOME/trainlog/sync.lock
|
DESKTOP_TESTS=19/19 PASS
|
||||||
|
ANDROID_BUILD=PASS
|
||||||
|
HARDWARE_SYNC_VALIDATION=PASS
|
||||||
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
```
|
```
|
||||||
|
|
||||||
A TUI-triggered transaction waits for the lock. Daemon request polling is
|
## Current implementation cursor
|
||||||
non-blocking and retries later.
|
|
||||||
|
|
||||||
### Sync history
|
No next product feature is frozen by this documentation checkpoint.
|
||||||
|
|
||||||
Every actual synchronization transaction creates:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json
|
NEXT_FEATURE=UNFROZEN
|
||||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
and appends a compact entry to:
|
Candidate future areas may include richer analytics, measured-max semantics,
|
||||||
|
additional editing workflows, synchronization hardening, or other product work,
|
||||||
|
but none is canonical until explicitly selected.
|
||||||
|
|
||||||
|
## Permanent constraints
|
||||||
|
|
||||||
|
Do not regress to:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$XDG_DATA_HOME/trainlog/sync_history.log
|
SQLite file synchronization
|
||||||
|
mandatory mounted Android filesystem
|
||||||
|
exercise-name identity heuristics
|
||||||
|
fake performed sets for continuous activity
|
||||||
|
fake uniform targets for heterogeneous actual sets
|
||||||
|
incompatible changes to Trainlog JSON v1
|
||||||
```
|
```
|
||||||
|
|
||||||
The TUI behaves like:
|
|
||||||
|
|
||||||
```text
|
|
||||||
git log
|
|
||||||
↑/↓ select synchronization
|
|
||||||
|
|
||||||
git show
|
|
||||||
Enter opens structured detail
|
|
||||||
```
|
|
||||||
|
|
||||||
Legacy three-field history entries remain readable but have no structured
|
|
||||||
detail file.
|
|
||||||
|
|
||||||
### Android request and receipt
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format = trainlog-sync-request
|
|
||||||
version = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Receipt:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format = trainlog-sync-receipt
|
|
||||||
version = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
The receipt carries the originating `request_id`, a generated `sync_id`,
|
|
||||||
status, summary and synchronization counts. Android ignores a receipt for a
|
|
||||||
different request ID.
|
|
||||||
|
|
||||||
### User service
|
|
||||||
|
|
||||||
Install/refresh the user service with:
|
|
||||||
|
|
||||||
```text
|
|
||||||
bash tools/install_syncd_user.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
No root privilege is required.
|
|
||||||
|
|
||||||
### Status
|
|
||||||
|
|
||||||
```text
|
|
||||||
COMMON_SYNC_ENGINE=PASS
|
|
||||||
TUI_SYNC_LOG_SHOW=PASS
|
|
||||||
TRAINLOG_SYNCD=PASS
|
|
||||||
ANDROID_TRIGGERED_SYNC=PASS
|
|
||||||
ANDROID_SYNC_RECEIPT=PASS
|
|
||||||
BIDIRECTIONAL_SYNC_V1=PASS
|
|
||||||
```
|
|
||||||
|
|
||||||
Frozen `TRAINLOG_FORMAT_V1` remains unchanged.
|
|
||||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 _END -->
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,51 @@
|
||||||
# Trainlog synchronization exchange
|
# Synchronization exchange
|
||||||
|
|
||||||
## Status
|
## 1. Status
|
||||||
|
|
||||||
```text
|
```text
|
||||||
MOBILE_EXPORT_V1=FROZEN_FOR_IMPLEMENTATION
|
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
|
||||||
|
|
||||||
TRAINLOG_FORMAT_V1=FROZEN_UNCHANGED
|
TRAINLOG_FORMAT_V1=FROZEN_UNCHANGED
|
||||||
```
|
```
|
||||||
|
|
||||||
This document defines a synchronization artifact. It is not the frozen
|
Synchronization artifacts are separate from the frozen Trainlog session JSON
|
||||||
Trainlog session JSON v1 format.
|
v1 format.
|
||||||
|
|
||||||
## Android → PC artifact
|
## 2. Exchange directory
|
||||||
|
|
||||||
Shared-storage path:
|
Canonical Android shared-storage directory:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Download/Trainlog/trainlog-mobile-export-v1.json
|
Download/Trainlog
|
||||||
```
|
```
|
||||||
|
|
||||||
Format header:
|
Desktop accesses this directory through direct MTP.
|
||||||
|
|
||||||
|
Android accesses PC-created artifacts through a persistent Storage Access
|
||||||
|
Framework folder grant.
|
||||||
|
|
||||||
|
## 3. Artifact table
|
||||||
|
|
||||||
|
| Direction | File | Format |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Android -> PC | `trainlog-mobile-export-v1.json` | `trainlog-mobile-export` v1 |
|
||||||
|
| PC -> Android | `trainlog-pc-catalog-v1.json` | `trainlog-pc-catalog` v1 |
|
||||||
|
| Android -> PC agent | `trainlog-sync-request-v1.json` | `trainlog-sync-request` v1 |
|
||||||
|
| PC agent -> Android | `trainlog-sync-receipt-v1.json` | `trainlog-sync-receipt` v1 |
|
||||||
|
|
||||||
|
No SQLite file is transferred.
|
||||||
|
|
||||||
|
## 4. Android -> PC mobile snapshot
|
||||||
|
|
||||||
|
Header:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
@ -35,9 +62,7 @@ sessions
|
||||||
body_observations
|
body_observations
|
||||||
```
|
```
|
||||||
|
|
||||||
### Exercises
|
Exercise profile fields:
|
||||||
|
|
||||||
Each exercise contains:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
exercise_id
|
exercise_id
|
||||||
|
|
@ -47,39 +72,27 @@ tracking_mode
|
||||||
data_fields
|
data_fields
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sessions
|
Set-based session exercise actuals use ordered:
|
||||||
|
|
||||||
Each session contains:
|
|
||||||
|
|
||||||
```text
|
|
||||||
session_id
|
|
||||||
started_at
|
|
||||||
session_type
|
|
||||||
exercises
|
|
||||||
```
|
|
||||||
|
|
||||||
Each session exercise snapshots:
|
|
||||||
|
|
||||||
```text
|
|
||||||
exercise_id
|
|
||||||
name
|
|
||||||
recording_mode
|
|
||||||
tracking_mode
|
|
||||||
data_fields
|
|
||||||
load_mode
|
|
||||||
rest_seconds
|
|
||||||
```
|
|
||||||
|
|
||||||
For `SETS`:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
sets[]
|
sets[]
|
||||||
reps
|
|
||||||
or
|
|
||||||
duration_seconds
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For `CONTINUOUS`:
|
with either:
|
||||||
|
|
||||||
|
```text
|
||||||
|
reps
|
||||||
|
```
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
```text
|
||||||
|
duration_seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
Heterogeneous repetition values are valid.
|
||||||
|
|
||||||
|
Continuous actuals use:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
continuous
|
continuous
|
||||||
|
|
@ -88,391 +101,224 @@ continuous
|
||||||
distance_km optional
|
distance_km optional
|
||||||
```
|
```
|
||||||
|
|
||||||
A continuous exercise has no synthetic set.
|
No synthetic set is created for continuous work.
|
||||||
|
|
||||||
### Body observations
|
## 5. Desktop mobile importer
|
||||||
|
|
||||||
Each body observation contains:
|
Reference importer:
|
||||||
|
|
||||||
```text
|
|
||||||
observation_id
|
|
||||||
observed_at
|
|
||||||
only the metrics actually measured
|
|
||||||
```
|
|
||||||
|
|
||||||
## Transport
|
|
||||||
|
|
||||||
Android writes its own export into shared Downloads storage.
|
|
||||||
|
|
||||||
Desktop reads the artifact through direct libmtp transport.
|
|
||||||
|
|
||||||
No filesystem mount is required.
|
|
||||||
|
|
||||||
No SQLite database file is transferred.
|
|
||||||
|
|
||||||
## PC → Android
|
|
||||||
|
|
||||||
A separate canonical catalog artifact will be defined and implemented after
|
|
||||||
Android → PC export is validated on physical hardware.
|
|
||||||
|
|
||||||
The PC → Android path must not overload frozen Trainlog JSON v1.
|
|
||||||
|
|
||||||
<!-- TRAINLOG_DESKTOP_MOBILE_IMPORT_V1 -->
|
|
||||||
## Desktop import of mobile export v1
|
|
||||||
|
|
||||||
The desktop importer is:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
tools/import_mobile_export.py
|
tools/import_mobile_export.py
|
||||||
```
|
```
|
||||||
|
|
||||||
It validates the complete mobile snapshot before opening a write transaction.
|
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
transactional
|
strict full-snapshot validation
|
||||||
idempotent by stable IDs
|
transactional import
|
||||||
exercise reconciliation by normalized name
|
stable-ID idempotence
|
||||||
profile conflicts rejected
|
catalog reconciliation
|
||||||
unknown JSON fields rejected
|
profile conflict rejection
|
||||||
no SQLite file copying
|
heterogeneous performed-set preservation
|
||||||
|
targetless schema-v5 import when no true target exists
|
||||||
|
continuous activity kept separate
|
||||||
```
|
```
|
||||||
|
|
||||||
For mobile `SETS` v1, the Android form records one uniform set metric. The
|
The importer never invents a uniform target merely to fit desktop persistence.
|
||||||
desktop importer derives:
|
|
||||||
|
## 6. PC -> Android catalog
|
||||||
|
|
||||||
|
The desktop publishes:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
target_sets = number of logged sets
|
trainlog-pc-catalog-v1.json
|
||||||
target_reps or target_duration = uniform logged value
|
|
||||||
```
|
```
|
||||||
|
|
||||||
and preserves all performed sets separately.
|
It is a canonical exercise catalog snapshot containing stable profile metadata.
|
||||||
|
|
||||||
A v1 mobile session with heterogeneous set metrics or `0 reps` is rejected
|
Android reconciles the received catalog into its local exercise catalog.
|
||||||
rather than inventing a desktop target.
|
|
||||||
|
|
||||||
Continuous activities remain target-less and are imported only into
|
This direction does not overload frozen Trainlog session JSON v1.
|
||||||
`continuous_activity`.
|
|
||||||
|
|
||||||
Recommended validation sequence:
|
## 7. Android sync request
|
||||||
|
|
||||||
```bash
|
Android writes:
|
||||||
python tools/import_mobile_export.py /tmp/trainlog-mobile-export-v1.json --dry-run
|
|
||||||
|
|
||||||
python tools/import_mobile_export.py /tmp/trainlog-mobile-export-v1.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Running the real import a second time must import nothing new and report the
|
|
||||||
existing IDs as skipped.
|
|
||||||
<!-- TRAINLOG_DESKTOP_MOBILE_IMPORT_V1 _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_BIDIRECTIONAL_SYNC_V1 -->
|
|
||||||
## Bidirectional synchronization v1
|
|
||||||
|
|
||||||
One desktop Sync action now performs both directions:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Android → PC
|
trainlog-sync-request-v1.json
|
||||||
direct-MTP download
|
|
||||||
strict transactional import
|
|
||||||
|
|
||||||
PC → Android
|
|
||||||
canonical PC exercise catalog export
|
|
||||||
direct-MTP publication
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The Android app obtains one persistent Storage Access Framework grant for:
|
Header:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"format": "trainlog-sync-request",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Required synchronization identity:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Download/Trainlog
|
request_id = sr_<uuid-v4>
|
||||||
```
|
```
|
||||||
|
|
||||||
After this one-time grant, Android can import the PC-created catalog without
|
The artifact also carries the request timestamp.
|
||||||
broad storage permissions.
|
|
||||||
|
|
||||||
The Sync page displays persistent synchronization history instead of remote
|
A new `request_id` represents a new synchronization request.
|
||||||
snapshot counts. A snapshot remaining present is not a pending queue item and
|
|
||||||
must not be shown as a "candidate".
|
|
||||||
|
|
||||||
User-facing session history timestamps are displayed as:
|
## 8. PC sync receipt
|
||||||
|
|
||||||
|
After processing an Android request, the PC publishes:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
DD/MM/YYYY HH:MM
|
trainlog-sync-receipt-v1.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Canonical RFC3339 storage remains unchanged.
|
Header:
|
||||||
<!-- TRAINLOG_BIDIRECTIONAL_SYNC_V1 _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_ANDROID_AUTO_OUTBOX_REQUEST -->
|
```json
|
||||||
## Automatic Android outbox and sync request
|
{
|
||||||
|
"format": "trainlog-sync-receipt",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Android no longer requires a manual export action.
|
The receipt contains:
|
||||||
|
|
||||||
The mobile snapshot is refreshed automatically on:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
application start
|
request_id
|
||||||
exercise save
|
sync_id
|
||||||
session save
|
status
|
||||||
body observation save
|
summary
|
||||||
PC catalog apply
|
Android -> PC counts
|
||||||
|
PC -> Android catalog count
|
||||||
```
|
```
|
||||||
|
|
||||||
The Android Sync screen exposes:
|
Android accepts a receipt only when its `request_id` matches the pending
|
||||||
|
request.
|
||||||
|
|
||||||
|
## 9. Shared desktop engine
|
||||||
|
|
||||||
|
Canonical implementation:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Synchroniser maintenant
|
trainlog_sync_run()
|
||||||
```
|
```
|
||||||
|
|
||||||
This writes:
|
TUI path:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Download/Trainlog/trainlog-sync-request-v1.json
|
TUI
|
||||||
|
-> shared engine
|
||||||
```
|
```
|
||||||
|
|
||||||
with a stable request ID and timestamp.
|
Android-triggered path:
|
||||||
|
|
||||||
The next PC-agent slice consumes this request and writes a sync receipt.
|
|
||||||
<!-- TRAINLOG_ANDROID_AUTO_OUTBOX_REQUEST _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_BIDIRECTIONAL_VALIDATED_CHECKPOINT -->
|
|
||||||
## Validated bidirectional transport checkpoint
|
|
||||||
|
|
||||||
Validated on the physical Samsung device:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ANDROID_TO_PC_MTP=PASS
|
Android request
|
||||||
DESKTOP_MOBILE_IMPORT_V1=PASS
|
-> trainlog-syncd
|
||||||
DESKTOP_MOBILE_IMPORT_IDEMPOTENT=PASS
|
-> shared engine
|
||||||
|
-> receipt
|
||||||
PC_CATALOG_EXPORT_V1=PASS
|
|
||||||
PC_TO_ANDROID_MTP_PUBLISH=PASS
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Artifacts:
|
One synchronization transaction performs:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Android → PC
|
mobile snapshot download
|
||||||
Download/Trainlog/trainlog-mobile-export-v1.json
|
-> mobile import
|
||||||
|
-> PC catalog export
|
||||||
PC → Android
|
-> PC catalog MTP publication
|
||||||
Download/Trainlog/trainlog-pc-catalog-v1.json
|
-> optional receipt publication
|
||||||
|
-> structured run history
|
||||||
```
|
```
|
||||||
|
|
||||||
Both are synchronization artifacts and remain separate from frozen
|
## 10. Concurrency and request consumption
|
||||||
`TRAINLOG_FORMAT_V1`.
|
|
||||||
|
|
||||||
The Android Storage Access Framework folder grant must target:
|
Synchronization owns:
|
||||||
|
|
||||||
```text
|
|
||||||
Download/Trainlog
|
|
||||||
```
|
|
||||||
|
|
||||||
and the UI must permit changing the stored folder selection.
|
|
||||||
|
|
||||||
Remaining synchronization work:
|
|
||||||
|
|
||||||
```text
|
|
||||||
persistent structured sync history
|
|
||||||
selectable sync detail
|
|
||||||
common sync engine
|
|
||||||
trainlog-syncd
|
|
||||||
Android-triggered request/receipt workflow
|
|
||||||
automatic mobile snapshot maintenance
|
|
||||||
```
|
|
||||||
<!-- TRAINLOG_BIDIRECTIONAL_VALIDATED_CHECKPOINT _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_VARIABLE_SET_REPS_V1 -->
|
|
||||||
## Variable repetition sets
|
|
||||||
|
|
||||||
Trainlog preserves each performed set independently.
|
|
||||||
|
|
||||||
Accepted repetition input:
|
|
||||||
|
|
||||||
```text
|
|
||||||
5x10
|
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
|
||||||
4..10..4
|
|
||||||
```
|
|
||||||
|
|
||||||
`4..10..4` expands to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
|
||||||
```
|
|
||||||
|
|
||||||
Desktop schema v5 permits targetless `SETS` rows for actual-only mobile
|
|
||||||
observations. Synchronization therefore does not invent a uniform target when
|
|
||||||
performed sets are heterogeneous.
|
|
||||||
|
|
||||||
`performed_sets` remains the source of truth for actual per-set values.
|
|
||||||
|
|
||||||
Existing planned desktop sessions may still carry explicit target sets/reps or
|
|
||||||
target durations.
|
|
||||||
|
|
||||||
`trainlog-mobile-export` v1 keeps ordered heterogeneous `sets[]`.
|
|
||||||
|
|
||||||
Frozen `TRAINLOG_FORMAT_V1` is unchanged.
|
|
||||||
<!-- TRAINLOG_VARIABLE_SET_REPS_V1 _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL -->
|
|
||||||
## Variable sets and session exercise removal checkpoint
|
|
||||||
|
|
||||||
Validated functionality in this checkpoint:
|
|
||||||
|
|
||||||
```text
|
|
||||||
VARIABLE_REPETITION_SETS=PASS
|
|
||||||
REPETITION_SHORTHAND_5x10=PASS
|
|
||||||
REPETITION_EXPLICIT_LIST=PASS
|
|
||||||
REPETITION_PYRAMID=PASS
|
|
||||||
|
|
||||||
DESKTOP_SCHEMA_V5=PASS
|
|
||||||
V4_TO_V5_MIGRATION_REGRESSION=PASS
|
|
||||||
MOBILE_HETEROGENEOUS_SET_IMPORT=PASS
|
|
||||||
MOBILE_IMPORT_IDEMPOTENCE=PASS
|
|
||||||
NO_FAKE_UNIFORM_TARGET=PASS
|
|
||||||
|
|
||||||
ANDROID_SESSION_DRAFT_EXERCISE_REMOVE=PASS
|
|
||||||
DESKTOP_SESSION_EXERCISE_REMOVE=PASS
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepted repetition examples:
|
|
||||||
|
|
||||||
```text
|
|
||||||
5x10
|
|
||||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
|
||||||
4..10..4
|
|
||||||
```
|
|
||||||
|
|
||||||
A heterogeneous mobile session is persisted as ordered `performed_sets`.
|
|
||||||
The desktop does not invent `target_sets`, `target_reps` or
|
|
||||||
`target_duration_seconds` for actual-only mobile observations.
|
|
||||||
|
|
||||||
On Android, an exercise already added to the current session can be removed
|
|
||||||
before saving the session.
|
|
||||||
|
|
||||||
On the desktop TUI, session editing already supports:
|
|
||||||
|
|
||||||
```text
|
|
||||||
d supprimer
|
|
||||||
```
|
|
||||||
|
|
||||||
for removing the selected exercise from a current or persisted session draft.
|
|
||||||
The database replacement remains transactional.
|
|
||||||
|
|
||||||
`TRAINLOG_FORMAT_V1` remains frozen and unchanged.
|
|
||||||
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 -->
|
|
||||||
## Shared bidirectional synchronization v1
|
|
||||||
|
|
||||||
Validated architecture:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Android local write
|
|
||||||
-> automatic mobile snapshot
|
|
||||||
|
|
||||||
Android "Synchroniser maintenant"
|
|
||||||
-> trainlog-sync-request-v1.json
|
|
||||||
|
|
||||||
trainlog-syncd
|
|
||||||
-> shared C synchronization engine
|
|
||||||
-> Android → PC mobile import
|
|
||||||
-> PC → Android catalog publish
|
|
||||||
-> trainlog-sync-receipt-v1.json
|
|
||||||
|
|
||||||
Android
|
|
||||||
-> receipt matched by request_id
|
|
||||||
-> PC catalog applied locally
|
|
||||||
-> final result displayed
|
|
||||||
```
|
|
||||||
|
|
||||||
The ncurses TUI and `trainlog-syncd` call the same
|
|
||||||
`trainlog_sync_run()` implementation.
|
|
||||||
|
|
||||||
Direct libmtp remains mandatory. No filesystem mount and no SQLite-file
|
|
||||||
synchronization are introduced.
|
|
||||||
|
|
||||||
### Concurrency
|
|
||||||
|
|
||||||
The shared engine owns:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$XDG_DATA_HOME/trainlog/sync.lock
|
$XDG_DATA_HOME/trainlog/sync.lock
|
||||||
```
|
```
|
||||||
|
|
||||||
A TUI-triggered transaction waits for the lock. Daemon request polling is
|
The daemon uses non-blocking acquisition while polling.
|
||||||
non-blocking and retries later.
|
|
||||||
|
|
||||||
### Sync history
|
The TUI manual action waits for the active synchronization lock.
|
||||||
|
|
||||||
Every actual synchronization transaction creates:
|
After a request is completed and its receipt is published, the request ID is
|
||||||
|
recorded locally so the same request is not processed as a new request again.
|
||||||
|
|
||||||
|
## 11. Structured sync history
|
||||||
|
|
||||||
|
Every real run has:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sy_<uuid-v4>
|
||||||
|
```
|
||||||
|
|
||||||
|
Artifacts:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json
|
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json
|
||||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt
|
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt
|
||||||
```
|
|
||||||
|
|
||||||
and appends a compact entry to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
$XDG_DATA_HOME/trainlog/sync_history.log
|
$XDG_DATA_HOME/trainlog/sync_history.log
|
||||||
```
|
```
|
||||||
|
|
||||||
The TUI behaves like:
|
The TUI presents newest runs in a selectable list and opens the detail file with
|
||||||
|
`Enter`.
|
||||||
|
|
||||||
```text
|
Legacy history rows without a `sync_id` remain readable as list entries but
|
||||||
git log
|
cannot have structured detail.
|
||||||
↑/↓ select synchronization
|
|
||||||
|
|
||||||
git show
|
## 12. PC user service
|
||||||
Enter opens structured detail
|
|
||||||
```
|
|
||||||
|
|
||||||
Legacy three-field history entries remain readable but have no structured
|
Install or refresh:
|
||||||
detail file.
|
|
||||||
|
|
||||||
### Android request and receipt
|
```bash
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format = trainlog-sync-request
|
|
||||||
version = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Receipt:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format = trainlog-sync-receipt
|
|
||||||
version = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
The receipt carries the originating `request_id`, a generated `sync_id`,
|
|
||||||
status, summary and synchronization counts. Android ignores a receipt for a
|
|
||||||
different request ID.
|
|
||||||
|
|
||||||
### User service
|
|
||||||
|
|
||||||
Install/refresh the user service with:
|
|
||||||
|
|
||||||
```text
|
|
||||||
bash tools/install_syncd_user.sh
|
bash tools/install_syncd_user.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user is-active trainlog-syncd.service
|
||||||
|
systemctl --user --no-pager --full status trainlog-syncd.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Daemon log:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -f ~/.local/state/trainlog/syncd.log
|
||||||
|
```
|
||||||
|
|
||||||
No root privilege is required.
|
No root privilege is required.
|
||||||
|
|
||||||
### Status
|
## 13. Transport invariants
|
||||||
|
|
||||||
|
Do not regress to:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
COMMON_SYNC_ENGINE=PASS
|
SQLite database copying
|
||||||
TUI_SYNC_LOG_SHOW=PASS
|
mandatory GVFS/FUSE mounts
|
||||||
TRAINLOG_SYNCD=PASS
|
exercise-name identity heuristics
|
||||||
ANDROID_TRIGGERED_SYNC=PASS
|
fake sets for continuous activity
|
||||||
ANDROID_SYNC_RECEIPT=PASS
|
fake uniform targets for heterogeneous actual sets
|
||||||
BIDIRECTIONAL_SYNC_V1=PASS
|
overloading frozen Trainlog JSON v1
|
||||||
```
|
```
|
||||||
|
|
||||||
Frozen `TRAINLOG_FORMAT_V1` remains unchanged.
|
## 14. Hardware validation
|
||||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 _END -->
|
|
||||||
|
Validated on the physical Android device:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MTP device discovery PASS
|
||||||
|
storage access PASS
|
||||||
|
read/write/list/delete PASS
|
||||||
|
Android mobile snapshot download PASS
|
||||||
|
desktop idempotent import PASS
|
||||||
|
PC catalog publication PASS
|
||||||
|
Android request detection PASS
|
||||||
|
trainlog-syncd processing PASS
|
||||||
|
receipt publication/readback PASS
|
||||||
|
multiple distinct Android request IDs PASS
|
||||||
|
```
|
||||||
|
|
|
||||||
328
docs/tests.md
328
docs/tests.md
|
|
@ -1,12 +1,13 @@
|
||||||
# Tests and Validation
|
# Tests and validation
|
||||||
|
|
||||||
## 1. Principle
|
## 1. Principle
|
||||||
|
|
||||||
A feature is not complete without relevant validation.
|
A Trainlog feature is not complete without relevant validation.
|
||||||
|
|
||||||
The exchange validator is executable specification during the format gates.
|
Frozen formats, persistence migrations, synchronization semantics, and user-data
|
||||||
|
mutations require executable coverage where practical.
|
||||||
|
|
||||||
## 2. Canonical exchange validation
|
## 2. Frozen Trainlog JSON v1
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
|
|
@ -14,123 +15,30 @@ Run:
|
||||||
python tools/validate_json.py
|
python tools/validate_json.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The command validates:
|
It validates:
|
||||||
|
|
||||||
- `examples/session-v1.json`;
|
- `examples/session-v1.json`;
|
||||||
- all `tests/fixtures/valid/*.json` as valid;
|
- all positive fixtures;
|
||||||
- all `tests/fixtures/invalid/*.json` as invalid.
|
- all negative fixtures.
|
||||||
|
|
||||||
A negative fixture passes only when Trainlog rejects it.
|
A negative fixture passes only when Trainlog rejects it.
|
||||||
|
|
||||||
## 3. Validation layers
|
Semantic validation includes:
|
||||||
|
|
||||||
A v1 document must pass:
|
|
||||||
|
|
||||||
1. JSON Schema validation;
|
|
||||||
2. Trainlog semantic validation.
|
|
||||||
|
|
||||||
Schema handles shape, enumerations, and primitive ranges.
|
|
||||||
|
|
||||||
Semantic validation handles cross-object and normalized rules.
|
|
||||||
|
|
||||||
## 4. Gate 1 semantic coverage
|
|
||||||
|
|
||||||
The canonical validator checks:
|
|
||||||
|
|
||||||
- unique `exercise_id`;
|
|
||||||
- normalized exercise-name uniqueness;
|
|
||||||
- explicit timestamp offsets;
|
|
||||||
- end time later than start time;
|
|
||||||
- exact catalog/reference set equality;
|
|
||||||
- one workout entry per exercise;
|
|
||||||
- catalog tracking mode matching target;
|
|
||||||
- catalog tracking mode matching actual sets;
|
|
||||||
- load-mode/weight consistency;
|
|
||||||
- non-blank notes.
|
|
||||||
|
|
||||||
## 5. Positive fixture coverage
|
|
||||||
|
|
||||||
Gate 1 includes:
|
|
||||||
|
|
||||||
- mixed loaded repetition + timed session;
|
|
||||||
- active session without `ended_at`;
|
|
||||||
- planned exercise with zero actual sets;
|
|
||||||
- bodyweight exercise with zero-repetition failed attempt;
|
|
||||||
- assistance load;
|
|
||||||
- completely interrupted session with zero exercises.
|
|
||||||
|
|
||||||
## 6. Negative fixture coverage
|
|
||||||
|
|
||||||
Gate 1 includes rejection of:
|
|
||||||
|
|
||||||
- duplicate exercise IDs;
|
|
||||||
- duplicate normalized exercise names;
|
|
||||||
- duplicate workout exercise entries;
|
|
||||||
- end timestamp before start;
|
|
||||||
- missing timestamp offset;
|
|
||||||
- target/actual tracking mismatch;
|
|
||||||
- target containing both repetitions and duration;
|
|
||||||
- unknown exercise reference;
|
|
||||||
- unknown JSON field;
|
|
||||||
- catalog tracking-mode mismatch;
|
|
||||||
- `load_mode=none` carrying weight;
|
|
||||||
- loaded target missing weight;
|
|
||||||
- loaded actual set missing weight;
|
|
||||||
- unreferenced catalog entries;
|
|
||||||
- blank session notes;
|
|
||||||
- blank exercise notes;
|
|
||||||
- negative actual repetitions.
|
|
||||||
|
|
||||||
## 7. Gate 2 compiled validation
|
|
||||||
|
|
||||||
The normal Meson suite currently covers:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
database
|
stable-ID uniqueness
|
||||||
catalog
|
normalized exercise-name uniqueness
|
||||||
session_detail
|
timestamp offsets
|
||||||
duration
|
end > start
|
||||||
body_metrics
|
catalog/reference equality
|
||||||
bodyviz
|
tracking-mode consistency
|
||||||
exercise_performance
|
load-mode/weight consistency
|
||||||
session_type_schema
|
unknown-field rejection
|
||||||
session_edit
|
non-blank notes
|
||||||
body_observation_edit
|
zero actual repetitions allowed
|
||||||
```
|
```
|
||||||
|
|
||||||
The session-edit test verifies transactional child replacement without changing
|
## 3. Import reconciliation contract
|
||||||
the parent session identity. The body-observation edit test verifies stable
|
|
||||||
observation identity while metric values and notes are updated.
|
|
||||||
|
|
||||||
Schema validation includes the v1 -> v2 `session_type` migration.
|
|
||||||
|
|
||||||
## 8. C validation
|
|
||||||
|
|
||||||
Current pre-push validation includes:
|
|
||||||
|
|
||||||
- normal strict-warning build;
|
|
||||||
- the complete Meson test suite;
|
|
||||||
- `git diff --check`;
|
|
||||||
- frozen JSON v1 validators.
|
|
||||||
|
|
||||||
ASan/UBSan is run for meaningful implementation checkpoints before declaring a
|
|
||||||
gate complete.
|
|
||||||
|
|
||||||
## 9. Pre-push checklist
|
|
||||||
|
|
||||||
Before every meaningful push:
|
|
||||||
|
|
||||||
1. run `python tools/validate_json.py`;
|
|
||||||
2. run `python tools/validate_import_contract.py`;
|
|
||||||
3. run `meson compile -C build`;
|
|
||||||
4. run `meson test -C build --print-errorlogs`;
|
|
||||||
5. run sanitizers when relevant;
|
|
||||||
6. run `git diff --check`;
|
|
||||||
7. inspect `git status --short`;
|
|
||||||
8. review documentation changes.
|
|
||||||
## 10. Catalog reconciliation contract
|
|
||||||
|
|
||||||
Before the C17 importer exists, Gate 1 defines local catalog merge behavior through an executable Python specification.
|
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
|
|
@ -138,87 +46,171 @@ Run:
|
||||||
python tools/validate_import_contract.py
|
python tools/validate_import_contract.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Canonical cases cover:
|
Coverage includes:
|
||||||
|
|
||||||
- exact existing exercise reuse;
|
|
||||||
- same identity with renamed display text;
|
|
||||||
- same identity with incompatible tracking mode;
|
|
||||||
- different identities with equivalent normalized names;
|
|
||||||
- new unique exercise creation.
|
|
||||||
|
|
||||||
The full Gate 1 validation command is:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python tools/validate_json.py
|
|
||||||
python tools/validate_import_contract.py
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- TRAINLOG_MTP_VALIDATION -->
|
|
||||||
## 11. USB/MTP transport validation
|
|
||||||
|
|
||||||
The compiled suite now contains:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
usb
|
same ID + same name/profile -> reuse
|
||||||
mtp
|
same ID + renamed display text -> controlled reuse/warning
|
||||||
|
same ID + incompatible mode -> reject
|
||||||
|
different ID + equivalent normalized name -> reject
|
||||||
|
new unique identity -> create
|
||||||
```
|
```
|
||||||
|
|
||||||
The `usb` test covers API validation, physical-device enumeration invariants,
|
## 4. Desktop Meson suite
|
||||||
and rejection of duplicated USB interface children.
|
|
||||||
|
|
||||||
The `mtp` test covers bounded API argument validation for storage, folder,
|
Current normal suite:
|
||||||
upload, listing, and download entry points.
|
|
||||||
|
|
||||||
Hardware probes additionally validate the real device path:
|
```text
|
||||||
|
1 database
|
||||||
|
2 catalog
|
||||||
|
3 session_detail
|
||||||
|
4 duration
|
||||||
|
5 body_metrics
|
||||||
|
6 bodyviz
|
||||||
|
7 exercise_performance
|
||||||
|
8 session_type_schema
|
||||||
|
9 session_edit
|
||||||
|
10 body_observation_edit
|
||||||
|
11 mtp
|
||||||
|
12 continuous_session
|
||||||
|
13 continuous_detail
|
||||||
|
14 reps
|
||||||
|
15 exercise_profile_schema
|
||||||
|
16 usb
|
||||||
|
17 variable_sets
|
||||||
|
18 schema_v5_migration
|
||||||
|
19 mobile_import_variable_sets
|
||||||
|
```
|
||||||
|
|
||||||
|
Validated checkpoint:
|
||||||
|
|
||||||
|
```text
|
||||||
|
19/19 PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Notable regression coverage:
|
||||||
|
|
||||||
|
- transactional persisted-session replacement;
|
||||||
|
- exercise removal from a session;
|
||||||
|
- body-observation stable-identity editing;
|
||||||
|
- profile-aware exercise constraints;
|
||||||
|
- continuous activity without fake sets;
|
||||||
|
- repetition shorthand/list/pyramid parsing;
|
||||||
|
- direct v4 -> v5 database migration;
|
||||||
|
- heterogeneous mobile-set import;
|
||||||
|
- targetless mobile SETS persistence;
|
||||||
|
- mobile-import idempotence.
|
||||||
|
|
||||||
|
## 5. Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
meson setup --reconfigure build
|
||||||
|
meson compile -C build
|
||||||
|
meson test -C build --print-errorlogs
|
||||||
|
```
|
||||||
|
|
||||||
|
Strict warning flags remain active. Do not weaken warnings to make a change pass.
|
||||||
|
|
||||||
|
## 6. Android build
|
||||||
|
|
||||||
|
When Android code changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd android
|
||||||
|
|
||||||
|
printf 'sdk.dir=%s\n' "$HOME/Android/Sdk" > local.properties
|
||||||
|
|
||||||
|
JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
|
||||||
|
./gradlew assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
Install to the connected device when hardware behavior changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Hardware MTP validation
|
||||||
|
|
||||||
|
Hardware probes and real synchronization are separate from the normal automated
|
||||||
|
suite because a test runner cannot assume an unlocked MTP phone.
|
||||||
|
|
||||||
|
Available probe binaries include:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
trainlog-usb-probe
|
trainlog-usb-probe
|
||||||
trainlog-mtp-probe
|
trainlog-mtp-probe
|
||||||
trainlog-mtp-exchange-probe
|
trainlog-mtp-exchange-probe
|
||||||
trainlog-mtp-roundtrip-probe
|
trainlog-mtp-roundtrip-probe
|
||||||
|
trainlog-mtp-mobile-export-probe
|
||||||
```
|
```
|
||||||
|
|
||||||
Physical checkpoint result:
|
Current physical baseline:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
MTP devices: 1
|
USB_MTP_DETECTION=PASS
|
||||||
ROUNDTRIP=PASS Trainlog/trainlog-probe.txt
|
MTP_STORAGE_ACCESS=PASS
|
||||||
|
MTP_WRITE=PASS
|
||||||
|
MTP_LIST_FOLDER=PASS
|
||||||
|
MTP_READ=PASS
|
||||||
|
MTP_ROUNDTRIP=PASS
|
||||||
```
|
```
|
||||||
|
|
||||||
The hardware probe is intentionally separate from the normal automated test
|
## 8. Bidirectional synchronization validation
|
||||||
suite because CI is not expected to have a connected unlocked Android MTP
|
|
||||||
device.
|
|
||||||
<!-- TRAINLOG_MTP_VALIDATION _END -->
|
|
||||||
|
|
||||||
<!-- TRAINLOG_CONTINUOUS_TEST_CHECKPOINT -->
|
Validated workflow:
|
||||||
## Profile-aware / continuous validation
|
|
||||||
|
|
||||||
Expected normal test suite after this checkpoint:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
15 tests
|
Android
|
||||||
|
-> Synchroniser maintenant
|
||||||
|
-> unique sr_ request
|
||||||
|
-> trainlog-syncd
|
||||||
|
-> shared engine
|
||||||
|
-> Android -> PC import
|
||||||
|
-> PC -> Android catalog
|
||||||
|
-> sy_ structured run
|
||||||
|
-> matching receipt
|
||||||
|
-> Android final status
|
||||||
```
|
```
|
||||||
|
|
||||||
Coverage added around:
|
Multiple distinct Android request IDs were processed successfully without
|
||||||
|
reprocessing one request as a new one.
|
||||||
|
|
||||||
```text
|
The TUI also invokes the same engine manually and exposes structured run
|
||||||
exercise profile schema
|
details.
|
||||||
profiled catalog creation
|
|
||||||
schema migration
|
## 9. Sanitizers
|
||||||
continuous session persistence
|
|
||||||
continuous session detail loading
|
For meaningful C checkpoints:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CC=clang meson setup build-asan \
|
||||||
|
-Db_sanitize=address,undefined \
|
||||||
|
-Db_lundef=false
|
||||||
|
|
||||||
|
meson compile -C build-asan
|
||||||
|
meson test -C build-asan --print-errorlogs
|
||||||
```
|
```
|
||||||
|
|
||||||
Manual TUI validation includes:
|
## 10. Pre-push checklist
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
Marche configured CONTINUOUS + DURATION + SPEED_KMH
|
meson compile -C build
|
||||||
entry asks duration minutes + speed
|
meson test -C build --print-errorlogs
|
||||||
no sets/rest/load prompts
|
|
||||||
continuous_activity row persisted
|
python tools/validate_json.py
|
||||||
performed_sets count remains zero
|
python tools/validate_import_contract.py
|
||||||
history reopens as continuous
|
|
||||||
duration and speed render correctly
|
git diff --check
|
||||||
|
git status --short
|
||||||
```
|
```
|
||||||
<!-- TRAINLOG_CONTINUOUS_TEST_CHECKPOINT _END -->
|
|
||||||
|
When Android changed, add:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd android
|
||||||
|
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
Documentation must describe the resulting state, not retain contradictory old
|
||||||
|
`NEXT` checkpoints.
|
||||||
|
|
|
||||||
1071
docs/tui.md
1071
docs/tui.md
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue