[OCaml] High Intensity Training Online
feat replace the history endpoint with a logbook
Rename the /history routes and page to /logbook: the index shows all recorded evidence for the user, and the saved-record detail and correction routes follow to /logbook/:id. The service keeps its history query; only the web route, page, and labels change.
Changed files
ARCHITECTURE.org
@@ -358,10 +358,10 @@
358
358
| =/workout/slots/:slot/edit= | POST | Correct a recorded slot |
359
359
| =/workout/finish= | POST | Complete and persist |
360
360
| =/workout/cancel= | POST | Discard without saving |
361
Removed:
| =/history= | GET | What has been performed |
362
Removed:
| =/history/:id= | GET | A saved workout |
363
Removed:
| =/history/:id/slots/:slot= | POST | Add a missing record |
364
Removed:
| =/history/:id/slots/:slot/edit= | POST | Correct a saved slot |
361
Added:
| =/logbook= | GET | All recorded evidence for the user |
362
Added:
| =/logbook/:id= | GET | A saved workout |
363
Added:
| =/logbook/:id/slots/:slot= | POST | Add a missing record |
364
Added:
| =/logbook/:id/slots/:slot/edit= | POST | Correct a saved slot |
365
365
| =/assets/hito.css= | GET | Stylesheet (embedded at build time) |
366
366
| =/assets/workout-client.js= | GET | Enhancement client (embedded at build time) |
367
367
ENHANCEMENTS.org
@@ -1,5 +1,6 @@
1
Removed:
#+title: Hito UI Enhancements
2
Removed:
#+startup: overview
1
Added:
#+TITLE: Hito Enhancements log
2
Added:
#+AUTHOR: Marius Peter
3
Added:
#+STARTUP: overview
3
4
4
5
* UI enhancement requests
5
6
** DONE Remove all doctrine-note elements
@@ -21,9 +22,23 @@
21
22
Style the workout accordions with CSS.
22
23
** DONE Use brass for secondary buttons
23
24
Use a secondary brass color for secondary buttons.
24
Removed:
** TODO Replace history with a logbook endpoint
25
Added:
** DONE Replace history with a logbook endpoint
25
26
Replace the history endpoint with a logbook endpoint that shows all recorded evidence for the user.
26
27
** TODO Fix active bottom-navigation state for current workout and logbook
27
28
When a current workout is open, selecting the logbook tab marks both Current Workout and Logbook as active in the bottom navigation. Ensure only the selected page is highlighted.
28
29
** TODO Increase the masthead brand size
29
30
Make the masthead brand name appear much larger.
31
Added:
** TODO Style exercise names as headings
32
Added:
In the exercise fieldset, style the exercise name as a black Work Sans heading.
33
Added:
** TODO Limit Courier Prime Mono usage
34
Added:
Use Courier Prime Mono only for numerical inputs and outputs, and eventually for tabular data.
35
Added:
** TODO Hide sign-out on mobile
36
Added:
Do not show the sign-out button on mobile.
37
Added:
** TODO Show an in-progress workout card on Home
38
Added:
When the user is logging a current workout, show a Home card that indicates the workout is in progress and links to the Current Workout tab.
39
Added:
** TODO Harmonize UI corner radii
40
Added:
Use uniformly rounded corners for all buttons, cards, and other UI elements.
41
Added:
** TODO Make workout cancellation unconditional
42
Added:
When cancelling a workout, always leave the current workout view without showing an invalid-form response. Save the workout log as-is for later editing, then redirect the user to Home.
43
Added:
** TODO Remove thick black bars from mobile bottom navigation
44
Added:
Remove the thick black bars visible in the mobile bottom navigation.
lib/web/assets/hito.css
@@ -124,7 +124,7 @@
124
124
.page-home .primary-nav > a[href="/"],
125
125
.page-routine .primary-nav > a[href="/routine"],
126
126
.page-workout .primary-nav > a[href="/workout"],
127
Removed:
.page-history .primary-nav > a[href="/history"] {
127
Added:
.page-logbook .primary-nav > a[href="/logbook"] {
128
128
color: var(--paper-raised);
129
129
background: var(--oxblood);
130
130
}
lib/web/handlers.ml
@@ -365,7 +365,7 @@
365
365
| Ok () -> (
366
366
Service.finish t.service trainee.Trainee.id ~ended_at:(t.now ())
367
367
>>= function
368
Removed:
| Some _ -> redirect_to request Routes.history
368
Added:
| Some _ -> redirect_to request Routes.logbook
369
369
| None -> not_found Present.no_workout)
370
370
371
371
(* Discard the workout in progress and return home. CSRF-guarded like
@@ -397,12 +397,12 @@
397
397
]
398
398
end
399
399
400
Removed:
module History = struct
400
Added:
module Logbook = struct
401
401
let index t trainee request =
402
402
Service.in_progress t.service trainee.Trainee.id >>= fun in_progress ->
403
403
Service.history t.service trainee.Trainee.id >>= fun records ->
404
404
html
405
Removed:
(Pages.history request
405
Added:
(Pages.logbook request
406
406
~logging:(Option.is_some in_progress)
407
407
~trainee records)
408
408
@@ -466,7 +466,7 @@
466
466
467
467
let routes t =
468
468
[
469
Removed:
Dream_html.get Routes.history (fun request ->
469
Added:
Dream_html.get Routes.logbook (fun request ->
470
470
authenticated t request (fun trainee -> index t trainee request));
471
471
Dream_html.get Routes.record (fun request id ->
472
472
authenticated t request (fun trainee -> show t trainee request id));
@@ -506,5 +506,5 @@
506
506
507
507
let routes t =
508
508
Auth.routes t @ Overview.routes t @ Current_workout.routes t
509
Removed:
@ History.routes t @ Assets.routes
509
Added:
@ Logbook.routes t @ Assets.routes
510
510
end
lib/web/pages.ml
@@ -46,7 +46,7 @@
46
46
nav_link "home" Routes.home "Home";
47
47
(if logging then nav_link "workout" Routes.workout "Current workout"
48
48
else nav_link "routine" Routes.routine "Routine");
49
Removed:
nav_link "history" Routes.history username;
49
Added:
nav_link "logbook" Routes.logbook username;
50
50
]
51
51
in
52
52
let account_area =
@@ -609,13 +609,13 @@
609
609
610
610
(* The base path a slot's button links to, without the [?slot=] the exercise
611
611
group appends. Current-workout buttons return to [/workout]; a saved record's
612
Removed:
buttons return to [/history/<id>]. The URL is built directly so the [slot]
612
Added:
buttons return to [/logbook/<id>]. The URL is built directly so the [slot]
613
613
query can be appended. *)
614
614
let exercise_href ~record_id slot =
615
615
let base =
616
616
match record_id with
617
617
| None -> "/workout"
618
Removed:
| Some record_id -> "/history/" ^ record_id
618
Added:
| Some record_id -> "/logbook/" ^ record_id
619
619
in
620
620
Printf.sprintf "%s?slot=%d" base slot
621
621
@@ -832,10 +832,10 @@
832
832
| (slot, _) :: _ -> slot
833
833
| [] -> 0
834
834
835
Removed:
let history request ?(logging = false) ~trainee records =
836
Removed:
html_page ~trainee ~request ~active:"history" ~logging "History"
835
Added:
let logbook request ?(logging = false) ~trainee records =
836
Added:
html_page ~trainee ~request ~active:"logbook" ~logging "Logbook"
837
837
[
838
Removed:
tag "h1" [] [ txt "History" ];
838
Added:
tag "h1" [] [ txt "Logbook" ];
839
839
(if records = [] then tag "p" [] [ txt "Nothing logged yet." ]
840
840
else
841
841
tag "ul" []
lib/web/pages.mli
@@ -54,7 +54,7 @@
54
54
(** The slot a workout view opens on: the first slot still awaiting a record, or
55
55
the first slot when every slot is filled. *)
56
56
57
Removed:
val history :
57
Added:
val logbook :
58
58
Dream.request ->
59
59
?logging:bool ->
60
60
trainee:Trainee.t ->
lib/web/routes.ml
@@ -13,9 +13,9 @@
13
13
let%path workout_slot_edit = "/workout/slots/%d/edit"
14
14
let%path finish_workout = "/workout/finish"
15
15
let%path cancel_workout = "/workout/cancel"
16
Removed:
let%path history = "/history"
17
Removed:
let%path record = "/history/%s"
18
Removed:
let%path record_slot = "/history/%s/slots/%d"
19
Removed:
let%path record_slot_edit = "/history/%s/slots/%d/edit"
16
Added:
let%path logbook = "/logbook"
17
Added:
let%path record = "/logbook/%s"
18
Added:
let%path record_slot = "/logbook/%s/slots/%d"
19
Added:
let%path record_slot_edit = "/logbook/%s/slots/%d/edit"
20
20
let%path stylesheet = "/assets/hito.css"
21
21
let%path workout_client = "/assets/workout-client.js"
test/test_web.ml
@@ -173,8 +173,8 @@
173
173
"starts the overview with a page-level heading" true
174
174
(contains ~substring:"<h1>Routines</h1>" (body overview));
175
175
Alcotest.(check bool)
176
Removed:
"shows the username in history navigation" true
177
Removed:
(contains ~substring:"href=\"/history\"" (body overview)
176
Added:
"shows the username in logbook navigation" true
177
Added:
(contains ~substring:"href=\"/logbook\"" (body overview)
178
178
&& contains ~substring:">lifter</a>" (body overview));
179
179
Alcotest.(check bool)
180
180
"omits the header username" false
@@ -268,7 +268,7 @@
268
268
let _ =
269
269
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
270
270
in
271
Removed:
let logging = body (get c "/history") in
271
Added:
let logging = body (get c "/logbook") in
272
272
Alcotest.(check bool)
273
273
"swaps in the current workout action while logging" true
274
274
(contains ~substring:">Current workout</a>" logging);
@@ -553,7 +553,7 @@
553
553
Alcotest.(check bool)
554
554
"shows the corrected load" true
555
555
(contains ~substring:"16" page) );
556
Removed:
( "a saved workout's slot can be corrected from the history view",
556
Added:
( "a saved workout's slot can be corrected from the logbook view",
557
557
`Quick,
558
558
fun () ->
559
559
let c = client () in
@@ -580,7 +580,7 @@
580
580
let _ = post c "/workout/finish" [ ("dream.csrf", token) ] in
581
581
(* The one saved workout is w1 for this trainee. Its recorded slot is
582
582
reached through the same [?slot=] state. *)
583
Removed:
let record_page = body (get c "/history/w1?slot=1") in
583
Added:
let record_page = body (get c "/logbook/w1?slot=1") in
584
584
Alcotest.(check bool)
585
585
"renders a vertical exercise button group for the saved workout"
586
586
true
@@ -606,10 +606,10 @@
606
606
(contains ~substring:"data-hito-app-form" record_page);
607
607
Alcotest.(check bool)
608
608
"offers a saved-slot correction form" true
609
Removed:
(contains ~substring:"/history/w1/slots/1/edit" record_page);
609
Added:
(contains ~substring:"/logbook/w1/slots/1/edit" record_page);
610
610
let token = Option.get (csrf_token record_page) in
611
611
let corrected =
612
Removed:
post c "/history/w1/slots/1/edit"
612
Added:
post c "/logbook/w1/slots/1/edit"
613
613
[
614
614
("dream.csrf", token);
615
615
("load", "20");
@@ -618,7 +618,7 @@
618
618
]
619
619
in
620
620
Alcotest.(check int) "corrected" 303 (status corrected);
621
Removed:
let record_page = body (get c "/history/w1?slot=1") in
621
Added:
let record_page = body (get c "/logbook/w1?slot=1") in
622
622
Alcotest.(check bool)
623
623
"still one filled slot" true
624
624
(contains ~substring:"1 of 4 recorded" record_page);
@@ -793,12 +793,12 @@
793
793
Alcotest.(check int)
794
794
"no workout in progress" 303
795
795
(status (get c "/workout"));
796
Removed:
(* No history record: the history view holds no saved workout. *)
797
Removed:
let history_page = body (get c "/history") in
796
Added:
(* No logbook record: the logbook view holds no saved workout. *)
797
Added:
let logbook_page = body (get c "/logbook") in
798
798
Alcotest.(check bool)
799
Removed:
"history shows no saved workout" false
800
Removed:
(contains ~substring:"/history/w1" history_page) );
801
Removed:
( "the saved history view does not offer a cancel control",
799
Added:
"logbook shows no saved workout" false
800
Added:
(contains ~substring:"/logbook/w1" logbook_page) );
801
Added:
( "the saved logbook view does not offer a cancel control",
802
802
`Quick,
803
803
fun () ->
804
804
let c = client () in
@@ -811,7 +811,7 @@
811
811
in
812
812
let token = Option.get (csrf_token (body (get c "/workout"))) in
813
813
let _ = post c "/workout/finish" [ ("dream.csrf", token) ] in
814
Removed:
let record_page = body (get c "/history/w1") in
814
Added:
let record_page = body (get c "/logbook/w1") in
815
815
Alcotest.(check bool)
816
816
"the saved view has no cancel route" false
817
817
(contains ~substring:"/workout/cancel" record_page);