7.5 KiB
Architecture
1. System boundary
Trainlog separates capture, durable history, transport, and presentation.
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 desktop SQLite database is the canonical long-term history.
Android local SQLite is a capture store, not a synchronization format.
2. Components
Android application
Responsibilities:
- exercise catalog entry;
- stable-ID exercise rename/editing;
- 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.
Android is not responsible for canonical long-term analytics.
Desktop core
The C17 core owns:
- 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
The Notcurses layer owns interaction and rendering. It is confined to the desktop executable; persistence, synchronization, and core services have no terminal-library dependency.
It consumes core services for:
- session entry/editing;
- history;
- exercise performance;
- body tracking;
- graphs;
- manual synchronization;
- synchronization log/detail display.
trainlog-syncd
trainlog-syncd is a small user-session agent.
It polls for a new Android request and invokes the same shared C synchronization engine used by the TUI.
It does not implement a second synchronization algorithm.
3. Exercise model
Trainlog is metadata-driven:
recording_mode = SETS | CONTINUOUS
tracking_mode = REPS | DURATION
data_fields = SPEED_KMH | DISTANCE_KM
Valid model-v1 combinations:
SETS + REPS
SETS + DURATION
CONTINUOUS + DURATION
Load mode is session-specific:
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:
exercises
sessions
session_exercises
performed_sets
continuous_activity
body_observations
Android
Android has an independent local SQLite schema, currently v4.
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.
TrainlogRepository owns a singleton active-session draft, its ordered exercise
and actual-value children, and raw form text. Compose sends meaningful mutations
to that repository; lifecycle callbacks are not the sole persistence boundary.
Normal navigation never deletes the draft. Home restores the resume affordance
from SQLite after process recreation.
Drafts use separate tables from completed sessions and are never export sources. Finalization inserts the completed session and deletes the draft in one transaction; failures retain the draft. Catalog row references preserve draft identity through existing PC-catalog reconciliation. Missing editing-selection recovery preserves the raw fields and added exercises with a specific warning.
TrainlogRepository.editExercise() owns all Android exercise edits. It changes
the display name and normalized form in the existing catalog row identified by
exercise_id; foreign-key ownership consequently preserves completed history
and active drafts. A profile edit is admitted only before that row is referenced
by either completed or active-draft data. Android and desktop same-ID catalog
reconciliation apply name metadata in place and reject a collision with a
different stable ID.
Compose presentation has one TrainlogScreen header component for every page.
It uses the TUI's compact accent ◆ TRAINLOG ◆ plaque and muted context line;
screen navigation and data ownership remain independent from the header.
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:
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:
physical Android USB device
|
v
libudev discovery
|
| bus + device number
v
libmtp exact raw-device access
|
v
Android internal storage
No GVFS/FUSE mount is required.
Canonical exchange directory:
Download/Trainlog
7. Shared synchronization engine
Both user-trigger paths call:
trainlog_sync_run()
Manual path:
TUI -> trainlog_sync_run()
Android-triggered path:
Android request
-> trainlog-syncd
-> trainlog_sync_run()
-> receipt
The engine performs:
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:
$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:
sy_<uuid-v4>
Structured history is stored under:
$XDG_DATA_HOME/trainlog/sync_runs/
The TUI exposes list/detail semantics comparable to:
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
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.
12. Body analytics boundary
Body analytics belong to the desktop analysis layer.
Android
real measurements only
|
v
desktop body_observations
|
v
pure derived analytics
|
v
TUI display
The analytics layer does not modify canonical observations.
A desktop-only configuration file stores the estimation profile:
$XDG_CONFIG_HOME/trainlog/body_analytics.conf
with ~/.config fallback.
This profile is not synchronized to Android and does not require a SQLite schema change.