(** View models: the simple data structures the presenter renders. The Clean Architecture rule for crossing a boundary is that the data passed is a plain structure, never an entity or a database row. These DTOs are that structure. A handler (the controller) maps a repository record or a core entity into a view model, and the presenter ({!Pages}) renders the view model without ever naming an inner-circle type. Every page now crosses the boundary as a DTO. No page names a core entity. Add a page DTO here before the presenter renders it. *) module Viewer = struct type t = { username : string } (** The signed-in trainee, as the shell needs them: a name to show, nothing more. Replaces passing the [Trainee.t] entity into the presenter. *) end module App_feedback = struct type t = { id : string; (** Opaque feedback identity, already stringified. *) author : string; contributions : int; submitted_at : string; (** Pre-formatted "YYYY-MM-DD HH:MM" in UTC. *) message : string; upvotes : int; viewer_upvoted : bool; viewer_owns : bool; } (** One application-feedback entry, ready to render. Carries no timestamp type, no opaque id, no repository record — only what the view shows. *) end module Feedback = struct type report = { at_unix : int; (** Report time in Unix seconds, for chronological order. *) signals : string list; (** Each signal already described, for the list. *) factor_scores : (string * int option) list; (** Score per leveled factor field, aligned with the factor order the presenter graphs. [None] where the report gave that factor no level. Flags such as pain carry no score and are absent here. *) } (** One subjective-feedback report, ready to render. Carries no [Evidence.Feedback] variant and no timestamp type. *) end module Routine = struct type exercise = { name : string; reps : string } (** One prescribed exercise line, its name and rep band already phrased. *) type workout = { name : string; exercises : exercise list } type t = { name : string; workouts : workout list } (** A routine as the accordion descriptions render it: names, numeric rep bands, and workout grouping only. No [Prescription] entity. *) end module Routine_choice = struct type t = { id : string; name : string; workout_count : int } (** One selectable routine on the chooser: its id for the select form, its name, and how many workouts the cycle holds. *) end module Home = struct type gate = | Ready | Recovering of { status : string } (** Whether the next workout may begin, and the recovery status line to show while it may not. *) type t = { routine_id : string; (** For the begin/override forms. *) routine_name : string; next_workout : string; gate : gate; } (** The overview while no workout is in progress. The recovery policy has already decided [gate] in the use case. The page only renders it. *) end module Workout = struct type movement = { name : string; (** The exercise name shown for this input row. *) load_field : string; (** Form field name for the load input. *) reps_field : string; (** Form field name for the reps input. *) reps_label : string; load_value : string option; (** Pre-fill when correcting a recorded slot. *) reps_value : string option; } (** One input row of a slot's record/correction form. The controller emits one movement for a single-exercise slot and two for a pre-exhaust pair, so the presenter renders whatever movements it is given and never inspects the prescription's [delivery] shape itself. *) type slot = { index : int; label : string; (** Exercise label for the selector option and heading. *) movements : movement list; recorded_summary : string option; (** [Some description] once the slot is filled. [None] while outstanding. Drives the recorded/outstanding branch without an entity. *) selected_extension : string; (** The ending code to pre-select when correcting a recorded slot ("", "forced", "negatives", "rest-pause", "static"). "" for an outstanding slot. *) } type t = { name : string; (** The workout (prescription) name, for the page title. *) slots : slot list; active_slot : int; started_at_unix : int; (** Workout start, for the elapsed timer. *) overridden : bool; (** Begun before recovery finished. *) all_recorded : bool; (** Every prescribed slot has a record. *) } (** A workout view: the slot the trainee is on, the record/correction state of each slot, and the timer and override facts. Carries no Evidence or Prescription entity. Extension/ending selection stays in the presenter form, since its option set is fixed UI vocabulary, not domain data. *) end module Logbook_entry = struct type t = { id : string; (** Record id for the link. *) workout_name : string; stimuli_count : int; complete : bool; } (** One saved workout as the logbook list shows it: a link, a name, how many stimuli it holds, and whether it is complete. No Repository record and no Evidence.Workout entity. *) end module Import_warning = struct type t = { row : int; (** The source row that the adapter dropped. *) detail : string; (** Why the row was dropped, already phrased. *) } (** One dropped import row, ready to render. The controller phrases the reason, so no adapter warning variant crosses the boundary. *) end module Import_set = struct type t = { row : int; (** The source row, for provenance. *) exercise_name : string; (** The export's exercise name, before mapping. *) set_type : string; (** The set type, already phrased. *) weight : string; (** The load, already formatted. *) reps : string; (** The rep count, already formatted. *) mapping : string option; (** The catalog exercise name this set maps to, when confirmed. *) } (** One provisional import set, ready to render. Carries only strings, no source-set entity. *) end module Import_workout = struct type t = { id : string; (** The workout id, for the per-workout forms. *) title : string; sets : Import_set.t list; blockers : string list; (** Why the workout cannot promote yet, each already phrased. Empty when the workout can promote. *) acknowledgement : string option; (** The retained normal-set acknowledgement, when present. *) revision : int; (** The workout revision, for provenance display. *) } (** One provisional import workout, ready to render: its sets, its blockers, and its acknowledgement state. No Workout_import entity. *) end module Import_review = struct type t = { batch_id : string; warnings : Import_warning.t list; workouts : Import_workout.t list; } (** A provisional import batch under review: the rows dropped on parse and the workouts awaiting cleaning and promotion. No batch entity crosses the boundary. *) end