From e68b6cdc141d3f45869980d67764867d50502871 Mon Sep 17 00:00:00 2001 From: fy59 Date: Sun, 6 Sep 2026 19:36:21 +0200 Subject: [PATCH] Clean up canonical project documentation --- AGENTS.md | 357 +++++++----- CHANGELOG.md | 427 ++------------ README.md | 212 +++++-- docs/android.md | 885 +++++------------------------ docs/architecture.md | 386 ++++++++----- docs/current_state.md | 146 +++++ docs/database.md | 394 +++++-------- docs/exercise_data_model.md | 364 ++++++------ docs/roadmap.md | 716 +++-------------------- docs/sync_exchange.md | 534 +++++++---------- docs/tests.md | 328 ++++++----- docs/tui.md | 1071 ++++++----------------------------- 12 files changed, 1874 insertions(+), 3946 deletions(-) create mode 100644 docs/current_state.md diff --git a/AGENTS.md b/AGENTS.md index 637f320..52806a2 100644 --- a/AGENTS.md +++ b/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_ exercise +se_ session +bo_ body observation +sy_ 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. diff --git a/CHANGELOG.md b/CHANGELOG.md index a5184c8..f06a0e1 100644 --- a/CHANGELOG.md +++ b/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_` 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. - -### 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). - - - -### 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). - - -### 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. - - -### 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. - - - -### 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. - - - -### 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. - - - -### 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. - - - -### 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. - - - -### 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`. - - - -## 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. - - - -## 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. - - - -## 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. - diff --git a/README.md b/README.md index 5498bcf..fa96c37 100644 --- a/README.md +++ b/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. diff --git a/docs/android.md b/docs/android.md index f1c8342..e11a244 100644 --- a/docs/android.md +++ b/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_ -``` - -When Android creates a new session, it generates: - -```text -se_ -``` - -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. - - -## 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. - - - -## 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. - - - -## 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. - - - -## 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. - - - -## 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 -``` - - - -## 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 -``` - - - -## 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 ``` - - -## 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_ ``` -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_ ``` -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 ``` - - -## 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_ ``` -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 -``` - - - -## 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 -``` - - - -## 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 -``` - - - -## 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. - - - -## 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. - - - -## 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. - +- canonical long-term analytics; +- complex body/performance graphs; +- cloud accounts; +- direct SQLite-file synchronization; +- exercise-name heuristics; +- a mounted-filesystem dependency. diff --git a/docs/architecture.md b/docs/architecture.md index bc34623..1415d5c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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. - - -## 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. - - - -## 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. - +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_ +``` + +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. diff --git a/docs/current_state.md b/docs/current_state.md new file mode 100644 index 0000000..1827fca --- /dev/null +++ b/docs/current_state.md @@ -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. diff --git a/docs/database.md b/docs/database.md index 2542271..7a1eb26 100644 --- a/docs/database.md +++ b/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_ -se_ -bo_ +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 -``` - - -## 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. - - - -## 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. - +The current normal suite contains 19 tests. diff --git a/docs/exercise_data_model.md b/docs/exercise_data_model.md index 56ba9d1..88d0b3c 100644 --- a/docs/exercise_data_model.md +++ b/docs/exercise_data_model.md @@ -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 -``` - - -## 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. - +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. diff --git a/docs/roadmap.md b/docs/roadmap.md index 82b8e12..701c70c 100644 --- a/docs/roadmap.md +++ b/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. - - -## 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. - - - -## 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. - - -```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. - - -## 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 -``` - - - -## 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 -``` - - - -## 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. - - - -## 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. - - - -## 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`. - - - -## 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. - - - -## 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 -``` - - - -## 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 -``` - - - -## 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. - - - -## 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 -``` - - - -## 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 -``` - - - -## 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 -``` - - - -## 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 -``` - +- 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. - -## 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. - - - -## 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. - - - -## 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. - diff --git a/docs/sync_exchange.md b/docs/sync_exchange.md index a1a0009..29fd141 100644 --- a/docs/sync_exchange.md +++ b/docs/sync_exchange.md @@ -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. - - -## 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. - - - -## 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_ ``` -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. - +Header: - -## 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. - - - -## 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 -``` - - - -## 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. - - - -## 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. - - - -## 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_ +``` + +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. - +## 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 +``` diff --git a/docs/tests.md b/docs/tests.md index 8a52065..f695a0b 100644 --- a/docs/tests.md +++ b/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 -``` - - -## 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. - +## 8. Bidirectional synchronization validation - -## 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 ``` - + +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. diff --git a/docs/tui.md b/docs/tui.md index 95cd7fb..02cbfb7 100644 --- a/docs/tui.md +++ b/docs/tui.md @@ -1,632 +1,16 @@ -# TUI +# Desktop TUI ## 1. Purpose -The Trainlog TUI is the primary local history, analysis, and visualization application. +The Trainlog desktop application is a C17/ncursesw interface for durable +history, correction, analysis, visualization, direct data entry, and manual +synchronization. -It is implemented in C17 with `ncursesw`. +Desktop SQLite is the canonical long-term history. -## 2. Primary screens +## 2. Navigation -Initial screen plan: - -- Dashboard; -- Sessions; -- New session; -- Exercises; -- Body; -- Import. - -## 3. Color - -The TUI is intentionally colorful. - -Color reinforces meaning but is never the sole indicator. - -Conceptual roles: - -- accent: titles and current selection; -- success: completed target; -- warning: partial target or attention state; -- error: invalid input or failed operation; -- muted: secondary information; -- graph series: consistent distinguishable colors. - -All color pairs must be centralized in a dedicated theme module. - -Raw screen code must not scatter `COLOR_*` decisions. - -## 4. Monochrome fallback - -Meaningful states also use text or symbols. - -Examples: - -```text -✓ completed -! warning -x failed -> selected -``` - -## 5. UTF-8 and exercise-name normalization - -The TUI uses wide-character ncurses support and initializes locale before ncurses. - -Trainlog v1 duplicate-name validation requires Unicode NFC normalization, whitespace normalization, and Unicode case folding. - -The C implementation must use a tested Unicode library or equivalent implementation that reproduces the v1 contract exactly. - -A likely implementation dependency is `utf8proc`; the final dependency choice is frozen before the relevant C module is implemented. - -## 6. Exercise catalog - -Each exercise stores: - -- stable `exercise_id`; -- mutable display name; -- stable `tracking_mode` (`reps` or `duration`). - -The TUI uses this metadata to select the correct data-entry control. - -A session import may introduce a previously unknown exercise. - -If an existing ID arrives with a different display name, the session may import but the TUI must surface a metadata warning and must not silently rename the canonical local exercise. - -## 7. New session - -The TUI can record a workout directly using the same logical exercise model as Android. - -Per exercise: - -- exercise; -- load mode; -- target sets; -- target repetitions or duration; -- target load when applicable; -- planned rest; -- actual sets; -- optional note. - -## 8. Load semantics - -The TUI must distinguish: - -- no separate load; -- external resistance; -- assistance. - -Analytics must not rank assistance as though more assistance represented more strength. - -Machine-displayed kilograms are stored faithfully but must not be presented as exact cross-machine mechanical equivalence. - -## 9. Dashboard - -The dashboard should eventually show: - -- current body weight; -- recent weight change; -- sessions in a selected period; -- total training duration; -- recent performance highlights; -- compact terminal graphs. - -## 10. Body tracking - -The TUI database may store standalone body observations independently from workout imports. - -The session exchange format can also attach weight and measurements to one session timestamp. - -Body-trend graphs operate on the canonical database representation, not directly on raw JSON files. - -## 11. Graphs - -Terminal-native graph targets include: - -- body-weight trend; -- measurement trend; -- external-load trend; -- measured or estimated maximum trend; -- training-volume trend. - -Assistance exercises require direction-aware analytics. - -## 12. Minimum terminal size - -A minimum supported terminal size will be defined during the first TUI milestone. - -Below that size, Trainlog displays a clear fallback message rather than a corrupted layout. - -## 13. Input safety - -Numeric input is validated before persistent state is committed. - -Invalid input must never partially mutate a saved session. - -Imports use full validation before the database transaction commits. -## 14. Catalog reconciliation during import - -Before creating any exercise or session rows, the TUI classifies incoming exercise metadata against the canonical local catalog. - -Rules: - -```text -same ID + same mode + same normalized name - -> reuse - -same ID + same mode + different name - -> reuse + metadata warning - -same ID + different mode - -> reject entire import - -different ID + same normalized name - -> reject entire import - -new ID + unique normalized name - -> create inside import transaction -``` - -The TUI must never silently merge different exercise IDs merely because names match. - -The TUI must never create two identities with equivalent normalized display names. - -Any hard catalog conflict aborts the complete session import transaction. - -## 15. Generated identifiers - -When the TUI creates a new exercise directly, it generates: - -```text -ex_ -``` - -When the TUI creates a new session directly, it generates: - -```text -se_ -``` - -The database stores these identifiers as opaque stable text. - - -## 16. Current usable TUI checkpoint - -The first usable ncurses interface is implemented. - -Current daily-use flow: - -```text -Dashboard - -> New session - -> History - -> Exercises - -> Body -``` - -Implemented interaction: - -- colored centralized theme; -- bordered screens; -- arrow-key navigation; -- `Enter` activation; -- `F1` new session; -- `F2` history; -- `F3` exercises; -- `F4` body; -- `q` quit; -- minimum terminal fallback at `72x20`. - -The dashboard shows: - -- session count; -- exercise count; -- latest body weight; -- recorded weight delta; -- terminal-native body-weight graph. - -A flat weight history with only one distinct value renders one centered axis -value instead of repeating the same minimum and maximum label. - -## 17. Session history and detail - -History is navigable with the keyboard. - -Selecting a workout and pressing `Enter` opens its detail view. - -For every exercise the detail view exposes: - -- exercise name; -- repetition or duration tracking mode; -- load mode; -- planned rest; -- planned number of sets; -- planned repetitions or duration; -- target load when applicable; -- actual set count; -- ordered performed sets and their actual loads. - -Example: - -```text -Presse à cuisses - -Mode : répétitions -Charge : externe -Repos : 1 min - -Cible : 4 séries × 5 reps -Charge cible : 80.0 kg - -Réalisé: -5@80.0 / 5@80.0 / 5@80.0 / 3@80.0 -``` - -Inside one workout, left/right or up/down changes the selected exercise. `e` opens the persisted session editor without replacing the parent session identity or timestamps. - -Assistance remains direction-aware: more assistance kilograms mean more help, -not greater strength. - -## 18. Duration input and display — implemented - -Persistent duration and rest units remain **seconds**. - -No SQLite schema or Trainlog JSON v1 change is required. - -The TUI parser will accept these equivalent forms: - -```text -90 -90s -1:30 -1m30 -1m30s -``` - -All represent 90 seconds. - -Additional examples: - -```text -2m -> 120 seconds -45s -> 45 seconds -2:05 -> 125 seconds -``` - -A bare integer remains seconds for fast backward-compatible entry. - -Canonical display formatting: - -```text -45 seconds -> 45 s -60 seconds -> 1 min -90 seconds -> 1 min 30 s -120 seconds -> 2 min -125 seconds -> 2 min 5 s -``` - -The same parser and formatter are reused for: - -- timed exercise targets; -- timed actual sets; -- planned rest. - -Invalid malformed forms are rejected before persistence. - -## 19. Body screen and measurement history — implemented - -`F4 Corps` is record-oriented. - -The primary body screen shows: - -- the weight evolution graph; -- recorded body observations newest first; -- a compact summary per observation; -- a visual scrollbar when the history is longer than the visible area. - -Controls: - -```text -↑↓ / PgUp / PgDn select a recorded observation -Enter open observation detail -e edit the selected observation -a add an observation -g open the normalized global overlay -b / Esc return -``` - -Each observation detail is split into two framed pages: - -```text -GENERAL -MEMBRES -``` - -Left/right changes page and `e` edits the observation. - -Editing preserves the observation identity, timestamp, and optional session -link. Enter keeps the existing value, `-` clears a metric, and Escape cancels -the complete edit without persistence. - -Canonical persisted metrics remain: - -```text -body_weight_kg -neck_cm -shoulders_cm -chest_cm -waist_cm -hips_cm -left_arm_cm -right_arm_cm -left_forearm_cm -right_forearm_cm -left_thigh_cm -right_thigh_cm -left_calf_cm -right_calf_cm -``` - -Missing observations are never invented as zero values. - -## 20. Current priority before Android - -The TUI editability checkpoint is complete enough to move toward the Android -input workflow. - -Current order: - -```text -1. finish visual/navigation consistency -2. keep persisted session/body editing safe -3. detect an Android phone over USB/ADB -4. build the minimal Android recorder -5. transfer/import through the frozen Trainlog JSON v1 contract -``` - -Measured-max analytics remain separate from ordinary best-set performance and -are not required for the Android transport milestone. - - - - -## 21. Global body evolution graph — implemented - -`F4 Corps` provides a record-oriented observation history plus a global -normalized overlay view. - -The global graph must not overlay raw kilograms and centimeters directly. - -Each metric is normalized to its own first real observation: - -```text -first recorded value = 100 -``` - -Examples: - -```text -waist 100 -> 96 = -4 % -right arm 100 -> 103 = +3 % -weight 100 -> 98 = -2 % -``` - -This makes unlike units visually comparable without changing persisted data. - -Rules: -- no missing observation becomes zero; -- each metric begins only at its first real value; -- original dates remain ordered; -- every series has both a color and a distinct text/symbol identity; -- left/right limb metrics remain separate; -- normalization is display-only; -- canonical SQLite values remain untouched. - -The global view is toggled from `F4 Corps` with `g`. The normal `F4 Corps` screen remains a newest-first observation history; left/right navigation is used inside observation detail pages. - -The global view also shows a compact percentage summary from first to latest -recorded value for each available metric. - -## 22. Dashboard graph v2 — implemented - -The dashboard body graph is implemented as a compact rolling 12-month -multi-metric summary. - -It shows available body metrics normalized to their first visible value in the -window, while the legend preserves each latest raw value and percentage change. - -The dashboard remains intentionally compact. Detailed absolute observation -history and the complete normalized overlay belong to `F4 Corps`. - -## 23. Current implementation cursor - -```text -TUI_DURATION_HUMAN_INPUT=IMPLEMENTED -TUI_BODY_METRIC_GRAPHS=IMPLEMENTED -TUI_GLOBAL_BODY_OVERLAY=IMPLEMENTED -DASHBOARD_GRAPH_V2=IMPLEMENTED -DASHBOARD_12_MONTHS=IMPLEMENTED -TUI_EXERCISE_PERFORMANCE=IMPLEMENTED -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 -``` - -The frozen Trainlog JSON v1 contract remains unchanged. - - - -## Dashboard graph-only layout - -The home screen avoids duplicating numeric summaries already visible in the -graph. - -The dashboard uses: - -```text -X = recorded date -Y = percentage evolution from the first real value of each metric -``` - -The legend identifies every available series using a symbol, theme color, -human metric name, unit (`kg` or `cm`), and current percentage evolution. - -Detailed absolute values remain available in `F4 Corps`. - -## Dashboard rolling 12-month window - -The dashboard graph uses a rolling calendar window ending in the current month. - -Exactly 12 month slots are displayed. - -Rules: - -- months with no observation remain visible and empty; -- missing months are never filled with zero; -- missing months are never interpolated; -- if several observations exist in one month, the last one is used on the - dashboard; -- each metric is normalized from its first visible month in the 12-month - window; -- the detailed F4 history keeps the exact original timestamps and values. - -The dashboard legend keeps the last visible raw value and the percentage -change over the visible 12-month window. - -## Exercise performance history - -`F3 Exercices` opens an exercise performance screen with `Enter`. - -The current slice tracks representative actual performance per workout without -inventing a maximum. - -Semantics: - -```text -load none - greatest successful reps/duration - -external load - greatest actual load - tie -> greatest reps/duration - -assistance - lowest actual assistance - tie -> greatest reps/duration -``` - -Assistance graphs keep kilograms as actual assistance and explicitly state that -lower assistance is better. - -A recorded best set is not a measured maximum. - -Measured maxima, max-session scheduling and working-load percentages remain a -separate later contract. - - -## Current navigation and editability checkpoint - -Primary large-layout screens share the same visual identity: - -```text -TRAINLOG ASCII banner -top navigation bar -framed page content -footer shortcuts -``` - -The top navigation is: - -```text -0 Accueil 1 Séance 2 Historique 3 Exercices 4 Corps -``` - -Direct shortcuts keep `1`-`4` / `F1`-`F4`; `0` or `Home` returns to the -dashboard. On multi-zone pages, `Tab` / `Shift+Tab` changes focus. Only the -border and title of the focused frame use the warning/yellow role; content -colors are unchanged. - -Large-layout framed/bannnered views include: - -- dashboard; -- history; -- exercise catalog; -- body history; -- new-session type selection; -- in-progress session review; -- session detail; -- exercise performance; -- body-observation detail; -- exercise selection during session entry. - -Session entry can create a missing exercise directly from the exercise chooser -with `a`, then return to the chooser. - -Text prompts treat Escape as immediate cancellation. A cancelled draft or edit -does not persist partial state. - -Persisted session replacement edits only session children. The parent session -row, stable ID, timestamps, session type, notes, and body-observation links are -preserved. - -The dashboard rolling 12-month axis clamps the final `MM/YY` label inside the -dashboard frame so the current-month label does not overwrite the right border. - - - -## Sync page - -The primary TUI navigation includes: - -```text -5 Sync / F5 -``` - -The Sync screen uses the same ASCII banner, top navigation, ncurses frames and -footer conventions as the other primary pages. - -The screen is a live overview of the direct USB/MTP transport: - -- connected physical MTP device; -- USB bus/device and VID:PID; -- device serial when available; -- selected MTP storage; -- free/capacity values; -- readiness of the root `Trainlog` exchange area. - -Incoming categories are presented explicitly: - -```text -Séances -Exercices -Mensurations -``` - -New exercise metadata arriving inside a valid session is intended to reconcile -automatically against the canonical local catalog. - -Body measurements carried by a valid session are imported with that session. - -The opposite direction is also explicit: exercises created directly on the PC -must be exportable to the Android application so both sides use the same stable -exercise IDs, names and tracking modes. This catalog synchronization uses a -separate versioned catalog snapshot; it does not overload or modify the frozen -Trainlog session JSON v1 contract. - -The first Sync-page slice displays remote JSON candidates and the local exercise -count. Actual JSON classification/import and catalog-snapshot export are the -next synchronization slices. - - - -## Sync page checkpoint - -Primary navigation now includes: +Large-layout primary navigation: ```text 0 Accueil @@ -637,228 +21,70 @@ Primary navigation now includes: 5 Sync ``` -`5 Sync / F5` opens a dedicated page rather than drawing over the dashboard. +Direct shortcuts include the matching function keys where implemented. -The page contains: - -- `APPAREIL CONNECTE`; -- `SYNCHRONISATION`. - -Focus rules: +Common controls: ```text -Tab / Shift+Tab switch focused frame -Left/Right move inside top navigation -Enter activate selected navigation item -Up/Down move through synchronization rows -PgUp/PgDn scroll synchronization content -r rescan USB/MTP state -b / Escape return +↑ ↓ list navigation +Enter open/activate +Tab change focus on multi-zone pages +Esc / b return or cancel +0 / Home dashboard +q quit from the application shell ``` -Only the focused frame uses the yellow border/title role. - -The live page reports the connected Android MTP device and exposes these sync -directions: +Minimum terminal size: ```text -Android -> PC - sessions - exercises carried by sessions - body measurements carried by sessions - -PC -> Android - canonical exercise catalog +72x20 ``` -The session exchange remains frozen Trainlog JSON v1. -Catalog synchronization is a separate versioned contract. - +Smaller terminals display a clear fallback instead of corrupt layout. - -## Profile-aware exercise entry +## 3. Visual rules -The TUI form is driven by exercise metadata. +The TUI uses centralized semantic theme roles. + +Color is not the sole state carrier. + +Typical roles: ```text -SETS + REPS - series, reps, optional load, rest - -SETS + DURATION - series, duration, optional load, rest - -CONTINUOUS + DURATION + SPEED_KMH - duration, speed +accent +success +warning +error +muted +graph series ``` -Continuous exercises do not display a set count. +Focused frames use the warning role for border/title without recoloring all +content. -`Marche` will use the continuous form only after its catalog metadata is -explicitly changed; behavior is never inferred from its name. - +## 4. Exercise catalog - -## Continuous exercise TUI — implemented - -Exercise creation supports explicit organization: +Exercise behavior is driven by: ```text -1 séries -2 continu +recording_mode +tracking_mode +data_fields ``` -Continuous creation forces duration tracking in model v1 and can enable: +No exercise-name heuristic determines an entry form. -```text -speed -distance -``` +Catalog identities are stable. -The same creation path is available: +Unicode-aware normalized-name uniqueness prevents duplicate logical names. -```text -from Exercices page -inline while recording a session -``` +## 5. Session entry -Session entry is profile-aware. +The TUI can record sessions directly. -Set-based exercises retain: +Set-based entry supports planned targets and actual work. -```text -charge -sets -reps/duration -rest -performed sets -``` - -Continuous exercises display only their relevant fields. - -For `Marche + SPEED_KMH`: - -```text -Durée (minutes) -Vitesse km/h -``` - -Bare continuous duration input is interpreted as minutes. - -Example: - -```text -15 -> 15 min -> 900 seconds in SQLite -``` - -Session detail is also profile-aware. - -Continuous detail example: - -```text -Mode : continu -Durée : 15 min -Vitesse : 7.0 km/h -Réalisé : activité continue -``` - -Do not render set-oriented labels for a valid continuous activity. - - - -## Android → PC synchronization action - -The Sync page now exposes: - -```text -s synchroniser -``` - -The action performs the complete validated chain: - -```text -detect exact MTP device -→ locate Download/Trainlog/trainlog-mobile-export-v1.json -→ direct libmtp download -→ transactional mobile-export importer -→ refresh desktop overview -``` - -No mount is used. - -The TUI resolves the reference importer relative to `/proc/self/exe`. In the -development layout this means: - -```text -build/tui/trainlog -→ ../../tools/import_mobile_export.py -``` - -and therefore also works when `trainlog` is launched through the user's -`~/.local/bin/trainlog` symlink. - - - -## Sync foundation checkpoint - -The desktop Sync backend has validated physical transport in both directions: - -```text -Android → PC mobile snapshot import -PC → Android canonical exercise catalog publication -``` - -Direct libmtp remains mandatory; no mount is introduced. - -The current category/count presentation is transitional. - -A persistent mobile snapshot is not a pending item, so a displayed -`JSON candidate count` must not be treated as the final synchronization model. - -Next TUI design: - -```text -HISTORIQUE DES SYNCHRONISATIONS - -↑/↓ select -Enter detail -s synchronize -r refresh -``` - -The history/detail interaction should follow the conceptual model of -`git log` / `git show`. - -User-facing session timestamps should be normalized to: - -```text -DD/MM/YYYY HH:MM -``` - -while stored timestamps remain RFC3339. - - - -## 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: +For repetition work, compact actual-set input supports: ```text 5x10 @@ -866,134 +92,175 @@ Accepted repetition examples: 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: +For timed work, the shared duration parser accepts forms such as: ```text -d supprimer +90 +90s +1:30 +1m30 +1m30s +2m ``` -for removing the selected exercise from a current or persisted session draft. -The database replacement remains transactional. +Persistent duration/rest units remain seconds. -`TRAINLOG_FORMAT_V1` remains frozen and unchanged. - +Continuous exercise entry asks for duration and configured supplemental fields +without set/rest/load prompts. - -## Shared bidirectional synchronization v1 +## 6. Session history and editing -Validated architecture: +History is keyboard navigable. + +`Enter` opens full session detail. + +Persisted session editing preserves the parent session identity and timestamps +while replacing child exercise/set data transactionally. + +Inside editable session exercise lists: ```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 +d delete selected exercise from the session ``` -The ncurses TUI and `trainlog-syncd` call the same -`trainlog_sync_run()` implementation. +A failed replacement rolls back completely. -Direct libmtp remains mandatory. No filesystem mount and no SQLite-file -synchronization are introduced. +Removing an exercise from one session does not remove the exercise from the +catalog. -### Concurrency +## 7. Body tracking -The shared engine owns: +`4 Corps / F4` provides: + +- newest-first body observations; +- detail and correction; +- body trend visualization; +- normalized multi-metric overlay; +- left/right metric separation; +- no invented zero values for missing measurements. + +Editing preserves observation identity, timestamp, and optional session link. + +## 8. Dashboard + +The dashboard includes a rolling 12-month normalized body graph. + +Rules include: + +- fixed calendar month slots; +- missing months remain empty; +- no zero fill; +- no interpolation; +- when multiple observations exist in one month, the last visible monthly value + is used for the compact dashboard graph. + +Detailed raw observations remain in `Corps`. + +## 9. Exercise performance + +Exercise detail exposes recorded performance history. + +Representative comparison semantics: ```text -$XDG_DATA_HOME/trainlog/sync.lock +load none + greatest successful reps/duration + +external + greatest actual load + tie -> greatest reps/duration + +assistance + lowest assistance + tie -> greatest reps/duration ``` -A TUI-triggered transaction waits for the lock. Daemon request polling is -non-blocking and retries later. +A best recorded set is not automatically a measured maximum. -### Sync history +## 10. Sync page -Every actual synchronization transaction creates: +`5 Sync / F5` uses the shared synchronization engine. + +The page shows: + +- connected MTP device status; +- storage availability; +- structured synchronization history. + +Manual action: ```text -$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json -$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt +s run bidirectional synchronization +r refresh device status ``` -and appends a compact entry to: +History behaves like a compact Git log: ```text -$XDG_DATA_HOME/trainlog/sync_history.log +↑ ↓ select run +Enter open run detail ``` -The TUI behaves like: +The detail view behaves like a compact `git show` and contains: ```text -git log - ↑/↓ select synchronization - -git show - Enter opens structured detail +sync ID +trigger +time +status +request ID when applicable +Android -> PC counts +PC -> Android catalog count +summary +error when applicable ``` -Legacy three-field history entries remain readable but have no structured -detail file. +## 11. Shared sync engine -### Android request and receipt +The TUI does not own a separate synchronization implementation. -Request: +It calls: ```text -format = trainlog-sync-request -version = 1 +trainlog_sync_run(TRAINLOG_SYNC_TRIGGER_TUI, ...) ``` -Receipt: +The Android-triggered daemon calls the same engine. + +This keeps import/export, MTP publication, locking, history, and diagnostics in +one implementation. + +## 12. Direct MTP + +Transport uses: ```text -format = trainlog-sync-receipt -version = 1 +libudev -> exact physical USB device +libmtp -> storage/object operations ``` -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. +No filesystem mount is required. -### User service +Raw libmtp output is suppressed while ncurses owns the terminal. -Install/refresh the user service with: +## 13. Error behavior + +Input is validated before persistent mutation. + +Escape cancels prompts without committing partial edits. + +Synchronization failure displays a useful final diagnostic and records the +structured run when a transaction actually begins. + +## 14. Build and test + +```bash +meson compile -C build +meson test -C build --print-errorlogs +``` + +Current normal suite: ```text -bash tools/install_syncd_user.sh +19/19 PASS ``` - -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. -