trainlog/docs/reviews/bidirectional_sync_checkpoint.md

3.9 KiB

Bidirectional synchronization checkpoint

Validated status

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_SELECTION=PASS
ANDROID_SAF_FOLDER_CHANGE=PASS

This checkpoint represents the validated synchronization foundation on the physical Samsung device.

Android → PC

Android produces:

Download/Trainlog/trainlog-mobile-export-v1.json

The desktop retrieves it through direct libmtp.

No GVFS/FUSE mount is used.

The desktop importer:

tools/import_mobile_export.py

is:

strict
transactional
idempotent by stable IDs
profile-aware

Validated behavior:

first import:
    new exercise/session/body observation imported

second import:
    no duplicates
    existing stable IDs skipped

PC → Android

The desktop produces:

/tmp/trainlog-pc-catalog-v1.json

with:

format  = trainlog-pc-catalog
version = 1

The catalog is published by direct MTP to:

Download/Trainlog/trainlog-pc-catalog-v1.json

Physical-device publication has been validated.

The Android application accesses the PC-created file through one persistent Storage Access Framework grant.

The selected folder must be:

Download/Trainlog

The Sync screen must always allow changing this folder because a wrong persisted SAF grant must be recoverable without clearing application data.

Important semantics

The synchronization layer exchanges versioned Trainlog domain artifacts.

It does not synchronize SQLite files.

The frozen legacy session format remains:

TRAINLOG_FORMAT_V1=FROZEN

Profile-aware synchronization uses separate explicit synchronization artifacts.

Current TUI state

The existing desktop Sync action can:

Android → PC import
PC → Android catalog publication

The current status/count-oriented Sync presentation is temporary.

A snapshot file remaining on Android is not a pending queue item. Therefore labels such as:

1 JSON candidate

must not be considered a final synchronization UX.

Next synchronization UX

The TUI Sync page must become a persistent synchronization history, similar to:

git log

Example:

06/09/2026 16:00  ✓ Android +1 session · PC catalog 3 exercises
06/09/2026 15:42  ✓ no changes
06/09/2026 15:31  ! device disconnected

A synchronization entry must be selectable.

Enter opens a detailed view analogous to:

git show

The detail must include:

sync ID
trigger
start/end local time
status

Android → PC
    exercises imported/reconciled/skipped
    sessions imported/skipped
    body observations imported/skipped

PC → Android
    catalog exercises published
    Android catalog apply result when available

transport/artifact diagnostics

Android-triggered synchronization

The final Android Sync UX must not require:

Prepare export
then use the PC manually

Target behavior:

Android data change
    -> mobile snapshot maintained automatically

Android "Synchronize now"
    -> synchronization request

PC trainlog-syncd
    -> detects request over direct MTP
    -> runs the same bidirectional sync engine
    -> writes synchronization receipt

Android
    -> reads receipt
    -> applies PC catalog
    -> displays final synchronization result

MTP remains host-initiated. Therefore Android-triggered synchronization needs a small PC-side agent; it cannot directly command libmtp operations on the PC.

Display normalization

User-facing session history must use local presentation:

DD/MM/YYYY HH:MM

Canonical RFC3339 timestamps remain unchanged in persistence and exchange.

Next cursor

SYNC_HISTORY_GIT_LIKE=NEXT
COMMON_SYNC_ENGINE=NEXT
TRAINLOG_SYNCD=NEXT
ANDROID_SYNC_REQUEST_RECEIPT=AFTER
ANDROID_AUTO_OUTBOX=AFTER_AGENT_FOUNDATION