[OCaml] High Intensity Training Online
fix discard workout on cancel instead of saving it
The cancel handler called Service.finish, which persisted the in-progress workout to history and only cleared the slot. Cancelling therefore navigated away but never took effect as a cancellation: it saved a record instead of discarding one. Call Service.cancel, which clears the slot without writing history. This restores the doctrine that plan and record stay distinct: an abandoned session is not evidence, so it leaves no record (domain.md, "Keep plan and record distinct"). ARCHITECTURE.org already documents /workout/cancel as "Discard without saving"; the handler now matches it. Update the web flow test to assert the discarded workout leaves no logbook entry and its record route returns 404.
Changed files
lib/web/handlers.ml
@@ -369,17 +369,18 @@
369
369
| None -> not_found Present.no_workout)
370
370
371
371
(* Leave the current workout unconditionally. Cancelling never shows an
372
Removed:
invalid-form response: the workout in progress is saved as-is — a partial
373
Removed:
record the logbook keeps and stays editable later — and the user returns
374
Removed:
to the Routine tab, where the middle navigation action reverts from
375
Removed:
"Current workout" to "Routine". A forged or missing CSRF token skips the
376
Removed:
save but still navigates away, so the view is always left cleanly. *)
372
Added:
invalid-form response: the workout in progress is discarded — an
373
Added:
abandoned session is not evidence, so it leaves no record — and the user
374
Added:
returns to the Routine tab, where the middle navigation action reverts
375
Added:
from "Current workout" to "Routine". A forged or missing CSRF token skips
376
Added:
the discard but still navigates away, so the view is always left
377
Added:
cleanly. *)
377
378
let cancel t trainee request =
378
379
guard_csrf request >>= function
379
380
| Error _ -> redirect_to request Routes.routine
380
381
| Ok () ->
381
Removed:
Service.finish t.service trainee.Trainee.id ~ended_at:(t.now ())
382
Removed:
>>= fun _ -> redirect_to request Routes.routine
382
Added:
Service.cancel t.service trainee.Trainee.id >>= fun _ ->
383
Added:
redirect_to request Routes.routine
383
384
384
385
let routes t =
385
386
[
test/test_web.ml
@@ -825,7 +825,7 @@
825
825
Alcotest.(check int)
826
826
"workout still in progress" 200
827
827
(status (get c "/workout")) );
828
Removed:
( "cancelling after recording a set saves the workout for later editing",
828
Added:
( "cancelling after recording a set discards the workout",
829
829
`Quick,
830
830
fun () ->
831
831
let c = client () in
@@ -850,7 +850,7 @@
850
850
]
851
851
in
852
852
Alcotest.(check int) "recorded a set" 303 (status recorded);
853
Removed:
(* Cancel with a valid token. It saves the log as a partial entry
853
Added:
(* Cancel with a valid token. It discards the in-progress workout
854
854
and returns to the routine tab. *)
855
855
let token = Option.get (csrf_token (body (get c "/workout"))) in
856
856
let cancelled =
@@ -865,14 +865,14 @@
865
865
Alcotest.(check int)
866
866
"no workout in progress" 303
867
867
(status (get c "/workout"));
868
Removed:
(* The workout was saved as a partial entry: the logbook holds the
869
Removed:
record and it opens for later editing. *)
868
Added:
(* The workout left no record: an abandoned session is not
869
Added:
evidence, so the logbook holds nothing and no record opens. *)
870
870
let logbook_page = body (get c "/logbook") in
871
871
Alcotest.(check bool)
872
Removed:
"logbook shows the saved workout" true
872
Added:
"logbook shows no saved workout" false
873
873
(contains ~substring:"/logbook/w1" logbook_page);
874
874
Alcotest.(check int)
875
Removed:
"the saved workout opens for editing" 200
875
Added:
"the discarded workout has no record" 404
876
876
(status (get c "/logbook/w1")) );
877
877
( "the saved logbook view does not offer a cancel control",
878
878
`Quick,