View raw

1 (** View models: the simple data structures the presenter renders. 2 3 The Clean Architecture rule for crossing a boundary is that the data passed 4 is a plain structure, never an entity or a database row. These DTOs are that 5 structure. A handler (the controller) maps a repository record or a core 6 entity into a view model, and the presenter ({!Pages}) renders the view 7 model without ever naming an inner-circle type. 8 9 Every page now crosses the boundary as a DTO. No page names a core entity. 10 Add a page DTO here before the presenter renders it. *) 11 12 module Viewer = struct 13 type t = { username : string } 14 (** The signed-in trainee, as the shell needs them: a name to show, nothing 15 more. Replaces passing the [Trainee.t] entity into the presenter. *) 16 end 17 18 module App_feedback = struct 19 type t = { 20 id : string; (** Opaque feedback identity, already stringified. *) 21 author : string; 22 contributions : int; 23 submitted_at : string; (** Pre-formatted "YYYY-MM-DD HH:MM" in UTC. *) 24 message : string; 25 upvotes : int; 26 viewer_upvoted : bool; 27 viewer_owns : bool; 28 } 29 (** One application-feedback entry, ready to render. Carries no timestamp 30 type, no opaque id, no repository record — only what the view shows. *) 31 end 32 33 module Feedback = struct 34 type report = { 35 at_unix : int; (** Report time in Unix seconds, for chronological order. *) 36 signals : string list; (** Each signal already described, for the list. *) 37 factor_scores : (string * int option) list; 38 (** Score per leveled factor field, aligned with the factor order the 39 presenter graphs. [None] where the report gave that factor no level. 40 Flags such as pain carry no score and are absent here. *) 41 } 42 (** One subjective-feedback report, ready to render. Carries no 43 [Evidence.Feedback] variant and no timestamp type. *) 44 end 45 46 module Routine = struct 47 type exercise = { name : string; reps : string } 48 (** One prescribed exercise line, its name and rep band already phrased. *) 49 50 type workout = { name : string; exercises : exercise list } 51 52 type t = { name : string; workouts : workout list } 53 (** A routine as the accordion descriptions render it: names, numeric rep 54 bands, and workout grouping only. No [Prescription] entity. *) 55 end 56 57 module Routine_choice = struct 58 type t = { id : string; name : string; workout_count : int } 59 (** One selectable routine on the chooser: its id for the select form, its 60 name, and how many workouts the cycle holds. *) 61 end 62 63 module Home = struct 64 type gate = 65 | Ready 66 | Recovering of { status : string } 67 (** Whether the next workout may begin, and the recovery status line to 68 show while it may not. *) 69 70 type t = { 71 routine_id : string; (** For the begin/override forms. *) 72 routine_name : string; 73 next_workout : string; 74 gate : gate; 75 } 76 (** The overview while no workout is in progress. The recovery policy has 77 already decided [gate] in the use case. The page only renders it. *) 78 end 79 80 module Workout = struct 81 type movement = { 82 name : string; (** The exercise name shown for this input row. *) 83 load_field : string; (** Form field name for the load input. *) 84 reps_field : string; (** Form field name for the reps input. *) 85 reps_label : string; 86 load_value : string option; 87 (** Pre-fill when correcting a recorded slot. *) 88 reps_value : string option; 89 } 90 (** One input row of a slot's record/correction form. The controller emits one 91 movement for a single-exercise slot and two for a pre-exhaust pair, so the 92 presenter renders whatever movements it is given and never inspects the 93 prescription's [delivery] shape itself. *) 94 95 type slot = { 96 index : int; 97 label : string; (** Exercise label for the selector option and heading. *) 98 movements : movement list; 99 recorded_summary : string option; 100 (** [Some description] once the slot is filled. [None] while 101 outstanding. Drives the recorded/outstanding branch without an 102 entity. *) 103 selected_extension : string; 104 (** The ending code to pre-select when correcting a recorded slot ("", 105 "forced", "negatives", "rest-pause", "static"). "" for an 106 outstanding slot. *) 107 } 108 109 type t = { 110 name : string; (** The workout (prescription) name, for the page title. *) 111 slots : slot list; 112 active_slot : int; 113 started_at_unix : int; (** Workout start, for the elapsed timer. *) 114 overridden : bool; (** Begun before recovery finished. *) 115 all_recorded : bool; (** Every prescribed slot has a record. *) 116 } 117 (** A workout view: the slot the trainee is on, the record/correction state of 118 each slot, and the timer and override facts. Carries no Evidence or 119 Prescription entity. Extension/ending selection stays in the presenter 120 form, since its option set is fixed UI vocabulary, not domain data. *) 121 end 122 123 module Logbook_entry = struct 124 type t = { 125 id : string; (** Record id for the link. *) 126 workout_name : string; 127 stimuli_count : int; 128 complete : bool; 129 } 130 (** One saved workout as the logbook list shows it: a link, a name, how many 131 stimuli it holds, and whether it is complete. No Repository record and no 132 Evidence.Workout entity. *) 133 end 134 135 module Import_warning = struct 136 type t = { 137 row : int; (** The source row that the adapter dropped. *) 138 detail : string; (** Why the row was dropped, already phrased. *) 139 } 140 (** One dropped import row, ready to render. The controller phrases the 141 reason, so no adapter warning variant crosses the boundary. *) 142 end 143 144 module Import_set = struct 145 type t = { 146 row : int; (** The source row, for provenance. *) 147 exercise_name : string; (** The export's exercise name, before mapping. *) 148 set_type : string; (** The set type, already phrased. *) 149 weight : string; (** The load, already formatted. *) 150 reps : string; (** The rep count, already formatted. *) 151 mapping : string option; 152 (** The catalog exercise name this set maps to, when confirmed. *) 153 } 154 (** One provisional import set, ready to render. Carries only strings, no 155 source-set entity. *) 156 end 157 158 module Import_workout = struct 159 type t = { 160 id : string; (** The workout id, for the per-workout forms. *) 161 title : string; 162 sets : Import_set.t list; 163 blockers : string list; 164 (** Why the workout cannot promote yet, each already phrased. Empty when 165 the workout can promote. *) 166 acknowledgement : string option; 167 (** The retained normal-set acknowledgement, when present. *) 168 revision : int; (** The workout revision, for provenance display. *) 169 } 170 (** One provisional import workout, ready to render: its sets, its blockers, 171 and its acknowledgement state. No Workout_import entity. *) 172 end 173 174 module Import_review = struct 175 type t = { 176 batch_id : string; 177 warnings : Import_warning.t list; 178 workouts : Import_workout.t list; 179 } 180 (** A provisional import batch under review: the rows dropped on parse and the 181 workouts awaiting cleaning and promotion. No batch entity crosses the 182 boundary. *) 183 end 184