Portal requirements (POR-*)
@enode/portal — the browser-first coach/admin console (responsive, no native
shell). Shared behavior (auth flow, i18n, units, error engine) lives in
shared.md. See the overview for the ID scheme and
status legend. Test cases:
portal-system-tests.md.
Areas: AUTH · DASH (dashboard) · PLAN (planner) · DEVICES · USERS ·
EXER (exercises) · WKT (workout templates) · TAGS · INS (insights) ·
PROF (profiles/performance) · HIST (history) · MIGR (migration) ·
EXP (export) · NOTIF (notifications) · RBAC (privilege gating).
Template/description sharing spans WKT, EXER and PLAN — the model behind
those rows is in sharing.md.
Auth & entry (POR-AUTH)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-AUTH-01 | Pro users sign in via the shared flow (SHR-AUTH-01); registration is Pro-only. A One user is blocked by the backend (314, missing canAccessPortal) with a message directing them to the One app — same policy as the Pro app (product decision 2026-07-28; previously One logins were allowed for migration). | stable | POR-ST-1.1..1.3, AUTH-LOG-11 |
| POR-AUTH-02 | Device-limit (310), onboarding (311), and invitation (312) responses are handled with their dedicated dialogs/redirects (SHR-AUTH-06..08). | stable | POR-ST-1.6..1.8 |
Dashboard (POR-DASH)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-DASH-01 | The dashboard shows time-range-scoped summaries (active athletes, completed sessions, total volume, sets) with trends vs the prior period. | stable | POR-ST-2.1, POR-ST-2.2 |
| POR-DASH-02 | Top-exercise and athlete-activity rankings and a completed-sessions table are shown for the selected range. | stable | POR-ST-2.3, POR-ST-2.4 |
Planner (POR-PLAN)
Covered in depth by the dedicated Planner manual test protocol.
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-PLAN-01 | The planner offers month/week/day calendar views of scheduled and recurring workouts. | stable | Planner protocol §1–§5 |
| POR-PLAN-02 | Workouts can be created, edited, deleted, and rescheduled (including drag & drop); recurring schedules are supported. | stable | Planner protocol §6, §8, §9 + POR-ST-3.1 |
| POR-PLAN-03 | Saving a change to a shared workout plan surfaces the shared-definition conflict dialog (HTTP 313) with fork/update/cancel options. The "edit the shared definition" option is offered only when the user owns the definition or holds write privilege on it. | stable | Planner protocol §7 |
| POR-PLAN-04 | Picking a template the user does not own asks once, before anything is applied to the draft, whether the schedule should follow the original (the author's later edits reach it; it disappears if they stop sharing) or make its own copy (independent of later changes). Cancelling re-opens the picker and leaves the draft untouched. | stable | |
| POR-PLAN-09 | A schedule that follows someone else's template renders its exercises read-only, says so above them naming the author, and offers Copy to edit — taking your own copy is the only way to change the plan, and it is also what stops the schedule following the original. The appointment itself (date, time, recurrence, athletes, the schedule's own name and tags) stays editable while following. | stable | |
| POR-PLAN-08 | The follow control is always a single line; its consequences ("whose later changes reach this appointment", "what your own copy keeps") open from an info icon that starts collapsed every time. | stable | |
| POR-PLAN-06 | Because a followed plan cannot be edited in place, the save-time conflict it used to produce cannot arise from this surface. The HTTP 313 dialog remains the backstop for what the client cannot see (a definition shared with other schedules), and without write privilege it offers only "save as a private copy". | stable | |
| POR-PLAN-07 | The schedule drawer's template picker and the Workouts page's Explore drawer are ONE component (TemplateBrowser), differing only in whether the viewer's own templates are included and in what a row does. Both offer a self-hiding single-select Yours / Internal / Public strip, one collapsible section per author, the shared ranked search, and a preview: the card body selects in one tap, a chevron opens the read-only detail. The picker offers no way to author a template — the schedule form's own "Add exercise" is the lighter path. | stable | |
| POR-PLAN-05 | A calendar chip for a workout planned from someone else's template carries its provenance glyph, and states it in the chip's accessible name. | stable |
Devices (POR-DEVICES)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-DEVICES-01 | Connected device sessions (stations) can be listed, searched, and removed. | stable | POR-ST-4.1, POR-ST-4.2 |
Users & roster (POR-USERS)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-USERS-01 | Users are listed in a searchable table filterable by role (admin/coach/athlete). | stable | POR-ST-5.1 |
| POR-USERS-06 | The signed-in user appears in the roster only when they are trainable — whatever their role, owner included — badged "You" and excluded from bulk selection. Any other owner stays hidden. | stable | POR-ST-5.8 |
| POR-USERS-02 | A user can be created and edited in the user drawer, including athlete metadata; height/weight respect the metric/imperial setting. Editing someone else needs the tier privilege (writeUserAthlete / writeUserTrainer / writeUserAdmin); editing your own row needs writeSelf, whatever your role — which is how an owner, who is no tier, edits themselves. Archive and delete stay tier-only, so neither is offered on your own row. | stable | POR-ST-5.2, POR-ST-5.6, POR-ST-5.8 |
| POR-USERS-03 | Bulk operations: add tags to multiple users; delete multiple users (with confirmation). | stable | POR-ST-5.3, POR-ST-5.4 |
| POR-USERS-04 | A roster CSV can be uploaded through the wizard: dropzone → parse → column auto-mapping → validation → review → asynchronous creation with live progress. Privilege: featureUserBatchUpload. | stable | POR-ST-5.5 |
| POR-USERS-05 | Per-user actions include data export and viewing upcoming workouts. | stable | POR-ST-5.7 |
Exercises (POR-EXER)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-EXER-01 | The exercise catalogue is searchable by name/tag and filterable by equipment and muscle category; exercises can be favourited. | stable | POR-ST-6.1, POR-ST-6.2 |
| POR-EXER-02 | The exercise detail drawer shows catalogue data and lets the user override name/details with custom values. | stable | POR-ST-6.3 |
| POR-EXER-03 | The detail drawer's Naming card publishes the user's own name for a movement at one of three access levels, and updates it afterwards. Publishing requires a name of the user's own; the card states what each level does before it is picked. | stable | |
| POR-EXER-04 | Other people's published namings for the same movement are listed as selectable cards with their reach and author. Selecting one and confirming stages that name and description into the drawer's own fields — it is a rename of the user's exercise, creating no link to the other user's naming; their own values and history are untouched until they save. | stable |
Workout templates (POR-WKT)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-WKT-01 | Workout templates are listed as cards with search and tag filtering; templates can be created and edited in a drawer and bulk-deleted. The page is an early build (in-code note: structural first pass). | stable | POR-ST-7.1..7.3 |
| POR-WKT-02 | The table lists the viewer's OWN templates only; what other people share reaches them through the Explore action, which appears only when something usable is actually shared with them. Search matches the template's name, its tags, the names of the exercises it contains, and the muscles and muscle groups it trains — ranked by how much of the session the searched muscle is. | stable | |
| POR-WKT-03 | The template drawer has an Access level card: a three-level picker (Private / Internal / Public) with a live line stating what the chosen level does, including what un-sharing would cost. It replaces the previous create-time hardcode, so a template's reach can be changed after creation. | stable | |
| POR-WKT-04 | A template the user does not own opens read-only — no Save, Delete, Add or Select — showing its name, tags, access level, a Source row with the author's provenance, and its exercises as inert slot cards. | stable | |
| POR-WKT-05 | Save a copy on a read-only template creates the user's own copy, closes the Explore drawer and swaps onto the normal editor for it. The copy is self-contained: any movement it references that the copier does not own is adopted into their catalogue. If the template stopped being shared in the meantime, the user is told so and the list refreshes. | stable | |
| POR-WKT-06 | The table carries a sortable Access column: the level for an own unshared template, "Shared by you" once it goes out, and "Shared by {author}" on a foreign row. It is the surface that answers "what have I actually shared". | stable |
Tags (POR-TAGS)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-TAGS-01 | Tags are listed with access-level filter chips; a tag can be created/edited (color, details) and bulk-deleted; tag relations are shown. | stable | POR-ST-8.1..8.3 |
Insights — custom charts (POR-INS)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-INS-01 | Users with the writeCustomCharts privilege can build custom charts (5 chart types) on a draggable board; charts persist per user. | stable | POR-ST-9.1..9.3 |
| POR-INS-02 | The builder preview, empty state, and walkthrough render synthetic example charts (educational visuals, not real data). | mocked | — (known behaviour) |
Profiles — performance matrix (POR-PROF)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-PROF-01 | The Profiles page shows a two-axis athlete × exercise performance matrix with list/grid views (persisted preference), pair detail, and per-athlete trend. | stable | POR-ST-10.1..10.3 |
History & session details (POR-HIST)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-HIST-01 | Completed sessions can be searched with filters and paginated; the default window is the last year. | stable | POR-ST-11.1, POR-ST-11.2 |
| POR-HIST-02 | A session opens in a drawer with a summary tab and technique board. | stable | POR-ST-11.3 |
| POR-HIST-03 | The session summary's zone-distribution and muscle-map cards render from the V2 history-stats DTO; marked WIP in code, zone card limited to conventional-strength exercises. | wip | — (known behaviour) |
| POR-HIST-04 | A dedicated date-range picker is planned for a later milestone (dev-only all-time toggle exists). | wip | — |
| POR-HIST-05 | An Archived filter splits the sessions table into disjoint compartments: by default sessions from archived athletes are hidden; the filter shows only those (SHR-ARCH-03). Like the page's other facets it narrows the loaded page only, so the counts are page-scoped. | stable | POR-ST-17.2 |
Migration (POR-MIGR)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-MIGR-01 | A wizard migrates athletes, groups, and history from enode Pro; reachable as a route and as a sidebar drawer. Privilege: featureMigration. | stable | POR-ST-12.1, POR-ST-12.2 |
| POR-MIGR-02 | Exercise mapping during migration is chosen manually by the coach (auto-suggestion intentionally stubbed — backend returns no targets by design). | stable | POR-ST-12.3 |
Export (POR-EXP)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-EXP-01 | The export flow produces data exports (including PDF); reachable as a route (deep-linkable via URL params) and as a sidebar drawer. Privilege: featureDataExport. | stable | POR-ST-13.1, POR-ST-13.2 |
Notifications (POR-NOTIF)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-NOTIF-01 | Notifications arrive live (stream + arrival peek + bell unread badge) and are listed in an inbox with filter segments, mark-read, delete, and deep-link expansion. | stable | POR-ST-14.1..14.3 |
| POR-NOTIF-02 | Long-running roster uploads report progress live (stream host + peek). | stable | POR-ST-5.5 |
Privileges & roles (POR-RBAC)
| ID | Requirement | Status | Tests |
|---|---|---|---|
| POR-RBAC-01 | Navigation entries and routes are gated by privileges (useHasPrivilege): Users pages, Insights, roster upload, migration, and export are hidden/blocked without the corresponding privilege. | stable | POR-ST-15.1, POR-ST-15.2 |
| POR-RBAC-02 | The role model is owner → administrator ("Facility") → coach → athlete; the owner is an all-access singleton excluded from role filters and from other users' rosters (see POR-USERS-06 for the signed-in user's own row). | stable | POR-ST-15.3 |
Dev-only surfaces
/dashboard/debug, /dashboard/debug/wizard, and /dashboard/debug/benchmark
plus the mock-data harness render only in dev builds (render-time guards; all
mock setters no-op in production). Not release-tested beyond confirming they
are absent in production builds (POR-ST-16.5).