fix Label planned workouts and show version

Show the routine and workout name in planned captions. Show the copyright owner and the deployed commit date in the shared footer.

Commit
351288a66eff6a8cc6183ae3300273fe733ccc42
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
ARCHITECTURE.org
index 073ba757..b80f9d38 100644..100644
@@ -597,7 +597,7 @@
597 597 | =HITO_DB= | Caqti database URI | =sqlite3:hito.sqlite= |
598 598 | =HITO_SECRET= | Signs session cookies and CSRF tokens | random per run |
599 599 | =HITO_PORT= | TCP port | =8080= |
600 Removed: | =HITO_VERSION= | Deployment identifier | =development= |
600 Added: | =HITO_VERSION= | Deployed commit date in YYYYMMDD format | =development= |
601 601 | =HITO_SEED= | Enable or disable startup seeding | =1= in development, =0= otherwise |
602 602 | =HITO_SEED_PASSWORD= | Password for an explicit deployment seed | none — refuses to start |
603 603 | =HITO_SEED_CSV= | Hevy export for an explicit deployment seed | =workout_data.csv= |
README.org
index 34371429..1cb473b0 100644..100644
@@ -46,7 +46,7 @@
46 46 | ~HITO_DB~ | The Caqti URI. Default: ~sqlite3:local_db/hito.sqlite~. |
47 47 | ~HITO_SECRET~ | Signs sessions and CSRF tokens. Set it in production. |
48 48 | ~HITO_PORT~ | The TCP port. Default: ~8080~. The server binds loopback. |
49 Removed: | ~HITO_VERSION~ | The deployment identifier in the footer. |
49 Added: | ~HITO_VERSION~ | The deployed commit date in YYYYMMDD format. |
50 50 | ~HITO_SEED~ | ~1~ seeds an account at startup. Development defaults to ~1~. Deployment defaults to ~0~. |
51 51 | ~HITO_SEED_PASSWORD~ | Password for the deployment seed when ~HITO_SEED=1~. |
52 52 | ~HITO_SEED_CSV~ | The Hevy export for the deployment seed. |
bin/main.ml
index eba93fbb..b7df1328 100644..100644
@@ -10,8 +10,9 @@
10 10 unset, the server generates a random secret for this run only,
11 11 so sessions do not survive a restart. Set it in production.
12 12 - HITO_PORT TCP port. Default: 8080
13 Removed: - HITO_VERSION Deployment commit identifier shown in the page footer. Defaults
14 Removed: to "development" outside a deployment.
13 Added: - HITO_VERSION Deployment version date shown in the page footer. Deployments
14 Added: set YYYYMMDD from the deployed commit date. Defaults to
15 Added: "development" outside a deployment.
15 16 - HITO_SEED Set to 1 to seed an account on startup. In development,
16 17 the default is 1 and the account is [test] with password
17 18 [test]. In a deployment, the default is 0.
@@ -78,8 +79,8 @@
78 79 exit 1
79 80 | Ok repo ->
80 81 (* Development seeds are automatic unless HITO_SEED=0 disables them.
81 Removed: Deployments default to no seed because HITO_VERSION carries the commit
82 Removed: identifier. The deployment seed still requires an explicit password. *)
82 Added: Deployments default to no seed because HITO_VERSION is not
83 Added: "development". The deployment seed still requires an explicit password. *)
83 84 (match (version, seed_mode) with
84 85 | "development", "1" -> Lwt_main.run (Seed.development_user repo)
85 86 | _, "1" ->
deploy/post-receive
index 47195aee..728ffe85 100755..100755
@@ -79,8 +79,10 @@
79 79 # Write per-release provenance and append one durable history line.
80 80 record_metadata() {
81 81 local sha="$1" rel="$2" ts="$3" build_secs="$4"
82 Removed: local subject
82 Added: local subject version
83 83 subject="$(git --git-dir="${BARE}" log -1 --format=%s "${sha}")"
84 Added: version="$(git --git-dir="${BARE}" log -1 --format=%cd \
85 Added: --date=format:%Y%m%d "${sha}")"
84 86
85 87 jq -n \
86 88 --arg commit "${sha}" \
@@ -97,7 +99,7 @@
97 99 printf '%s\t%s\t%ss\t%s\t%s\n' \
98 100 "${ts}" "${sha:0:12}" "${build_secs}" "${DEPLOY_BRANCH}" "${subject}" \
99 101 >>"${SHARED}/deploy-history.tsv"
100 Removed: printf 'HITO_VERSION=%s\n' "${sha:0:12}" >"${SHARED}/deploy.env"
102 Added: printf 'HITO_VERSION=%s\n' "${version}" >"${SHARED}/deploy.env"
101 103 }
102 104
103 105 # Point current at ${rel} in a single rename so no request sees a half state.
deploy/setup.sh
index 6e1d43f8..cc4bfd47 100755..100755
@@ -144,7 +144,7 @@
144 144 # Runs entirely as the service user; root only orchestrates.
145 145 build_and_flip() {
146 146 local start="${SECONDS}"
147 Removed: local ts rel stage sha subject build_secs prev
147 Added: local ts rel stage sha subject version build_secs prev
148 148 ts="$(date -u +"%Y-%m-%dT%H-%M-%SZ")"
149 149 rel="${RELEASES}/${ts}"
150 150 prev="$(readlink -f "${SRV}/current" 2>/dev/null || true)"
@@ -172,6 +172,8 @@
172 172 ln -sfn "${DB}" "${rel}/hito.sqlite"
173 173
174 174 subject="$(git --git-dir="${BARE}" log -1 --format=%s "${sha}")"
175 Added: version="$(git --git-dir="${BARE}" log -1 --format=%cd \
176 Added: --date=format:%Y%m%d "${sha}")"
175 177 jq -n \
176 178 --arg commit "${sha}" \
177 179 --arg subject "${subject}" \
@@ -186,7 +188,7 @@
186 188 printf '%s\t%s\t%ss\t%s\t%s\n' \
187 189 "${ts}" "${sha:0:12}" "${build_secs}" "master" "${subject}" \
188 190 >>"${SHARED}/deploy-history.tsv"
189 Removed: printf 'HITO_VERSION=%s\n' "${sha:0:12}" >"${SHARED}/deploy.env"
191 Added: printf 'HITO_VERSION=%s\n' "${version}" >"${SHARED}/deploy.env"
190 192
191 193 chown -R "${SVC_USER}:${SVC_GROUP}" "${rel}"
192 194 chown "${SVC_USER}:${SVC_GROUP}" "${SHARED}/deploy-history.tsv" "${SHARED}/deploy.env"
lib/presenter/page_kit.ml
index 622e6373..c7e65ba4 100644..100644
@@ -104,7 +104,7 @@
104 104 [
105 105 tag "strong"
106 106 [ class_ "workout-evidence-caption-name" ]
107 Removed: [ Dream_html.txt "%s" planned.name ];
107 Added: [ Dream_html.txt "%s, %s" planned.routine_name planned.name ];
108 108 tag "span"
109 109 [ class_ "workout-evidence-caption-status upcoming" ]
110 110 [ Dream_html.txt "Planned on %s" planned.scheduled_on ];
lib/presenter/page_shell.ml
index 22221d4f..f21eca15 100644..100644
@@ -10,18 +10,18 @@
10 10 | Some version when version <> "" -> version
11 11 | _ -> "development"
12 12
13 Removed: let copyright_year () =
14 Removed: let now = Unix.gmtime (Unix.gettimeofday ()) in
15 Removed: now.Unix.tm_year + 1900
16 Removed:
17 13 let footer () =
18 14 tag "footer"
19 15 [ class_ "site-footer" ]
20 16 [
21 Removed: txt "© %d Marius Peter · version " (copyright_year ());
22 Removed: tag "code"
23 Removed: [ class_ "deployment-version" ]
24 Removed: [ txt "%s" (deployment_version ()) ];
17 Added: tag "div" [] [ txt "© 2026 Marius PETER. All rights reserved." ];
18 Added: tag "div" []
19 Added: [
20 Added: txt "Version: ";
21 Added: tag "code"
22 Added: [ class_ "deployment-version" ]
23 Added: [ txt "%s" (deployment_version ()) ];
24 Added: ];
25 25 ]
26 26
27 27 let html_page ?viewer ?request ?(active = "") ?(show_nav = false)
lib/presenter/view_model.ml
index 49f89142..9bb10267 100644..100644
@@ -65,7 +65,12 @@
65 65
66 66 type group = { exercises : exercise list }
67 67
68 Removed: type t = { name : string; scheduled_on : string; groups : group list }
68 Added: type t = {
69 Added: routine_name : string;
70 Added: name : string;
71 Added: scheduled_on : string;
72 Added: groups : group list;
73 Added: }
69 74 (** A prescribed workout that has no logging record yet. Latest performance is
70 75 evidence for the next attempt, not a repetition target. *)
71 76 end
lib/web/handlers_logbook.ml
index 5d8e3a5e..3d712076 100644..100644
@@ -36,6 +36,7 @@
36 36
37 37 type scheduled_workout = {
38 38 at_unix : int;
39 Added: routine_name : string;
39 40 workout_name : string;
40 41 groups : planned_group list;
41 42 }
@@ -84,6 +85,7 @@
84 85 else
85 86 {
86 87 at_unix = planned_at;
88 Added: routine_name = Prescription.Routine.name routine;
87 89 workout_name = Prescription.Workout.name next;
88 90 groups = groups_of_prescription next;
89 91 }
@@ -445,6 +447,7 @@
445 447 Some
446 448 View_model.Upcoming_workout.
447 449 {
450 Added: routine_name = planned.routine_name;
448 451 name = planned.workout_name;
449 452 scheduled_on = utc_long_date planned.at_unix;
450 453 groups;
lib/web/handlers_overview.ml
index 718b9196..652fb5ca 100644..100644
@@ -58,7 +58,7 @@
58 58
59 59 (* Build the next planned workout with the latest observed performance for
60 60 each exercise. Groups preserve a pre-exhaust pair. *)
61 Removed: let scheduled_workout_view t trainee next ~scheduled_on :
61 Added: let scheduled_workout_view t trainee next ~routine_name ~scheduled_on :
62 62 View_model.Upcoming_workout.t Lwt.t =
63 63 Lwt_list.map_s
64 64 (fun stimulus ->
@@ -78,7 +78,12 @@
78 78 (Prescription.Workout.stimuli next)
79 79 >|= fun groups ->
80 80 View_model.Upcoming_workout.
81 Removed: { name = Prescription.Workout.name next; scheduled_on; groups }
81 Added: {
82 Added: routine_name;
83 Added: name = Prescription.Workout.name next;
84 Added: scheduled_on;
85 Added: groups;
86 Added: }
82 87
83 88 let home_view ~(routine : Repository.routine_id) ~routine_name ~next_workout
84 89 ~readiness : View_model.Home.t =
@@ -134,6 +139,7 @@
134 139 - Recovery.duration_to_seconds rested)
135 140 in
136 141 scheduled_workout_view t trainee.Trainee.id next
142 Added: ~routine_name:(Prescription.Routine.name selected)
137 143 ~scheduled_on:(utc_long_date scheduled_on)
138 144 >|= fun next_workout ->
139 145 Pages.home request ~viewer
test/web/auth.ml
index b2cb903a..fc1da7fa 100644..100644
@@ -17,6 +17,19 @@
17 17 fun () ->
18 18 let c = client () in
19 19 Alcotest.(check int) "GET /login" 200 (status (get c "/login")) );
20 Added: ( "the sign-in page identifies the copyright owner and version",
21 Added: `Quick,
22 Added: fun () ->
23 Added: let c = client () in
24 Added: let page = body (get c "/login") in
25 Added: Alcotest.(check bool)
26 Added: "copyright notice is visible" true
27 Added: (Option.is_some
28 Added: (index_from ~needle:"© 2026 Marius PETER. All rights reserved."
29 Added: page 0));
30 Added: Alcotest.(check bool)
31 Added: "version is visible" true
32 Added: (Option.is_some (index_from ~needle:"Version: " page 0)) );
20 33 ( "every response carries the browser hardening headers",
21 34 `Quick,
22 35 fun () ->
test/web/logbook.ml
index d0e17cdd..c1df6d73 100644..100644
@@ -60,9 +60,10 @@
60 60 "selected planned workout is displayed" true
61 61 (Option.is_some (index_from ~needle:"Day 2" page 0));
62 62 Alcotest.(check bool)
63 Removed: "planned workout dates itself in the table caption" true
63 Added: "planned workout names its routine and day in the table caption"
64 Added: true
64 65 (Option.is_some
65 Removed: (index_from ~needle:"Planned on 13th January 1970" page 0));
66 Added: (index_from ~needle:"Ideal Routine, Day 2" page 0));
66 67 Alcotest.(check bool)
67 68 "planned workout omits change columns" true
68 69 (Option.is_none (index_from ~needle:"Δ" page 0));
test/web/overview.ml
index cdd05e47..4559f2cd 100644..100644
@@ -116,8 +116,11 @@
116 116 "the Day 1 preview lists Laterals" true
117 117 (Option.is_some (index_from ~needle:"Laterals" page 0));
118 118 Alcotest.(check bool)
119 Removed: "Laterals names the planned workout in the table caption" true
120 Removed: (Option.is_some (index_from ~needle:"Day 1" page 0));
119 Added: "Laterals names the routine and planned workout in the table \
120 Added: caption"
121 Added: true
122 Added: (Option.is_some
123 Added: (index_from ~needle:"Ideal Routine, Day 1" page 0));
121 124 Alcotest.(check bool)
122 125 "Laterals shows its best load and reps" true
123 126 (Option.is_some (index_from ~needle:"Load (kg)" page 0)