[OCaml] High Intensity Training Online
docs apply STE100 fixes to prose comments
Documentation-only STE100 compliance pass across the web, presenter, and bin layers. Split semicolon-joined prose into separate sentences, rewrite passive voice with a known agent into active voice, remove a phrasal verb and a nominalized subject, and repair a garbled comment. Also correct a stale progress-page doc that described a search that does not exist, and align the cancel-dialog copy with 'cannot'. No behavior change.
Changed files
bin/main.ml
@@ -7,8 +7,8 @@
7
7
[local_db/] is excluded from the dune build and watch tree, so
8
8
the database and its journal never retrigger [dune -w].
9
9
- HITO_SECRET Secret for signing session cookies and CSRF tokens. When
10
Removed:
unset, a random secret is generated for this run only, so
11
Removed:
sessions do not survive a restart. Set it in production.
10
Added:
unset, the server generates a random secret for this run only,
11
Added:
so sessions do not survive a restart. Set it in production.
12
12
- HITO_PORT TCP port. Default: 8080
13
13
- HITO_VERSION Deployment commit identifier shown in the page footer. Defaults
14
14
to "development" outside a deployment.
bin/seed.ml
@@ -10,12 +10,13 @@
10
10
(* The trainee's real Hevy export, imported as personal history through the
11
11
Workout_import use case rather than fabricated. The use case gates what the
12
12
core ever sees: only a set whose source exercise maps to the curated catalog
13
Removed:
promotes to canonical evidence. Warmups, cardio, and bodyweight rows without
14
Removed:
a load are dropped by the parser as warnings. A drop set is discarded the way
15
Removed:
a warmup is — HD1 keeps only the drive to failure, never the strip rounds —
16
Removed:
so it neither promotes nor blocks. An unmapped exercise blocks only itself.
17
Removed:
All of that is doctrine, not loss: HD1 favours a curated catalog over
18
Removed:
novelty, and a deviation is retained, never guessed at. *)
13
Added:
promotes to canonical evidence. The parser drops warmups, cardio, and
14
Added:
bodyweight rows without a load as warnings. The parser discards a drop set
15
Added:
the way it discards a warmup, because HD1 keeps only the drive to failure,
16
Added:
never the strip rounds. A drop set therefore neither promotes nor blocks. An
17
Added:
unmapped exercise blocks only itself. All of that is doctrine, not loss: HD1
18
Added:
favours a curated catalog over novelty, and the app retains a deviation
19
Added:
rather than guessing at it. *)
19
20
20
21
(* Hevy datetimes are local wall time. The export was recorded at UTC+2. *)
21
22
let utc_offset_seconds = 7200
@@ -25,8 +26,8 @@
25
26
unmapped, so its workout blocks on [Unmapped_exercise] rather than promoting
26
27
a guess — "focus over novelty" (HD1, "The Routine"). Trap-bar and Romanian
27
28
deadlifts, the machine squat, machine chest and shoulder presses, and the
28
Removed:
flat barbell bench have no Standard catalog entry, so they are left unmapped
29
Removed:
on purpose. *)
29
Added:
flat barbell bench have no Standard catalog entry, so the table leaves them
30
Added:
unmapped on purpose. *)
30
31
let catalog_of_source name =
31
32
let id =
32
33
match name with
@@ -80,8 +81,8 @@
80
81
81
82
(* Import [csv_path] for [trainee_id] as one batch: store it, map every source
82
83
name that has a catalog equivalent, acknowledge the normal sets of any
83
Removed:
workout that has one, then promote the whole batch. Promotion is partial —
84
Removed:
a blocked workout is left in the batch for later review, never forced. *)
84
Added:
workout that has one, then promote the whole batch. Promotion is partial. The
85
Added:
batch keeps a blocked workout for later review and never forces it. *)
85
86
let import_csv repo trainee_id ~csv_path =
86
87
let source = read_file csv_path in
87
88
let fingerprint = Digest.to_hex (Digest.string source) in
@@ -139,7 +140,8 @@
139
140
(List.length result.WI.already_promoted)
140
141
(List.length result.WI.blocked)
141
142
142
Removed:
(* Idempotent: skip the import when the trainee already has promoted records. *)
143
Added:
(* Idempotent. The function skips the import when the trainee already has
144
Added:
promoted records. *)
143
145
let seed_history repo trainee_id ~csv_path =
144
146
Service.make ~repo |> fun service ->
145
147
Service.imported_records service trainee_id >>= fun records ->
bin/seed.mli
@@ -8,10 +8,10 @@
8
8
unit Lwt.t
9
9
(** Register [blendux] with [password] and select the Ideal Routine, or reuse
10
10
the existing account. Then, when the account has no promoted import records,
11
Removed:
import the Hevy export at [csv_path] as personal history: parse it, map each
12
Removed:
exercise the curated catalog names, acknowledge normal sets, and promote the
13
Removed:
batch. Blocked workouts — drop sets, or exercises outside the catalog — are
14
Removed:
left in the batch for review, never forced. When no file exists at
15
Removed:
[csv_path] the import is skipped, so a deployment that ships no export still
16
Removed:
seeds the account and routine. Exits the process on a malformed seed
17
Removed:
username or an import failure. *)
11
Added:
import the Hevy export at [csv_path] as personal history. The import parses
12
Added:
the export, maps each exercise the curated catalog names, acknowledges
13
Added:
normal sets, and promotes the batch. The batch keeps a blocked workout for
14
Added:
review and never forces it. A drop set or an exercise outside the catalog
15
Added:
blocks a workout. When no file exists at [csv_path] the import is skipped,
16
Added:
so a deployment that ships no export still seeds the account and routine.
17
Added:
Exits the process on a malformed seed username or an import failure. *)
lib/presenter/pages.mli
@@ -82,9 +82,8 @@
82
82
viewer:View_model.Viewer.t ->
83
83
View_model.Progress_page.t ->
84
84
page
85
Removed:
(** The per-exercise progress page: a no-JS exercise search that reloads with
86
Removed:
the chosen exercise's dual-axis line chart (weight on the left, reps on the
87
Removed:
right), from the merged native and imported history. *)
85
Added:
(** The per-exercise progress page. One dual-axis line chart per exercise with
86
Added:
at least two observations in the merged native and imported history. *)
88
87
89
88
val profile :
90
89
Dream.request ->
lib/presenter/pages_routine.ml
@@ -92,7 +92,7 @@
92
92
[
93
93
txt
94
94
"Heavy Duty grows muscle during recovery, not in the gym. \
95
Removed:
Training early can cut into it. This is recorded as an \
95
Added:
Training early can reduce it. The app records this as an \
96
96
override.";
97
97
];
98
98
tag "div"
lib/presenter/pages_workout.ml
@@ -379,7 +379,7 @@
379
379
]
380
380
[
381
381
tag "h2" [] [ txt "Cancel logging this workout?" ];
382
Removed:
tag "p" [] [ txt "This action can not be undone." ];
382
Added:
tag "p" [] [ txt "This action cannot be undone." ];
383
383
tag "div"
384
384
[ class_ "button-group" ]
385
385
[
lib/web/handlers_current_workout.ml
@@ -56,8 +56,9 @@
56
56
57
57
(* The response after a successful record or correction: a POST-redirect-GET
58
58
that flashes [message] and reloads the workout. Record and correction
59
Removed:
submits navigate natively (full page), which guarantees the reloaded view
60
Removed:
reflects exactly what was saved — no client swap that could race. *)
59
Added:
submits navigate natively with a full page. The full page guarantees that
60
Added:
the reloaded view reflects exactly what the app saved, with no client swap
61
Added:
that could race. *)
61
62
let saved _t _trainee request message =
62
63
redirect_with_flash request
63
64
(Dream_html.path_attr Dream_html.HTML.href Routes.workout)
lib/web/handlers_import.ml
@@ -75,7 +75,7 @@
75
75
{ View_model.Viewer.username = Trainee.username_to_string trainee.username }
76
76
77
77
(* A distinct batch id even when a duplicate override re-imports the same
78
Removed:
source: the fingerprint prefix, the import time in seconds, and a random
78
Added:
source. The fingerprint prefix, the import time in seconds, and a random
79
79
suffix keep two imports of one export apart, so an override never
80
80
overwrites the original batch. *)
81
81
let generate_batch_id ~fingerprint ~imported_at =
lib/web/handlers_kit.ml
@@ -49,9 +49,9 @@
49
49
50
50
Every domain and service error becomes a user-facing sentence here, so a
51
51
handler renders a message rather than deciding its wording. One module owns
52
Removed:
the phrasing, and adding an error variant surfaces as a missing case. The
53
Removed:
core's [pp] functions are debugging output and are not used for copy: a
54
Removed:
wording change must never reach the entity layer. *)
52
Added:
the phrasing. When you add an error variant, the compiler surfaces a
53
Added:
missing case. The core's [pp] functions are debugging output and are not
54
Added:
used for copy: a wording change must never reach the entity layer. *)
55
55
module Present = struct
56
56
let form_invalid = "The submitted form is not valid."
57
57
lib/web/handlers_logbook.ml
@@ -11,9 +11,9 @@
11
11
12
12
let field name fields = List.assoc_opt name fields
13
13
14
Removed:
(* Each factor is its own radio group. A group left untouched is absent from
15
Removed:
the post, and a value outside the three choices is treated the same way, so
16
Removed:
a partial report needs no separate Skip. *)
14
Added:
(* Each factor is its own radio group. An untouched group is absent from the
15
Added:
post. A value outside the three choices counts the same way, so a partial
16
Added:
report needs no separate Skip. *)
17
17
let feedback_signals fields =
18
18
let level = function
19
19
| "worse" -> Some Feedback.Worse_than_usual
@@ -46,9 +46,9 @@
46
46
| 2 -> if is_leap year then 29 else 28
47
47
| _ -> 30
48
48
49
Removed:
(* The weekday of a given UTC date, 0 = Monday .. 6 = Sunday. Uses Howard
50
Removed:
Hinnant's day-count from the epoch, so it needs no timezone-aware libc call
51
Removed:
([timegm] is not in the stdlib). *)
49
Added:
(* The weekday of a given UTC date, 0 = Monday .. 6 = Sunday. This uses Howard
50
Added:
Hinnant's day-count from the epoch. The count needs no timezone-aware libc
51
Added:
call, because [timegm] is not in the stdlib. *)
52
52
let monday_first_weekday ~year ~month ~day =
53
53
let y = if month <= 2 then year - 1 else year in
54
54
let era = (if y >= 0 then y else y - 399) / 400 in
@@ -177,7 +177,7 @@
177
177
(* A promoted import: canonical evidence, read-only. It carries no
178
178
prescription, so the native progress columns do not apply. Its detail lists
179
179
the imported efforts verbatim. It still feeds progress per exercise through
180
Removed:
the service; the logbook only shows it. *)
180
Added:
the service. The logbook only shows it. *)
181
181
let imported_effort_view ~history (o : Evidence.Observation.t) :
182
182
View_model.Imported_effort.t =
183
183
let effort = Evidence.Observation.effort o in
@@ -197,8 +197,9 @@
197
197
198
198
(* Build one imported entry. Each imported effort carries the progress since
199
199
that exercise was last performed, compared against the merged native and
200
Removed:
imported history the service supplies. History is fetched once per distinct
201
Removed:
exercise in the record, so a workout of repeated exercises stays cheap. *)
200
Added:
imported history the service supplies. The service fetches history once per
201
Added:
distinct exercise in the record, so a workout of repeated exercises stays
202
Added:
cheap. *)
202
203
let imported_entry_view t trainee (record : Repository.imported_record) :
203
204
View_model.Logbook_entry.t Lwt.t =
204
205
let started =
@@ -273,7 +274,7 @@
273
274
in
274
275
let year = query_int "year" (now_tm.Unix.tm_year + 1900) in
275
276
let month = clamp 1 12 (query_int "month" (now_tm.Unix.tm_mon + 1)) in
276
Removed:
(* Only the chosen month's entries fill the table; the calendar's year range
277
Added:
(* Only the chosen month's entries fill the table. The calendar's year range
277
278
still spans all history. *)
278
279
let in_month (e : View_model.Logbook_entry.t) =
279
280
let tm = Unix.gmtime (float_of_int e.at_unix) in
@@ -314,9 +315,9 @@
314
315
| _ -> false)
315
316
~feedback_open ?delete_record ?expanded_record records)
316
317
317
Removed:
(* One post saves the whole report. Nothing is stored before Submit, so there
318
Removed:
is no partial state to keep or discard. An empty submission is a mistake
319
Removed:
worth naming rather than an empty report worth storing. *)
318
Added:
(* One post saves the whole report. The app stores nothing before Submit, so
319
Added:
there is no partial state to keep or discard. An empty submission is a
320
Added:
mistake worth naming rather than an empty report worth storing. *)
320
321
let record_feedback t trainee request =
321
322
let back = Dream_html.path_attr Dream_html.HTML.href Routes.logbook in
322
323
Dream.form request >>= function
@@ -430,14 +431,10 @@
430
431
(Dream_html.path_attr Dream_html.HTML.href Routes.logbook)
431
432
"That workout log is no longer available.")
432
433
433
Removed:
(* The per-exercise progress page. The search submits a GET with the exercise
434
Removed:
name; resolve it to a catalog entry, then plot its merged native and
435
Removed:
imported history. A chart needs at least two observations to draw a line;
436
Removed:
fewer reports as insufficient. *)
437
434
(* The progress page. One dual-axis line chart per exercise that has at least
438
Removed:
two observations in the merged native and imported history; exercises with
439
Removed:
too little to draw a line are omitted. No search — the page shows every
440
Removed:
chartable exercise. *)
435
Added:
two observations in the merged native and imported history. Exercises with
436
Added:
too little to draw a line are omitted. There is no search. The page shows
437
Added:
every chartable exercise. *)
441
438
let progress t trainee request =
442
439
let viewer =
443
440
{
lib/web/workout_client.ml
@@ -73,8 +73,8 @@
73
73
| Some started -> (
74
74
(* Epoch seconds exceed js_of_ocaml's 31-bit [int] range, so the
75
75
start and the current time are kept as floats. Only the small
76
Removed:
elapsed difference is narrowed to an [int]. Doing the subtraction
77
Removed:
in [int] would overflow and yield a wrong count. *)
76
Added:
elapsed difference is narrowed to an [int]. If you subtract in
77
Added:
[int], the value overflows and yields a wrong count. *)
78
78
let started =
79
79
try float_of_string (String.trim (Js.to_string started))
80
80
with _ -> 0.
@@ -535,8 +535,8 @@
535
535
Js._true))
536
536
Js._false
537
537
|> ignore;
538
Removed:
(* Mark the document as script-enhanced so CSS can hide no-JS fallbacks such
539
Removed:
in CSS. Set only once the client runs, so
538
Added:
(* Mark the document as script-enhanced so the CSS can hide the no-JS
539
Added:
fallbacks. Set only once the client runs, so
540
540
a no-JS visitor keeps every fallback. *)
541
541
document##.documentElement##setAttribute
542
542
(Js.string "data-hito-js") (Js.string "on");