feat gate logbook feedback form behind a button

The logbook showed the subjective feedback form outright, competing with the workout list. Wrap it in a native <details> disclosure whose <summary> is a dedicated "Record feedback" button, so the form appears only when the trainee reveals it. The disclosure needs no script and stays reachable without JavaScript, preserving progressive enhancement. A fresh-workout suggestion still opens the disclosure, so the post-workout prompt lands on a visible form. No Heavy Duty doctrine bearing: this is a pure presentation change. The feedback form and its route are unchanged; only its visibility is gated.

Commit
6cc90629b48a569aef341111a7fb51c9c87b0c9d
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/web/assets/hito.css
index f4c01d34..88dff280 100644..100644
@@ -532,6 +532,28 @@
532 532 border-top: 1px solid var(--rule);
533 533 padding-top: 1rem;
534 534 }
535 Added: /* The feedback form is gated behind a disclosure. The summary reads as a
536 Added: secondary button so the reveal control matches the rest of the UI. */
537 Added: .feedback-toggle {
538 Added: display: inline-block;
539 Added: cursor: pointer;
540 Added: padding: 0.55rem 1.1rem;
541 Added: border: 1px solid var(--brass);
542 Added: border-radius: var(--radius);
543 Added: background: var(--brass-wash);
544 Added: color: #47350f;
545 Added: font-weight: 600;
546 Added: list-style: none;
547 Added: }
548 Added: .feedback-toggle::-webkit-details-marker {
549 Added: display: none;
550 Added: }
551 Added: .feedback-toggle:hover {
552 Added: background: var(--brass);
553 Added: }
554 Added: .feedback-disclosure[open] .feedback-toggle {
555 Added: margin-bottom: 1rem;
556 Added: }
535 557 .feedback-flag {
536 558 display: flex;
537 559 align-items: center;
lib/web/pages.ml
index b36ee6b3..4696bef1 100644..100644
@@ -1112,13 +1112,25 @@
1112 1112 [ class_ "eyebrow" ]
1113 1113 [ txt "Workout saved — add feedback while it is fresh." ]
1114 1114 else txt "");
1115 Removed: tag "p" []
1115 Added: (* The form is gated behind a dedicated disclosure button rather than
1116 Added: shown outright. A native [details] needs no script and stays
1117 Added: accessible without JavaScript. After a workout the suggestion opens
1118 Added: it so the prompt is not buried; otherwise it stays collapsed. *)
1119 Added: tag "details"
1120 Added: ([ class_ "feedback-disclosure" ]
1121 Added: @ if suggest_feedback then [ Dream_html.attr "open" ] else [])
1116 1122 [
1117 Removed: txt
1118 Removed: "Record subjective feedback at any time. Report only what you \
1119 Removed: mean to; leave the rest blank.";
1123 Added: tag "summary"
1124 Added: [ class_ "feedback-toggle" ]
1125 Added: [ txt "Record feedback" ];
1126 Added: tag "p" []
1127 Added: [
1128 Added: txt
1129 Added: "Record subjective feedback at any time. Report only what \
1130 Added: you mean to; leave the rest blank.";
1131 Added: ];
1132 Added: feedback_form request;
1120 1133 ];
1121 Removed: feedback_form request;
1122 1134 ]
1123 1135 @ feedback_list feedback);
1124 1136 ]
test/test_web.ml
index ad816fa5..83beb397 100644..100644
@@ -909,18 +909,32 @@
909 909 Alcotest.(check bool)
910 910 "shows bottom navigation" true
911 911 (contains ~substring:"bottom-nav" record_page) );
912 Removed: ( "the logbook offers a standalone feedback form",
912 Added: ( "the logbook gates the feedback form behind a disclosure button",
913 913 `Quick,
914 914 fun () ->
915 915 let c = client () in
916 916 let _ = sign_in_new c in
917 917 let logbook_page = body (get c "/logbook") in
918 918 Alcotest.(check bool)
919 Removed: "renders the feedback form" true
919 Added: "still renders the feedback form" true
920 920 (contains ~substring:"feedback-form" logbook_page);
921 921 Alcotest.(check bool)
922 922 "posts feedback to its own route" true
923 Removed: (contains ~substring:"action=\"/feedback\"" logbook_page) );
923 Added: (contains ~substring:"action=\"/feedback\"" logbook_page);
924 Added: (* The form is not shown outright: it is wrapped in a native
925 Added: disclosure whose summary is the dedicated reveal button, and the
926 Added: disclosure is collapsed (no [open]) on a plain logbook visit. *)
927 Added: Alcotest.(check bool)
928 Added: "wraps the form in a disclosure" true
929 Added: (contains ~substring:"feedback-disclosure" logbook_page);
930 Added: Alcotest.(check bool)
931 Added: "offers a dedicated reveal button" true
932 Added: (contains ~substring:"class=\"feedback-toggle\"" logbook_page
933 Added: && contains ~substring:"<summary" logbook_page);
934 Added: Alcotest.(check bool)
935 Added: "keeps the disclosure collapsed by default" false
936 Added: (contains ~substring:"<details class=\"feedback-disclosure\" open"
937 Added: logbook_page) );
924 938 ( "submitting feedback stores it and it appears on the logbook",
925 939 `Quick,
926 940 fun () ->
@@ -960,7 +974,13 @@
960 974 let prompted = body (get c "/logbook?prompt=feedback") in
961 975 Alcotest.(check bool)
962 976 "shows the feedback suggestion" true
963 Removed: (contains ~substring:"add feedback while it is fresh" prompted) );
977 Added: (contains ~substring:"add feedback while it is fresh" prompted);
978 Added: (* The gated form opens on a suggestion, so the prompt lands on a
979 Added: visible form rather than a collapsed disclosure. *)
980 Added: Alcotest.(check bool)
981 Added: "opens the feedback disclosure on a suggestion" true
982 Added: (contains ~substring:"<details class=\"feedback-disclosure\" open"
983 Added: prompted) );
964 984 ] );
965 985 ]
966 986