release: prepare Trainlog 0.1.1
This commit is contained in:
parent
d4b56a3aa5
commit
7a12dd3728
12 changed files with 628 additions and 72 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -8,6 +8,11 @@ meson-private/
|
||||||
.gradle/
|
.gradle/
|
||||||
local.properties
|
local.properties
|
||||||
**/build/
|
**/build/
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
/android/release-signing.properties
|
||||||
|
/android/release-signing.env
|
||||||
|
/android/keystore.properties
|
||||||
|
|
||||||
# Editors
|
# Editors
|
||||||
*.swp
|
*.swp
|
||||||
|
|
|
||||||
56
CHANGELOG.md
56
CHANGELOG.md
|
|
@ -1,10 +1,60 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
- Documented stable-release distribution through GitHub Releases, including
|
## 0.1.1 — 2026-09-16
|
||||||
|
|
||||||
|
- Completed `TRAINLOG_ANDROID_FIELD_INPUT_REFINEMENT_V1`: active and completed
|
||||||
|
occurrence handles now keep one stable `entry_id`-keyed pointer gesture alive,
|
||||||
|
accumulate total drag distance, jump directly across multiple positions, show
|
||||||
|
residual pointer-following motion, restore the origin on cancellation, and
|
||||||
|
commit only once at completion where persistence applies. Decimal distance,
|
||||||
|
speed, MAX, target-load, and set-load editors use decimal keyboards, accept
|
||||||
|
both `.` and `,`, preserve incomplete typing states, and parse finite
|
||||||
|
locale-independent values only at commit. Units, schemas, formats, identities,
|
||||||
|
active/completed ownership, and version 0.1.1 remain unchanged.
|
||||||
|
|
||||||
|
- Completed `TRAINLOG_ANDROID_COMPLETED_SESSION_REORDER_V1`: the Android
|
||||||
|
completed-session correction form reuses the dedicated localized right-side
|
||||||
|
drag handle and accessibility move actions. Reordering remains transient until
|
||||||
|
Save; Cancel/Back writes nothing, while Save atomically replaces positions and
|
||||||
|
any factual corrections under the same session/entry/exercise identities.
|
||||||
|
Stable Compose keys keep editor state attached to `entry_id`; equipment,
|
||||||
|
targets, actuals, MAX, feedback revisions, and J+1 ownership survive. Existing
|
||||||
|
V3 synchronization reconciles the same desktop session without duplication.
|
||||||
|
No schema, exchange format, active-session behavior, or version changed.
|
||||||
|
|
||||||
|
- Completed `TRAINLOG_ANDROID_FIELD_WORKFLOW_REFINEMENT_V1`: Android now uses a
|
||||||
|
full-width semantic-success **Add to session** action, provides a dedicated
|
||||||
|
accessible right-side drag handle for durable active-draft reordering, and
|
||||||
|
supports atomic completed-session factual correction. Reorder and correction
|
||||||
|
retain stable session, occurrence, exercise, equipment, plan, actual, feedback,
|
||||||
|
and follow-up ownership; corrected facts continue through the existing V3
|
||||||
|
synchronization identity. Continuous values reject non-finite input and show
|
||||||
|
explicit km, km/h, and seconds units. No SQLite schema, exchange format,
|
||||||
|
desktop/TUI behavior, or product version changed.
|
||||||
|
|
||||||
|
- Documented stable-release distribution through GitHub and Forgejo Releases,
|
||||||
|
including
|
||||||
prebuilt Android and architecture-specific dynamically linked Linux assets,
|
prebuilt Android and architecture-specific dynamically linked Linux assets,
|
||||||
SHA-256 verification, runtime/build/optional dependencies, and the release
|
SHA-256 verification, runtime/build/optional dependencies, and the release
|
||||||
signing and exact-tag requirements. No v0.1.0 release is claimed until a
|
signing and exact-tag requirements. The Android artifact is built with the
|
||||||
signed APK and authenticated GitHub publication path are available.
|
durable Trainlog release identity and is verified before publication.
|
||||||
|
|
||||||
|
- Completed `TRAINLOG_I18N_V0_1_1`: Trainlog 0.1.1 is one synchronized Android
|
||||||
|
and desktop product version, with French as the default interface language
|
||||||
|
and English selectable through **Settings → Language** on both surfaces.
|
||||||
|
The preference is local presentation state (dedicated Android
|
||||||
|
SharedPreferences; desktop XDG `trainlog/presentation.conf`) and successful
|
||||||
|
selection updates the interface immediately. Localized Trainlog-owned text,
|
||||||
|
BODY ZONE labels selected by stable IDs, and visible number/date formatting
|
||||||
|
do not change training data, user exercise/catalogue names, IDs, SQLite,
|
||||||
|
schemas, exchange/protocol artifacts, AI, MAX, or feedback. Desktop and
|
||||||
|
Android synchronization views render local typed status/counters; raw
|
||||||
|
core/receipt/history summaries remain opaque protocol/operational bytes and
|
||||||
|
are never injected as cross-device French text. Final validation passed
|
||||||
|
60/60 normal and 60/60 ASan/UBSan desktop tests, Android 202 total tests
|
||||||
|
(198 passed, 4 skipped, 0 failed), both validators, source-derived
|
||||||
|
`TRANSLATABLE_UI=0`, and Android resource parity. Real device/manual visual
|
||||||
|
switching remains an explicit smoke validation.
|
||||||
|
|
||||||
- Completed `TRAINLOG_FULL_REPOSITORY_DOCUMENTATION_AUDIT_V2`: added the
|
- Completed `TRAINLOG_FULL_REPOSITORY_DOCUMENTATION_AUDIT_V2`: added the
|
||||||
canonical documentation index, made README a concise entry point, separated
|
canonical documentation index, made README a concise entry point, separated
|
||||||
|
|
|
||||||
58
README.md
58
README.md
|
|
@ -1,9 +1,10 @@
|
||||||
# Trainlog
|
# Trainlog
|
||||||
|
|
||||||
Trainlog is a local-first workout and body-data system. Its native Android app
|
Trainlog is a local-first workout and body-data system. Trainlog Core owns
|
||||||
is the lightweight field companion for fast capture and glanceable summaries;
|
business truth and canonical desktop persistence. Its native Android app is the
|
||||||
its C17/Notcurses desktop TUI is the detailed surface for correction,
|
field companion, its C17/Notcurses desktop TUI is the administration and
|
||||||
catalogue management, analytics, graphs, and long-term follow-up.
|
technical surface, and its documented future local Web sibling is the analysis,
|
||||||
|
visualization, and program/session-preparation surface.
|
||||||
|
|
||||||
Android and desktop each own a local SQLite database. The desktop database is
|
Android and desktop each own a local SQLite database. The desktop database is
|
||||||
the canonical long-term history. Trainlog synchronizes versioned JSON artifacts
|
the canonical long-term history. Trainlog synchronizes versioned JSON artifacts
|
||||||
|
|
@ -13,26 +14,41 @@ over direct MTP; it never copies SQLite database files between devices.
|
||||||
|
|
||||||
| Surface | Primary responsibility |
|
| Surface | Primary responsibility |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Android | Capture sets, repetitions, loads, durations, continuous activities, feedback, J+1 follow-ups, body measurements, active drafts, and AI proposals; trigger sync; show quick summaries. |
|
| Android | Capture and quickly correct sets, repetitions, loads, durations, and continuous activities; reorder active and completed-session occurrences; capture feedback, J+1 follow-ups, body measurements, and AI proposals; trigger sync; show quick summaries. |
|
||||||
| Desktop TUI | Consult and correct canonical history; manage exercises and equipment; analyze body data, MAX results, training statistics, body zones, calendar periods, and longitudinal graphs. |
|
| Desktop TUI | Administer, inspect, maintain, import/export, correct canonical history, and provide technical tools. |
|
||||||
|
| Local Web (planned) | Analyze and visualize canonical data; prepare programs and sessions through typed Trainlog Core services. |
|
||||||
|
|
||||||
In short: **Android captures and summarizes. The TUI analyzes and tracks over
|
No interface reconstructs business truth from SQLite tables. The local Web is
|
||||||
time.** Android intentionally remains lightweight for analytics.
|
a sibling adapter, not an extension of the TUI; `TRAINLOG_WEB_V1` is currently
|
||||||
|
contract-only and has no implementation.
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
Prebuilt Trainlog releases are published on both official mirrors:
|
When a Trainlog release is published, its prebuilt assets are available from
|
||||||
|
both official mirrors:
|
||||||
|
|
||||||
- [GitHub Releases](https://github.com/labfytools/trainlog/releases)
|
- [GitHub Releases](https://github.com/labfytools/trainlog/releases)
|
||||||
- [Forgejo Releases](https://git.labfytools.com/fy59/trainlog/releases)
|
- [Forgejo Releases](https://git.labfytools.com/fy59/trainlog/releases)
|
||||||
|
|
||||||
Each stable release provides:
|
Each published stable release provides:
|
||||||
|
|
||||||
- a signed Android APK;
|
- a signed Android APK;
|
||||||
- a Linux x86-64 TUI executable;
|
- a Linux x86-64 TUI executable;
|
||||||
- SHA-256 checksums.
|
- SHA-256 checksums.
|
||||||
|
|
||||||
Both mirrors publish the same Trainlog version and release assets.
|
Both mirrors publish the same Trainlog product version and release assets. The
|
||||||
|
implemented product version is **0.1.1** on Android and desktop; this is one
|
||||||
|
shared Trainlog version, not separate interface versions. Version 0.1.1 is the
|
||||||
|
current stable release.
|
||||||
|
|
||||||
|
Version 0.1.1 presents Trainlog in French by default, with English selectable
|
||||||
|
from **Settings → Language** on Android and the desktop TUI. The selection is
|
||||||
|
local to that installation and updates the visible interface immediately; it
|
||||||
|
does not translate user exercise/catalogue names or change training data,
|
||||||
|
stable IDs, databases, schemas, exchange artifacts, synchronization protocols,
|
||||||
|
AI, MAX, or feedback. Synchronization status is rendered locally from typed
|
||||||
|
status and counters; raw protocol and operational summaries are not translated
|
||||||
|
or injected into the other surface.
|
||||||
|
|
||||||
See the [documentation](docs/README.md) and [changelog](CHANGELOG.md) for more
|
See the [documentation](docs/README.md) and [changelog](CHANGELOG.md) for more
|
||||||
details.
|
details.
|
||||||
|
|
@ -41,7 +57,7 @@ details.
|
||||||
|
|
||||||
### Download a prebuilt release
|
### Download a prebuilt release
|
||||||
|
|
||||||
You can download Trainlog from either official mirror:
|
For a published release, download Trainlog from either official mirror:
|
||||||
|
|
||||||
- [GitHub](https://github.com/labfytools/trainlog/releases)
|
- [GitHub](https://github.com/labfytools/trainlog/releases)
|
||||||
- [Forgejo](https://git.labfytools.com/fy59/trainlog/releases)
|
- [Forgejo](https://git.labfytools.com/fy59/trainlog/releases)
|
||||||
|
|
@ -55,8 +71,8 @@ not a universally portable Linux binary: the host must provide compatible
|
||||||
runtime libraries listed under [Dependencies](#dependencies).
|
runtime libraries listed under [Dependencies](#dependencies).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x trainlog-tui-linux-x86_64-v0.1.0
|
chmod +x trainlog-tui-linux-x86_64-v<version>
|
||||||
./trainlog-tui-linux-x86_64-v0.1.0
|
./trainlog-tui-linux-x86_64-v<version>
|
||||||
```
|
```
|
||||||
|
|
||||||
The executable can remain in the download directory. Moving it into a directory
|
The executable can remain in the download directory. Moving it into a directory
|
||||||
|
|
@ -94,6 +110,19 @@ JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleRelease
|
||||||
is configured by the release operator. The repository does not contain signing
|
is configured by the release operator. The repository does not contain signing
|
||||||
credentials and does not silently generate them.
|
credentials and does not silently generate them.
|
||||||
|
|
||||||
|
Release hosts provide `TRAINLOG_RELEASE_STORE_FILE`,
|
||||||
|
`TRAINLOG_RELEASE_STORE_PASSWORD`, `TRAINLOG_RELEASE_KEY_ALIAS`, and
|
||||||
|
`TRAINLOG_RELEASE_KEY_PASSWORD`, or a private
|
||||||
|
`~/.config/trainlog/release-signing.properties` file containing the equivalent
|
||||||
|
`storeFile`, `storePassword`, `keyAlias`, and `keyPassword` keys. Environment
|
||||||
|
variables take precedence. Keystores and local credential files are ignored by
|
||||||
|
Git; neither belongs in source control, build logs, or release notes.
|
||||||
|
|
||||||
|
The Android release key is the permanent update identity for the application.
|
||||||
|
The release operator must preserve an encrypted external backup of both the
|
||||||
|
keystore and its credentials. Losing either prevents future compatible Android
|
||||||
|
updates; generating a replacement key is not a normal release procedure.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
### Linux runtime dependencies
|
### Linux runtime dependencies
|
||||||
|
|
@ -200,6 +229,7 @@ As of 2026-09-15:
|
||||||
- `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2`;
|
- `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2`;
|
||||||
- `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING` pending a real Drive plus
|
- `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING` pending a real Drive plus
|
||||||
Android-triggered bidirectional smoke test.
|
Android-triggered bidirectional smoke test.
|
||||||
|
- `TRAINLOG_WEB_V1=CONTRACT_FROZEN / IMPLEMENTATION_NOT_STARTED`.
|
||||||
|
|
||||||
The latest executable result belongs in
|
The latest executable result belongs in
|
||||||
[current state](docs/current_state.md), not in multiple README narratives.
|
[current state](docs/current_state.md), not in multiple README narratives.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,40 @@
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.plugin.compose")
|
id("org.jetbrains.kotlin.plugin.compose")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CONTRACT: release-signing material is machine-local. Environment variables
|
||||||
|
* take precedence over the private properties file so CI and release hosts can
|
||||||
|
* inject secrets without copying them into the source tree.
|
||||||
|
*/
|
||||||
|
val releaseSigningProperties = Properties()
|
||||||
|
val releaseSigningPropertiesPath =
|
||||||
|
providers.environmentVariable("TRAINLOG_RELEASE_CREDENTIALS_FILE").orNull
|
||||||
|
?: "${System.getProperty("user.home")}/.config/trainlog/release-signing.properties"
|
||||||
|
val releaseSigningPropertiesFile = file(releaseSigningPropertiesPath)
|
||||||
|
if (releaseSigningPropertiesFile.isFile) {
|
||||||
|
releaseSigningPropertiesFile.inputStream().use(releaseSigningProperties::load)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun releaseSigningValue(environmentName: String, propertyName: String): String? =
|
||||||
|
providers.environmentVariable(environmentName).orNull
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?: releaseSigningProperties.getProperty(propertyName)?.takeIf { it.isNotBlank() }
|
||||||
|
|
||||||
|
val releaseStoreFile = releaseSigningValue("TRAINLOG_RELEASE_STORE_FILE", "storeFile")
|
||||||
|
val releaseStorePassword = releaseSigningValue("TRAINLOG_RELEASE_STORE_PASSWORD", "storePassword")
|
||||||
|
val releaseKeyAlias = releaseSigningValue("TRAINLOG_RELEASE_KEY_ALIAS", "keyAlias")
|
||||||
|
val releaseKeyPassword = releaseSigningValue("TRAINLOG_RELEASE_KEY_PASSWORD", "keyPassword")
|
||||||
|
val releaseSigningComplete = listOf(
|
||||||
|
releaseStoreFile,
|
||||||
|
releaseStorePassword,
|
||||||
|
releaseKeyAlias,
|
||||||
|
releaseKeyPassword,
|
||||||
|
).all { !it.isNullOrBlank() }
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.labfytools.trainlog"
|
namespace = "com.labfytools.trainlog"
|
||||||
compileSdk = 37
|
compileSdk = 37
|
||||||
|
|
@ -12,12 +44,31 @@ android {
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
|
|
||||||
versionCode = 1
|
versionCode = 2
|
||||||
versionName = "0.1.0"
|
versionName = "0.1.1"
|
||||||
testInstrumentationRunner =
|
testInstrumentationRunner =
|
||||||
"androidx.test.runner.AndroidJUnitRunner"
|
"androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (releaseSigningComplete) {
|
||||||
|
create("release") {
|
||||||
|
storeFile = file(requireNotNull(releaseStoreFile))
|
||||||
|
storePassword = requireNotNull(releaseStorePassword)
|
||||||
|
keyAlias = requireNotNull(releaseKeyAlias)
|
||||||
|
keyPassword = requireNotNull(releaseKeyPassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
getByName("release") {
|
||||||
|
if (releaseSigningComplete) {
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
|
@ -53,6 +104,27 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* WHY: Android Gradle otherwise emits app-release-unsigned.apk successfully,
|
||||||
|
* which is build evidence but can be mistaken for a publishable artifact.
|
||||||
|
* INVARIANT: every task that packages a release fails before packaging unless
|
||||||
|
* all four external values exist and the configured keystore is a regular
|
||||||
|
* file. Gradle/apksigner still perform the cryptographic validation. */
|
||||||
|
tasks.matching { it.name == "packageRelease" }.configureEach {
|
||||||
|
doFirst {
|
||||||
|
if (!releaseSigningComplete) {
|
||||||
|
throw GradleException(
|
||||||
|
"Trainlog release signing is not configured. Set the four " +
|
||||||
|
"TRAINLOG_RELEASE_* variables or provide the private " +
|
||||||
|
"release-signing.properties file.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val configuredStore = file(requireNotNull(releaseStoreFile))
|
||||||
|
if (!configuredStore.isFile) {
|
||||||
|
throw GradleException("Trainlog release keystore does not exist: $configuredStore")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val composeBom =
|
val composeBom =
|
||||||
platform("androidx.compose:compose-bom:2026.08.00")
|
platform("androidx.compose:compose-bom:2026.08.00")
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ evidence and chronology.
|
||||||
glanceable summaries, permissions, and Android interaction rules.
|
glanceable summaries, permissions, and Android interaction rules.
|
||||||
- [Desktop TUI](tui.md) owns Notcurses navigation, correction, detailed
|
- [Desktop TUI](tui.md) owns Notcurses navigation, correction, detailed
|
||||||
consultation, analytics, graphs, and long-term follow-up behavior.
|
consultation, analytics, graphs, and long-term follow-up behavior.
|
||||||
|
- [Architecture](architecture.md) owns the contract for the planned local Web
|
||||||
|
sibling, including Core/API boundaries, loopback networking, shell,
|
||||||
|
Dashboard, layout, build/runtime separation, and security invariants.
|
||||||
|
|
||||||
## Data model
|
## Data model
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,50 @@ It is a native Kotlin/Jetpack Compose application with local SQLite persistence.
|
||||||
|
|
||||||
The desktop remains the canonical long-term history and analytics store.
|
The desktop remains the canonical long-term history and analytics store.
|
||||||
|
|
||||||
|
## Interface language (`TRAINLOG_I18N_V0_1_1`)
|
||||||
|
|
||||||
|
Trainlog version 0.1.1 is synchronized with the desktop TUI as one product
|
||||||
|
version; Android does not have an independent interface version. French is the
|
||||||
|
default interface language. The only alternate language is English, selected
|
||||||
|
from **Settings → Language**. The selection updates the Compose UI immediately.
|
||||||
|
|
||||||
|
`LanguageSettingsOwner` stores exactly one language tag in its dedicated
|
||||||
|
`trainlog_presentation_settings` SharedPreferences file. An absent or invalid
|
||||||
|
value deterministically selects French. This is presentation state only: it
|
||||||
|
does not call the repository and is never stored in Android SQLite, exported,
|
||||||
|
imported, or synchronized.
|
||||||
|
|
||||||
|
Translations apply to Trainlog-owned UI text, accessibility text, and visible
|
||||||
|
number/date formatting. They never translate user exercise or catalogue names,
|
||||||
|
stable IDs, JSON/protocol literals, training data, schemas, AI proposals, MAX,
|
||||||
|
or feedback. Recognized BODY ZONES are presented from their stable IDs; an
|
||||||
|
unknown future ID deliberately retains its catalogue label rather than changing
|
||||||
|
domain data.
|
||||||
|
|
||||||
|
The current synchronization view renders local typed status and counters. It
|
||||||
|
does not translate, reinterpret, or inject a raw desktop/core/receipt/history
|
||||||
|
summary: those protocol and operational bytes remain opaque. This presentation
|
||||||
|
boundary changes no Android import, export, receipt, or history format.
|
||||||
|
|
||||||
|
The active-session editor presents its add action as a full-width semantic
|
||||||
|
success button. Existing occurrences can be moved from a dedicated right-side
|
||||||
|
drag handle; the resulting list is persisted once the gesture completes. The
|
||||||
|
permutation retains `session_id`, `entry_id`, `exercise_id`, equipment, targets,
|
||||||
|
actual form values, MAX results, and feedback ownership. A failed write reloads
|
||||||
|
the last durable ordering instead of leaving presentation and SQLite divergent.
|
||||||
|
The pointer gesture is keyed by stable `entry_id`, accumulates its full vertical
|
||||||
|
delta, and calculates a direct bounded destination from the gesture origin. A
|
||||||
|
single drag can therefore cross several positions while the handle follows the
|
||||||
|
residual pointer offset and surrounding occurrences move away. Cancellation
|
||||||
|
restores the origin permutation; completion performs one draft write.
|
||||||
|
|
||||||
|
Decimal capture fields use a decimal-capable Android keyboard and retain raw
|
||||||
|
editing text. Both `.` and `,` are accepted independently of interface language,
|
||||||
|
including intermediate forms such as `0.`, `0,`, `.3`, and `,3`. Commit replaces
|
||||||
|
`,` with `.` and parses a finite locale-independent number before applying the
|
||||||
|
existing positive/non-negative domain rule. Distance remains explicitly km; no
|
||||||
|
unit guessing or automatic correction occurs.
|
||||||
|
|
||||||
## Session exchange V3
|
## Session exchange V3
|
||||||
|
|
||||||
Completed session occurrences persist an `entry_id`; it is never regenerated
|
Completed session occurrences persist an `entry_id`; it is never regenerated
|
||||||
|
|
@ -79,6 +123,21 @@ Completed Session Detail exposes **Modifier la séance**. Its bounded form is
|
||||||
initialized from occurrence snapshots and writes nothing before Save. Save
|
initialized from occurrence snapshots and writes nothing before Save. Save
|
||||||
replaces all children in one transaction while retaining `session_id`, every
|
replaces all children in one transaction while retaining `session_id`, every
|
||||||
retained `entry_id`, feedback roots/revisions, and the session follow-up parent.
|
retained `entry_id`, feedback roots/revisions, and the session follow-up parent.
|
||||||
|
The form edits only facts allowed by each stored occurrence profile, including
|
||||||
|
repetitions, set load, set duration, continuous duration, distance in kilometres,
|
||||||
|
and speed in km/h. The existing V3 snapshot republishes those corrected facts,
|
||||||
|
so synchronization updates the same stable session and occurrences rather than
|
||||||
|
creating correction IDs.
|
||||||
|
The same dedicated right-side handle can reorder completed occurrences in this
|
||||||
|
form. Its list permutation remains transient until Save; Cancel or Back performs
|
||||||
|
no repository mutation. Stable `entry_id` keys keep Compose editor/focus state
|
||||||
|
with the same occurrence even when duplicate exercises exchange positions. Save
|
||||||
|
persists facts and positions together through the same atomic replacement, and
|
||||||
|
a failed Save restores the original completed order with all child ownership.
|
||||||
|
Completed decimal fields likewise retain keyed raw text until Save, so typing a
|
||||||
|
separator is not collapsed into a premature numeric value. Invalid incomplete,
|
||||||
|
malformed, non-finite, or negative final values block the transaction with a
|
||||||
|
localized error.
|
||||||
Set and occurrence removal require explicit confirmation; Cancel writes
|
Set and occurrence removal require explicit confirmation; Cancel writes
|
||||||
nothing. Exercise Detail separately exposes **Modifier l'exercice**. A
|
nothing. Exercise Detail separately exposes **Modifier l'exercice**. A
|
||||||
confirmed incompatible catalogue edit controls future Android occurrences;
|
confirmed incompatible catalogue edit controls future Android occurrences;
|
||||||
|
|
@ -607,8 +666,9 @@ trainlog-sync-receipt-v1.json
|
||||||
|
|
||||||
The receipt is matched by `request_id`.
|
The receipt is matched by `request_id`.
|
||||||
|
|
||||||
On success Android then applies the latest PC catalog and displays the final
|
On success Android then applies the latest PC catalog and renders the final
|
||||||
result.
|
local typed status/counters. Raw receipt summaries remain opaque operational
|
||||||
|
bytes and are not displayed as injected cross-device text.
|
||||||
|
|
||||||
Before applying the PC catalog or its V2 artifacts, Android applies
|
Before applying the PC catalog or its V2 artifacts, Android applies
|
||||||
`trainlog-pc-equipment-definitions-v1.json`. Thus custom definitions are known
|
`trainlog-pc-equipment-definitions-v1.json`. Thus custom definitions are known
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
## 1. System boundary
|
## 1. System boundary
|
||||||
|
|
||||||
Trainlog separates capture, durable history, transport, and presentation.
|
Trainlog Core is the product authority and separates capture, durable history,
|
||||||
|
business logic, transport, and presentation. The TUI, Web, and Android are
|
||||||
|
sibling adapters with different product responsibilities; none owns a parallel
|
||||||
|
business model.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Android capture client
|
Android field client
|
||||||
|
|
|
|
||||||
| local SQLite
|
| local SQLite
|
||||||
|
|
|
|
||||||
|
|
@ -23,10 +26,16 @@ Android capture client
|
||||||
desktop SQLite directional PC artifacts
|
desktop SQLite directional PC artifacts
|
||||||
| |
|
| |
|
||||||
v v
|
v v
|
||||||
desktop TUI Android
|
Trainlog Core Android
|
||||||
|
|
|
||||||
|
+-- desktop TUI
|
||||||
|
+-- local Web adapter -> browser
|
||||||
```
|
```
|
||||||
|
|
||||||
The desktop SQLite database is the canonical long-term history.
|
The desktop SQLite database is the canonical local source of truth and
|
||||||
|
long-term history. Core owns data, persistence, business logic, calculations,
|
||||||
|
read models, command services, and versioned contracts. An interface must not
|
||||||
|
query tables directly to reconstruct its own business rules.
|
||||||
|
|
||||||
Android local SQLite is a capture store, not a synchronization format.
|
Android local SQLite is a capture store, not a synchronization format.
|
||||||
|
|
||||||
|
|
@ -48,14 +57,23 @@ Responsibilities:
|
||||||
- PC catalog application;
|
- PC catalog application;
|
||||||
- synchronization request creation;
|
- synchronization request creation;
|
||||||
- synchronization receipt display.
|
- synchronization receipt display.
|
||||||
|
- retrieval of a session prepared through Trainlog;
|
||||||
|
- future sensor-data acquisition, including BLE heart rate.
|
||||||
|
|
||||||
Android is not responsible for canonical long-term analytics.
|
Android is the field adapter for gym use, actual-work capture, feedback, and
|
||||||
|
returning observations to Trainlog. It is not responsible for canonical
|
||||||
|
long-term analytics or business truth and never accesses desktop SQLite
|
||||||
|
directly.
|
||||||
|
|
||||||
### Desktop core
|
### Desktop core
|
||||||
|
|
||||||
The C17 core owns:
|
The C17 core owns:
|
||||||
|
|
||||||
|
- data and domain rules;
|
||||||
- desktop SQLite persistence;
|
- desktop SQLite persistence;
|
||||||
|
- business calculations;
|
||||||
|
- typed read models and transactional command services;
|
||||||
|
- versioned contracts;
|
||||||
- exercise/catalog rules;
|
- exercise/catalog rules;
|
||||||
- generated body-zone taxonomy and relation APIs;
|
- generated body-zone taxonomy and relation APIs;
|
||||||
- profile-aware session data;
|
- profile-aware session data;
|
||||||
|
|
@ -67,9 +85,10 @@ The C17 core owns:
|
||||||
|
|
||||||
### TUI
|
### TUI
|
||||||
|
|
||||||
The Notcurses layer owns interaction and rendering. It is confined to the
|
The TUI is the administration, inspection, maintenance, import/export, and
|
||||||
desktop executable; persistence, synchronization, and core services have no
|
technical-tooling adapter. The Notcurses layer owns terminal interaction and
|
||||||
terminal-library dependency.
|
rendering. It is confined to the desktop executable; persistence,
|
||||||
|
synchronization, and core services have no terminal-library dependency.
|
||||||
|
|
||||||
It consumes core services for:
|
It consumes core services for:
|
||||||
|
|
||||||
|
|
@ -100,6 +119,145 @@ layout is compact from 72x20, has a sidebar from 100x26, and expands it from
|
||||||
and run-scoped terminal planes keep Notcurses infrastructure separate from
|
and run-scoped terminal planes keep Notcurses infrastructure separate from
|
||||||
product semantics.
|
product semantics.
|
||||||
|
|
||||||
|
The current Dashboard implementation retains one known layering violation:
|
||||||
|
`tui.c` uses SQLite and `database_internal.h` directly to build Dashboard facts
|
||||||
|
and calculations. This is a blocking debt for the Web Dashboard. Before any
|
||||||
|
Dashboard HTTP endpoint exists, characterization tests must capture the current
|
||||||
|
behavior, the query and calculations must move incrementally into a typed Core
|
||||||
|
read model, and the TUI must consume that read model. This requirement does not
|
||||||
|
authorize a global `tui.c` refactor.
|
||||||
|
|
||||||
|
### Local Web application
|
||||||
|
|
||||||
|
`TRAINLOG_WEB_V1` is a sibling adapter of the TUI and Android, never an
|
||||||
|
extension of the TUI. It is launched by either:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trainlog -w
|
||||||
|
trainlog --web
|
||||||
|
```
|
||||||
|
|
||||||
|
Trainlog serves the Web application and its versioned HTTP API to a browser.
|
||||||
|
The Web owns analysis, visualization, program preparation, and session
|
||||||
|
preparation. Initial API introduction is read-only; later program and session
|
||||||
|
mutations must call explicit transactional Core command services. Generic CRUD
|
||||||
|
over SQLite tables is forbidden.
|
||||||
|
|
||||||
|
The V1 network contract is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
address = 127.0.0.1
|
||||||
|
default port = 8080
|
||||||
|
explicit override = trainlog -w --port <port>
|
||||||
|
IPv6 = out of scope
|
||||||
|
```
|
||||||
|
|
||||||
|
A requested occupied port is an explicit startup failure. Trainlog never
|
||||||
|
selects another port silently and never binds to a LAN address implicitly.
|
||||||
|
|
||||||
|
The architectural boundary is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Browser
|
||||||
|
|
|
||||||
|
| HTTP / independently versioned /api/v1/
|
||||||
|
v
|
||||||
|
Web adapter
|
||||||
|
|
|
||||||
|
| typed Core services
|
||||||
|
v
|
||||||
|
Trainlog Core
|
||||||
|
|
|
||||||
|
v
|
||||||
|
desktop SQLite
|
||||||
|
```
|
||||||
|
|
||||||
|
The following dependencies are forbidden:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Browser -> SQLite
|
||||||
|
React -> SQL
|
||||||
|
Web route -> database_internal.h
|
||||||
|
Web route -> duplicated TUI calculation
|
||||||
|
```
|
||||||
|
|
||||||
|
The Web API version is independent of the SQLite schema,
|
||||||
|
`TRAINLOG_FORMAT_V1`, mobile/synchronization/export formats, and the Trainlog
|
||||||
|
product version. Web evolution must not alter a frozen exchange format.
|
||||||
|
|
||||||
|
V1 preserves serialized Core access: one process-owned `TrainlogDatabase` is
|
||||||
|
opened and migrated once before HTTP listening, used without concurrent direct
|
||||||
|
SQLite-handle access, and closed by the process owner after server shutdown. A
|
||||||
|
single-threaded event loop is the preferred server architecture.
|
||||||
|
|
||||||
|
Every Web page retains one application shell:
|
||||||
|
|
||||||
|
- Header navigation: Dashboard, Analyse, Programmes, Séances, Exercices;
|
||||||
|
- Body: responsive tile-organized page content;
|
||||||
|
- Footer: user, date of the latest completed session, and all primary BODY
|
||||||
|
ZONES worked in that completed session.
|
||||||
|
|
||||||
|
The Unix login is the V1 user fallback until Trainlog owns an explicit display
|
||||||
|
name. Footer zones come only from the latest completed session; missing facts
|
||||||
|
remain unavailable rather than being invented. Header and Footer remain
|
||||||
|
present during navigation.
|
||||||
|
|
||||||
|
The sole V1 theme is Catppuccin Mocha with Lavender as its primary accent.
|
||||||
|
There is no V1 light theme. The browser is the client and Electron is outside
|
||||||
|
the architecture. React, TypeScript, Vite, and Apache ECharts are the intended
|
||||||
|
frontend stack. Node and npm are build-only dependencies; installed Trainlog
|
||||||
|
must not require either. All production assets are local, built ahead of time,
|
||||||
|
and incorporated into the `trainlog` binary from the sibling root `web/`
|
||||||
|
directory. The grid library remains open pending a dedicated spike.
|
||||||
|
|
||||||
|
The Dashboard is a canonical 12-column grid containing exactly seven V1 tiles:
|
||||||
|
|
||||||
|
1. Prochaine séance;
|
||||||
|
2. Activité;
|
||||||
|
3. Progression;
|
||||||
|
4. Dernière séance;
|
||||||
|
5. Records / MAX;
|
||||||
|
6. Répartition musculaire;
|
||||||
|
7. Cardio / récupération.
|
||||||
|
|
||||||
|
V1 supports drag and drop, horizontal and vertical resizing, tile-specific
|
||||||
|
minimum/maximum sizes, automatic reorganization, responsive projection,
|
||||||
|
persistence, and restoration of the Trainlog default. Dragging and resizing
|
||||||
|
are enabled only by the explicit `Modifier l'agencement` mode. A larger tile
|
||||||
|
may reveal more information, but tile size never changes a metric's business
|
||||||
|
definition.
|
||||||
|
|
||||||
|
The single persisted source layout is the canonical desktop 12-column layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$XDG_CONFIG_HOME/trainlog/web/dashboard-layout-v1.json
|
||||||
|
```
|
||||||
|
|
||||||
|
with fallback:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$HOME/.config/trainlog/web/dashboard-layout-v1.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Its separately versioned format is interface configuration, not training data.
|
||||||
|
It does not enter business SQLite V1, `TRAINLOG_FORMAT_V1`, scientific or AI
|
||||||
|
exports, or session synchronization. Small-screen layouts are derived
|
||||||
|
responsive projections, not a second persisted truth.
|
||||||
|
|
||||||
|
Dashboard values come only from real Trainlog data or documented deterministic
|
||||||
|
Core calculations. Missing data produces an unavailable/empty state. Prochaine
|
||||||
|
séance displays only a genuinely prepared and persisted session; a generated,
|
||||||
|
AI, hidden, or unaccepted proposal is not a validated session. Cardio /
|
||||||
|
récupération remains unavailable until real relevant data exists. The exact
|
||||||
|
Activité and Progression mathematics remain open and must be specified before
|
||||||
|
their read models are implemented.
|
||||||
|
|
||||||
|
V1 security invariants are: loopback-only binding, Host validation, no
|
||||||
|
permissive CORS, foreign-origin/CSRF protection for mutations, explicit bounds
|
||||||
|
for headers, bodies, connections and timeouts, local assets only, no CDN, no
|
||||||
|
implicit telemetry, a Content Security Policy, and orderly `SIGINT`/`SIGTERM`
|
||||||
|
shutdown.
|
||||||
|
|
||||||
Android's `AppNavigationController` is the corresponding route owner. Its
|
Android's `AppNavigationController` is the corresponding route owner. Its
|
||||||
Material 3 drawer exposes the same seven roots and derives drawer selection
|
Material 3 drawer exposes the same seven roots and derives drawer selection
|
||||||
from the route. It uses local vector resources, a sans-serif hierarchy and
|
from the route. It uses local vector resources, a sans-serif hierarchy and
|
||||||
|
|
@ -463,20 +621,21 @@ known errors with generic placeholders.
|
||||||
## 11. Layering rule
|
## 11. Layering rule
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Notcurses / Compose rendering
|
Notcurses / Web / Compose adapters
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
application workflow
|
typed Core application services
|
||||||
|
|
|
|
||||||
+-- domain model
|
+-- domain model and calculations
|
||||||
+-- synchronization
|
+-- read models and command services
|
||||||
+-- validation
|
+-- synchronization and validation
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
persistence / MTP transport
|
persistence / MTP transport
|
||||||
```
|
```
|
||||||
|
|
||||||
Rendering does not own persistence rules.
|
Rendering does not own persistence rules, SQL, or duplicate business
|
||||||
|
calculations.
|
||||||
|
|
||||||
Persistence and MTP code do not depend on Notcurses rendering.
|
Persistence and MTP code do not depend on Notcurses rendering.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Current implementation state
|
# Current implementation state
|
||||||
|
|
||||||
Snapshot date: **2026-09-15**.
|
Snapshot date: **2026-09-16**.
|
||||||
|
|
||||||
This document owns the current implemented state. Historical checkpoints and
|
This document owns the current implemented state. Historical checkpoints and
|
||||||
closed incidents belong in [reviews](reviews/) and the
|
closed incidents belong in [reviews](reviews/) and the
|
||||||
|
|
@ -10,11 +10,14 @@ closed incidents belong in [reviews](reviews/) and the
|
||||||
|
|
||||||
Android is the field companion: it captures training and body data, preserves
|
Android is the field companion: it captures training and body data, preserves
|
||||||
the active draft, receives AI proposals, triggers synchronization, and shows
|
the active draft, receives AI proposals, triggers synchronization, and shows
|
||||||
quick summaries. The C17/Notcurses TUI is the detailed consultation,
|
quick summaries. The C17/Notcurses TUI is the administration, inspection,
|
||||||
correction, catalogue, analytics, graphing, and long-term tracking surface.
|
maintenance, import/export, and technical-tooling surface. The documented but
|
||||||
|
not yet implemented local Web sibling will own analysis, visualization, and
|
||||||
|
program/session preparation through Trainlog Core.
|
||||||
|
|
||||||
Android captures and summarizes. The TUI analyzes and tracks over time. The
|
Trainlog Core owns business truth. The desktop SQLite database remains the
|
||||||
desktop SQLite database remains the canonical long-term history.
|
canonical local source of truth and long-term history; no interface owns a
|
||||||
|
parallel implementation of its rules.
|
||||||
|
|
||||||
## Versions and compatibility
|
## Versions and compatibility
|
||||||
|
|
||||||
|
|
@ -25,16 +28,36 @@ desktop SQLite database remains the canonical long-term history.
|
||||||
| Android SQLite | schema v17 |
|
| Android SQLite | schema v17 |
|
||||||
| Mobile snapshot | V3 active; V1/V2 readable legacy inputs |
|
| Mobile snapshot | V3 active; V1/V2 readable legacy inputs |
|
||||||
| Desktop terminal backend | Notcurses only |
|
| Desktop terminal backend | Notcurses only |
|
||||||
|
| Trainlog product version | `0.1.1` stable, synchronized across Android and desktop (no independent interface versions) |
|
||||||
|
| Interface language | `TRAINLOG_I18N_V0_1_1=PASS`: French default; English selectable in Settings → Language on both surfaces |
|
||||||
| AI history export | `TRAINLOG_AI_EXPORT_V1` active |
|
| AI history export | `TRAINLOG_AI_EXPORT_V1` active |
|
||||||
| AI session proposals | `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING` |
|
| AI session proposals | `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING` |
|
||||||
| Training knowledge | `TRAINING_KNOWLEDGE_V1=PASS` |
|
| Training knowledge | `TRAINING_KNOWLEDGE_V1=PASS` |
|
||||||
| Session generator | `SESSION_GENERATOR_V1=PASS`, hidden pending V2 |
|
| Session generator | `SESSION_GENERATOR_V1=PASS`, hidden pending V2 |
|
||||||
| Application shell | `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2` |
|
| Application shell | `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2` |
|
||||||
| Statistics | `STATS_V1=IMPLEMENTED` |
|
| Statistics | `STATS_V1=IMPLEMENTED` |
|
||||||
|
| Local Web | `TRAINLOG_WEB_V1=CONTRACT_FROZEN / IMPLEMENTATION_NOT_STARTED` |
|
||||||
|
|
||||||
Desktop and Android schema numbers are independent. Neither changes the frozen
|
Desktop and Android schema numbers are independent. Neither changes the frozen
|
||||||
Trainlog JSON V1 contract.
|
Trainlog JSON V1 contract.
|
||||||
|
|
||||||
|
Language is device-local presentation state. Android keeps it in dedicated
|
||||||
|
SharedPreferences; desktop keeps it in the XDG configuration file
|
||||||
|
`trainlog/presentation.conf`. A successful selection updates the UI
|
||||||
|
immediately. It does not enter either SQLite database, training data, stable
|
||||||
|
IDs, schemas, exchange artifacts, synchronization protocols, AI proposals,
|
||||||
|
MAX, or feedback. User-defined exercise and catalogue names are never
|
||||||
|
automatically translated. Where a recognized stable body-zone ID has a local
|
||||||
|
presentation label, only that label is localized; unknown IDs retain their
|
||||||
|
catalogue label. Numbers, dates, and other visible formatting are owned by the
|
||||||
|
selected presentation language.
|
||||||
|
|
||||||
|
Synchronization remains a protocol boundary: core, receipt, and persisted
|
||||||
|
history summaries stay opaque operational bytes. Android and the TUI render
|
||||||
|
their current synchronization state from local typed status and counters; they
|
||||||
|
never inject a raw cross-device French summary into the visible interface. No
|
||||||
|
raw exchange or protocol format changed for interface language support.
|
||||||
|
|
||||||
## Storage and synchronization
|
## Storage and synchronization
|
||||||
|
|
||||||
- Android stores capture data in its private SQLite database.
|
- Android stores capture data in its private SQLite database.
|
||||||
|
|
@ -59,9 +82,12 @@ Android currently provides:
|
||||||
|
|
||||||
- metadata-driven SETS+REPS, SETS+DURATION, and CONTINUOUS+DURATION capture;
|
- metadata-driven SETS+REPS, SETS+DURATION, and CONTINUOUS+DURATION capture;
|
||||||
- actual per-set values, optional occurrence equipment, and explicit MAX rows;
|
- actual per-set values, optional occurrence equipment, and explicit MAX rows;
|
||||||
- exactly one durable active session draft with resume, confirmed discard, and
|
- exactly one durable active session draft with identity-preserving continuous
|
||||||
atomic completion;
|
multi-position drag reorder,
|
||||||
- completed-session consultation and correction;
|
resume, confirmed discard, and atomic completion;
|
||||||
|
- completed-session consultation, transient drag reorder, and atomic factual or
|
||||||
|
order correction while retaining session/occurrence/feedback identities for
|
||||||
|
synchronization;
|
||||||
- exercise catalogue editing with stable `exercise_id` and referenced-profile
|
- exercise catalogue editing with stable `exercise_id` and referenced-profile
|
||||||
protection;
|
protection;
|
||||||
- supplied/custom equipment and machine-exercise metadata;
|
- supplied/custom equipment and machine-exercise metadata;
|
||||||
|
|
@ -70,8 +96,10 @@ Android currently provides:
|
||||||
follow-ups;
|
follow-ups;
|
||||||
- pending AI proposal collection, explicit start/delete, and durable tombstones;
|
- pending AI proposal collection, explicit start/delete, and durable tombstones;
|
||||||
- 7/30/90-day, year, and all-history quick statistics;
|
- 7/30/90-day, year, and all-history quick statistics;
|
||||||
- direct-storage snapshot publication, sync triggering, receipt display, and
|
- direct-storage snapshot publication, sync triggering, locally rendered typed
|
||||||
companion application.
|
receipt status/counters, and companion application.
|
||||||
|
- French-default/English-selectable presentation from **Settings → Language**,
|
||||||
|
applied immediately without changing repository or synchronization state.
|
||||||
|
|
||||||
Android does not own canonical long-term analytics. The V1 session generator is
|
Android does not own canonical long-term analytics. The V1 session generator is
|
||||||
implemented but intentionally hidden from normal navigation pending V2.
|
implemented but intentionally hidden from normal navigation pending V2.
|
||||||
|
|
@ -90,10 +118,25 @@ The desktop is a strict C17 application using Notcurses. It currently provides:
|
||||||
- statistics derived from canonical occurrence-owned actual work, including
|
- statistics derived from canonical occurrence-owned actual work, including
|
||||||
exact calendar-week/month buckets, performance, body, frequency, exercise,
|
exact calendar-week/month buckets, performance, body, frequency, exercise,
|
||||||
and BODY ZONES views;
|
and BODY ZONES views;
|
||||||
- detailed synchronization status and history through the shared engine.
|
- detailed locally rendered typed synchronization status/history through the
|
||||||
|
shared engine.
|
||||||
|
- French-default/English-selectable presentation from **Settings → Language**,
|
||||||
|
including selected-language formatting without changing canonical data.
|
||||||
|
|
||||||
Rendering does not own SQL or business rules. Statistics are read-only
|
Rendering is not permitted to own SQL or business rules. Statistics are
|
||||||
projections; they are not persisted as facts.
|
read-only projections; they are not persisted as facts. The current TUI
|
||||||
|
Dashboard still has a known exception: its direct SQLite/
|
||||||
|
`database_internal.h` fact projection must be characterized, extracted into a
|
||||||
|
typed Core read model, and consumed by the TUI before any Web Dashboard
|
||||||
|
endpoint is created. This is an incremental extraction, not authorization for
|
||||||
|
a global `tui.c` refactor.
|
||||||
|
|
||||||
|
## Local Web
|
||||||
|
|
||||||
|
`TRAINLOG_WEB_V1` architecture, API independence, local-network boundary,
|
||||||
|
browser shell, Dashboard, layout ownership, build/runtime separation, and
|
||||||
|
security invariants are now canonical. No Web server, frontend, CLI option,
|
||||||
|
HTTP endpoint, embedded asset, or layout persistence is implemented yet.
|
||||||
|
|
||||||
## Data semantics
|
## Data semantics
|
||||||
|
|
||||||
|
|
@ -113,10 +156,19 @@ projections; they are not persisted as facts.
|
||||||
|
|
||||||
## Validation status
|
## Validation status
|
||||||
|
|
||||||
The durable commands are owned by [tests.md](tests.md). On 2026-09-15 this audit
|
The durable commands are owned by [tests.md](tests.md). On 2026-09-15 final
|
||||||
passed desktop compilation, **58/58 Meson tests**, Android unit tests, Android
|
validation passed desktop compilation, **60/60 normal Meson tests** and
|
||||||
debug assembly, the JSON validator, and the import-contract validator. Link and
|
**60/60 ASan/UBSan Meson tests**, Android debug assembly and **202 Android
|
||||||
diff safety checks also pass in the final audit evidence.
|
tests (198 passed, 4 skipped, 0 failed)**, the JSON validator, and the
|
||||||
|
import-contract validator. The source-derived TUI
|
||||||
|
`TRANSLATABLE_UI=0` check and Android resource parity also passed. Link and
|
||||||
|
diff safety checks pass in the final audit evidence.
|
||||||
|
|
||||||
|
`TRAINLOG_I18N_V0_1_1=PASS` is covered by desktop presentation, persistence,
|
||||||
|
formatting, layout-invariance and source-derived text-boundary tests, plus
|
||||||
|
Android resource-parity, language-owner, typed sync-presentation, and
|
||||||
|
stable-data presentation tests. A real-device/manual visual language-switch
|
||||||
|
smoke remains a manual validation; it does not change the validated status.
|
||||||
|
|
||||||
Hardware-dependent MTP and the final real Drive plus Android-triggered AI-draft
|
Hardware-dependent MTP and the final real Drive plus Android-triggered AI-draft
|
||||||
smoke test are not automated. The latter is why
|
smoke test are not automated. The latter is why
|
||||||
|
|
@ -124,6 +176,8 @@ smoke test are not automated. The latter is why
|
||||||
|
|
||||||
## Active limitations
|
## Active limitations
|
||||||
|
|
||||||
|
- `TRAINLOG_WEB_V1` is contract-only; its implementation starts with Dashboard
|
||||||
|
characterization and Core extraction, not React or HTTP infrastructure.
|
||||||
- `APP_SHELL_V1` still awaits the recorded human visual/accessibility review.
|
- `APP_SHELL_V1` still awaits the recorded human visual/accessibility review.
|
||||||
- AI proposal exchange still awaits one real Drive plus Android-triggered
|
- AI proposal exchange still awaits one real Drive plus Android-triggered
|
||||||
bidirectional smoke test.
|
bidirectional smoke test.
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ in [reviews](reviews/) and [CHANGELOG.md](../CHANGELOG.md).
|
||||||
## Product boundary
|
## Product boundary
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Android captures and summarizes.
|
Trainlog Core owns business truth and canonical desktop persistence.
|
||||||
The TUI analyzes and tracks over time.
|
The TUI administers, inspects, maintains, and imports/exports.
|
||||||
Desktop SQLite is canonical long-term history.
|
The local Web analyzes, visualizes, and prepares programs/sessions.
|
||||||
|
Android executes in the field, captures actual work/feedback, and returns data.
|
||||||
```
|
```
|
||||||
|
|
||||||
Future work must preserve the frozen Trainlog JSON V1 contract, stable
|
Future work must preserve the frozen Trainlog JSON V1 contract, stable
|
||||||
|
|
@ -23,16 +24,45 @@ Desktop schema v18, Android schema v17, Notcurses, direct
|
||||||
Zones V1, Training Feedback V1/V2, STATS V1, and Session Generator V1 are
|
Zones V1, Training Feedback V1/V2, STATS V1, and Session Generator V1 are
|
||||||
implemented. Session Generator V1 is hidden pending V2. AI session-draft
|
implemented. Session Generator V1 is hidden pending V2. AI session-draft
|
||||||
exchange and APP_SHELL_V1 retain their explicit manual validation/review gates.
|
exchange and APP_SHELL_V1 retain their explicit manual validation/review gates.
|
||||||
|
`TRAINLOG_WEB_V1=CONTRACT_FROZEN / IMPLEMENTATION_NOT_STARTED`; its local-only
|
||||||
|
architecture is canonical, but no Web code exists yet.
|
||||||
|
|
||||||
## Current cursor
|
## Current cursor
|
||||||
|
|
||||||
```text
|
```text
|
||||||
CURRENT_OPERATIONAL_CURSOR=REAL_DATA_BASELINE_V1
|
CURRENT_OPERATIONAL_CURSOR=WEB_DASHBOARD_CHARACTERIZATION_V1
|
||||||
```
|
```
|
||||||
|
|
||||||
Collect real body observations and training sessions through Android, sync them
|
Characterize the existing TUI Dashboard before moving its direct SQLite and
|
||||||
to desktop, and record MAX only through explicit `max_test` results. Do not seed
|
`database_internal.h` projection into a typed Core read model. Then adapt the
|
||||||
fictitious user history into canonical databases.
|
TUI to consume that read model. This incremental sequence is a blocking
|
||||||
|
prerequisite for a Web Dashboard endpoint and does not authorize a global
|
||||||
|
`tui.c` refactor.
|
||||||
|
|
||||||
|
## TRAINLOG_WEB_V1 implementation gate
|
||||||
|
|
||||||
|
Implementation order is fixed as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WEB_DASHBOARD_CHARACTERIZATION_V1
|
||||||
|
-> WEB_DASHBOARD_CORE_READ_MODEL_V1
|
||||||
|
-> WEB_TUI_READ_MODEL_ADOPTION_V1
|
||||||
|
-> WEB_CLI_HTTP_INFRASTRUCTURE_V1
|
||||||
|
-> WEB_FRONTEND_SHELL_V1
|
||||||
|
-> WEB_DASHBOARD_V1
|
||||||
|
```
|
||||||
|
|
||||||
|
The Web must not start with React. Characterization proves current behavior;
|
||||||
|
Core extraction establishes one business implementation; TUI adoption proves
|
||||||
|
reuse; only then may `trainlog -w`, the loopback HTTP adapter, embedded assets,
|
||||||
|
and browser UI be introduced. Initial API delivery is read-only. Later program
|
||||||
|
and session writes require explicit transactional Core command services.
|
||||||
|
|
||||||
|
`TRAINLOG_WEB_V1` preserves `/api/v1/` as an independently versioned boundary,
|
||||||
|
the `127.0.0.1:8080` default, explicit `--port`, no silent port fallback, a
|
||||||
|
single serialized Core/SQLite owner, build-only Node/npm, and no change to
|
||||||
|
business SQLite or frozen exchange formats. Dashboard Activité and Progression
|
||||||
|
mathematics remain specification gates before their read models.
|
||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
|
|
@ -75,7 +105,11 @@ normal capture.
|
||||||
## Canonical order
|
## Canonical order
|
||||||
|
|
||||||
```text
|
```text
|
||||||
REAL_DATA_BASELINE_V1
|
WEB_DASHBOARD_CHARACTERIZATION_V1
|
||||||
|
-> WEB_DASHBOARD_CORE_READ_MODEL_V1
|
||||||
|
-> WEB_TUI_READ_MODEL_ADOPTION_V1
|
||||||
|
-> TRAINLOG_WEB_V1
|
||||||
|
-> REAL_DATA_BASELINE_V1
|
||||||
-> GYM_CATALOG_V1
|
-> GYM_CATALOG_V1
|
||||||
-> EXERCISE_METADATA_V1
|
-> EXERCISE_METADATA_V1
|
||||||
-> SESSION_GENERATOR_V2
|
-> SESSION_GENERATOR_V2
|
||||||
|
|
@ -101,3 +135,6 @@ filesystem, exercise-name identity heuristics, fake sets for continuous work,
|
||||||
synthetic uniform actuals, ordinary training promoted to measured MAX,
|
synthetic uniform actuals, ordinary training promoted to measured MAX,
|
||||||
assistance interpreted as external load, or estimates presented as direct
|
assistance interpreted as external load, or estimates presented as direct
|
||||||
measurements. Incompatible exchange semantics require a new version.
|
measurements. Incompatible exchange semantics require a new version.
|
||||||
|
Web UI configuration remains outside training data, scientific/AI exports,
|
||||||
|
session synchronization, and business SQLite. Browser, React, TUI, and Android
|
||||||
|
must not query desktop tables or duplicate Core calculations.
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,23 @@ It validates:
|
||||||
|
|
||||||
A negative fixture passes only when Trainlog rejects it.
|
A negative fixture passes only when Trainlog rejects it.
|
||||||
|
|
||||||
|
Android field-workflow regression coverage additionally permutes distinct and
|
||||||
|
duplicate active occurrences, checks stable IDs/equipment/targets/actuals and raw
|
||||||
|
form state, recreates the repository, injects a transactional write failure, and
|
||||||
|
finalizes the reordered draft. Completed-session tests edit reps, loads, set and
|
||||||
|
continuous durations, distance, and speed; preserve feedback/follow-up roots;
|
||||||
|
exercise atomic rollback; and inspect the corrected V3 export identity.
|
||||||
|
Completed-order coverage also exercises duplicate exercises, SETS, CONTINUOUS,
|
||||||
|
MAX, planning, equipment, feedback revisions, J+1 follow-up, Cancel/no-write,
|
||||||
|
successful position persistence, injected Save rollback, and desktop V3
|
||||||
|
reconciliation without a duplicate canonical session.
|
||||||
|
Field-input coverage maps one accumulated drag directly from index 5 to 1 and
|
||||||
|
between both list extremes, restores the origin permutation on cancellation,
|
||||||
|
and checks decimal editing/final parsing for both separators. It covers `0.3`,
|
||||||
|
`0,3`, quarter-unit values, whole values, incomplete separator states, malformed
|
||||||
|
text, non-finite spellings, negative final distance, stored numeric equality,
|
||||||
|
and locale-independent V3 JSON publication.
|
||||||
|
|
||||||
Semantic validation includes:
|
Semantic validation includes:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
@ -118,6 +135,7 @@ body_analytics
|
||||||
terminal_input_event_type_policy
|
terminal_input_event_type_policy
|
||||||
tui_workflows
|
tui_workflows
|
||||||
app_shell
|
app_shell
|
||||||
|
presentation_i18n
|
||||||
```
|
```
|
||||||
|
|
||||||
The desktop suite includes APP_SHELL_V1 production-transition coverage and the
|
The desktop suite includes APP_SHELL_V1 production-transition coverage and the
|
||||||
|
|
@ -150,6 +168,26 @@ The desktop executable is additionally smoke-checked in isolated tmux PTYs at
|
||||||
recover before a clean keyboard quit. Notcurses is verified as the executable's
|
recover before a clean keyboard quit. Notcurses is verified as the executable's
|
||||||
direct terminal dependency with `readelf -d`.
|
direct terminal dependency with `readelf -d`.
|
||||||
|
|
||||||
|
`presentation_i18n` covers the deterministic French default, French/English
|
||||||
|
catalogue lookup, immediate in-process language switching, selected-language
|
||||||
|
decimal/date formatting even under a contrary host numeric locale, unchanged
|
||||||
|
layout at the 72x20 minimum, and local XDG configuration persistence/failure
|
||||||
|
semantics. It also proves that persisted catalogue names are not translation
|
||||||
|
keys, the source-derived `TRANSLATABLE_UI=0` boundary, and that synchronization
|
||||||
|
presentation uses local typed status/counters rather than raw opaque
|
||||||
|
core/receipt/history summaries.
|
||||||
|
|
||||||
|
Android Robolectric coverage verifies both resource locales, Settings language
|
||||||
|
ownership and dedicated SharedPreferences persistence, French fallback for an
|
||||||
|
absent/invalid preference, and localized presentation of known stable BODY ZONE
|
||||||
|
IDs without changing unknown catalogue labels or repository values. Resource
|
||||||
|
parity and typed synchronization-receipt presentation are also covered; raw
|
||||||
|
protocol/operational summaries remain opaque. Final i18n validation passed
|
||||||
|
60/60 normal desktop tests, 60/60 ASan/UBSan desktop tests, and 202 Android
|
||||||
|
tests (198 passed, 4 skipped, 0 failed), with both JSON validators passing.
|
||||||
|
Automated coverage does not replace a real device/manual visual language-switch
|
||||||
|
smoke; that remains explicit manual validation.
|
||||||
|
|
||||||
Notable regression coverage:
|
Notable regression coverage:
|
||||||
|
|
||||||
- transactional persisted-session replacement;
|
- transactional persisted-session replacement;
|
||||||
|
|
@ -238,6 +276,24 @@ Notable regression coverage:
|
||||||
Stable-release validation additionally builds the signed release variant and
|
Stable-release validation additionally builds the signed release variant and
|
||||||
checks the packaged product versions and binary linkage:
|
checks the packaged product versions and binary linkage:
|
||||||
|
|
||||||
|
The release machine supplies all four `TRAINLOG_RELEASE_*` environment
|
||||||
|
variables, or a mode-0600 private file at
|
||||||
|
`~/.config/trainlog/release-signing.properties` with this shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
storeFile=/absolute/private/path/trainlog-release.jks
|
||||||
|
storePassword=<local secret>
|
||||||
|
keyAlias=<local alias>
|
||||||
|
keyPassword=<local secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
`TRAINLOG_RELEASE_CREDENTIALS_FILE` may select another private properties file.
|
||||||
|
Environment variables take precedence over file values. The keystore and
|
||||||
|
credentials are the durable Android update identity: back them up through an
|
||||||
|
approved encrypted external mechanism, never Git or release assets. Missing
|
||||||
|
configuration makes `packageRelease` fail explicitly rather than producing an
|
||||||
|
apparently publishable unsigned APK.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson compile -C build
|
meson compile -C build
|
||||||
meson test -C build --print-errorlogs
|
meson test -C build --print-errorlogs
|
||||||
|
|
@ -246,10 +302,13 @@ cd android
|
||||||
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew test
|
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew test
|
||||||
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleRelease
|
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleRelease
|
||||||
|
|
||||||
file trainlog-tui-linux-x86_64-v0.1.0
|
apksigner verify --verbose --print-certs \
|
||||||
ldd trainlog-tui-linux-x86_64-v0.1.0
|
app/build/outputs/apk/release/app-release.apk
|
||||||
sha256sum trainlog-android-v0.1.0.apk \
|
|
||||||
trainlog-tui-linux-x86_64-v0.1.0
|
file trainlog-tui-linux-x86_64-v<version>
|
||||||
|
ldd trainlog-tui-linux-x86_64-v<version>
|
||||||
|
sha256sum trainlog-android-v<version>.apk \
|
||||||
|
trainlog-tui-linux-x86_64-v<version>
|
||||||
```
|
```
|
||||||
|
|
||||||
An unsigned release APK is build evidence only and must not be published as the
|
An unsigned release APK is build evidence only and must not be published as the
|
||||||
|
|
|
||||||
29
docs/tui.md
29
docs/tui.md
|
|
@ -115,6 +115,32 @@ Minimum terminal size:
|
||||||
|
|
||||||
Smaller terminals display a clear fallback instead of corrupt layout.
|
Smaller terminals display a clear fallback instead of corrupt layout.
|
||||||
|
|
||||||
|
## Interface language (`TRAINLOG_I18N_V0_1_1`)
|
||||||
|
|
||||||
|
Trainlog version 0.1.1 is synchronized with Android as one product version;
|
||||||
|
the TUI has no independent interface version. French is the default. English is
|
||||||
|
the only alternate language and is selected through **Settings → Language**.
|
||||||
|
The active TUI rerenders immediately after a successful selection.
|
||||||
|
|
||||||
|
The choice is local presentation state in the XDG configuration file
|
||||||
|
`trainlog/presentation.conf` (under `$XDG_CONFIG_HOME`, or
|
||||||
|
`$HOME/.config` when the XDG variable is unavailable). Invalid, absent, or
|
||||||
|
unreadable preference data falls back to French. A failed persistence attempt
|
||||||
|
does not claim a committed choice. The setting never enters the desktop SQLite
|
||||||
|
database, training data, stable IDs, schemas, exchange artifacts, transport
|
||||||
|
protocols, AI proposals, MAX, or feedback.
|
||||||
|
|
||||||
|
Trainlog-owned strings and selected-language number/date formatting are
|
||||||
|
localized. Persisted/user exercise and catalogue names are byte-exact display
|
||||||
|
data, never translation keys. Stable BODY ZONE IDs select localized known-zone
|
||||||
|
labels where applicable; unknown IDs retain their catalogue label. Layout and
|
||||||
|
minimum-terminal behavior remain language-invariant.
|
||||||
|
|
||||||
|
The current synchronization screen renders local typed status and counters. It
|
||||||
|
does not translate, reinterpret, or inject Android/core/receipt/history raw
|
||||||
|
summaries: those protocol and operational bytes remain opaque. No exchange,
|
||||||
|
receipt, or synchronization-history format changed for this presentation work.
|
||||||
|
|
||||||
## 3. Visual rules
|
## 3. Visual rules
|
||||||
|
|
||||||
The TUI uses centralized semantic theme roles.
|
The TUI uses centralized semantic theme roles.
|
||||||
|
|
@ -174,7 +200,8 @@ Unicode-aware normalized-name uniqueness prevents duplicate logical names.
|
||||||
`3 Exercices` also owns Body Zones V1. Creation opens a keyboard-only manifest
|
`3 Exercices` also owns Body Zones V1. Creation opens a keyboard-only manifest
|
||||||
selector: `p` chooses the sole primary, Space toggles secondaries, `n` selects
|
selector: `p` chooses the sole primary, Space toggles secondaries, `n` selects
|
||||||
the explicit unclassified state and clears all relations, Enter validates and Escape
|
the explicit unclassified state and clears all relations, Enter validates and Escape
|
||||||
cancels without mutation. Only French display names are shown; stable IDs such
|
cancels without mutation. Visible names use the selected presentation language
|
||||||
|
where Trainlog owns the label; stable IDs such
|
||||||
as `chest` remain internal. New set-based exercises require a primary zone.
|
as `chest` remain internal. New set-based exercises require a primary zone.
|
||||||
|
|
||||||
The exercise list supports `/` prefix search and `z` cycling through all
|
The exercise list supports `/` prefix search and `z` cycling through all
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
project(
|
project(
|
||||||
'trainlog',
|
'trainlog',
|
||||||
'c',
|
'c',
|
||||||
version: '0.1.0',
|
version: '0.1.1',
|
||||||
meson_version: '>=1.3.0',
|
meson_version: '>=1.3.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c17',
|
'c_std=c17',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue