254
254
]
255
255
else []
256
256
in
257
-
Removed:
(* The exercise selector: a dropdown naming every prescription slot. Choosing
258
-
Removed:
an option opens that exercise below. The selector is a GET form so it works
259
-
Removed:
without script — submitting navigates to the slot via a [?slot=] query, the
260
-
Removed:
same contract the panel already reads. The client enhances it: it navigates
261
-
Removed:
the moment the selection changes, so the submit button is a no-JS fallback.
262
-
Removed:
A recorded slot is marked done in its label. *)
263
-
Removed:
let select_action_base =
264
-
Removed:
match record_id with None -> "/workout" | Some id -> "/logbook/" ^ id
265
-
Removed:
in
266
-
Removed:
let exercise_group =
267
-
Removed:
let option_for (slot : View_model.Workout.slot) =
268
-
Removed:
let done_ = Option.is_some slot.recorded_summary in
269
-
Removed:
let label =
270
-
Removed:
if done_ then Printf.sprintf "%d. %s (done)" (slot.index + 1) slot.label
271
-
Removed:
else Printf.sprintf "%d. %s" (slot.index + 1) slot.label
272
-
Removed:
in
273
-
Removed:
let attrs = [ Dream_html.string_attr "value" "%d" slot.index ] in
274
-
Removed:
let attrs =
275
-
Removed:
if slot.index = active_slot then Dream_html.attr "selected" :: attrs
276
-
Removed:
else attrs
277
-
Removed:
in
278
-
Removed:
tag "option" attrs [ txt "%s" label ]
279
-
Removed:
in
280
-
Removed:
let form_attrs =
281
-
Removed:
[
282
-
Removed:
Dream_html.string_attr "method" "get";
283
-
Removed:
Dream_html.string_attr "action" "%s" select_action_base;
284
-
Removed:
class_ "exercise-select";
285
-
Removed:
]
286
-
Removed:
in
287
-
Removed:
let form_attrs =
288
-
Removed:
if enhanced then Dream_html.attr "data-hito-exercise-form" :: form_attrs
289
-
Removed:
else form_attrs
290
-
Removed:
in
291
-
Removed:
tag "form" form_attrs
292
-
Removed:
[
293
-
Removed:
tag "label"
294
-
Removed:
[ Dream_html.string_attr "for" "exercise-choice"; class_ "sr-only" ]
295
-
Removed:
[ txt "Exercise" ];
296
-
Removed:
tag "select"
257
+
Added:
(* The exercise accordion: one <details> per prescription slot, all sharing a
258
+
Added:
[name], which the browser treats as an exclusive group — opening one closes
259
+
Added:
the rest, with no script. The active slot is open; the handler still picks
260
+
Added:
it from [?slot=], so a record or correction reopens the slot it belongs to.
261
+
Added:
A recorded slot shows its summary and a pre-filled correction form (which
262
+
Added:
replaces the slot rather than adding volume); an outstanding slot shows the
263
+
Added:
record form. Errors render only under the slot being edited. *)
264
+
Added:
let slot_section (slot : View_model.Workout.slot) =
265
+
Added:
let index = slot.index in
266
+
Added:
let errors = if editing = Some index then errors else [] in
267
+
Added:
let done_ = Option.is_some slot.recorded_summary in
268
+
Added:
let body =
269
+
Added:
match slot.recorded_summary with
270
+
Added:
| Some summary ->
297
271
[
298
-
Removed:
name "slot";
299
-
Removed:
id "exercise-choice";
300
-
Removed:
Dream_html.attr "data-hito-exercise-select";
272
+
Added:
tag "p" [ class_ "eyebrow" ] [ txt "Recorded" ];
273
+
Added:
tag "p" [ class_ "done" ] [ txt "%s" summary ];
274
+
Added:
form_for_stimulus request ~action_path:(edit_action index)
275
+
Added:
~slot:index ~movements:slot.movements
276
+
Added:
~selected_extension:slot.selected_extension
277
+
Added:
~selected_extension_amount:slot.selected_extension_amount
278
+
Added:
~enhanced ~submit:"Save correction" ~errors ();
301
279
]
302
-
Removed:
(List.map option_for vm.slots);
303
-
Removed:
void "input"
280
+
Added:
| None ->
304
281
[
305
-
Removed:
type_ "submit"; class_ "secondary exercise-select-go"; value "Open";
282
+
Added:
form_for_stimulus request ~action_path:(record_action index)
283
+
Added:
~slot:index ~movements:slot.movements
284
+
Added:
~selected_extension:slot.selected_extension
285
+
Added:
~selected_extension_amount:slot.selected_extension_amount
286
+
Added:
~enhanced ~errors ();
287
+
Added:
]
288
+
Added:
in
289
+
Added:
tag "details"
290
+
Added:
([
291
+
Added:
class_ "slot-accordion";
292
+
Added:
Dream_html.string_attr "name" "exercise";
293
+
Added:
Dream_html.string_attr "id" "slot-%d" index;
294
+
Added:
]
295
+
Added:
@ (if index = active_slot then [ Dream_html.attr "open" ] else [])
296
+
Added:
@ if done_ then [ Dream_html.string_attr "data-done" "true" ] else [])
297
+
Added:
[
298
+
Added:
tag "summary" []
299
+
Added:
[
300
+
Added:
tag "span" [ class_ "slot-number" ] [ txt "%d." (index + 1) ];
301
+
Added:
tag "span" [ class_ "slot-label" ] [ txt "%s" slot.label ];
302
+
Added:
(if done_ then tag "span" [ class_ "slot-done" ] [ txt "done" ]
303
+
Added:
else txt "");
306
304
];
305
+
Added:
tag "div" [ class_ "slot-panel" ] body;
307
306
]
308
307
in
309
-
Removed:
(* The panel for the active slot. It shows one exercise at a time: a recorded
310
-
Removed:
slot renders its read-only summary and a pre-filled correction form (which
311
-
Removed:
replaces the slot rather than adding volume). An outstanding slot renders
312
-
Removed:
the record form. *)
313
-
Removed:
let active_panel =
314
-
Removed:
match List.nth_opt vm.slots active_slot with
315
-
Removed:
| None ->
316
-
Removed:
[
317
-
Removed:
tag "p"
318
-
Removed:
[ class_ "warn" ]
319
-
Removed:
[ txt "That exercise is not part of this workout." ];
320
-
Removed:
]
321
-
Removed:
| Some (slot : View_model.Workout.slot) ->
322
-
Removed:
let errors = if editing = Some active_slot then errors else [] in
323
-
Removed:
let heading = tag "h2" [] [ txt "%s" slot.label ] in
324
-
Removed:
let body =
325
-
Removed:
match slot.recorded_summary with
326
-
Removed:
| Some summary ->
327
-
Removed:
[
328
-
Removed:
tag "p" [ class_ "eyebrow" ] [ txt "Recorded" ];
329
-
Removed:
tag "p" [ class_ "done" ] [ txt "%s" summary ];
330
-
Removed:
form_for_stimulus request ~action_path:(edit_action active_slot)
331
-
Removed:
~slot:active_slot ~movements:slot.movements
332
-
Removed:
~selected_extension:slot.selected_extension
333
-
Removed:
~selected_extension_amount:slot.selected_extension_amount
334
-
Removed:
~enhanced ~submit:"Save correction" ~errors ();
335
-
Removed:
]
336
-
Removed:
| None ->
337
-
Removed:
[
338
-
Removed:
form_for_stimulus request
339
-
Removed:
~action_path:(record_action active_slot)
340
-
Removed:
~slot:active_slot ~movements:slot.movements
341
-
Removed:
~selected_extension:slot.selected_extension
342
-
Removed:
~selected_extension_amount:slot.selected_extension_amount
343
-
Removed:
~enhanced ~errors ();
344
-
Removed:
]
345
-
Removed:
in
346
-
Removed:
[ tag "section" [ class_ "slot-panel" ] (heading :: body) ]
308
+
Added:
let accordion =
309
+
Added:
[ tag "div" [ class_ "slot-accordions" ] (List.map slot_section vm.slots) ]
347
310
in
348
311
let complete_note =
349
312
if vm.all_recorded then