Skip to main content

Planner — Manual Test Protocol

A human-driven, click-through test script for the portal Planner section (apps/portal/src/app/dashboard/planner). Work top to bottom; each case is self-contained. Record Result (✅ pass / ❌ fail / ⚠️ partial) and Notes (what you actually saw, plus any console/network error) in the right-hand columns.

Scope: the calendar (month/week/day), data loading, the workout chips, and the schedule drawer (create / edit / delete, athletes, workout builder, recurring, the shared-definition 313 flow). Behaviour is described as currently implemented — if something differs, that difference is the finding.

Test environment​

ItemValue
App@enode/portal dev server
Start commandNEXT_PUBLIC_API_SERVER=backdev npm run dev (from apps/portal), or the repo npm run dev picker
Backendbackdev (https://backdev.enode.ai/api) — serves the V2 workout DTO
URLhttp://localhost:3000/dashboard/planner (or :3001 if 3000 is taken)
AuthLogged in as a coach with at least one athlete and one workout template
ToolsBrowser DevTools open — Console (JS errors) + Network (request/response) tabs

Before you start: have DevTools open the whole time. A case "passes" only if the visible result is correct and the Console shows no errors and the relevant Network call returned 2xx (or an expected 313 — see §7).

Test-data prerequisites​

  • At least 2 athletes exist (for the athlete picker).
  • At least 1 workout template with several exercises exists (for "select a template").
  • At least 1 scheduled workout with exercises and athletes already on a known day (to test edit/display). Create one via §6 first if needed.
  • At least 1 recurring workout (created via §6 with "Repeats weekly").
  • Optional, for §7: a workout whose plan is shared across schedules (so the backend answers 313).

1. View switching & responsive layout​

#Use caseStepsExpected resultResultNotes
1.1Default viewOpen /dashboard/planner on desktopMonth view renders, scrolled to the week containing today
1.2Switch to WeekClick WEEK in the segmented controlWeek view (Mon–Sun time grid) renders; control highlights WEEK
1.3Switch to DayClick DAYDay view renders (single day column + mini-calendar on desktop)
1.4Switch back to MonthClick MONTHMonth view renders, re-scrolled to today's week
1.5Desktop keeps all 3 viewsAt ≥768px width, inspect the switcherDAY, WEEK, MONTH all visible and selectable; no auto-switching on resize
1.6Mobile portrait hides WeekNarrow window <768px, portraitWEEK option is hidden; only DAY + MONTH selectable
1.7Rotate to landscape adopts WeekMobile, rotate portrait→landscapeView auto-switches to Week
1.8Rotate to portrait adopts DayMobile, rotate landscape→portraitView auto-switches to Day (Week is unavailable in portrait)
1.9Manual override within orientationMobile portrait, on Day, tap MONTHView stays MONTH until the next orientation change (no snap-back)

2. Month view​

#Use caseStepsExpected resultResultNotes
2.1Grid fills viewportObserve the month gridExactly 6 week rows fill the scroll area (no clipped half-row)
2.2Weekday header orderRead the column headersMon → Sun (Monday-start)
2.3Today highlightedFind today's cellToday's date number sits in an orange filled circle
2.4Weekend cells mutedCompare Sat/Sun cells to weekdaysWeekend cells have a subtly muted background
2.5Out-of-month dates dimmedScroll so two months showDays outside the focused month render in a more-muted colour
2.6Continuous scrollScroll up/down several weeksCalendar scrolls smoothly week-by-week; weeks render far in both directions (no blank gap within ~40 weeks each way)
2.7Header tracks scrollScroll across a month boundaryThe header month + year updates to the month occupying most of the viewport
2.8Resize re-measuresResize the browser heightRows resize so 6 weeks still fill the viewport; no overflow/scroll-jump
2.9Hover affordanceHover an empty day cellA faint + icon appears top-left
2.10Today button (month)Scroll far away, click TodaySmoothly scrolls back to today's week

3. Week view​

#Use caseStepsExpected resultResultNotes
3.1Time grid layoutSwitch to WeekLeft hour gutter + 7 day columns (Mon–Sun); ~12 hours fill the viewport
3.2All-day sectionObserve the top stripA sticky All day row sits above the scrolling hour grid
3.3Off-hours mutingLook at rows before 07:00 and at/after 22:00Those hour rows have a muted background
3.4Today column markedFind today in the day headerToday's date number is in an orange circle
3.5Prev/next weekClick the ‹ / › chevronsThe week shifts back/forward by 7 days; header updates
3.6Today button (week)Navigate away, click TodayReturns to the current week (offset 0)
3.7Hover affordanceHover an hour cellA faint + appears centered in the cell
3.8Timed workout placementView a day with a timed workoutIts chip is positioned vertically at its time-of-day (local time)

4. Day view & mini-calendar​

#Use caseStepsExpected resultResultNotes
4.1Desktop layoutSwitch to Day (desktop)Persistent mini-calendar side panel on the left + single-day time grid on the right
4.2Prev/next dayClick ‹ / ›Selected day moves back/forward by one day; grid + header update
4.3Mini-calendar month navClick the mini-calendar's ‹ / ›Mini month changes independently of the selected day
4.4Pick a dateClick a date in the mini-calendarMain grid jumps to that day; that date shows as selected (orange)
4.5Today vs selected stylingCompare today and a different selected dayToday = orange text/ring; selected = orange filled; only one filled at a time
4.6"Has events" dotFind a day with workouts (not selected)A small green dot appears under that date
4.7Mobile mini-calendarNarrow to mobile portrait, tap the date headerMini-calendar opens in a bottom sheet; picking a date closes it

5. Workout chips (display) — V1→V2 regression focus​

These verify the chips read the V2 model (workoutDefinition.items). A regression here shows up as "0 exercises" on every chip.

#Use caseStepsExpected resultResultNotes
5.1Chip contentFind a scheduled workout chipShows name + meta line: Scheduled · N exercises · M athletes
5.2Exercise count correctCompare a chip's N exercises to the actual exercise count in that workoutCount matches the workout's real exercise count — not 0
5.3Athlete count correctCompare M athletes to assigned athletesCount matches assigned participants
5.4Singular/pluralFind a workout with exactly 1 exercise and 1 athleteReads 1 exercise · 1 athlete (singular), not 1 exercises
5.5Recurring labelFind a recurring workout chipMeta line begins Recurring (not Scheduled)
5.6Recurring appears weeklyNote a recurring workout's weekday, scroll several weeksThe chip appears on every matching weekday across weeks
5.7Ordering within a dayOpen a day with all-day + timed + recurring itemsOrder is: all-day first → by time-of-day → scheduled before recurring → by name
5.8Network sanityIn DevTools, inspect the GET /workouts/schedules?from=&to= responseItems are nested under workoutDefinition.items; chip count equals that array's length

6. Schedule drawer — create ("new")​

Opened by clicking an empty cell (month) or hour (week/day).

#Use caseStepsExpected resultResultNotes
6.1Open from month cellClick an empty day cell in MonthDrawer opens titled "Schedule workout"; Date prefilled to that day; All day = on
6.2Open from hour cellClick an empty hour cell in Week/DayDrawer opens; Date = that day; All day = off; Time prefilled to the clicked hour
6.3Toggle All dayTurn All day offA Time row appears with a time input
6.4Date editableChange the Date fieldValue updates; no error
6.5Repeats weeklyTurn Repeats weekly onA Repeats on row shows Every {weekday} matching the chosen date
6.6Athletes empty stateLook at the Athletes section (fresh)Shows "No athlete selected." + Add athletes button
6.7Add athletesClick Add athletes, select 2, confirmTwo athlete chips appear in a horizontal row; Edit athletes… link shows
6.8Workout empty stateLook at the Workout section (fresh)Shows "No exercises added yet." + Add exercise + or select a template
6.9Build from scratchClick Add exercise, pick 2 exercises, confirmTwo slot cards appear; a Name field shows above the list
6.10Name required hintClear the Name fieldHint "Name your workout to schedule it." shows; Schedule button disabled
6.11Pick a templateClick or select a template, pick one, confirmName/items populate from the template; slot cards reflect its exercises
6.12Edit a slotTap a slot cardThe slot editor drawer pushes on top; edits reflect back on close
6.13Reorder slotsDrag a slot card to a new positionOrder persists in the list
6.14Break a supersetOn a superset chain, trigger break linkThe linked items split into separate slots
6.15Save disabled until validObserve Schedule before items/name/date are setButton disabled until: ≥1 item and a non-empty name and a date
6.16Save (create)Fill everything, click ScheduleButton shows "Saving…"; on success the drawer closes and the chip appears immediately on the calendar (no full refetch/flash)
6.17Persisted across reloadClick the Refresh button (or reload page)The created workout is still there (round-tripped to backend)
6.18All-day time modelCreate an all-day, non-recurring workout, inspect the create_schedule request bodytimeMode: "none", date = a UTC midnight timestamp
6.19Timed time modelCreate a timed, non-recurring workout, inspect the bodytimeMode: "universal", date encodes the chosen UTC wall-clock time
6.20Recurring all-day modelCreate a recurring all-day workout, inspect the bodystate: "recurring", date: null, weekday set to the short day name
6.21Recurring timed modelCreate a recurring timed workout, inspect the bodystate: "recurring", date = the 1971 template + time-of-day, weekday set

7. Schedule drawer — shared-definition conflict (HTTP 313)​

Requires a workout whose plan/definition is shared across schedules. The backend answers the first save with 313 instead of 2xx.

#Use caseStepsExpected resultResultNotes
7.1Conflict surfacesSave a change to a shared-plan workoutBackend returns 313 (visible in Network); instead of an error, the "Shared workout plan" dialog opens
7.2Dialog optionsRead the dialogThree actions: Only this schedule, Update the shared plan, Cancel
7.3Fork a copyClick Only this scheduleRe-sends to the *_definition_copy endpoint; saves; only this schedule changes
7.4Edit shared plan(Fresh conflict) click Update the shared planRe-sends to the *_definition_update/schedule_and_definition endpoint; saves; the shared plan changes
7.5Cancel(Fresh conflict) click CancelDialog closes; no save performed; drawer stays open with edits intact

8. Schedule drawer — edit ("edit") — V1→V2 regression focus​

Opened by clicking an existing workout chip. Verifies V2 item seeding.

#Use caseStepsExpected resultResultNotes
8.1Open from chipClick a scheduled workout chipDrawer opens titled with the workout's name
8.2Existing items showLook at the Workout sectionThe workout's existing exercises render as slot cards — not the "No exercises added yet" empty state
8.3Options seededCheck Date / All day / Time / RepeatsAll match the saved workout (correct date, all-day vs time, recurring on/off)
8.4Athletes seededCheck the Athletes sectionThe workout's assigned athletes appear as chips
8.5Save enabled immediatelyObserve the Save button on openEnabled right away (no stuck "Loading workout details…" hint, no perma-disabled Save)
8.6Edit & save (update)Change name/items/athletes, click Save"Saving…" then drawer closes; the chip updates on the calendar immediately
8.7No 404 on updateInspect the PUT /workouts/update/schedule responseReturns 2xx (or 313 → §7) — not 404 entityNotFound
8.8Move to another dayEdit a workout's date to a different month/day, saveChip disappears from the old day and appears on the new one
8.9Convert scheduled → recurringEdit a scheduled workout, turn Repeats weekly on, saveIt now appears as a recurring chip on its weekday
8.10Add an athlete in editOpen edit, add another athlete, saveAthlete count on the chip increments
8.11Redundant GET noteInspect Network on edit-open(Known) a GET /workouts/{id} fires on open — should not break anything; flag if it errors or blocks the UI

9. Delete​

#Use caseStepsExpected resultResultNotes
9.1Delete button (edit only)Open a workout in edit modeA Delete button shows (bottom-left); it is absent in create mode
9.2Confirm dialogClick DeleteA confirm dialog "Delete schedule?" with Delete / Cancel appears
9.3Cancel deleteClick CancelDialog closes; workout unchanged
9.4Confirm deleteClick Delete in the dialogDELETE /workouts/{id} fires; drawer closes; the chip disappears immediately
9.5Delete persistsRefresh / reloadThe workout stays gone
9.6Delete recurringDelete a recurring workoutIt disappears from all its weekday occurrences

10. Data loading, caching & refresh​

#Use caseStepsExpected resultResultNotes
10.1Recurring loads onceOn first load, watch NetworkA single GET /workouts/recurring fires
10.2Month range fetchWatch Network while in Month viewGET /workouts/schedules?from=&to= fires for the focused month (±1 neighbouring months)
10.3No duplicate month fetchScroll back into an already-loaded monthNo second fetch for that month (cached)
10.4Week view fetchSwitch to WeekSchedules fetched for the months spanning the visible week's first/last day
10.5Day view fetchSwitch to DaySchedules fetched for the selected day's month + the mini-calendar's month
10.6Reload buttonClick the Refresh (circular-arrows) buttonAll loaded months + recurring refetch; the icon spins while loading
10.7Reload disabled while loadingClick Refresh, watch the buttonButton is disabled (dimmed) until fetches complete
10.8Error resilience(If reproducible) force a failing fetchConsole logs the failure; the calendar doesn't crash; a retry via Refresh recovers

11. Cross-cutting​

#Use caseStepsExpected resultResultNotes
11.1No console errorsClick through §1–§10 with Console openNo uncaught errors or React warnings
11.2Drawer close pathsClose the drawer via backdrop / close controlDrawer dismisses cleanly; calendar interactive again
11.3Rapid open/closeOpen and close the drawer repeatedlyNo stuck overlay, no duplicated drawers, no leaked scroll-lock
11.4Timezone placementCreate a workout near local midnight, verify the day it lands onIt lands on the local calendar day (option-b: UTC instant placed on the user's local day)
11.5i18n wrappingScan visible stringsAll user-facing copy is real text (no raw keys / undefined)

Defect log​

#Case refSeverity (blocker/major/minor)What happenedExpectedConsole / Network detail

Known/expected behaviours (not defects)​

  • Roobert font 404s in dev (/fonts/Roobert-*.ttf) are expected locally and unrelated to the Planner.
  • A GET /workouts/{id} on edit-open (case 8.11) is currently redundant V1-era scaffolding; it is slated for removal but should not affect behaviour.
  • The Planner is an evolving scaffold (inline components, finite ~40-week scroll window); "infinite" scroll and extracted components are future work, not defects.