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.

Commit
66fbe0ea641a17be396162b362a855fa66aae61d
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
ARCHITECTURE.org
index 45424f01..69f9d459 100644..100644
@@ -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
index e3bfaad1..9beff9f7 100644..100644
@@ -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
index 26143d31..fff99f04 100644..100644
@@ -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
index ae1af346..d7edcff4 100644..100644
@@ -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
index 4df209cb..58c0d0f4 100644..100644
@@ -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
index f4836cfc..10e5f218 100644..100644
@@ -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
index b7ecde9b..f182f90f 100644..100644
@@ -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
index 82ace3bf..2d2fcf84 100644..100644
@@ -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);