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 cardsnot 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.