trainlog/docs/roadmap.md
2026-09-06 19:25:13 +02:00

699 lines
16 KiB
Markdown

# Roadmap
## Gate 0 — Project contract
Status: PASS
## Gate 1 — Exchange format v1 freeze
Status: PASS
```text
TRAINLOG_FORMAT_V1=FROZEN
```
## Gate 2 — Persistence + usable TUI
Status: IN PROGRESS
```text
FIRST_USABLE_TUI=PASS
TUI_V0_2_POLISH=IMPLEMENTED
GATE_2=IN_PROGRESS
```
Current TUI capabilities:
- direct workout entry;
- exercise catalog;
- body tracking;
- weight graph;
- colored dashboard;
- arrow/F-key navigation;
- navigable history;
- Unicode anti-duplicate exercise names.
Current remaining Gate 2 direction:
1. detect Android over USB/ADB;
2. build the minimal Android recorder;
3. transfer/export one frozen Trainlog JSON v1 document over USB;
4. import it transactionally into the canonical SQLite store;
5. validate the complete Android -> JSON -> TUI -> SQLite path.
Measured-max semantics remain a later independent analytics contract.
<!-- TRAINLOG_TUI_V02_ROADMAP -->
## TUI v0.2 checkpoint
Current state:
```text
FIRST_USABLE_TUI=PASS
TUI_V0_2_POLISH=IMPLEMENTED
TUI_SESSION_DETAILS=IMPLEMENTED
TUI_DURATION_HUMAN_INPUT=IMPLEMENTED
TUI_BODY_METRIC_GRAPHS=IMPLEMENTED
GATE_2=IN_PROGRESS
TRAINLOG_FORMAT_V1=FROZEN
```
Completed before this checkpoint:
- C17/Meson persistence core;
- SQLite schema v1 foundation;
- UUIDv4 generation;
- Unicode catalog normalization;
- direct workout recording;
- direct body observation recording;
- colored ncursesw dashboard;
- keyboard navigation;
- body-weight graph;
- flat-series graph rendering;
- navigable workout history;
- full read-only session detail.
Next implementation slice:
1. shared duration parser accepting seconds and minute-oriented syntax;
2. shared human duration formatter;
3. generic body-metric history query;
4. F4 metric selector;
5. graphs for weight and every body measurement;
6. left/right asymmetry presentation.
No incompatible change to frozen Trainlog JSON v1 is required.
<!-- TRAINLOG_TUI_V02_ROADMAP _END -->
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_ROADMAP -->
## Next TUI visualization slice
Canonical state after the current checkpoint:
```text
FIRST_USABLE_TUI=PASS
TUI_V0_2_POLISH=IMPLEMENTED
TUI_SESSION_DETAILS=IMPLEMENTED
TUI_DURATION_HUMAN_INPUT=IMPLEMENTED
TUI_BODY_METRIC_GRAPHS=IMPLEMENTED
TUI_GLOBAL_BODY_OVERLAY=IMPLEMENTED
DASHBOARD_GRAPH_ONLY=IMPLEMENTED
GATE_2=IN_PROGRESS
TRAINLOG_FORMAT_V1=FROZEN
```
Next deliverables:
1. normalized global body graph in `F4`;
2. color + symbol identity for every overlaid metric;
3. global percent-change summary;
4. richer home weight graph;
5. previous-measurement delta on dashboard;
6. dashboard min/max weight;
7. latest waist summary when available;
8. compact asymmetry warning when relevant.
No database schema migration is expected.
No Trainlog JSON v1 change is expected.
<!-- TRAINLOG_GLOBAL_BODY_GRAPH_ROADMAP _END -->
```text
DASHBOARD_12_MONTHS=IMPLEMENTED
```
```text
TUI_EXERCISE_PERFORMANCE=IMPLEMENTED
TUI_SESSION_EDIT=IMPLEMENTED
TUI_BODY_OBSERVATION_EDIT=IMPLEMENTED
ANDROID_USB_DETECTION=NEXT
MEASURED_MAX_TRACKING=LATER
```
## Session type / measured max foundation
```text
DATABASE_SCHEMA_V2=IMPLEMENTED
SESSION_TYPE_PERSISTENCE=IMPLEMENTED
SESSION_TYPE_TUI=IMPLEMENTED
TUI_SESSION_EDIT=IMPLEMENTED
TUI_BODY_OBSERVATION_EDIT=IMPLEMENTED
TUI_PRIMARY_NAVIGATION=IMPLEMENTED
TUI_SECONDARY_VIEW_POLISH=IMPLEMENTED
ANDROID_USB_DETECTION=NEXT
MEASURED_MAX_TRACKING=LATER
TRAINLOG_FORMAT_V1=FROZEN
```
SQLite schema v2 adds `sessions.session_type` with `training` and `max_test`.
Existing v1 rows migrate to `training`. No existing session is retroactively
classified as a max test.
## Editable session data
```text
SESSION_EDIT_PERSISTENCE=IMPLEMENTED
SESSION_EDIT_TUI=IMPLEMENTED
BODY_OBSERVATION_EDIT=IMPLEMENTED
USB_PHONE_DETECTION=NEXT
```
Recorded session exercise/set correction uses atomic replacement of child rows
while preserving the parent session row and linked body observations.
The TUI exposes correction both while reviewing an in-progress draft and after
persistence. Escape cancels without committing partial edits.
## Body observation record workflow
```text
BODY_OBSERVATION_HISTORY_UI=IMPLEMENTED
BODY_OBSERVATION_EDIT=IMPLEMENTED
BODY_OBSERVATION_SCROLLBAR=IMPLEMENTED
USB_PHONE_DETECTION=NEXT
```
`F4 Corps` now uses newest-first observation records with detail/edit views
instead of making individual metrics the primary navigation model.
<!-- TRAINLOG_EDITABILITY_ROADMAP -->
## Editability/navigation checkpoint
Completed:
- local schema v2 migration with `training` / `max_test`;
- session-type selection and persistence;
- persisted session editing with stable parent identity;
- body-observation history, detail, and editing;
- Escape-safe prompt cancellation;
- framed ASCII-banner primary and secondary views;
- top `Accueil / Séance / Historique / Exercices / Corps` navigation;
- Tab focus with yellow border-only focus indication;
- direct exercise creation while building a session;
- 12-month dashboard axis kept inside its frame.
Next implementation cursor:
```text
ANDROID_USB_DETECTION=NEXT
ANDROID_MINIMAL_RECORDER=AFTER
JSON_V1_USB_IMPORT_EXPORT=AFTER
```
<!-- TRAINLOG_EDITABILITY_ROADMAP _END -->
<!-- TRAINLOG_MTP_TRANSPORT_ROADMAP -->
## Direct MTP transport checkpoint
```text
USB_MTP_DETECTION=PASS
MTP_STORAGE_ACCESS=PASS
MTP_ROOT_FOLDER_ACCESS=PASS
MTP_WRITE=PASS
MTP_LIST_FOLDER=PASS
MTP_READ=PASS
MTP_ROUNDTRIP=PASS
MTP_TRANSPORT_FOUNDATION=PASS
JSON_V1_MTP_TRANSFER=NEXT
ANDROID_MINIMAL_RECORDER=AFTER
TRAINLOG_FORMAT_V1=FROZEN
```
The Linux side now detects one physical MTP phone without counting USB
interface children, opens the exact device with libmtp, accesses internal
storage, and performs a verified write/list/read roundtrip without mounting the
phone.
Next implementation slice:
```text
1. freeze the MTP exchange directory/file convention
2. roundtrip a real examples/session-v1.json
3. validate/import the downloaded JSON
4. begin the minimal Android recorder
```
<!-- TRAINLOG_MTP_TRANSPORT_ROADMAP _END -->
<!-- TRAINLOG_SYNC_PAGE_ROADMAP -->
## Sync TUI integration
```text
MTP_TRANSPORT_FOUNDATION=PASS
TUI_SYNC_PAGE=IMPLEMENTED
TUI_SYNC_DEVICE_STATUS=IMPLEMENTED
TUI_SYNC_REMOTE_JSON_CANDIDATES=IMPLEMENTED
TUI_SYNC_LOCAL_CATALOG_COUNT=IMPLEMENTED
JSON_V1_MTP_CLASSIFICATION_IMPORT=NEXT
CATALOG_SNAPSHOT_SYNC=AFTER
ANDROID_MINIMAL_RECORDER=AFTER
TRAINLOG_FORMAT_V1=FROZEN
```
Sync direction:
```text
Android -> PC
sessions
new exercises embedded in sessions -> automatic reconciliation
body data embedded in sessions -> automatic import
PC -> Android
canonical exercise catalog snapshot
```
Standalone body-observation exchange, if required by the Android recorder, gets
its own explicit versioned contract rather than changing session JSON v1.
<!-- TRAINLOG_SYNC_PAGE_ROADMAP _END -->
<!-- TRAINLOG_SYNC_FINAL_ROADMAP -->
## Sync UI checkpoint complete
```text
MTP_TRANSPORT_FOUNDATION=PASS
TUI_SYNC_PAGE=IMPLEMENTED
TUI_SYNC_DEVICE_STATUS=IMPLEMENTED
TUI_SYNC_FOCUS_NAVIGATION=IMPLEMENTED
TUI_SYNC_REMOTE_JSON_CANDIDATES=IMPLEMENTED
TUI_SYNC_LOCAL_CATALOG_COUNT=IMPLEMENTED
ANDROID_APP_SCAFFOLD=NEXT
ANDROID_FAKE_DATA_FLOW=AFTER
JSON_V1_ANDROID_EXPORT=AFTER
CATALOG_SNAPSHOT_SYNC=AFTER
TRAINLOG_FORMAT_V1=FROZEN
```
Development now moves to the Android client.
Fictitious sessions, exercises, and body observations are used during Android
development and synchronization testing. The development database will be
purged before normal production use begins.
<!-- TRAINLOG_SYNC_FINAL_ROADMAP _END -->
<!-- TRAINLOG_EXERCISE_DATA_MODEL_ROADMAP -->
## Exercise data-model checkpoint
```text
EXERCISE_DATA_MODEL_V1=FROZEN_FOR_IMPLEMENTATION
DATABASE_SCHEMA_V3=NEXT
PROFILE_AWARE_C_MODEL=AFTER_SCHEMA
PROFILE_AWARE_TUI=AFTER
ANDROID_PROFILE_AWARE_UI=AFTER
SESSION_EXCHANGE_V2=DESIGN_LATER
TRAINLOG_FORMAT_V1=FROZEN
```
The model separates:
```text
recording organization: SETS | CONTINUOUS
primary metric: REPS | DURATION
supplemental fields: SPEED_KMH | DISTANCE_KM
```
Existing schema-v2 data migrates conservatively to `SETS`.
<!-- TRAINLOG_EXERCISE_DATA_MODEL_ROADMAP _END -->
<!-- TRAINLOG_PROFILE_AWARE_ROADMAP_FINAL -->
## Current implementation cursor
```text
MTP_TRANSPORT_FOUNDATION=PASS
TUI_SYNC_PAGE=PASS
EXERCISE_DATA_MODEL_V1=PASS
DATABASE_SCHEMA_V4=PASS
PROFILED_CATALOG_API=PASS
PROFILE_AWARE_EXERCISE_CREATION=PASS
CONTINUOUS_ACTIVITY_PERSISTENCE=PASS
CONTINUOUS_ACTIVITY_DETAIL_DISPLAY=PASS
CONTINUOUS_DURATION_MINUTES_UI=PASS
ANDROID_APP=NEXT
TRAINLOG_FORMAT_V1=FROZEN
PROFILE_AWARE_SESSION_EXCHANGE=DESIGN_LATER
```
Next Android slice:
```text
1. Android project scaffold
2. shared Trainlog visual identity
3. launcher icon = themed T
4. home/navigation
5. session recording
6. inline exercise creation
7. standalone exercise creation
8. standalone body measurement recording
9. fictitious local records
10. only then connect exchange/sync
```
Do not revert continuous activities to performed sets.
Do not modify JSON v1 to accommodate continuous metrics.
<!-- TRAINLOG_PROFILE_AWARE_ROADMAP_FINAL _END -->
<!-- TRAINLOG_ANDROID_LOCAL_CHECKPOINT -->
## Android local checkpoint
```text
ANDROID_PROJECT=PASS
ANDROID_THEME=PASS
ANDROID_EXERCISE_CATALOG=PASS
ANDROID_SESSION_RECORDING=PASS
ANDROID_SESSION_HISTORY=PASS
ANDROID_BODY_RECORDING=PASS
ANDROID_MTP_SYNC=NEXT
```
The next implementation cursor is synchronization between the Android client
and the desktop TUI over the existing direct-MTP transport architecture.
Constraints remain:
```text
no GVFS/FUSE dependency
no SQLite-file synchronization
TRAINLOG_FORMAT_V1 remains frozen
profile-aware data must not be forced into v1
```
<!-- TRAINLOG_ANDROID_LOCAL_CHECKPOINT _END -->
<!-- TRAINLOG_MOBILE_EXPORT_V1 -->
## MTP mobile export v1
Android now prepares a versioned full mobile snapshot at:
```text
Download/Trainlog/trainlog-mobile-export-v1.json
```
The file contains:
```text
exercise profiles
sessions
body observations
```
It is explicitly separate from frozen `TRAINLOG_FORMAT_V1`.
Desktop direct-MTP validation is available through:
```text
./build/tui/trainlog-mtp-mobile-export-probe
```
The probe traverses:
```text
internal storage
→ Download
→ Trainlog
→ trainlog-mobile-export-v1.json
```
and downloads it directly through libmtp without a mount.
Next after hardware PASS:
```text
DESKTOP_MOBILE_EXPORT_IMPORT=NEXT
PC_TO_ANDROID_CATALOG=AFTER
```
<!-- TRAINLOG_MOBILE_EXPORT_V1 _END -->
<!-- TRAINLOG_DESKTOP_MOBILE_IMPORT_CURSOR -->
## Mobile import cursor
```text
MOBILE_EXPORT_MTP=PASS
DESKTOP_MOBILE_IMPORT_V1=IMPLEMENTED
TUI_SYNC_ACTION=NEXT
PC_TO_ANDROID_CATALOG=AFTER
```
The CLI importer is the reference import engine for the next TUI Sync action.
<!-- TRAINLOG_DESKTOP_MOBILE_IMPORT_CURSOR _END -->
<!-- TRAINLOG_TUI_MOBILE_SYNC_CURSOR -->
## Sync implementation cursor
```text
MOBILE_EXPORT_MTP=PASS
DESKTOP_MOBILE_IMPORT_V1=PASS
TUI_ANDROID_TO_PC_SYNC_ACTION=IMPLEMENTED
TUI_ANDROID_TO_PC_SYNC_HARDWARE_VALIDATION=NEXT
PC_TO_ANDROID_CATALOG=AFTER
```
<!-- TRAINLOG_TUI_MOBILE_SYNC_CURSOR _END -->
<!-- TRAINLOG_BIDIRECTIONAL_SYNC_CURSOR -->
## Bidirectional sync cursor
```text
ANDROID_TO_PC_MTP=PASS
DESKTOP_MOBILE_IMPORT=PASS
PC_TO_ANDROID_CATALOG=IMPLEMENTED
SYNC_HISTORY_UI=IMPLEMENTED
BIDIRECTIONAL_HARDWARE_VALIDATION=NEXT
```
<!-- TRAINLOG_BIDIRECTIONAL_SYNC_CURSOR _END -->
<!-- TRAINLOG_SYNC_AGENT_CURSOR -->
## Sync agent cursor
```text
ANDROID_AUTO_OUTBOX=IMPLEMENTED
ANDROID_SYNC_REQUEST=IMPLEMENTED
TRAINLOG_SYNCD=NEXT
ANDROID_SYNC_RECEIPT=AFTER
TUI_SYNC_LOG_SHOW=AFTER_AGENT_FOUNDATION
```
<!-- TRAINLOG_SYNC_AGENT_CURSOR _END -->
<!-- TRAINLOG_SYNC_VALIDATED_ROADMAP -->
## Synchronization cursor
```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
```
Do not regress to:
```text
SQLite file synchronization
filesystem mounts
exercise-name heuristics
manual fake sets for continuous activities
overloading frozen Trainlog JSON v1
```
<!-- TRAINLOG_SYNC_VALIDATED_ROADMAP _END -->
<!-- TRAINLOG_VARIABLE_SET_REPS_V1 -->
## Variable repetition sets
Trainlog preserves each performed set independently.
Accepted repetition input:
```text
5x10
4,5,6,7,8,9,10,9,8,7,6,5,4
4..10..4
```
`4..10..4` expands to:
```text
4,5,6,7,8,9,10,9,8,7,6,5,4
```
Desktop schema v5 permits targetless `SETS` rows for actual-only mobile
observations. Synchronization therefore does not invent a uniform target when
performed sets are heterogeneous.
`performed_sets` remains the source of truth for actual per-set values.
Existing planned desktop sessions may still carry explicit target sets/reps or
target durations.
`trainlog-mobile-export` v1 keeps ordered heterogeneous `sets[]`.
Frozen `TRAINLOG_FORMAT_V1` is unchanged.
<!-- TRAINLOG_VARIABLE_SET_REPS_V1 _END -->
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL -->
## Variable sets and session exercise removal checkpoint
Validated functionality in this checkpoint:
```text
VARIABLE_REPETITION_SETS=PASS
REPETITION_SHORTHAND_5x10=PASS
REPETITION_EXPLICIT_LIST=PASS
REPETITION_PYRAMID=PASS
DESKTOP_SCHEMA_V5=PASS
V4_TO_V5_MIGRATION_REGRESSION=PASS
MOBILE_HETEROGENEOUS_SET_IMPORT=PASS
MOBILE_IMPORT_IDEMPOTENCE=PASS
NO_FAKE_UNIFORM_TARGET=PASS
ANDROID_SESSION_DRAFT_EXERCISE_REMOVE=PASS
DESKTOP_SESSION_EXERCISE_REMOVE=PASS
```
Accepted repetition examples:
```text
5x10
4,5,6,7,8,9,10,9,8,7,6,5,4
4..10..4
```
A heterogeneous mobile session is persisted as ordered `performed_sets`.
The desktop does not invent `target_sets`, `target_reps` or
`target_duration_seconds` for actual-only mobile observations.
On Android, an exercise already added to the current session can be removed
before saving the session.
On the desktop TUI, session editing already supports:
```text
d supprimer
```
for removing the selected exercise from a current or persisted session draft.
The database replacement remains transactional.
`TRAINLOG_FORMAT_V1` remains frozen and unchanged.
<!-- TRAINLOG_VARIABLE_SETS_CHECKPOINT_FINAL _END -->
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 -->
## Shared bidirectional synchronization v1
Validated architecture:
```text
Android local write
-> automatic mobile snapshot
Android "Synchroniser maintenant"
-> trainlog-sync-request-v1.json
trainlog-syncd
-> shared C synchronization engine
-> Android → PC mobile import
-> PC → Android catalog publish
-> trainlog-sync-receipt-v1.json
Android
-> receipt matched by request_id
-> PC catalog applied locally
-> final result displayed
```
The ncurses TUI and `trainlog-syncd` call the same
`trainlog_sync_run()` implementation.
Direct libmtp remains mandatory. No filesystem mount and no SQLite-file
synchronization are introduced.
### Concurrency
The shared engine owns:
```text
$XDG_DATA_HOME/trainlog/sync.lock
```
A TUI-triggered transaction waits for the lock. Daemon request polling is
non-blocking and retries later.
### Sync history
Every actual synchronization transaction creates:
```text
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt
```
and appends a compact entry to:
```text
$XDG_DATA_HOME/trainlog/sync_history.log
```
The TUI behaves like:
```text
git log
↑/↓ select synchronization
git show
Enter opens structured detail
```
Legacy three-field history entries remain readable but have no structured
detail file.
### Android request and receipt
Request:
```text
format = trainlog-sync-request
version = 1
```
Receipt:
```text
format = trainlog-sync-receipt
version = 1
```
The receipt carries the originating `request_id`, a generated `sync_id`,
status, summary and synchronization counts. Android ignores a receipt for a
different request ID.
### User service
Install/refresh the user service with:
```text
bash tools/install_syncd_user.sh
```
No root privilege is required.
### Status
```text
COMMON_SYNC_ENGINE=PASS
TUI_SYNC_LOG_SHOW=PASS
TRAINLOG_SYNCD=PASS
ANDROID_TRIGGERED_SYNC=PASS
ANDROID_SYNC_RECEIPT=PASS
BIDIRECTIONAL_SYNC_V1=PASS
```
Frozen `TRAINLOG_FORMAT_V1` remains unchanged.
<!-- TRAINLOG_SHARED_SYNC_ENGINE_V1 _END -->