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
|
||||
|
||||
Trainlog is composed of two applications sharing a versioned exchange format:
|
||||
Trainlog consists of:
|
||||
|
||||
- a lightweight Android application for fast workout data entry;
|
||||
- a Unix/Linux TUI for storage, review, analysis, and visualization.
|
||||
- a native Android application for fast workout and body-data capture;
|
||||
- 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:
|
||||
|
||||
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.
|
||||
`TRAINLOG_FORMAT_V1` is frozen.
|
||||
|
||||
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;
|
||||
- unique identifiers;
|
||||
- anti-duplication constraints.
|
||||
Display names are not identities.
|
||||
|
||||
## 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:
|
||||
|
||||
- build;
|
||||
- run tests;
|
||||
- verify formatting;
|
||||
- verify compiler warnings;
|
||||
- validate JSON examples against the schema;
|
||||
- verify documentation impacted by the change.
|
||||
```bash
|
||||
meson compile -C build
|
||||
meson test -C build --print-errorlogs
|
||||
|
||||
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:
|
||||
|
||||
`git@github.com:labfytools/trainlog.git`
|
||||
|
||||
Normal development must push to Forgejo.
|
||||
```text
|
||||
git@github.com:labfytools/trainlog.git
|
||||
```
|
||||
|
||||
Do not develop directly against the GitHub mirror.
|
||||
|
||||
## 12. Definition of Done
|
||||
## 13. Definition of Done
|
||||
|
||||
A task is complete only when:
|
||||
|
||||
- the expected behavior is implemented;
|
||||
- the code builds without accepted warnings;
|
||||
- behavior is implemented;
|
||||
- the affected code builds without accepted warnings;
|
||||
- relevant tests pass;
|
||||
- new error paths are handled;
|
||||
- documentation is current;
|
||||
- examples and schemas are updated when required;
|
||||
- new error paths are explicit;
|
||||
- persistent-format changes have migrations;
|
||||
- synchronization remains idempotent where applicable;
|
||||
- documentation describes the resulting state;
|
||||
- no known regression is intentionally left behind.
|
||||
|
|
|
|||
427
CHANGELOG.md
427
CHANGELOG.md
|
|
@ -1,395 +1,86 @@
|
|||
# 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
|
||||
|
||||
### Added
|
||||
|
||||
- Frozen Trainlog JSON v1 contract.
|
||||
- SQLite persistence foundation.
|
||||
- Direct workout entry.
|
||||
- Exercise catalog.
|
||||
- Body tracking.
|
||||
- Colored ncursesw dashboard.
|
||||
- Body-weight graph.
|
||||
- Arrow-key and F1-F4 navigation.
|
||||
- Highlighted list selections.
|
||||
- Navigable session history.
|
||||
- native Kotlin/Compose Android capture client;
|
||||
- Android local exercise, session, continuous-activity, and body persistence;
|
||||
- C17/ncursesw desktop TUI with direct session entry and durable SQLite history;
|
||||
- profile-aware exercise model using recording mode, tracking mode, and
|
||||
supplemental fields;
|
||||
- continuous activity persistence without synthetic sets;
|
||||
- variable repetition-set input including `5x10`, explicit lists, and pyramid
|
||||
shorthand such as `4..10..4`;
|
||||
- persisted desktop session editing and exercise removal;
|
||||
- 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
|
||||
|
||||
- Exercise selection now uses interactive keyboard navigation.
|
||||
- Body tracking is now a persistent history screen instead of entry-only.
|
||||
- TUI polish is prioritized before Android development.
|
||||
- desktop SQLite schema evolved to v5;
|
||||
- schema v5 permits targetless set-session rows for actual-only mobile data;
|
||||
- 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 -->
|
||||
### TUI v0.2 checkpoint
|
||||
### Fixed
|
||||
|
||||
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;
|
||||
- arrow-key and F-key navigation;
|
||||
- weight history visualization;
|
||||
- flat-series weight graph handling;
|
||||
- navigable session history;
|
||||
- complete read-only workout details.
|
||||
### Validation
|
||||
|
||||
Planned next:
|
||||
|
||||
- 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:
|
||||
Current validated baseline:
|
||||
|
||||
```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
|
||||
TRAINLOG_FORMAT_V1=FROZEN
|
||||
|
||||
DESKTOP_SCHEMA_V5=PASS
|
||||
V4_TO_V5_MIGRATION_REGRESSION=PASS
|
||||
MOBILE_HETEROGENEOUS_SET_IMPORT=PASS
|
||||
MOBILE_IMPORT_IDEMPOTENCE=PASS
|
||||
NO_FAKE_UNIFORM_TARGET=PASS
|
||||
DESKTOP_TESTS=19/19 PASS
|
||||
|
||||
ANDROID_SESSION_DRAFT_EXERCISE_REMOVE=PASS
|
||||
DESKTOP_SESSION_EXERCISE_REMOVE=PASS
|
||||
```
|
||||
ANDROID_BUILD=PASS
|
||||
ANDROID_LOCAL_WORKFLOWS=PASS
|
||||
|
||||
Accepted repetition examples:
|
||||
USB_MTP_DETECTION=PASS
|
||||
MTP_ROUNDTRIP=PASS
|
||||
|
||||
```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
|
||||
$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
|
||||
ANDROID_TO_PC_MTP=PASS
|
||||
PC_TO_ANDROID_MTP_PUBLISH=PASS
|
||||
COMMON_SYNC_ENGINE=PASS
|
||||
TUI_SYNC_LOG_SHOW=PASS
|
||||
TRAINLOG_SYNCD=PASS
|
||||
ANDROID_TRIGGERED_SYNC=PASS
|
||||
ANDROID_SYNC_RECEIPT=PASS
|
||||
TUI_SYNC_LOG_SHOW=PASS
|
||||
BIDIRECTIONAL_SYNC_V1=PASS
|
||||
```
|
||||
|
||||
Frozen `TRAINLOG_FORMAT_V1` remains unchanged.
|
||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 _END -->
|
||||
|
|
|
|||
212
README.md
212
README.md
|
|
@ -1,75 +1,189 @@
|
|||
# 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 colorful Unix/Linux TUI for history, statistics, graphs, and progress tracking.
|
||||
- a native Android application optimized for fast data entry during training;
|
||||
- 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;
|
||||
- exercise selection;
|
||||
- new exercise creation;
|
||||
- planned sets and repetitions;
|
||||
- actual sets and repetitions;
|
||||
- load;
|
||||
- rest time;
|
||||
- body weight;
|
||||
- body measurements;
|
||||
- JSON export.
|
||||
```text
|
||||
TRAINLOG_FORMAT_V1=FROZEN
|
||||
|
||||
The TUI is the main application:
|
||||
DESKTOP_SCHEMA_V5=PASS
|
||||
ANDROID_LOCAL_WORKFLOWS=PASS
|
||||
|
||||
- import Android exports;
|
||||
- create sessions directly from the terminal;
|
||||
- maintain the canonical exercise catalog;
|
||||
- maintain SQLite history;
|
||||
- display workout history;
|
||||
- track body weight;
|
||||
- track body measurements;
|
||||
- track performance;
|
||||
- display colorful terminal graphs;
|
||||
- export data for external use.
|
||||
VARIABLE_REPETITION_SETS=PASS
|
||||
CONTINUOUS_ACTIVITY_TRACKING=PASS
|
||||
|
||||
DIRECT_MTP_TRANSPORT=PASS
|
||||
COMMON_SYNC_ENGINE=PASS
|
||||
TRAINLOG_SYNCD=PASS
|
||||
ANDROID_TRIGGERED_SYNC=PASS
|
||||
ANDROID_SYNC_RECEIPT=PASS
|
||||
BIDIRECTIONAL_SYNC_V1=PASS
|
||||
|
||||
DESKTOP_TESTS=19/19 PASS
|
||||
ANDROID_BUILD=PASS
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
Android
|
||||
|
|
||||
| Trainlog JSON
|
||||
v
|
||||
Trainlog TUI
|
||||
|
|
||||
v
|
||||
SQLite
|
||||
Android application
|
||||
local SQLite store
|
||||
|
|
||||
automatic mobile snapshot
|
||||
|
|
||||
v
|
||||
Download/Trainlog on Android storage
|
||||
|
|
||||
| direct MTP / libmtp
|
||||
v
|
||||
trainlog_sync_run()
|
||||
/ \
|
||||
/ \
|
||||
Android -> PC PC -> Android
|
||||
snapshot import catalog publish
|
||||
\ /
|
||||
\ /
|
||||
sync receipt
|
||||
|
|
||||
v
|
||||
Android
|
||||
|
||||
Desktop TUI --------------------+
|
||||
| |
|
||||
+-- same sync engine -------+
|
||||
|
|
||||
v
|
||||
desktop SQLite
|
||||
canonical long-term history
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```text
|
||||
android/ Android application
|
||||
tui/ C17 ncursesw application
|
||||
docs/ Canonical project documentation
|
||||
format/ JSON schema and exchange-format material
|
||||
examples/ Valid exchange examples
|
||||
tools/ Development and validation tools
|
||||
android/ native Kotlin/Compose Android client
|
||||
tui/ C17 ncursesw desktop application and core
|
||||
docs/ canonical project documentation
|
||||
format/ frozen Trainlog JSON v1 schema material
|
||||
examples/ valid frozen-format examples
|
||||
tests/ fixtures and cross-component tests
|
||||
tools/ validators, import/export helpers, sync daemon tooling
|
||||
```
|
||||
|
||||
## Desktop build and validation
|
||||
|
||||
```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
|
||||
|
||||
- local-first;
|
||||
- no mandatory cloud account;
|
||||
- user-owned data;
|
||||
- versioned persistent formats;
|
||||
- stable exercise identifiers;
|
||||
- idempotent imports;
|
||||
- documentation and tests are part of every feature;
|
||||
- clear separation between UI, business logic, and persistence.
|
||||
|
||||
See `AGENTS.md` for the development contract.
|
||||
- versioned persistent and exchange formats;
|
||||
- stable identities;
|
||||
- idempotent synchronization;
|
||||
- no fake data representation to force incompatible models together;
|
||||
- strict compiler warnings;
|
||||
- documentation and tests are part of feature completion.
|
||||
|
|
|
|||
885
docs/android.md
885
docs/android.md
|
|
@ -1,556 +1,119 @@
|
|||
# Android Application
|
||||
# Android application
|
||||
|
||||
## 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
|
||||
|
||||
```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:
|
||||
## 2. Implemented navigation
|
||||
|
||||
```text
|
||||
Accueil
|
||||
├── Enregistrer une séance
|
||||
│ ├── Ajouter depuis le catalogue
|
||||
│ └── Créer un nouvel exercice
|
||||
│ └── returns to session flow
|
||||
├── 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
|
||||
background
|
||||
accent/cyan
|
||||
success/green
|
||||
warning/yellow
|
||||
error/red
|
||||
muted/blue
|
||||
graph/magenta
|
||||
3
|
||||
```
|
||||
|
||||
Android forms will be driven by:
|
||||
|
||||
```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:
|
||||
Domain tables cover:
|
||||
|
||||
```text
|
||||
exercises
|
||||
sessions
|
||||
session_exercises
|
||||
performed_sets
|
||||
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
|
||||
ANDROID_SESSION_HISTORY=NEXT
|
||||
ANDROID_BODY_PERSISTENCE=AFTER
|
||||
MTP_SYNC=AFTER_LOCAL_WORKFLOWS
|
||||
name
|
||||
recording_mode
|
||||
tracking_mode
|
||||
data_fields
|
||||
```
|
||||
<!-- TRAINLOG_ANDROID_SESSION_RECORDING _END -->
|
||||
|
||||
<!-- TRAINLOG_ANDROID_SESSION_HISTORY -->
|
||||
## Android session history checkpoint
|
||||
|
||||
Android now exposes persisted local sessions through:
|
||||
Stable identity:
|
||||
|
||||
```text
|
||||
Accueil
|
||||
→ Consultation
|
||||
→ Historique des séances
|
||||
→ Détail séance
|
||||
ex_<uuid-v4>
|
||||
```
|
||||
|
||||
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
|
||||
SETS + REPS
|
||||
one line per performed set with reps
|
||||
|
||||
SETS + DURATION
|
||||
one line per performed set with duration
|
||||
|
||||
CONTINUOUS
|
||||
duration
|
||||
configured speed
|
||||
configured distance
|
||||
se_<uuid-v4>
|
||||
```
|
||||
|
||||
The history reader uses the persisted session snapshot metadata rather than
|
||||
inferring behavior from exercise names.
|
||||
Session entry is profile-aware.
|
||||
|
||||
Next:
|
||||
### Sets + repetitions
|
||||
|
||||
Actual set values may be heterogeneous.
|
||||
|
||||
Compact entry supports:
|
||||
|
||||
```text
|
||||
ANDROID_BODY_PERSISTENCE=NEXT
|
||||
ANDROID_LOCAL_WORKFLOWS_THEN_MTP
|
||||
5x10
|
||||
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 -->
|
||||
## Android body measurement checkpoint
|
||||
### Sets + duration
|
||||
|
||||
The Android body workflow is now persistent and uses the same measurement set
|
||||
as the TUI.
|
||||
Each performed set stores its own duration.
|
||||
|
||||
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
|
||||
weight
|
||||
|
|
@ -568,304 +131,114 @@ left/right calf
|
|||
Rules:
|
||||
|
||||
```text
|
||||
empty field = measurement not taken
|
||||
empty field = not measured
|
||||
at least one positive metric required
|
||||
comma or dot accepted for decimal entry
|
||||
```
|
||||
|
||||
Android SQLite schema version:
|
||||
Stable identity:
|
||||
|
||||
```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
|
||||
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:
|
||||
Android maintains:
|
||||
|
||||
```text
|
||||
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
|
||||
exercise profiles
|
||||
sessions
|
||||
body observations
|
||||
```
|
||||
The user does not need a separate manual export step before synchronization.
|
||||
|
||||
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
|
||||
./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:
|
||||
The selected folder must be:
|
||||
|
||||
```text
|
||||
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
|
||||
Changer le dossier Trainlog
|
||||
Synchroniser maintenant
|
||||
```
|
||||
|
||||
This is required so a wrong persisted folder selection can be corrected without
|
||||
clearing the Android application database.
|
||||
|
||||
Validated PC catalog publication:
|
||||
Android writes:
|
||||
|
||||
```text
|
||||
trainlog-pc-catalog-v1.json
|
||||
trainlog-sync-request-v1.json
|
||||
```
|
||||
|
||||
The final Android synchronization workflow must evolve toward a single
|
||||
`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:
|
||||
and waits for a matching:
|
||||
|
||||
```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
|
||||
trainlog-sync-receipt-v1.json
|
||||
```
|
||||
|
||||
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
|
||||
5x10
|
||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
||||
4..10..4
|
||||
Android request
|
||||
-> PC trainlog-syncd
|
||||
-> shared desktop sync engine
|
||||
-> receipt
|
||||
```
|
||||
|
||||
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.
|
||||
## 13. Build
|
||||
|
||||
On Android, an exercise already added to the current session can be removed
|
||||
before saving the session.
|
||||
Example local configuration:
|
||||
|
||||
On the desktop TUI, session editing already supports:
|
||||
```bash
|
||||
cd android
|
||||
|
||||
```text
|
||||
d supprimer
|
||||
printf 'sdk.dir=%s\n' "$HOME/Android/Sdk" > local.properties
|
||||
|
||||
JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
for removing the selected exercise from a current or persisted session draft.
|
||||
The database replacement remains transactional.
|
||||
Install to a connected test device:
|
||||
|
||||
`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
|
||||
```bash
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
The ncurses TUI and `trainlog-syncd` call the same
|
||||
`trainlog_sync_run()` implementation.
|
||||
`local.properties` is local machine configuration and must not be committed.
|
||||
|
||||
Direct libmtp remains mandatory. No filesystem mount and no SQLite-file
|
||||
synchronization are introduced.
|
||||
## 14. Non-goals
|
||||
|
||||
### Concurrency
|
||||
Android is not intended to own:
|
||||
|
||||
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
|
||||
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 -->
|
||||
- canonical long-term analytics;
|
||||
- complex body/performance graphs;
|
||||
- cloud accounts;
|
||||
- direct SQLite-file synchronization;
|
||||
- exercise-name heuristics;
|
||||
- a mounted-filesystem dependency.
|
||||
|
|
|
|||
|
|
@ -1,199 +1,287 @@
|
|||
# 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
|
||||
|
||||
### Android client
|
||||
### Android application
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- start a session;
|
||||
- record the session start timestamp;
|
||||
- select an existing exercise;
|
||||
- create a new exercise;
|
||||
- record workout targets;
|
||||
- record actual performed sets;
|
||||
- record rest duration;
|
||||
- record body data;
|
||||
- record the session end timestamp;
|
||||
- export one valid Trainlog JSON document.
|
||||
- exercise catalog entry;
|
||||
- workout-session recording;
|
||||
- performed set entry;
|
||||
- continuous-activity entry;
|
||||
- body measurement entry;
|
||||
- local history/detail;
|
||||
- automatic mobile snapshot generation;
|
||||
- PC catalog application;
|
||||
- synchronization request creation;
|
||||
- synchronization receipt display.
|
||||
|
||||
Non-responsibilities:
|
||||
Android is not responsible for canonical long-term analytics.
|
||||
|
||||
- long-term analytics;
|
||||
- canonical history;
|
||||
- complex graphing;
|
||||
- cloud synchronization.
|
||||
### Desktop core
|
||||
|
||||
### Exchange format
|
||||
The C17 core owns:
|
||||
|
||||
The exchange format is the compatibility boundary between Android and the TUI.
|
||||
|
||||
It is:
|
||||
|
||||
- JSON;
|
||||
- UTF-8;
|
||||
- versioned;
|
||||
- self-contained enough to import newly created exercises;
|
||||
- designed for idempotent import.
|
||||
- desktop SQLite persistence;
|
||||
- exercise/catalog rules;
|
||||
- profile-aware session data;
|
||||
- body data;
|
||||
- ID and time helpers;
|
||||
- USB discovery;
|
||||
- direct MTP operations;
|
||||
- the shared bidirectional synchronization engine.
|
||||
|
||||
### TUI
|
||||
|
||||
Responsibilities:
|
||||
The ncursesw layer owns interaction and rendering.
|
||||
|
||||
- import Trainlog JSON;
|
||||
- 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.
|
||||
It consumes core services for:
|
||||
|
||||
### 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;
|
||||
- uniqueness constraints;
|
||||
- schema versioning;
|
||||
- explicit migration rules.
|
||||
It polls for a new Android request and invokes the same shared C synchronization
|
||||
engine used by the TUI.
|
||||
|
||||
## 3. Data flow
|
||||
It does not implement a second synchronization algorithm.
|
||||
|
||||
## 3. Exercise model
|
||||
|
||||
Trainlog is metadata-driven:
|
||||
|
||||
```text
|
||||
Android
|
||||
|
|
||||
| export
|
||||
v
|
||||
Trainlog JSON
|
||||
|
|
||||
| import + validation
|
||||
v
|
||||
TUI application
|
||||
|
|
||||
| persistence
|
||||
v
|
||||
SQLite
|
||||
recording_mode = SETS | CONTINUOUS
|
||||
tracking_mode = REPS | DURATION
|
||||
data_fields = SPEED_KMH | DISTANCE_KM
|
||||
```
|
||||
|
||||
## 4. Identity rules
|
||||
|
||||
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:
|
||||
Valid model-v1 combinations:
|
||||
|
||||
```text
|
||||
ncursesw rendering
|
||||
|
|
||||
v
|
||||
TUI state / navigation
|
||||
|
|
||||
v
|
||||
application services
|
||||
|
|
||||
+---- exchange-format parser
|
||||
|
|
||||
+---- analytics
|
||||
|
|
||||
v
|
||||
SQLite persistence
|
||||
SETS + REPS
|
||||
SETS + DURATION
|
||||
CONTINUOUS + DURATION
|
||||
```
|
||||
|
||||
The rendering layer must not own business rules.
|
||||
|
||||
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:
|
||||
Load mode is session-specific:
|
||||
|
||||
```text
|
||||
Android USB file-transfer mode
|
||||
none
|
||||
external
|
||||
assistance
|
||||
```
|
||||
|
||||
Continuous work is persisted separately from performed sets.
|
||||
|
||||
## 4. Persistence ownership
|
||||
|
||||
### Desktop
|
||||
|
||||
Desktop SQLite schema v5 is canonical long-term history.
|
||||
|
||||
Main tables:
|
||||
|
||||
```text
|
||||
exercises
|
||||
sessions
|
||||
session_exercises
|
||||
performed_sets
|
||||
continuous_activity
|
||||
body_observations
|
||||
```
|
||||
|
||||
### Android
|
||||
|
||||
Android has an independent local SQLite schema.
|
||||
|
||||
It mirrors domain concepts needed for capture, but its schema version is not
|
||||
coupled to the desktop schema.
|
||||
|
||||
Synchronization exchanges domain artifacts rather than database files.
|
||||
|
||||
## 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 physical-device discovery
|
||||
libudev discovery
|
||||
|
|
||||
| bus number + device number
|
||||
| bus + device number
|
||||
v
|
||||
libmtp exact raw-device open
|
||||
libmtp exact raw-device access
|
||||
|
|
||||
v
|
||||
Android internal MTP storage
|
||||
Android internal storage
|
||||
```
|
||||
|
||||
This avoids GVFS/FUSE mount state and manual mount/unmount lifecycle management.
|
||||
No GVFS/FUSE mount is required.
|
||||
|
||||
`libudev` owns physical-device discovery. `libmtp` owns storage and object
|
||||
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
|
||||
listing, file download, and verified byte-for-byte roundtrip.
|
||||
<!-- TRAINLOG_DIRECT_MTP_ARCHITECTURE _END -->
|
||||
|
||||
<!-- TRAINLOG_PROFILE_AWARE_ARCHITECTURE -->
|
||||
## Profile-aware activity architecture
|
||||
|
||||
Trainlog has two distinct actual-work persistence paths:
|
||||
Canonical exchange directory:
|
||||
|
||||
```text
|
||||
SET-based exercise
|
||||
session_exercises
|
||||
|
|
||||
+--> performed_sets [0..N]
|
||||
|
||||
CONTINUOUS exercise
|
||||
session_exercises
|
||||
|
|
||||
+--> continuous_activity [exactly 1]
|
||||
Download/Trainlog
|
||||
```
|
||||
|
||||
The two paths must remain semantically distinct.
|
||||
## 7. Shared synchronization engine
|
||||
|
||||
Catalog metadata determines future entry forms.
|
||||
Both user-trigger paths call:
|
||||
|
||||
Session-exercise snapshot metadata determines historical rendering/editing.
|
||||
```text
|
||||
trainlog_sync_run()
|
||||
```
|
||||
|
||||
The Android client must consume the same catalog profile metadata rather than
|
||||
maintaining an independent exercise-type system.
|
||||
<!-- TRAINLOG_PROFILE_AWARE_ARCHITECTURE _END -->
|
||||
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.
|
||||
394
docs/database.md
394
docs/database.md
|
|
@ -1,122 +1,90 @@
|
|||
# Database
|
||||
# Desktop database
|
||||
|
||||
## 1. Status
|
||||
|
||||
```text
|
||||
GATE_2=IN_PROGRESS
|
||||
DATABASE_SCHEMA_V2=IMPLEMENTED
|
||||
SESSION_TYPE_PERSISTENCE=IMPLEMENTED
|
||||
SESSION_EDIT_PERSISTENCE=IMPLEMENTED
|
||||
BODY_OBSERVATION_EDIT=IMPLEMENTED
|
||||
TRAINLOG_DATABASE_SCHEMA_VERSION=5
|
||||
DATABASE_SCHEMA_V5=PASS
|
||||
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.
|
||||
|
||||
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:
|
||||
Schema version uses:
|
||||
|
||||
```sql
|
||||
PRAGMA user_version;
|
||||
```
|
||||
|
||||
Current schema:
|
||||
Current value:
|
||||
|
||||
```text
|
||||
DATABASE_SCHEMA_V2=2
|
||||
5
|
||||
```
|
||||
|
||||
A new database starts with `user_version = 0` and is initialized atomically to
|
||||
the current schema.
|
||||
Supported historical databases are migrated explicitly through the implemented
|
||||
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
|
||||
0 -> 2 fresh initialization
|
||||
1 -> 2 transactional migration
|
||||
```
|
||||
## 3. Connection invariants
|
||||
|
||||
Schema v2 adds local session classification while leaving the frozen Trainlog
|
||||
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:
|
||||
Every connection enables:
|
||||
|
||||
```sql
|
||||
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 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:
|
||||
Canonical desktop exercise catalog.
|
||||
|
||||
```text
|
||||
id
|
||||
session_id UNIQUE
|
||||
started_at
|
||||
ended_at nullable
|
||||
session_type training | max_test
|
||||
notes nullable
|
||||
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
|
||||
```
|
||||
|
||||
`session_type` is a local SQLite concern in schema v2. Existing schema-v1 rows
|
||||
migrate to `training`; no historical workout is retroactively inferred to be a
|
||||
max test.
|
||||
Rules include:
|
||||
|
||||
Body data is stored separately so standalone body observations can use the same representation.
|
||||
- continuous implies duration tracking;
|
||||
- unknown supplemental field bits are rejected;
|
||||
- normalized names remain unique.
|
||||
|
||||
### 5.3 `session_exercises`
|
||||
### `sessions`
|
||||
|
||||
One ordered exercise within a session.
|
||||
```text
|
||||
id
|
||||
session_id UNIQUE stable identity
|
||||
started_at
|
||||
ended_at nullable
|
||||
session_type training | max_test
|
||||
notes nullable
|
||||
```
|
||||
|
||||
Fields include:
|
||||
### `session_exercises`
|
||||
|
||||
Ordered exercise occurrence inside one session.
|
||||
|
||||
```text
|
||||
session_row_id
|
||||
exercise_row_id
|
||||
recording_mode
|
||||
data_fields
|
||||
position
|
||||
load_mode
|
||||
rest_seconds
|
||||
|
|
@ -127,41 +95,77 @@ target_weight_kg
|
|||
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;
|
||||
- one row at each session position;
|
||||
- exactly one target metric: repetitions or duration;
|
||||
- target load presence consistent with `load_mode`.
|
||||
`SETS` rows support two target shapes in schema v5:
|
||||
|
||||
### 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
|
||||
session_exercise_row_id
|
||||
position
|
||||
reps
|
||||
duration_seconds
|
||||
weight_kg
|
||||
reps nullable
|
||||
duration_seconds nullable
|
||||
weight_kg nullable
|
||||
```
|
||||
|
||||
Exactly one of repetitions or duration 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:
|
||||
Exactly one primary actual metric is present:
|
||||
|
||||
```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
|
||||
session_row_id optional and UNIQUE
|
||||
session_row_id optional UNIQUE link
|
||||
body_weight_kg
|
||||
neck_cm
|
||||
shoulders_cm
|
||||
|
|
@ -181,180 +185,94 @@ notes
|
|||
|
||||
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`.
|
||||
|
||||
## 6. UUID generation
|
||||
|
||||
Official Trainlog creators generate UUID version 4 identifiers.
|
||||
|
||||
The C core provides generated IDs for:
|
||||
Official desktop creator prefixes:
|
||||
|
||||
```text
|
||||
ex_<uuid-v4>
|
||||
se_<uuid-v4>
|
||||
bo_<uuid-v4>
|
||||
ex_ exercise
|
||||
se_ session
|
||||
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
|
||||
BEGIN IMMEDIATE
|
||||
COMMIT
|
||||
ROLLBACK
|
||||
session_id
|
||||
started_at
|
||||
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
|
||||
session replaces only its `session_exercises` / `performed_sets` children and
|
||||
preserves the parent session row, stable `session_id`, timestamps,
|
||||
`session_type`, session notes, and any linked body observation.
|
||||
Body-observation editing preserves its stable identity, timestamp, and optional
|
||||
session link.
|
||||
|
||||
Body-observation correction preserves observation identity, timestamp, and
|
||||
optional session link.
|
||||
## 7. Mobile import semantics
|
||||
|
||||
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
|
||||
|
||||
Canonical persistent units remain:
|
||||
Canonical desktop persistence:
|
||||
|
||||
- weight/load: kilograms;
|
||||
- body circumference: centimeters;
|
||||
- duration/rest: seconds.
|
||||
```text
|
||||
weight/load kg
|
||||
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;
|
||||
- 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.
|
||||
Desktop and Android schema versions are not required to match.
|
||||
|
||||
The database remains independent from ncurses rendering.
|
||||
|
||||
The frozen Trainlog JSON v1 format remains a separate compatibility boundary
|
||||
and is not version-coupled to SQLite schema v2.
|
||||
Do not synchronize SQLite database files.
|
||||
|
||||
## 10. Validation
|
||||
|
||||
Normal build:
|
||||
|
||||
```bash
|
||||
CC=clang meson setup build
|
||||
meson compile -C build
|
||||
meson test -C build --print-errorlogs
|
||||
```
|
||||
|
||||
Sanitizer build:
|
||||
|
||||
```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:
|
||||
Migration-specific regression coverage includes:
|
||||
|
||||
```text
|
||||
recording_mode = sets | continuous
|
||||
data_fields = bounded bit mask
|
||||
schema_v5_migration
|
||||
```
|
||||
|
||||
Existing `tracking_mode = reps | duration` remains stable.
|
||||
|
||||
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 -->
|
||||
The current normal suite contains 19 tests.
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@
|
|||
## Status
|
||||
|
||||
```text
|
||||
EXERCISE_DATA_MODEL_V1=FROZEN_FOR_IMPLEMENTATION
|
||||
DATABASE_SCHEMA_V3=NEXT
|
||||
TUI_PROFILE_AWARE_ENTRY=AFTER_SCHEMA_V3
|
||||
ANDROID_PROFILE_AWARE_ENTRY=AFTER_TUI
|
||||
EXERCISE_DATA_MODEL_V1=PASS
|
||||
PROFILE_AWARE_DESKTOP=PASS
|
||||
PROFILE_AWARE_ANDROID=PASS
|
||||
CONTINUOUS_ACTIVITY=PASS
|
||||
VARIABLE_REPETITION_SETS=PASS
|
||||
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
|
||||
recording_mode = SETS | CONTINUOUS
|
||||
|
|
@ -21,196 +21,13 @@ tracking_mode = REPS | DURATION
|
|||
data_fields = supplemental field bit mask
|
||||
```
|
||||
|
||||
Initial valid combinations:
|
||||
|
||||
```text
|
||||
SETS + REPS
|
||||
SETS + DURATION
|
||||
CONTINUOUS + DURATION
|
||||
```
|
||||
|
||||
`CONTINUOUS + REPS` is invalid in model v1.
|
||||
|
||||
Initial supplemental fields:
|
||||
Known supplemental fields:
|
||||
|
||||
```text
|
||||
SPEED_KMH
|
||||
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:
|
||||
|
||||
```text
|
||||
|
|
@ -219,25 +36,166 @@ SETS + DURATION
|
|||
CONTINUOUS + DURATION
|
||||
```
|
||||
|
||||
Continuous exercise actual data is persisted as one `continuous_activity`
|
||||
record rather than a performed-set list.
|
||||
Invalid:
|
||||
|
||||
A continuous activity never manufactures a one-set representation.
|
||||
```text
|
||||
CONTINUOUS + REPS
|
||||
```
|
||||
|
||||
The TUI asks continuous duration in **minutes**, converts to seconds, and stores
|
||||
seconds internally.
|
||||
UI behavior must never be inferred from an exercise display name.
|
||||
|
||||
## 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:
|
||||
|
||||
```text
|
||||
Marche
|
||||
CONTINUOUS + DURATION + SPEED_KMH
|
||||
|
||||
TUI entry:
|
||||
Durée (minutes)
|
||||
Vitesse km/h
|
||||
Durée 45 min
|
||||
Vitesse 5.8 km/h
|
||||
```
|
||||
|
||||
Historical session rows snapshot recording metadata and are not reinterpreted
|
||||
when catalog metadata later changes.
|
||||
<!-- TRAINLOG_PROFILE_AWARE_IMPLEMENTED _END -->
|
||||
Actual persistence uses one `continuous_activity` record containing duration
|
||||
and configured supplemental values.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
GATE_1=PASS
|
||||
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
|
||||
GATE_2=PASS
|
||||
DESKTOP_SCHEMA_V5=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;
|
||||
- body tracking;
|
||||
- weight graph;
|
||||
- colored dashboard;
|
||||
- arrow/F-key navigation;
|
||||
- navigable history;
|
||||
- Unicode anti-duplicate exercise names.
|
||||
- profile-aware set and continuous work;
|
||||
- body history/editing/visualization;
|
||||
- exercise performance history;
|
||||
- strict validation and rollback behavior.
|
||||
|
||||
Current remaining Gate 2 direction:
|
||||
|
||||
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
|
||||
## Android local client
|
||||
|
||||
```text
|
||||
ANDROID_LOCAL_WORKFLOWS=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
|
||||
ANDROID_LOCAL_DATABASE_V3=PASS
|
||||
```
|
||||
|
||||
Do not regress to:
|
||||
Completed:
|
||||
|
||||
```text
|
||||
SQLite file synchronization
|
||||
filesystem mounts
|
||||
exercise-name heuristics
|
||||
manual fake sets for continuous activities
|
||||
overloading frozen Trainlog JSON v1
|
||||
```
|
||||
<!-- TRAINLOG_SYNC_VALIDATED_ROADMAP _END -->
|
||||
- exercise creation;
|
||||
- inline exercise creation;
|
||||
- profile-aware session entry;
|
||||
- heterogeneous repetition sets;
|
||||
- continuous activity;
|
||||
- session history/detail;
|
||||
- body measurements;
|
||||
- exercise removal from the current session draft.
|
||||
|
||||
<!-- 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:
|
||||
## Variable set checkpoint
|
||||
|
||||
```text
|
||||
VARIABLE_REPETITION_SETS=PASS
|
||||
|
|
@ -551,149 +69,95 @@ 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:
|
||||
## Direct MTP transport
|
||||
|
||||
```text
|
||||
5x10
|
||||
4,5,6,7,8,9,10,9,8,7,6,5,4
|
||||
4..10..4
|
||||
USB_MTP_DETECTION=PASS
|
||||
MTP_STORAGE_ACCESS=PASS
|
||||
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`.
|
||||
The desktop does not invent `target_sets`, `target_reps` or
|
||||
`target_duration_seconds` for actual-only mobile observations.
|
||||
No mounted-filesystem dependency is required.
|
||||
|
||||
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:
|
||||
## Bidirectional synchronization v1
|
||||
|
||||
```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:
|
||||
|
||||
```text
|
||||
Android local write
|
||||
-> automatic mobile snapshot
|
||||
Android local change
|
||||
-> automatic mobile snapshot
|
||||
|
||||
Android "Synchroniser maintenant"
|
||||
-> trainlog-sync-request-v1.json
|
||||
-> request
|
||||
|
||||
trainlog-syncd
|
||||
-> shared C synchronization engine
|
||||
-> Android → PC mobile import
|
||||
-> PC → Android catalog publish
|
||||
-> trainlog-sync-receipt-v1.json
|
||||
-> shared C sync engine
|
||||
-> Android -> PC import
|
||||
-> PC -> Android catalog
|
||||
-> receipt
|
||||
|
||||
Android
|
||||
-> receipt matched by request_id
|
||||
-> PC catalog applied locally
|
||||
-> final result displayed
|
||||
-> matching receipt
|
||||
-> catalog apply
|
||||
-> final result
|
||||
```
|
||||
|
||||
The ncurses TUI and `trainlog-syncd` call the same
|
||||
`trainlog_sync_run()` implementation.
|
||||
The TUI invokes the same engine manually.
|
||||
|
||||
Direct libmtp remains mandatory. No filesystem mount and no SQLite-file
|
||||
synchronization are introduced.
|
||||
|
||||
### Concurrency
|
||||
|
||||
The shared engine owns:
|
||||
## Current quality baseline
|
||||
|
||||
```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
|
||||
non-blocking and retries later.
|
||||
## Current implementation cursor
|
||||
|
||||
### Sync history
|
||||
|
||||
Every actual synchronization transaction creates:
|
||||
No next product feature is frozen by this documentation checkpoint.
|
||||
|
||||
```text
|
||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json
|
||||
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt
|
||||
NEXT_FEATURE=UNFROZEN
|
||||
```
|
||||
|
||||
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
|
||||
$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
|
||||
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
|
||||
```
|
||||
|
||||
This document defines a synchronization artifact. It is not the frozen
|
||||
Trainlog session JSON v1 format.
|
||||
Synchronization artifacts are separate from the frozen Trainlog session JSON
|
||||
v1 format.
|
||||
|
||||
## Android → PC artifact
|
||||
## 2. Exchange directory
|
||||
|
||||
Shared-storage path:
|
||||
Canonical Android shared-storage directory:
|
||||
|
||||
```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
|
||||
{
|
||||
|
|
@ -35,9 +62,7 @@ sessions
|
|||
body_observations
|
||||
```
|
||||
|
||||
### Exercises
|
||||
|
||||
Each exercise contains:
|
||||
Exercise profile fields:
|
||||
|
||||
```text
|
||||
exercise_id
|
||||
|
|
@ -47,39 +72,27 @@ tracking_mode
|
|||
data_fields
|
||||
```
|
||||
|
||||
### Sessions
|
||||
|
||||
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`:
|
||||
Set-based session exercise actuals use ordered:
|
||||
|
||||
```text
|
||||
sets[]
|
||||
reps
|
||||
or
|
||||
duration_seconds
|
||||
```
|
||||
|
||||
For `CONTINUOUS`:
|
||||
with either:
|
||||
|
||||
```text
|
||||
reps
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```text
|
||||
duration_seconds
|
||||
```
|
||||
|
||||
Heterogeneous repetition values are valid.
|
||||
|
||||
Continuous actuals use:
|
||||
|
||||
```text
|
||||
continuous
|
||||
|
|
@ -88,391 +101,224 @@ continuous
|
|||
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:
|
||||
|
||||
```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:
|
||||
Reference importer:
|
||||
|
||||
```text
|
||||
tools/import_mobile_export.py
|
||||
```
|
||||
|
||||
It validates the complete mobile snapshot before opening a write transaction.
|
||||
|
||||
Properties:
|
||||
|
||||
```text
|
||||
transactional
|
||||
idempotent by stable IDs
|
||||
exercise reconciliation by normalized name
|
||||
profile conflicts rejected
|
||||
unknown JSON fields rejected
|
||||
no SQLite file copying
|
||||
strict full-snapshot validation
|
||||
transactional import
|
||||
stable-ID idempotence
|
||||
catalog reconciliation
|
||||
profile conflict rejection
|
||||
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
|
||||
desktop importer derives:
|
||||
The importer never invents a uniform target merely to fit desktop persistence.
|
||||
|
||||
## 6. PC -> Android catalog
|
||||
|
||||
The desktop publishes:
|
||||
|
||||
```text
|
||||
target_sets = number of logged sets
|
||||
target_reps or target_duration = uniform logged value
|
||||
trainlog-pc-catalog-v1.json
|
||||
```
|
||||
|
||||
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
|
||||
rather than inventing a desktop target.
|
||||
Android reconciles the received catalog into its local exercise catalog.
|
||||
|
||||
Continuous activities remain target-less and are imported only into
|
||||
`continuous_activity`.
|
||||
This direction does not overload frozen Trainlog session JSON v1.
|
||||
|
||||
Recommended validation sequence:
|
||||
## 7. Android sync request
|
||||
|
||||
```bash
|
||||
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:
|
||||
Android writes:
|
||||
|
||||
```text
|
||||
Android → PC
|
||||
direct-MTP download
|
||||
strict transactional import
|
||||
|
||||
PC → Android
|
||||
canonical PC exercise catalog export
|
||||
direct-MTP publication
|
||||
trainlog-sync-request-v1.json
|
||||
```
|
||||
|
||||
The Android app obtains one persistent Storage Access Framework grant for:
|
||||
Header:
|
||||
|
||||
```json
|
||||
{
|
||||
"format": "trainlog-sync-request",
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
|
||||
Required synchronization identity:
|
||||
|
||||
```text
|
||||
Download/Trainlog
|
||||
request_id = sr_<uuid-v4>
|
||||
```
|
||||
|
||||
After this one-time grant, Android can import the PC-created catalog without
|
||||
broad storage permissions.
|
||||
The artifact also carries the request timestamp.
|
||||
|
||||
The Sync page displays persistent synchronization history instead of remote
|
||||
snapshot counts. A snapshot remaining present is not a pending queue item and
|
||||
must not be shown as a "candidate".
|
||||
A new `request_id` represents a new synchronization request.
|
||||
|
||||
User-facing session history timestamps are displayed as:
|
||||
## 8. PC sync receipt
|
||||
|
||||
After processing an Android request, the PC publishes:
|
||||
|
||||
```text
|
||||
DD/MM/YYYY HH:MM
|
||||
trainlog-sync-receipt-v1.json
|
||||
```
|
||||
|
||||
Canonical RFC3339 storage remains unchanged.
|
||||
<!-- TRAINLOG_BIDIRECTIONAL_SYNC_V1 _END -->
|
||||
Header:
|
||||
|
||||
<!-- TRAINLOG_ANDROID_AUTO_OUTBOX_REQUEST -->
|
||||
## Automatic Android outbox and sync request
|
||||
```json
|
||||
{
|
||||
"format": "trainlog-sync-receipt",
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
|
||||
Android no longer requires a manual export action.
|
||||
|
||||
The mobile snapshot is refreshed automatically on:
|
||||
The receipt contains:
|
||||
|
||||
```text
|
||||
application start
|
||||
exercise save
|
||||
session save
|
||||
body observation save
|
||||
PC catalog apply
|
||||
request_id
|
||||
sync_id
|
||||
status
|
||||
summary
|
||||
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
|
||||
Synchroniser maintenant
|
||||
trainlog_sync_run()
|
||||
```
|
||||
|
||||
This writes:
|
||||
TUI path:
|
||||
|
||||
```text
|
||||
Download/Trainlog/trainlog-sync-request-v1.json
|
||||
TUI
|
||||
-> shared engine
|
||||
```
|
||||
|
||||
with a stable request ID and timestamp.
|
||||
|
||||
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:
|
||||
Android-triggered path:
|
||||
|
||||
```text
|
||||
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 request
|
||||
-> trainlog-syncd
|
||||
-> shared engine
|
||||
-> receipt
|
||||
```
|
||||
|
||||
Artifacts:
|
||||
One synchronization transaction performs:
|
||||
|
||||
```text
|
||||
Android → PC
|
||||
Download/Trainlog/trainlog-mobile-export-v1.json
|
||||
|
||||
PC → Android
|
||||
Download/Trainlog/trainlog-pc-catalog-v1.json
|
||||
mobile snapshot download
|
||||
-> mobile import
|
||||
-> PC catalog export
|
||||
-> PC catalog MTP publication
|
||||
-> optional receipt publication
|
||||
-> structured run history
|
||||
```
|
||||
|
||||
Both are synchronization artifacts and remain separate from frozen
|
||||
`TRAINLOG_FORMAT_V1`.
|
||||
## 10. Concurrency and request consumption
|
||||
|
||||
The Android Storage Access Framework folder grant must target:
|
||||
|
||||
```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:
|
||||
Synchronization 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.
|
||||
The daemon uses non-blocking acquisition while polling.
|
||||
|
||||
### 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
|
||||
$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:
|
||||
The TUI presents newest runs in a selectable list and opens the detail file with
|
||||
`Enter`.
|
||||
|
||||
```text
|
||||
git log
|
||||
↑/↓ select synchronization
|
||||
Legacy history rows without a `sync_id` remain readable as list entries but
|
||||
cannot have structured detail.
|
||||
|
||||
git show
|
||||
Enter opens structured detail
|
||||
```
|
||||
## 12. PC user service
|
||||
|
||||
Legacy three-field history entries remain readable but have no structured
|
||||
detail file.
|
||||
Install or refresh:
|
||||
|
||||
### 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
|
||||
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.
|
||||
|
||||
### Status
|
||||
## 13. Transport invariants
|
||||
|
||||
Do not regress to:
|
||||
|
||||
```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
|
||||
SQLite database copying
|
||||
mandatory GVFS/FUSE mounts
|
||||
exercise-name identity heuristics
|
||||
fake sets for continuous activity
|
||||
fake uniform targets for heterogeneous actual sets
|
||||
overloading frozen Trainlog JSON v1
|
||||
```
|
||||
|
||||
Frozen `TRAINLOG_FORMAT_V1` remains unchanged.
|
||||
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 _END -->
|
||||
## 14. Hardware validation
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
|
|
@ -14,123 +15,30 @@ Run:
|
|||
python tools/validate_json.py
|
||||
```
|
||||
|
||||
The command validates:
|
||||
It validates:
|
||||
|
||||
- `examples/session-v1.json`;
|
||||
- all `tests/fixtures/valid/*.json` as valid;
|
||||
- all `tests/fixtures/invalid/*.json` as invalid.
|
||||
- all positive fixtures;
|
||||
- all negative fixtures.
|
||||
|
||||
A negative fixture passes only when Trainlog rejects it.
|
||||
|
||||
## 3. Validation layers
|
||||
|
||||
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:
|
||||
Semantic validation includes:
|
||||
|
||||
```text
|
||||
database
|
||||
catalog
|
||||
session_detail
|
||||
duration
|
||||
body_metrics
|
||||
bodyviz
|
||||
exercise_performance
|
||||
session_type_schema
|
||||
session_edit
|
||||
body_observation_edit
|
||||
stable-ID uniqueness
|
||||
normalized exercise-name uniqueness
|
||||
timestamp offsets
|
||||
end > start
|
||||
catalog/reference equality
|
||||
tracking-mode consistency
|
||||
load-mode/weight consistency
|
||||
unknown-field rejection
|
||||
non-blank notes
|
||||
zero actual repetitions allowed
|
||||
```
|
||||
|
||||
The session-edit test verifies transactional child replacement without changing
|
||||
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.
|
||||
## 3. Import reconciliation contract
|
||||
|
||||
Run:
|
||||
|
||||
|
|
@ -138,87 +46,171 @@ Run:
|
|||
python tools/validate_import_contract.py
|
||||
```
|
||||
|
||||
Canonical cases cover:
|
||||
|
||||
- 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:
|
||||
Coverage includes:
|
||||
|
||||
```text
|
||||
usb
|
||||
mtp
|
||||
same ID + same name/profile -> reuse
|
||||
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,
|
||||
and rejection of duplicated USB interface children.
|
||||
## 4. Desktop Meson suite
|
||||
|
||||
The `mtp` test covers bounded API argument validation for storage, folder,
|
||||
upload, listing, and download entry points.
|
||||
Current normal suite:
|
||||
|
||||
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
|
||||
trainlog-usb-probe
|
||||
trainlog-mtp-probe
|
||||
trainlog-mtp-exchange-probe
|
||||
trainlog-mtp-roundtrip-probe
|
||||
trainlog-mtp-mobile-export-probe
|
||||
```
|
||||
|
||||
Physical checkpoint result:
|
||||
Current physical baseline:
|
||||
|
||||
```text
|
||||
MTP devices: 1
|
||||
ROUNDTRIP=PASS Trainlog/trainlog-probe.txt
|
||||
USB_MTP_DETECTION=PASS
|
||||
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
|
||||
suite because CI is not expected to have a connected unlocked Android MTP
|
||||
device.
|
||||
<!-- TRAINLOG_MTP_VALIDATION _END -->
|
||||
## 8. Bidirectional synchronization validation
|
||||
|
||||
<!-- TRAINLOG_CONTINUOUS_TEST_CHECKPOINT -->
|
||||
## Profile-aware / continuous validation
|
||||
|
||||
Expected normal test suite after this checkpoint:
|
||||
Validated workflow:
|
||||
|
||||
```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
|
||||
exercise profile schema
|
||||
profiled catalog creation
|
||||
schema migration
|
||||
continuous session persistence
|
||||
continuous session detail loading
|
||||
The TUI also invokes the same engine manually and exposes structured run
|
||||
details.
|
||||
|
||||
## 9. Sanitizers
|
||||
|
||||
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
|
||||
Marche configured CONTINUOUS + DURATION + SPEED_KMH
|
||||
entry asks duration minutes + speed
|
||||
no sets/rest/load prompts
|
||||
continuous_activity row persisted
|
||||
performed_sets count remains zero
|
||||
history reopens as continuous
|
||||
duration and speed render correctly
|
||||
```bash
|
||||
meson compile -C build
|
||||
meson test -C build --print-errorlogs
|
||||
|
||||
python tools/validate_json.py
|
||||
python tools/validate_import_contract.py
|
||||
|
||||
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