Add training tracking: exercises with instructions, one-tap session log, consistency overview

Exercises (name + how-to note) are a new synced collection with the same
LWW/tombstone contract as events, served by POST /api/exercises/sync.
Training sessions are ordinary events (type "training") referencing an
exercise by id, so they ride the existing event sync unchanged.

The Training panel lists each exercise with last-trained / this-week /
streak stats, expandable instructions, and a one-tap Log button with the
usual undo/add-note snackbar. An exercise-by-day heatmap shows the last
14 days of consistency, and history and the daily overview count
training sessions like any other event.
This commit is contained in:
Alexander Heldt
2026-07-12 16:01:41 +00:00
parent 561b98b64f
commit 18fae835a5
6 changed files with 573 additions and 40 deletions
+69
View File
@@ -10,6 +10,7 @@
--pee: #ffd23f;
--poo: #8a5a3b;
--weight: #2bb3a3;
--training: #b04ecf;
--danger: #d64545;
--gain: #2e9e5b;
--border: #e9e6f5;
@@ -357,6 +358,7 @@ textarea { resize: vertical; }
.event[data-type="pee"] .dot { background: var(--pee); }
.event[data-type="poo"] .dot { background: var(--poo); }
.event[data-type="weight"] .dot { background: var(--weight); }
.event[data-type="training"] .dot { background: var(--training); }
.event .time { font-variant-numeric: tabular-nums; color: var(--muted); min-width: 60px; }
.event .label { font-weight: 600; min-width: 110px; }
@@ -755,6 +757,73 @@ input.switch:checked::after { transform: translateX(18px); }
.chart-svg .hm-pee { fill: var(--pee); }
.chart-svg .hm-poo { fill: var(--poo); }
.chart-svg .hm-eat { fill: var(--eat); }
.chart-svg .hm-training { fill: var(--training); }
/* ---------- training ---------- */
.training-list {
list-style: none;
margin: 0 0 10px;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.training-list:empty { margin: 0; }
.exercise {
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px 12px;
}
.ex-row {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
}
.ex-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.ex-name { font-weight: 600; }
.ex-meta { color: var(--muted); font-size: 0.8rem; }
button.ex-log {
background: var(--training);
padding: 8px 14px;
flex-shrink: 0;
}
.ex-detail { display: none; }
.exercise.expanded .ex-detail {
display: flex;
align-items: flex-start;
gap: 10px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--border);
}
.ex-note {
flex: 1;
margin: 0;
color: var(--muted);
font-size: 0.9rem;
line-height: 1.4;
white-space: pre-wrap;
}
button.ex-edit { padding: 6px 12px; flex-shrink: 0; }
.training-add { width: 100%; }
.training-chart { margin-top: 16px; }
/* ---------- collapsible panels ---------- */
section.collapsible > h2 {