trainlog/docs/tui.md

549 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
## 2. APP_SHELL_V1 navigation
The persistent shell has seven root sections:
```text
Accueil | Séances | Exercices | Équipements | 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.
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.
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.
## 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. Only French display names are shown; 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
```
A failed replacement rolls back completely.
Removing an exercise from one session does not remove the exercise from the
catalog.
## 7. Equipment
`Équipements` (direct alias `4/F4`) provides 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:
- newest-first body observations;
- detail and correction;
- body trend visualization;
- normalized multi-metric overlay;
- left/right metric separation;
- no invented zero values for missing measurements.
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.
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
```
## 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. 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).