refactor show the routine as a table, not accordions

Replace the routine's workout accordions with a basic HTML table. Each row is one workout of the cycle: a row-header names the workout, one cell shows its prescribed-stimuli count, and one cell lists the prescription. The disclosure widgets and their open animation are dropped with the markup they served. Presentation only; no doctrine bearing. The prescription stays a plain read view: the logbook still records and never interprets. Update the web flow test to assert the table replaces the details accordions.

Commit
4e4acacd1b55184d2b0d2d83f5ef652cf2164e25
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 cf87cf8d..f4c01d34 100644..100644
@@ -583,66 +583,38 @@
583 583 }
584 584 li li { color: var(--muted-ink); font-size: 0.92rem; }
585 585
586 Removed: /* The routine's workout accordions. Each details is one workout of the cycle.
587 Removed: The stack spaces the cards; each card reads as a bordered panel with a
588 Removed: pressable summary row and an inset description. */
589 Removed: .routine-accordions {
590 Removed: display: flex;
591 Removed: flex-direction: column;
592 Removed: gap: 0.6rem;
586 Added: /* The routine's workouts as a basic table: one row per workout of the cycle,
587 Added: with its name, prescribed-stimuli count, and the prescription. The row header
588 Added: names the workout; the prescription cell lists each stimulus. */
589 Added: .routine-table {
590 Added: width: 100%;
593 591 margin: 1.25rem 0 0;
592 Added: border-collapse: collapse;
594 593 }
595 594
596 Removed: .routine-accordion {
597 Removed: border: 1px solid var(--rule-strong);
598 Removed: border-left: 4px solid var(--oxblood);
599 Removed: background: var(--paper);
600 Removed: border-radius: var(--radius);
595 Added: .routine-table th,
596 Added: .routine-table td {
597 Added: padding: 0.6rem 0.7rem;
598 Added: border-bottom: 1px solid var(--rule);
599 Added: text-align: left;
600 Added: vertical-align: top;
601 601 }
602 602
603 Removed: .routine-accordion > summary {
604 Removed: display: flex;
605 Removed: align-items: center;
606 Removed: min-height: 2.75rem;
607 Removed: padding: 0.7rem 0.9rem;
603 Added: .routine-table thead th {
608 604 color: var(--ink);
609 Removed: cursor: pointer;
610 Removed: font-weight: 600;
611 Removed: list-style: none;
605 Added: font: 700 var(--font-size-small)/1.2 var(--sans);
606 Added: letter-spacing: 0.02em;
607 Added: border-bottom: 2px solid var(--rule-strong);
612 608 }
613 609
614 Removed: .routine-accordion > summary::-webkit-details-marker { display: none; }
615 Removed: .routine-accordion > summary::marker { content: ""; }
616 Removed:
617 Removed: /* A rotating chevron marks the open state. */
618 Removed: .routine-accordion > summary::after {
619 Removed: margin-left: auto;
620 Removed: color: var(--rule-strong);
621 Removed: content: "\25be";
622 Removed: transition: transform 180ms ease;
610 Added: .routine-table tbody th {
611 Added: color: var(--ink);
612 Added: font-weight: 700;
623 613 }
624 Removed: .routine-accordion[open] > summary::after { transform: rotate(180deg); }
625 614
626 Removed: .routine-accordion > summary:hover {
627 Removed: color: var(--oxblood-dark);
628 Removed: background: #ece4d5;
629 Removed: }
630 Removed:
631 Removed: .routine-description {
632 Removed: border-top: 1px solid var(--rule);
633 Removed: padding: 0.4rem 0.9rem 0.9rem;
634 Removed: }
635 Removed:
636 Removed: /* Routine accordions animate open: the disclosed panel fades and slides down
637 Removed: when the details opens. Reduced-motion users get the panel instantly, via
638 Removed: the global prefers-reduced-motion rule that clamps animation duration. */
639 Removed: @keyframes accordion-open {
640 Removed: from { opacity: 0; transform: translateY(-0.35rem); }
641 Removed: to { opacity: 1; transform: translateY(0); }
642 Removed: }
643 Removed:
644 Removed: .routine-accordion[open] > .routine-description {
645 Removed: animation: accordion-open 180ms ease;
615 Added: .routine-table ul {
616 Added: margin: 0;
617 Added: padding-left: 1.1rem;
646 618 }
647 619
648 620 @media (max-width: 42rem) {
lib/web/pages.ml
index 4ece0c41..b36ee6b3 100644..100644
@@ -425,32 +425,46 @@
425 425 html_page ~trainee ~request ~active:"routine" ~logging "Routine"
426 426 [
427 427 tag "h1" [] [ txt "%s" (Prescription.Routine.name routine) ];
428 Removed: tag "div"
429 Removed: [ class_ "routine-accordions" ]
430 Removed: (List.map
431 Removed: (fun workout ->
432 Removed: tag "details"
433 Removed: [ class_ "routine-accordion" ]
434 Removed: [
435 Removed: tag "summary" []
428 Added: tag "table"
429 Added: [ class_ "routine-table" ]
430 Added: [
431 Added: tag "thead" []
432 Added: [
433 Added: tag "tr" []
434 Added: [
435 Added: tag "th"
436 Added: [ Dream_html.string_attr "scope" "col" ]
437 Added: [ txt "Workout" ];
438 Added: tag "th"
439 Added: [ Dream_html.string_attr "scope" "col" ]
440 Added: [ txt "Stimuli" ];
441 Added: tag "th"
442 Added: [ Dream_html.string_attr "scope" "col" ]
443 Added: [ txt "Prescription" ];
444 Added: ];
445 Added: ];
446 Added: tag "tbody" []
447 Added: (List.map
448 Added: (fun workout ->
449 Added: let stimuli = Prescription.Workout.stimuli workout in
450 Added: tag "tr" []
436 451 [
437 Removed: txt "%s — %d prescribed stimuli"
438 Removed: (Prescription.Workout.name workout)
439 Removed: (List.length (Prescription.Workout.stimuli workout));
440 Removed: ];
441 Removed: tag "div"
442 Removed: [ class_ "routine-description" ]
443 Removed: [
444 Removed: tag "p" [] [ txt "Description" ];
445 Removed: tag "ul" []
446 Removed: (List.map
447 Removed: (fun stimulus ->
448 Removed: tag "li" []
449 Removed: [ txt "%s" (describe_prescription stimulus) ])
450 Removed: (Prescription.Workout.stimuli workout));
451 Removed: ];
452 Removed: ])
453 Removed: (Prescription.Routine.workouts routine));
452 Added: tag "th"
453 Added: [ Dream_html.string_attr "scope" "row" ]
454 Added: [ txt "%s" (Prescription.Workout.name workout) ];
455 Added: tag "td" [] [ txt "%d" (List.length stimuli) ];
456 Added: tag "td" []
457 Added: [
458 Added: tag "ul" []
459 Added: (List.map
460 Added: (fun stimulus ->
461 Added: tag "li" []
462 Added: [ txt "%s" (describe_prescription stimulus) ])
463 Added: stimuli);
464 Added: ];
465 Added: ])
466 Added: (Prescription.Routine.workouts routine));
467 Added: ];
454 468 ]
455 469
456 470 let error_for ~input_id (field : string) (errors : (string * string) list) =
test/test_web.ml
index fd69ed2a..ad816fa5 100644..100644
@@ -482,7 +482,7 @@
482 482 "client has JavaScript content type" true
483 483 (List.mem "application/javascript; charset=utf-8"
484 484 (Dream.headers asset "Content-Type")) );
485 Removed: ( "the routine page uses workout description accordions",
485 Added: ( "the routine page lists workouts in a basic table",
486 486 `Quick,
487 487 fun () ->
488 488 let c = client () in
@@ -495,14 +495,17 @@
495 495 Alcotest.(check int) "routine selected" 303 (status selection);
496 496 let page = body (get c "/routine") in
497 497 Alcotest.(check bool)
498 Removed: "uses details" true
498 Added: "uses a table" true
499 Added: (contains ~substring:"<table" page);
500 Added: Alcotest.(check bool)
501 Added: "no longer uses details accordions" false
499 502 (contains ~substring:"<details" page);
500 503 Alcotest.(check bool)
501 Removed: "summarizes Day 1" true
502 Removed: (contains ~substring:"<summary>Day 1" page);
504 Added: "heads the workout column" true
505 Added: (contains ~substring:"Workout" page);
503 506 Alcotest.(check bool)
504 Removed: "includes description" true
505 Removed: (contains ~substring:"Description" page);
507 Added: "names Day 1 as a row" true
508 Added: (contains ~substring:"Day 1" page);
506 509 Alcotest.(check bool)
507 510 "includes prescription" true
508 511 (contains ~substring:"Dumbbell Flyes into Incline Presses" page);