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/
|
||||
local.properties
|
||||
**/build/
|
||||
*.jks
|
||||
*.keystore
|
||||
/android/release-signing.properties
|
||||
/android/release-signing.env
|
||||
/android/keystore.properties
|
||||
|
||||
# Editors
|
||||
*.swp
|
||||
|
|
|
|||
56
CHANGELOG.md
56
CHANGELOG.md
|
|
@ -1,10 +1,60 @@
|
|||
# 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,
|
||||
SHA-256 verification, runtime/build/optional dependencies, and the release
|
||||
signing and exact-tag requirements. No v0.1.0 release is claimed until a
|
||||
signed APK and authenticated GitHub publication path are available.
|
||||
signing and exact-tag requirements. The Android artifact is built with the
|
||||
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
|
||||
canonical documentation index, made README a concise entry point, separated
|
||||
|
|
|
|||
58
README.md
58
README.md
|
|
@ -1,9 +1,10 @@
|
|||
# Trainlog
|
||||
|
||||
Trainlog is a local-first workout and body-data system. Its native Android app
|
||||
is the lightweight field companion for fast capture and glanceable summaries;
|
||||
its C17/Notcurses desktop TUI is the detailed surface for correction,
|
||||
catalogue management, analytics, graphs, and long-term follow-up.
|
||||
Trainlog is a local-first workout and body-data system. Trainlog Core owns
|
||||
business truth and canonical desktop persistence. Its native Android app is the
|
||||
field companion, its C17/Notcurses desktop TUI is the administration and
|
||||
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
|
||||
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 |
|
||||
|---|---|
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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 | 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
|
||||
time.** Android intentionally remains lightweight for analytics.
|
||||
No interface reconstructs business truth from SQLite tables. The local Web is
|
||||
a sibling adapter, not an extension of the TUI; `TRAINLOG_WEB_V1` is currently
|
||||
contract-only and has no implementation.
|
||||
|
||||
## 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)
|
||||
- [Forgejo Releases](https://git.labfytools.com/fy59/trainlog/releases)
|
||||
|
||||
Each stable release provides:
|
||||
Each published stable release provides:
|
||||
|
||||
- a signed Android APK;
|
||||
- a Linux x86-64 TUI executable;
|
||||
- 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
|
||||
details.
|
||||
|
|
@ -41,7 +57,7 @@ details.
|
|||
|
||||
### 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)
|
||||
- [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).
|
||||
|
||||
```bash
|
||||
chmod +x trainlog-tui-linux-x86_64-v0.1.0
|
||||
./trainlog-tui-linux-x86_64-v0.1.0
|
||||
chmod +x trainlog-tui-linux-x86_64-v<version>
|
||||
./trainlog-tui-linux-x86_64-v<version>
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
### Linux runtime dependencies
|
||||
|
|
@ -200,6 +229,7 @@ As of 2026-09-15:
|
|||
- `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2`;
|
||||
- `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING` pending a real Drive plus
|
||||
Android-triggered bidirectional smoke test.
|
||||
- `TRAINLOG_WEB_V1=CONTRACT_FROZEN / IMPLEMENTATION_NOT_STARTED`.
|
||||
|
||||
The latest executable result belongs in
|
||||
[current state](docs/current_state.md), not in multiple README narratives.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,40 @@
|
|||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
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 {
|
||||
namespace = "com.labfytools.trainlog"
|
||||
compileSdk = 37
|
||||
|
|
@ -12,12 +44,31 @@ android {
|
|||
minSdk = 26
|
||||
targetSdk = 36
|
||||
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
versionCode = 2
|
||||
versionName = "0.1.1"
|
||||
testInstrumentationRunner =
|
||||
"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 {
|
||||
sourceCompatibility = 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 {
|
||||
val composeBom =
|
||||
platform("androidx.compose:compose-bom:2026.08.00")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ evidence and chronology.
|
|||
glanceable summaries, permissions, and Android interaction rules.
|
||||
- [Desktop TUI](tui.md) owns Notcurses navigation, correction, detailed
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
replaces all children in one transaction while retaining `session_id`, every
|
||||
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
|
||||
nothing. Exercise Detail separately exposes **Modifier l'exercice**. A
|
||||
confirmed incompatible catalogue edit controls future Android occurrences;
|
||||
|
|
@ -607,8 +666,9 @@ trainlog-sync-receipt-v1.json
|
|||
|
||||
The receipt is matched by `request_id`.
|
||||
|
||||
On success Android then applies the latest PC catalog and displays the final
|
||||
result.
|
||||
On success Android then applies the latest PC catalog and renders the final
|
||||
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
|
||||
`trainlog-pc-equipment-definitions-v1.json`. Thus custom definitions are known
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
## 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
|
||||
Android capture client
|
||||
Android field client
|
||||
|
|
||||
| local SQLite
|
||||
|
|
||||
|
|
@ -23,10 +26,16 @@ Android capture client
|
|||
desktop SQLite directional PC artifacts
|
||||
| |
|
||||
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.
|
||||
|
||||
|
|
@ -48,14 +57,23 @@ Responsibilities:
|
|||
- PC catalog application;
|
||||
- synchronization request creation;
|
||||
- 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
|
||||
|
||||
The C17 core owns:
|
||||
|
||||
- data and domain rules;
|
||||
- desktop SQLite persistence;
|
||||
- business calculations;
|
||||
- typed read models and transactional command services;
|
||||
- versioned contracts;
|
||||
- exercise/catalog rules;
|
||||
- generated body-zone taxonomy and relation APIs;
|
||||
- profile-aware session data;
|
||||
|
|
@ -67,9 +85,10 @@ The C17 core owns:
|
|||
|
||||
### 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.
|
||||
The TUI is the administration, inspection, maintenance, import/export, and
|
||||
technical-tooling adapter. The Notcurses layer owns terminal 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:
|
||||
|
||||
|
|
@ -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
|
||||
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
|
||||
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
|
||||
|
|
@ -463,20 +621,21 @@ known errors with generic placeholders.
|
|||
## 11. Layering rule
|
||||
|
||||
```text
|
||||
Notcurses / Compose rendering
|
||||
|
|
||||
v
|
||||
application workflow
|
||||
|
|
||||
+-- domain model
|
||||
+-- synchronization
|
||||
+-- validation
|
||||
|
|
||||
v
|
||||
persistence / MTP transport
|
||||
Notcurses / Web / Compose adapters
|
||||
|
|
||||
v
|
||||
typed Core application services
|
||||
|
|
||||
+-- domain model and calculations
|
||||
+-- read models and command services
|
||||
+-- synchronization and validation
|
||||
|
|
||||
v
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Current implementation state
|
||||
|
||||
Snapshot date: **2026-09-15**.
|
||||
Snapshot date: **2026-09-16**.
|
||||
|
||||
This document owns the current implemented state. Historical checkpoints and
|
||||
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
|
||||
the active draft, receives AI proposals, triggers synchronization, and shows
|
||||
quick summaries. The C17/Notcurses TUI is the detailed consultation,
|
||||
correction, catalogue, analytics, graphing, and long-term tracking surface.
|
||||
quick summaries. The C17/Notcurses TUI is the administration, inspection,
|
||||
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
|
||||
desktop SQLite database remains the canonical long-term history.
|
||||
Trainlog Core owns business truth. The desktop SQLite database remains the
|
||||
canonical local source of truth and long-term history; no interface owns a
|
||||
parallel implementation of its rules.
|
||||
|
||||
## Versions and compatibility
|
||||
|
||||
|
|
@ -25,16 +28,36 @@ desktop SQLite database remains the canonical long-term history.
|
|||
| Android SQLite | schema v17 |
|
||||
| Mobile snapshot | V3 active; V1/V2 readable legacy inputs |
|
||||
| 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 session proposals | `TRAINLOG_AI_SESSION_DRAFT_V1=VALIDATION_PENDING` |
|
||||
| Training knowledge | `TRAINING_KNOWLEDGE_V1=PASS` |
|
||||
| Session generator | `SESSION_GENERATOR_V1=PASS`, hidden pending V2 |
|
||||
| Application shell | `APP_SHELL_V1=IMPLEMENTED_AWAITING_VISUAL_REVIEW_2` |
|
||||
| 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
|
||||
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
|
||||
|
||||
- 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;
|
||||
- actual per-set values, optional occurrence equipment, and explicit MAX rows;
|
||||
- exactly one durable active session draft with resume, confirmed discard, and
|
||||
atomic completion;
|
||||
- completed-session consultation and correction;
|
||||
- exactly one durable active session draft with identity-preserving continuous
|
||||
multi-position drag reorder,
|
||||
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
|
||||
protection;
|
||||
- supplied/custom equipment and machine-exercise metadata;
|
||||
|
|
@ -70,8 +96,10 @@ Android currently provides:
|
|||
follow-ups;
|
||||
- pending AI proposal collection, explicit start/delete, and durable tombstones;
|
||||
- 7/30/90-day, year, and all-history quick statistics;
|
||||
- direct-storage snapshot publication, sync triggering, receipt display, and
|
||||
companion application.
|
||||
- direct-storage snapshot publication, sync triggering, locally rendered typed
|
||||
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
|
||||
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
|
||||
exact calendar-week/month buckets, performance, body, frequency, exercise,
|
||||
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
|
||||
projections; they are not persisted as facts.
|
||||
Rendering is not permitted to own SQL or business rules. Statistics are
|
||||
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
|
||||
|
||||
|
|
@ -113,10 +156,19 @@ projections; they are not persisted as facts.
|
|||
|
||||
## Validation status
|
||||
|
||||
The durable commands are owned by [tests.md](tests.md). On 2026-09-15 this audit
|
||||
passed desktop compilation, **58/58 Meson tests**, Android unit tests, Android
|
||||
debug assembly, the JSON validator, and the import-contract validator. Link and
|
||||
diff safety checks also pass in the final audit evidence.
|
||||
The durable commands are owned by [tests.md](tests.md). On 2026-09-15 final
|
||||
validation passed desktop compilation, **60/60 normal Meson tests** and
|
||||
**60/60 ASan/UBSan Meson tests**, Android debug assembly and **202 Android
|
||||
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
|
||||
smoke test are not automated. The latter is why
|
||||
|
|
@ -124,6 +176,8 @@ smoke test are not automated. The latter is why
|
|||
|
||||
## 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.
|
||||
- AI proposal exchange still awaits one real Drive plus Android-triggered
|
||||
bidirectional smoke test.
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ in [reviews](reviews/) and [CHANGELOG.md](../CHANGELOG.md).
|
|||
## Product boundary
|
||||
|
||||
```text
|
||||
Android captures and summarizes.
|
||||
The TUI analyzes and tracks over time.
|
||||
Desktop SQLite is canonical long-term history.
|
||||
Trainlog Core owns business truth and canonical desktop persistence.
|
||||
The TUI administers, inspects, maintains, and imports/exports.
|
||||
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
|
||||
|
|
@ -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
|
||||
implemented. Session Generator V1 is hidden pending V2. AI session-draft
|
||||
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
|
||||
|
||||
```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
|
||||
to desktop, and record MAX only through explicit `max_test` results. Do not seed
|
||||
fictitious user history into canonical databases.
|
||||
Characterize the existing TUI Dashboard before moving its direct SQLite and
|
||||
`database_internal.h` projection into a typed Core read model. Then adapt the
|
||||
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
|
||||
|
||||
|
|
@ -75,7 +105,11 @@ normal capture.
|
|||
## Canonical order
|
||||
|
||||
```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
|
||||
-> EXERCISE_METADATA_V1
|
||||
-> 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,
|
||||
assistance interpreted as external load, or estimates presented as direct
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```text
|
||||
|
|
@ -118,6 +135,7 @@ body_analytics
|
|||
terminal_input_event_type_policy
|
||||
tui_workflows
|
||||
app_shell
|
||||
presentation_i18n
|
||||
```
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
- transactional persisted-session replacement;
|
||||
|
|
@ -238,6 +276,24 @@ Notable regression coverage:
|
|||
Stable-release validation additionally builds the signed release variant and
|
||||
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
|
||||
meson compile -C build
|
||||
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 assembleRelease
|
||||
|
||||
file trainlog-tui-linux-x86_64-v0.1.0
|
||||
ldd trainlog-tui-linux-x86_64-v0.1.0
|
||||
sha256sum trainlog-android-v0.1.0.apk \
|
||||
trainlog-tui-linux-x86_64-v0.1.0
|
||||
apksigner verify --verbose --print-certs \
|
||||
app/build/outputs/apk/release/app-release.apk
|
||||
|
||||
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
|
||||
|
|
|
|||
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.
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
selector: `p` chooses the sole primary, Space toggles secondaries, `n` selects
|
||||
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.
|
||||
|
||||
The exercise list supports `/` prefix search and `z` cycling through all
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
project(
|
||||
'trainlog',
|
||||
'c',
|
||||
version: '0.1.0',
|
||||
version: '0.1.1',
|
||||
meson_version: '>=1.3.0',
|
||||
default_options: [
|
||||
'c_std=c17',
|
||||
|
|
|
|||
Loading…
Reference in a new issue