836 lines
37 KiB
Markdown
836 lines
37 KiB
Markdown
# Desktop TUI
|
||
|
||
## 1. Purpose
|
||
|
||
The Trainlog desktop application is a C17/Notcurses interface for durable
|
||
history, correction, analysis, visualization, direct data entry, and manual
|
||
synchronization.
|
||
|
||
Desktop SQLite is the canonical long-term history.
|
||
|
||
From completed Session Detail, `e` opens the existing session editor around
|
||
`trainlog_database_replace_session_exercises()`. Per-set values, targets, rest,
|
||
continuous facts and explicit MAX values are corrected atomically under the
|
||
same `session_id`; retained occurrences keep `entry_id`. Destructive set or
|
||
occurrence removal uses the global confirmation flow (`Non` by default,
|
||
Escape/q cancel).
|
||
|
||
Desktop schema v16 snapshots `tracking_mode` on every `session_exercises`
|
||
occurrence. The exercise editor may therefore change profile axes for future
|
||
uses even when history exists. Incompatible changes require an explicit
|
||
`Non`-by-default confirmation which states that old sessions remain unchanged;
|
||
Escape and `q` cancel.
|
||
|
||
## 2. APP_SHELL_V1 navigation
|
||
|
||
The persistent shell has seven root sections:
|
||
|
||
```text
|
||
Accueil | Séances | Exercices | Statistiques | Synchronisation | Paramètres
|
||
```
|
||
|
||
`Séances` contains **Séance en cours**, **Programmer une séance**, **Nouvelle
|
||
séance manuelle**, and **Séances effectuées**. The latter replaces the former
|
||
top-level Historique entry. Existing mensurations, body graphs/analysis,
|
||
exercise performance and explicit MAX views are reached through
|
||
`Statistiques`; this relocation adds no statistic or analytical interpretation.
|
||
|
||
`STATS_V1` makes the Statistics root a data-fed Unicode dashboard rather than a
|
||
plain list. Before rendering, its controller takes one bounded read snapshot:
|
||
the newest actual-history working-performance series is identified by exact
|
||
exercise, nonempty resolved external equipment, tracking, load and performed
|
||
dose context; the latest recorded body metric
|
||
shows its real trend; and the selected period shows actual-work session counts.
|
||
An explicit MAX is reported separately and is never folded into the working
|
||
series. Assistance, targets, empty sessions and unavailable measurements do
|
||
not become progress facts. Timestamp selection and ordering use Trainlog's
|
||
exact instant comparator, including offsets and arbitrary fractional seconds.
|
||
Presentation buckets use the local date written in each persisted timestamp;
|
||
they never reinterpret history in the current machine timezone.
|
||
Malformed legacy timestamps are skipped before statistics sorting and produce a
|
||
nonfatal ignored-data warning; valid dashboard facts remain available. The same
|
||
four panels and detail actions remain visible at 72x20; terminals of at least
|
||
100 columns place performance and measurements side by side with frequency and
|
||
catalogue distribution below, rather than reusing the compact vertical stack.
|
||
Trend presentation is deliberately sparse: no observation presents an honest
|
||
unavailable state, one observation states that the trend is unavailable and
|
||
draws no graph, and two or more observations draw only their observed trend.
|
||
Dashboard exercise labels resolve the canonical display name from the stable
|
||
`exercise_id` when one is mapped; the resolved equipment label remains
|
||
secondary context rather than becoming the exercise identity.
|
||
The geometry policy is exact:
|
||
|
||
```text
|
||
under 72x20 small-terminal fallback
|
||
72x20 or larger usable compact shell with temporary Navigation control
|
||
100x26 or larger visible sidebar
|
||
120x32 or larger expanded sidebar
|
||
```
|
||
|
||
The terminal owns a run-scoped application context with persistent header,
|
||
content, sidebar when present, and two-line footer planes. It has a single
|
||
event loop; planes are rendering resources, not application state. Resize
|
||
recomputes the layout and preserves the route, stable list selection and a
|
||
visible equivalent focus target. A sidebar focus becomes the compact
|
||
Navigation control when the sidebar disappears.
|
||
|
||
Common controls:
|
||
|
||
```text
|
||
↑ ↓ list navigation
|
||
Enter open/activate
|
||
Tab / Shift+Tab cycle rendered focus targets
|
||
F6 open Navigation
|
||
F7 open the route's action registry
|
||
Esc / b return, cancel, or close/restore an overlay
|
||
0 / Home Accueil
|
||
1/F1 … 5/F5 manual session, completed sessions, exercises, equipment, mensurations
|
||
6 Synchronisation
|
||
q quit from the application shell
|
||
```
|
||
|
||
The root aliases and contextual actions are built from one action registry:
|
||
the footer and F7 therefore advertise the same enabled actions that dispatch.
|
||
After contextual and shell actions are collected, their stable action identities
|
||
are deduplicated before footer layout, so `F6 Navigation` and `F7 Actions` each
|
||
appear once.
|
||
The intentional APP_SHELL_V1 shortcut changes are the replacement of the old
|
||
top-level Historique/Corps aliases by `2/F2 Séances effectuées` and
|
||
`5/F5 Mensurations`, F6 Navigation and F7 Actions, as approved in design
|
||
§13. A route alias is ignored while an overlay is active; a local editor owns
|
||
its keys before shell aliases.
|
||
|
||
On an exercise detail, `F7 Actions` exposes `Fusionner avec…`. The current
|
||
exercise is the source; a bounded UTF-8 search overlay selects the canonical
|
||
target. A second overlay previews the explicit source → canonical direction
|
||
and the occurrence-owned data preserved by the transaction. Profile or
|
||
primary-zone conflicts leave both exercises untouched and show a specific
|
||
result. After success, the canonical target remains selected.
|
||
|
||
Minimum terminal size:
|
||
|
||
```text
|
||
72x20
|
||
```
|
||
|
||
Smaller terminals display a clear fallback instead of corrupt layout.
|
||
|
||
## Interface language (`TRAINLOG_I18N_V0_1_1`)
|
||
|
||
Trainlog version 0.1.2 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.
|
||
|
||
Notcurses provides a true-color Catppuccin-derived dark palette: background
|
||
`#1E1E2E`, surface `#181825`, text `#CDD6F4`, and semantic accent, success,
|
||
warning, error, muted, and graph roles. Unicode frames and visible selection
|
||
markers enhance presentation without becoming application semantics.
|
||
|
||
The backend owns run-scoped planes, translates terminal input into
|
||
Trainlog-owned keys, and accepts complete UTF-8 code points in bounded shell
|
||
editors without a runtime parser or `ncreader`. Search/form storage is capped
|
||
at 200 UTF-8 bytes plus NUL, rejects invalid or partial code points without
|
||
partial mutation, and keeps grapheme boundaries while moving or deleting.
|
||
Escape clears a non-empty search before closing an empty search. Filtered lists
|
||
retain selection by stable ID and report bounded page availability.
|
||
|
||
Input lifecycle is handled at that boundary: legacy/unknown terminal events,
|
||
Notcurses PRESS events, and deliberate auto-REPEAT events become one logical
|
||
Trainlog action; Notcurses RELEASE events are consumed and never reach screen
|
||
navigation or prompt handling. This prevents extended terminal keyboard
|
||
protocols from applying one physical keypress twice.
|
||
|
||
Color is not the sole state carrier.
|
||
|
||
Typical roles:
|
||
|
||
```text
|
||
accent
|
||
success
|
||
warning
|
||
error
|
||
muted
|
||
graph series
|
||
```
|
||
|
||
Focused frames use the focus semantic role without recoloring all content.
|
||
The palette uses synchronized semantic RGB role tokens; an optional Nerd Font
|
||
may improve symbols, while text fallbacks are mandatory.
|
||
|
||
## 4. Exercise catalog
|
||
|
||
Exercise behavior is driven by:
|
||
|
||
```text
|
||
recording_mode
|
||
tracking_mode
|
||
data_fields
|
||
```
|
||
|
||
No exercise-name heuristic determines an entry form.
|
||
|
||
Catalog identities are stable.
|
||
|
||
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. 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
|
||
manifest zones plus **Non renseignés**; `x` clears search and zone filter. Parent filters
|
||
include descendants, so **Membres supérieurs** finds direct chest/back/
|
||
shoulders/arms relations without stored parent rows. Search and filter combine.
|
||
Exercise detail displays the primary, ordered secondaries and the primary's
|
||
derived group. `e` edits name and zones transactionally; Escape at either edit
|
||
prompt leaves the durable row unchanged.
|
||
|
||
## 5. Session entry
|
||
|
||
The TUI can record sessions directly.
|
||
|
||
Set-based entry supports planned targets and actual work. Planned target values
|
||
remain planning metadata: they are never copied into an actual performed set.
|
||
|
||
For a normal `SETS` occurrence, creation collects planning metadata only: it
|
||
does not accept a compact performed-repetitions expression, a performed set
|
||
count, sequential performed durations, or a pre-table performed load. It starts
|
||
with zero actual rows, then opens the ordered, keyboard-first actual-set table
|
||
as the sole path for creating actual work. Each row contains its actual
|
||
repetitions (or duration) and, when the occurrence has a load mode, its
|
||
independently optional Charge or Assistance value. The table uses:
|
||
|
||
```text
|
||
Up/Down select a row
|
||
Left/Right/Tab select metric or load cell
|
||
Enter edit the selected cell
|
||
a append a row and enter its required actual metric
|
||
d/Delete delete the selected set
|
||
Escape cancel the active cell, or leave the table
|
||
f/b finish the table
|
||
```
|
||
|
||
Adding a row requires an explicit actual repetitions/duration value and creates
|
||
no actual load; an empty load cell remains absent rather than inheriting the
|
||
target. An entered actual load is finite and non-negative;
|
||
the planned target remains a separate, strictly positive planning value. The
|
||
current-session summary is assembled from each
|
||
actual row, so mixed repetitions, optional loads and assistance values are not
|
||
collapsed into one target value.
|
||
|
||
Finishing a normal `SETS` draft with zero actual rows is blocked with an
|
||
explicit diagnostic. This guard does not apply to `MAX` or continuous entries,
|
||
whose distinct persistence contracts contain no performed-set rows.
|
||
|
||
For timed work, the shared duration parser accepts forms such as:
|
||
|
||
```text
|
||
90
|
||
90s
|
||
1:30
|
||
1m30
|
||
1m30s
|
||
2m
|
||
```
|
||
|
||
Persistent duration/rest units remain seconds.
|
||
|
||
Continuous exercise entry asks for duration and configured supplemental fields
|
||
without set/rest/load prompts.
|
||
|
||
### Session generator
|
||
|
||
On the dashboard, `g` opens the generator. It selects a policy BODY ZONE, goal
|
||
and duration, captures one explicit reference time, and renders a SQLite-free
|
||
preview containing target sets/repetitions, optional observed load/source,
|
||
rest, equipment, primary zone, patterns, exposure and shortage reasons.
|
||
Preview, resize and cancel write nothing. A generator result with no exercise
|
||
cannot enter persistence; a nonempty partial result remains editable with its
|
||
warnings visible. Accepting a preview builds the ordinary normal session draft
|
||
with zero actual rows and enters the existing editor. The existing completion
|
||
guard still requires actual rows for every SETS exercise.
|
||
|
||
The preview and ordinary plan editor preserve direct kg and no-target entry and
|
||
also expose a `%MAX` calculator. It accepts only integers 1..100 and only the
|
||
chronologically latest explicit MAX for the exact exercise/equipment external-
|
||
resistance context. Assistance is unavailable. The formula is
|
||
`MAX × percentage / 100`; it is not a recommendation, and only the resulting
|
||
`target_weight_kg` is retained when the draft is accepted. In the generator,
|
||
`u` restores automatic V1 load qualification, `%` selects the calculator and
|
||
`x` selects no numeric target.
|
||
The preview labels these outcomes `user_selected_max_percentage` or
|
||
`compatible_max_unavailable` and clears a calculated target if its equipment
|
||
context changes.
|
||
|
||
Generator duration is labelled as a target alongside the estimate. A
|
||
meaningful shortfall is reported without padding, and V1 states explicitly that
|
||
it generates neither warm-up nor cool-down. Multi-session/program work remains
|
||
future `SESSION_GENERATOR_V2`; it is not implemented by these controls.
|
||
|
||
## 6. Session history and editing
|
||
|
||
History is keyboard navigable.
|
||
|
||
`Enter` opens full session detail.
|
||
|
||
For set-based occurrences, detail displays an ordered, scrollable table of the
|
||
persisted rows with series number, repetitions or duration, and Charge or
|
||
Assistance. It exposes every stored per-set value, including absent loads,
|
||
rather than only a compact aggregate.
|
||
|
||
Persisted session editing preserves the parent session identity and timestamps
|
||
while replacing child exercise/set data transactionally.
|
||
|
||
Inside editable session exercise lists:
|
||
|
||
```text
|
||
d delete selected exercise from the session
|
||
|
||
These delete keys now open the existing event-loop-owned confirmation state;
|
||
they do not commit directly. Selection defaults to **Non**, Escape and `q`
|
||
cancel, and Enter commits only when the explicit destructive choice is focused.
|
||
Draft abandon follows the same rule. Exercise merge keeps its impact preview
|
||
and confirmation. Feedback wording remains read-only in the TUI.
|
||
```
|
||
|
||
A failed replacement rolls back completely.
|
||
|
||
Removing an exercise from one session does not remove the exercise from the
|
||
catalog.
|
||
|
||
## 7. Equipment
|
||
|
||
The legacy Equipment catalogue is no longer a normal top-level workflow.
|
||
Its storage and compatibility APIs remain available for old history and peers.
|
||
Historically, `Équipements` (direct alias `4/F4`) provided supplied-equipment browsing,
|
||
search, detail, and custom-equipment creation and selection. Supplied definitions are generated
|
||
from `catalog/equipment-v1.json`; user-created definitions persist in desktop
|
||
SQLite and synchronize separately through definitions V1.
|
||
|
||
Equipment can be selected while editing an exercise occurrence. Session detail,
|
||
exercise history, and historical occurrence views resolve an `equipment_id`
|
||
against the supplied manifest or a local custom definition. If an historic ID
|
||
is no longer resolvable, the view shows that unknown reference explicitly.
|
||
Session detail displays the selected occurrence's stable `entry_id` together
|
||
with its resolved equipment, so repeated appearances of one exercise remain
|
||
visibly distinct; `i` opens the resolved equipment detail.
|
||
|
||
## 8. Body tracking
|
||
|
||
`Statistiques → Mensurations` (direct alias `5/F5`) provides:
|
||
|
||
- a current-profile snapshot of the selected newest-first body observation;
|
||
- `↑`/`↓` historical-observation selection and `Entrée` detail access;
|
||
- weight, when present, on a separate `kg` line;
|
||
- only present circumferences as exact `cm` values with thin horizontal bars
|
||
scaled to the largest circumference in that observation;
|
||
- `←`/`→` selection among only metrics that exist in recorded observations,
|
||
with weight selected by default when available;
|
||
- a reusable Notcurses evolution chart whose X axis represents elapsed time,
|
||
whose labelled Y axis adds modest visible headroom, and whose 2x4 Braille
|
||
raster draws a fine piecewise-linear line between stored observations;
|
||
- distinct accent diamonds for actual measurements, including one centered
|
||
point with a readable scale for a singleton series; zero points show
|
||
`Aucune donnée disponible.` without unnecessary axes;
|
||
- first/last civil-date labels and a non-colliding intermediate graduation
|
||
when the available width permits it;
|
||
- detail and correction;
|
||
- body trend visualization;
|
||
- normalized multi-metric overlay;
|
||
- no invented zero values for missing measurements.
|
||
|
||
The profile and evolution sections are deliberately separate: kilograms never
|
||
share a circumference scale, missing measurements are omitted, and the profile
|
||
bars make no progress or trend claim. Sparse profiles donate unused rows to the
|
||
chart. The current content-plane rectangle is recalculated on every render, so
|
||
the sections resize responsively down to the supported 72x20 terminal without
|
||
retaining stale coordinates.
|
||
|
||
Editing preserves observation identity, timestamp, and optional session link.
|
||
|
||
## 9. Statistics and dashboard
|
||
|
||
The rolling 12-month normalized body graph has moved from the dashboard to
|
||
`Statistiques → Mensurations`; its data rules are unchanged.
|
||
|
||
The landing dashboard itself is a read-only projection over persisted facts.
|
||
Rendering consumes the route-owned snapshot and performs no SQLite access.
|
||
Its performance plot is a selected-period bucket count of improvement events,
|
||
never a sum of
|
||
kilograms across exercises or equipment. Every performed set is a working
|
||
observation. Working and explicit-MAX improvements are separate and require a
|
||
strictly earlier canonical-instant comparable point; equal-instant stable IDs
|
||
only order presentation. Raw series remain in the
|
||
per-exercise drill-down. The 30-day default switches with `1` through `5` to 7,
|
||
30, 90, 365 days or all history. Progression and frequency share a deterministic
|
||
projection of that selection: seven daily buckets for 7 days, six 5-day buckets
|
||
for 30 days, six 15-day buckets for 90 days, twelve 31-day presentation buckets
|
||
for one year, and at most twelve equal day spans covering represented history
|
||
for Tout. Canonical rolling membership is unchanged; local persisted dates only
|
||
place included facts into a display bucket, with boundary dates clamped into the
|
||
first or current bucket. The summary reports actual sessions, performed sets,
|
||
distinct actual exercises as `Exercices pratiqués`, and explicit MAX records.
|
||
The body summary
|
||
selects the most recently observed metric; one point has no delta or graph.
|
||
Observable session history and the
|
||
frequency histogram include a stable session ID exactly once when an occurrence
|
||
owns a persisted performed set, continuous activity, or explicit MAX result.
|
||
`ended_at` is presentation/lifecycle metadata, not the history gate; planned
|
||
targets and empty occurrences do not count. A capacity warning is shown when a
|
||
bounded source reaches its local limit.
|
||
Malformed persisted timestamps are excluded before sorting, reported as ignored
|
||
legacy data, and do not make the remaining dashboard unavailable.
|
||
|
||
`WEB_DASHBOARD_CHARACTERIZATION_V1=PASS/FROZEN` fixes the current projection
|
||
before Core extraction. Its persisted sources are `sessions`,
|
||
`session_exercises`, `performed_sets`, `continuous_activity`, `max_results`,
|
||
`exercises`, exercise BODY ZONE relations, equipment resolution, and body
|
||
observations through their existing database readers. The landing snapshot has
|
||
no “last session” record and computes no loaded volume: it exposes counts,
|
||
period buckets, one selected working-performance identity/series, the latest
|
||
eligible explicit-MAX event, one latest body-metric series, and catalogue-zone
|
||
buckets.
|
||
|
||
SETS/REPS and SETS/DURATION each contribute one performed-set fact per stored
|
||
row; their dose is respectively repetitions or seconds. CONTINUOUS/DURATION
|
||
contributes one exercise/session fact but never a performed set or working
|
||
performance point. Multiple occurrences are retained as independent facts,
|
||
while the distinct-exercise total deduplicates by `exercise_id`. Plans without
|
||
actual sets, continuous data, or explicit MAX do not enter observable session
|
||
history. A valid actual session with empty `ended_at` does enter it.
|
||
|
||
Working improvement requires the exact exercise, equipment, tracking mode,
|
||
load mode and positive dose, a resolved equipment whose load semantics are
|
||
`external`, an occurrence load mode of `external`, a finite nonnegative actual
|
||
weight, and a strictly earlier comparable instant. Assistance facts remain in
|
||
session/set/exercise counts but not in working comparison. Explicit MAX remains
|
||
separate from ordinary heavy sets; it requires resolved external equipment and
|
||
a non-assistance occurrence. The headline MAX is the latest eligible event,
|
||
not the greatest weight. Equal-instant facts never compare against one another;
|
||
stable session/entry/kind/position order is used internally, the selected
|
||
exercise tie-break is bytewise `exercise_id`, and performance points then order
|
||
by instant and `session_id`.
|
||
|
||
The selected working series currently copies every matching exact-dose set and
|
||
marks its point as weighted even when the persisted set has no weight, yielding
|
||
`0.0 kg` for that point. This surprising legacy behavior is characterized, not
|
||
endorsed or corrected by this tranche. The prior-best scan likewise accepts a
|
||
matching earlier unweighted set as a `0.0 kg` baseline, so a later weighted set
|
||
can be classified as an improvement. Two selected-series points from the same
|
||
session and canonical instant compare equal because that final comparator has
|
||
no set-position tie-break; the preceding fact order is session, entry, kind,
|
||
then set position. The Core extraction must initially preserve these observed
|
||
semantics or explicitly return to contract review before changing them.
|
||
|
||
`WEB_DASHBOARD_CORE_READ_MODEL_V1=PASS/FROZEN` preserves those semantics in
|
||
the Core API `trainlog_dashboard_load()`. Its query makes the reference Unix
|
||
second explicit, so rolling boundaries are deterministic without a test-only
|
||
production clock. The snapshot is caller-owned and retains the characterized
|
||
128-session, 4096-fact, 128-performance-point, 256-body-point and 12-bucket
|
||
bounds plus conservative `partial` and `invalid_data` flags. The TUI owns only
|
||
period selection, the normal `time(NULL)` capture, localization, formatting,
|
||
layout, rendering and interaction. Consequently
|
||
`WEB_TUI_READ_MODEL_ADOPTION_V1=PASS/FROZEN` is complete as part of the same
|
||
extraction tranche.
|
||
|
||
The snapshot reads at most 128 observable sessions, 4096 occurrence facts, 128
|
||
selected performance points, and 256 points for each body metric. Reaching a
|
||
bound sets the conservative `partial` flag. At exactly 128 sessions the view is
|
||
already partial; beyond that limit the session total stays at 128 while facts
|
||
continue independently up to 4096. At exactly 4096 facts the view is complete;
|
||
the next fact is omitted and sets `partial`. Malformed fact/timestamp rows are
|
||
omitted and set `invalid_data`. A database with no training history still has
|
||
the seeded canonical exercise catalogue, so its training/MAX/body values are
|
||
empty while its catalogue zone distribution is not.
|
||
|
||
`Répartition du catalogue` is a separate global catalogue projection. It
|
||
counts every active canonical exercise exactly once by its persisted **primary**
|
||
BODY ZONE; secondary zones do not inflate the result, merged source IDs are not
|
||
catalogue rows, and an exercise with no primary zone is shown as `Non classés`.
|
||
Buckets are ordered by descending count then label. Each landing-chart row owns
|
||
an independent Unicode horizontal bar between its left label and right count;
|
||
the bounded leading buckets remain at compact sizes without colliding with the
|
||
frequency chart. Enter opens the existing global exercise catalogue, never an
|
||
arbitrary exercise detail.
|
||
|
||
The dashboard rendering regression covers global event/summary facts, primary
|
||
zone/unclassified catalogue projection, and sparse or multi-point measurements
|
||
at 120x35, 100x30, 80x24, and the 72x20 minimum. Unicode blocks/line/point
|
||
glyphs provide chart geometry; text totals remain the accessible factual
|
||
equivalent at every supported width. Detail charts show no graph for zero or
|
||
one actual point, and no ASCII placeholder trace for two or more points.
|
||
|
||
Rules include:
|
||
|
||
- fixed calendar month slots;
|
||
- missing months remain empty;
|
||
- no zero fill;
|
||
- no interpolation;
|
||
- when multiple observations exist in one month, the last visible monthly value
|
||
is used for the compact twelve-month graph.
|
||
|
||
Detailed raw observations remain in `Statistiques → Mensurations`.
|
||
|
||
## 10. Exercise performance
|
||
|
||
Exercise detail exposes recorded performance history.
|
||
|
||
Representative comparison semantics:
|
||
|
||
```text
|
||
load none
|
||
greatest successful reps/duration
|
||
|
||
external
|
||
greatest actual load
|
||
tie -> greatest reps/duration
|
||
|
||
assistance
|
||
lowest assistance
|
||
tie -> greatest reps/duration
|
||
```
|
||
|
||
A best recorded set is not automatically a measured maximum.
|
||
|
||
## 11. Sync page
|
||
|
||
`6 Sync` uses the shared synchronization engine. The supported shortcut is the
|
||
numeric `6` key.
|
||
|
||
The page shows:
|
||
|
||
- connected MTP device status;
|
||
- storage availability;
|
||
- structured synchronization history.
|
||
|
||
Manual action:
|
||
|
||
```text
|
||
a run Android -> PC synchronization
|
||
p run PC -> Android synchronization
|
||
b run bidirectional synchronization
|
||
r refresh device status
|
||
```
|
||
|
||
`a` imports definitions V1, mobile V3, the body-zone companion and associations
|
||
V2 only. `p` publishes definitions V1, catalog V1, the body-zone companion,
|
||
mobile V3 (including body observations), and associations V2 only. `b`
|
||
completes that inbound sequence before beginning the outbound sequence.
|
||
|
||
The direction keys are direct actions: pressing `a`, `p`, or `b` opens one
|
||
confirmation for that exact direction; there is no separate mode-selection
|
||
step. `Enter` accepts the pending action and can invoke the shared engine only
|
||
once. `Esc` cancels a pending confirmation without an operation; when no
|
||
confirmation is pending, `Esc` returns from Sync. `r` only refreshes connected
|
||
device status. The former `s` synchronization shortcut is retired and inert.
|
||
|
||
The footer states the same contract in both layouts. At 100 columns or wider it
|
||
reads `a Android→PC p PC→Android b PC↔Android r actualiser Échap retour`;
|
||
the compact footer uses `a A→PC p PC→A b A↔PC r act. Échap retour` without
|
||
changing any action or direction.
|
||
|
||
Before a confirmed run, Sync identifies the selected direction in its progress
|
||
feedback. It then shows the completed run summary on success, or a
|
||
direction-prefixed diagnostic on failure. Each structured local history run
|
||
persists its selected `a`, `p`, or `b` direction, and the history list displays
|
||
that direction label alongside its summary. `direction inconnue` is used only
|
||
for a legacy entry whose direction was never recorded, rather than being
|
||
guessed.
|
||
|
||
History behaves like a compact Git log:
|
||
|
||
```text
|
||
↑ ↓ select run
|
||
Enter open run detail
|
||
```
|
||
|
||
The detail view behaves like a compact `git show` and contains:
|
||
|
||
```text
|
||
sync ID
|
||
trigger
|
||
time
|
||
status
|
||
request ID when applicable
|
||
Android -> PC counts
|
||
PC -> Android catalog count
|
||
summary
|
||
error when applicable
|
||
```
|
||
|
||
## 12. Shared sync engine
|
||
|
||
The TUI does not own a separate synchronization implementation.
|
||
|
||
It calls:
|
||
|
||
```text
|
||
trainlog_sync_run(TRAINLOG_SYNC_TRIGGER_TUI, ...)
|
||
```
|
||
|
||
The Android-triggered daemon calls the same engine.
|
||
|
||
This keeps import/export, MTP publication, locking, history, and diagnostics in
|
||
one implementation.
|
||
|
||
## 13. Direct MTP
|
||
|
||
Transport uses:
|
||
|
||
```text
|
||
libudev -> exact physical USB device
|
||
libmtp -> storage/object operations
|
||
```
|
||
|
||
No filesystem mount is required.
|
||
|
||
Raw libmtp output is suppressed while Notcurses owns the terminal.
|
||
|
||
## 14. Error behavior
|
||
|
||
Input is validated before persistent mutation.
|
||
|
||
Escape cancels prompts without committing partial edits.
|
||
|
||
Synchronization failure displays a useful final diagnostic and records the
|
||
structured run when a transaction actually begins.
|
||
|
||
Conflict diagnostics identify the stable affected identity, the source
|
||
artifact/direction, and a concise source summary. They preserve existing
|
||
session, body-observation, association, and definition data rather than silently
|
||
overwriting it.
|
||
|
||
## 15. Build and test
|
||
|
||
```bash
|
||
meson compile -C build
|
||
meson test -C build --print-errorlogs
|
||
```
|
||
|
||
Validated current normal suite:
|
||
|
||
```text
|
||
39/39 Meson tests PASS (historical checkpoint)
|
||
```
|
||
|
||
## 16. Measured max view
|
||
|
||
Creating or editing a `Test de max` session uses the existing exercise search
|
||
and optional equipment selector, then asks only for `Poids max (kg)`. It does
|
||
not prompt for sets or repetitions and persists the value in `max_results`.
|
||
|
||
The history detail for a max-test session is a compact selectable table:
|
||
|
||
```text
|
||
Exercice Machine Max
|
||
Pec Fly Rear Delt / Pec Fly 100 kg
|
||
Rear Delt Fly Rear Delt / Pec Fly 86 kg
|
||
```
|
||
|
||
Equipment is occurrence context. The rows remain distinct by movement and
|
||
stable `entry_id`; no machine-level maximum is calculated. A preserved
|
||
non-MAX entry, such as continuous warm-up, remains visible with `—` in the MAX
|
||
column.
|
||
|
||
From `3 Exercices`, the selected exercise exposes:
|
||
|
||
```text
|
||
Enter ordinary performance history
|
||
m measured max
|
||
```
|
||
|
||
The measured-max page is deliberately separate from ordinary best-set history.
|
||
|
||
It shows:
|
||
|
||
- count of explicit max-test sessions;
|
||
- newest successful measured result;
|
||
- best historical result using the same load mode;
|
||
- dedicated max-test graph;
|
||
- max-test history;
|
||
- external-load working percentages at 60%, 70%, 80%, and 90%.
|
||
|
||
For external load, `r` cycles practical rounding increments:
|
||
|
||
```text
|
||
0.5 kg
|
||
1.0 kg
|
||
2.5 kg
|
||
5.0 kg
|
||
```
|
||
|
||
Working percentages are display calculations only.
|
||
|
||
Assistance remains inverse-direction:
|
||
|
||
```text
|
||
less assistance = better
|
||
```
|
||
|
||
No percentage-of-max working load is produced for assistance or no-load
|
||
performance.
|
||
|
||
## 17. Body analytics
|
||
|
||
`Statistiques → Mensurations` adds:
|
||
|
||
```text
|
||
v analyse corporelle
|
||
```
|
||
|
||
The analytics view has two pages:
|
||
|
||
```text
|
||
Composition et tendance
|
||
Proportions et symétrie
|
||
```
|
||
|
||
`p` configures a desktop-only estimation profile containing height and the
|
||
circumference-formula branch.
|
||
|
||
Composition can display:
|
||
|
||
- circumference-based body-fat estimate;
|
||
- estimated fat mass when body weight is present;
|
||
- estimated lean mass when body weight is present;
|
||
- weight change from the oldest available weight;
|
||
- waist change from the oldest available waist;
|
||
- body-fat estimate change when comparable observations exist.
|
||
|
||
Proportions can display:
|
||
|
||
- waist/hip ratio;
|
||
- shoulder/waist ratio;
|
||
- chest/waist ratio;
|
||
- left/right asymmetry for arms, forearms, thighs, and calves.
|
||
|
||
All estimates are explicitly labeled as estimates. No result is converted into
|
||
a medical or diagnostic classification.
|
||
|
||
## 18. Statistics center
|
||
|
||
`TRAINLOG_TUI_STATISTICS_V1=PASS` organizes the Notcurses statistics route as
|
||
five keyboard-first views: Corps, Vue globale, Exercices, Zones and MAX. The
|
||
body view retains recorded weight and circumference history. The other views
|
||
use read-only aggregates over the existing desktop schema and never persist a
|
||
derived score or cache.
|
||
|
||
The global view switches between the current calendar week, current calendar
|
||
month and complete history. It reports actual sessions, occurrences, sets, repetitions,
|
||
available durations, active days/weeks and loaded volume. Loaded volume is
|
||
strictly `repetitions × actual external load` when both the occurrence and the
|
||
exercise declare external-load semantics; duration, cardio, assistance and
|
||
no-load work are excluded. Its common Notcurses chart cycles through bucketed
|
||
loaded volume, sessions, sets and immediate-feedback counts.
|
||
|
||
Exercise statistics are selected by canonical `exercise_id`, include aliases
|
||
already reconciled by persistence, and omit inapplicable load facts. Their
|
||
graph cycles through recorded load, valid volume, repetitions, sets and
|
||
explicit MAX values. The MAX detail displays only persisted explicit tests,
|
||
including latest, previous, historical best and auditable deltas.
|
||
|
||
Zone statistics count distinct actual sessions through persisted
|
||
`exercise_body_zones` primary/secondary associations. Exercise names are never
|
||
used to infer a zone. Planning comparisons are limited to targets and actuals
|
||
owned by the same persisted occurrence. Immediate feedback remains attached
|
||
to its occurrence; J+1 feedback remains session-global, and free text is not
|
||
classified or attributed automatically.
|
||
|
||
`TRAINLOG_TUI_STATISTICS_REFINEMENT_V1=PASS` keeps these five routes while
|
||
making their visual semantics explicit. Finite 7-day and 30-day graphs retain
|
||
the complete selected time domain, including inactive space. Weekly totals use
|
||
Monday-based local calendar buckets, explicit empty weeks and discrete bars;
|
||
non-negative counts and volumes use a zero baseline. Continuous observations,
|
||
exercise progression and explicit MAX histories remain line graphs.
|
||
|
||
On sufficiently large planes, the training summary is grouped into Activity,
|
||
Work, Feedback and Planned/Performed panels; compact planes retain the bounded
|
||
vertical form. Zone primary and secondary bars share one scale across every
|
||
visible row and show their raw counts. The MAX catalogue defaults to exercises
|
||
with an explicit result, shows latest value/date, and uses `a` to reveal all
|
||
compatible set-based exercises. Prefix search and stable-ID drill-down are
|
||
unchanged.
|
||
|
||
`TRAINLOG_TUI_STATISTICS_PERIOD_BUCKETING_V1=PASS` separates the textual
|
||
summary window from the comparison depth of its chart. `7j` summarizes the
|
||
rolling last seven days but compares up to four available consecutive local
|
||
calendar weeks.
|
||
|
||
`TRAINLOG_TUI_STATISTICS_PERIOD_BUCKET_SIZE_FIX=PASS` is the historical
|
||
checkpoint that first separated comparison sizes. Its duration-based `Tout`
|
||
adaptation remains active; its finite rolling boundaries are superseded by the
|
||
calendar contract below. `Tout` uses Monday-based 7-day buckets while its
|
||
elapsed span fits the bounded graph, then switches the whole graph to calendar
|
||
months. The decision uses historical duration, so sparse old activity is not
|
||
silently discarded.
|
||
|
||
Explicit zero buckets remain visible between represented boundaries. Each
|
||
chart uses one bucket size and one zero-based scale, and its title explicitly
|
||
states 7-day periods, 30-day periods, complete-history weeks, or
|
||
complete-history months.
|
||
|
||
`TRAINLOG_TUI_STATISTICS_CALENDAR_BUCKETS_V1=PASS` supersedes rolling bucket
|
||
boundaries with calendar alignment. `7j` summarizes the current local calendar
|
||
week and compares Monday–Sunday weeks. `30j` summarizes the current Gregorian
|
||
calendar month and compares the current month with up to three preceding
|
||
months. The current week or month remains partial until its calendar endpoint.
|
||
|
||
Historical membership uses the `YYYY-MM-DD` civil date stored in each persisted
|
||
timestamp. It is never reassigned by converting that timestamp through the
|
||
current machine timezone. Month keys advance by Gregorian year/month, so
|
||
28-day February, leap-year February, 30-day months and 31-day months all expose
|
||
their exact exclusive chart endpoint. French abbreviated month labels and
|
||
Monday–Sunday ranges make the aggregation visible in the TUI.
|
||
|
||
## 19. Training knowledge infrastructure
|
||
|
||
The desktop core includes immutable `training_knowledge.h` catalog access and
|
||
read-only `training_context.h` composition. The context uses real persisted IDs
|
||
and returns persisted zones, optional science, compatible equipment, latest
|
||
explicit MAX and bounded chronological history; it does not write data. The
|
||
TUI provides a UTF-8 cell-aware scrolling knowledge screen, tested at the 72x20
|
||
minimum terminal. V1 provides no prescription, generator or scoring flow. Its
|
||
lifecycle is `TRAINING_KNOWLEDGE_V1=PASS`. Its occurrence and latest-MAX readers use the settled
|
||
temporal contract: exact instant/fraction ordering, bytewise stable-ID ties,
|
||
exclusive source-text cursors, bounded selection before hydration, and one
|
||
read snapshot per call. Malformed cursors and matching stored timestamps fail
|
||
explicitly. The full-tranche audit repair chain and independent verification
|
||
passed. See [Training knowledge system V1](domain/knowledge_system.md).
|
||
|
||
The normal Sessions hub has current/manual/completed paths only; the retained
|
||
SESSION_GENERATOR_V1 route is not advertised. Physical `/` and F7 Rechercher
|
||
dispatch the one registered list-search action on supported lists.
|
||
Equipment Detail lists V2 verified exercise options and confidence within the
|
||
72x20 layout. The generated core also exposes bounded reverse and BODY ZONE
|
||
queries without changing Body Focus geometry or session generation.
|
||
Completed-session detail also consults synchronized exercise feedback and the
|
||
session follow-up timeline read-only. Exercise labels use `Pendant la séance`,
|
||
`H+xx`, or neutral `Ressenti`; follow-ups use `H+xx` or `H+?`. The TUI never
|
||
renders a negative H+ and has no V1 feedback mutation.
|