Add dropdown exercise selection to Doris Barbell logs
Some checks failed
secret-guardrails / artifact-secret-scan (push) Has been cancelled
secret-guardrails / gitleaks (push) Has been cancelled

This commit is contained in:
Fizzlepoof
2026-05-28 18:09:51 +00:00
parent 6ba3fe208e
commit 3960e168bc
5 changed files with 34 additions and 4 deletions

View File

@@ -47,8 +47,9 @@ Tech stack: FastAPI, Jinja2, JSON state store for V1 bootstrap, Docker Compose o
- Homepage now renders current metrics, recent workouts, exercise bests, and placeholder template/routine scaffolding.
- Homepage now renders John's imported five-day split as the default routine board.
- Homepage now includes mobile-friendly HTML forms for quick weight entry, body measurements, exercise templates, routine drafts, and quick workout logging.
- Routine log sheets now support dropdown exercise selection with set-by-set rep and weight entry for seeded program days.
- Homepage now includes John-focused recent history snapshots so the app is useful before charts exist.
- Tests currently cover health, seeded users, BMI calculation, imperial measurement logging, exercise template creation, routine creation, history ordering, structured workout logging, progression summaries, homepage render, and form-post redirects.
- Tests currently cover health, seeded users, BMI calculation, imperial measurement logging, exercise template creation, routine creation, history ordering, structured workout logging, progression summaries, homepage render, routine log flows, and form-post redirects.
## Next build slices

View File

@@ -53,6 +53,7 @@ The current scaffold already supports:
- exercise template creation/listing
- routine creation/listing on top of John's imported base split
- structured workout logging with nested exercises and sets
- dedicated routine log sheets with dropdown exercise selection plus set-by-set reps and weight entry
- history endpoints for weight, measurements, and workouts
- exercise progression summaries with max weight and estimated 1RM per exercise
- mobile-friendly homepage with HTML forms for:

View File

@@ -133,6 +133,7 @@ def routine_log_sheet(request: Request, routine_id: str):
'title': f"Log {routine['name']}",
'routine': routine,
'routine_sheet': _build_routine_log_sheet(routine),
'exercise_options': _exercise_options_for_routine(request, routine),
'routine_action_label': _routine_action_label(routine['name']),
'default_performed_at': _default_performed_at_value(),
},
@@ -285,6 +286,24 @@ def _build_routine_log_sheet(routine: dict) -> list[dict]:
return sheet
def _exercise_options_for_routine(request: Request, routine: dict) -> list[str]:
routine_names = [exercise['exercise_name'] for exercise in routine.get('exercises', []) if exercise.get('exercise_name')]
template_names = [
template['name']
for template in request.app.state.store.list_exercise_templates()
if template.get('name')
]
ordered = []
seen = set()
for name in [*routine_names, *template_names]:
normalized = name.strip().lower()
if normalized in seen:
continue
ordered.append(name)
seen.add(normalized)
return ordered
def _default_reps_value(target_reps: str | None) -> str:
if target_reps is None:
return ''

View File

@@ -52,13 +52,18 @@
{% for exercise in routine_sheet %}
<section class="evidence-slab">
<input type="hidden" name="exercise_{{ exercise.exercise_index }}_name" value="{{ exercise.exercise_name }}">
<input type="hidden" name="exercise_{{ exercise.exercise_index }}_set_count" value="{{ exercise.target_sets }}">
<div class="case-legend">
<span class="section-label">Exercise {{ loop.index }}</span>
<span class="pill">{{ exercise.target_sets }} sets</span>
</div>
<h3>{{ exercise.exercise_name }}</h3>
<label>Exercise
<select name="exercise_{{ exercise.exercise_index }}_name">
{% for option_name in exercise_options %}
<option value="{{ option_name }}" {% if option_name == exercise.exercise_name %}selected{% endif %}>{{ option_name }}</option>
{% endfor %}
</select>
</label>
<p class="muted">Target reps: {{ exercise.target_reps or 'enter manually' }}</p>
<div class="grid cols-2 compact-grid">
{% for set in exercise.sets %}

View File

@@ -388,6 +388,9 @@ def test_seeded_routine_can_render_prefilled_log_sheet(tmp_path: Path) -> None:
assert 'Thursday: Shoulders' in body
assert 'Shoulder Press' in body
assert 'Save shoulder day workout' in body
assert 'name="exercise_0_name"' in body
assert '<option value="Shoulder Press" selected>' in body
assert 'value="Upright Row"' in body
assert 'name="exercise_0_set_0_reps"' in body
assert 'name="exercise_0_set_0_weight_lbs"' in body
assert 'value="10"' in body
@@ -417,7 +420,7 @@ def test_seeded_routine_log_submission_creates_structured_workout(tmp_path: Path
'exercise_0_set_1_weight_lbs': '80',
'exercise_0_set_2_reps': '8',
'exercise_0_set_2_weight_lbs': '90',
'exercise_1_name': 'Arnold Press',
'exercise_1_name': 'Upright Row',
'exercise_1_set_count': '3',
'exercise_1_set_0_reps': '10',
'exercise_1_set_0_weight_lbs': '35',
@@ -441,6 +444,7 @@ def test_seeded_routine_log_submission_creates_structured_workout(tmp_path: Path
assert payload[0]['total_volume_lbs'] == 3240
assert payload[0]['exercises'][0]['exercise_name'] == 'Shoulder Press'
assert payload[0]['exercises'][0]['sets'][2] == {'reps': 8, 'weight_lbs': 90.0}
assert payload[0]['exercises'][1]['exercise_name'] == 'Upright Row'
def test_existing_state_gets_seeded_routine_backfill(tmp_path: Path) -> None: