Table of Contents
19 — Coach-private notes
Technical reference. For task-based help, see the corresponding user guide.
Documentation status: complete for the current implementation
Reviewed: 2026-07-21
Primary users: the genuine assigned coach only
Surfaces: coach admin portal, mobile coach workspace and platform API
Purpose
Coach-private notes give a coach a durable scratchpad about a baby and individual activity logs without altering the family's own notes. They are deliberately scoped to the specific coach, organization and current family relationship.
“Private” here means the notes have no family API, are unavailable to other coaches and owner view-as, and are excluded from analytics and the generated coach-review PDF. It does not mean every possible local output is automatically redacted; the current browser-print caveat is documented below.
User outcomes
| User need | Outcome |
|---|---|
| Keep a running baby note | Store one substantial note for a baby within this coach/family relationship. |
| Annotate a log | Store one private note against an individual activity. |
| Avoid stale-tab loss | Receive a conflict instead of silently overwriting a newer saved note. |
| Remove a note | Clear the body and save to delete the durable note. |
| Protect family boundaries | Keep coach text out of family notes, family endpoints, analytics and ordinary exports. |
Note model and scope
| Target | Uniqueness scope | Typical presentation |
|---|---|---|
| Baby note | One per organization + coach + family + baby. | Collapsible Private notes about [baby] card on mobile family view-as and portal session prep. |
| Activity note | One per organization + coach + activity. Family and baby identifiers are retained on the row. | Inline editor in activity history/edit/analysis and the portal session-prep activity table. |
A body is at most 10,000 characters. Leading/trailing whitespace is removed. A blank string or null is treated as deletion, so the service does not retain empty note rows.
Entry points
| Surface | Location | Function |
|---|---|---|
| Mobile family view-as home | Baby private-note card | Read/edit the selected baby's note. |
| Mobile activity history/edit | Private coach note editor | Read/edit the selected activity note. |
| Mobile analysis/tracking | Activity edit sheet | Use the same activity-note editor for a log opened from analysis/history. |
| Coach portal session prep | Baby note above the report | Keep preparation context for the selected baby. |
| Coach portal activity report | Private coach note column | Open/save one note per activity row. |
Create, update and delete
- Opening an editor loads the current note or returns
note: null. - The draft stays local until Save.
- Saving non-empty text creates the note if absent or updates the existing note.
- Saving blank/null deletes the existing note and returns
note: null. - Baby-note cards cannot be collapsed while dirty or saving. Their collapsed preference is stored locally under the coach user identifier.
- Portal family/baby switching asks for confirmation when the selected baby note has unsaved edits.
- Activity note editors expose compact open/edit/cancel/save states so every row does not render a large text area at once.
Optimistic concurrency
Every write can include expectedUpdatedAt:
- For an existing note, the client sends the timestamp it loaded.
- For a new note, the client sends
nullto assert that no note exists. - If the durable state no longer matches, the API returns HTTP 409 with
privateNoteMutationConflict. - The UI keeps the coach's draft and reports the conflict rather than overwriting the other tab/device.
- Successful updates force
updatedAtto move forward by at least one millisecond, even when writes occur within the same clock tick.
Omitting expectedUpdatedAt remains a compatibility last-write-wins path. Current editors provide it and should continue to do so.
Batch activity-note loading
POST /coach/families/:familyId/activity-private-notes/query accepts 1–100 activity identifiers. The platform:
- verifies the coach/family scope first;
- validates that every requested activity belongs to that family;
- returns only notes belonging to the current coach and organization; and
- does not return placeholders for activities with no note.
Clients that need more than 100 must chunk their requests. The current portal session-prep loader does not: a report containing more than 100 activities triggers a non-fatal note-load warning and shows the report without those activity notes. See Feature 18.
Access lifecycle
Private-note access requires all of the following at request time:
- a genuine coach actor, not
ownerViewAsCoach; - the active assignment matching coach and family;
- assignment
canViewLogs; - active organization membership;
- active/trialing organization and coach subscription access; and
- a support entitlement that allows family-record access.
Paused support allows record access and therefore private notes. Expired/revoked/canceled/inactive-billing support does not. The note may remain stored for legitimate retention/audit purposes, but it is not retrievable through the coach endpoint after access ends.
Privacy boundary
| Consumer/output | Private-note body included? |
|---|---|
| Family mobile/API | No. There is no family private-note route or field. |
| Another assigned/team coach | No. Rows are keyed and queried by the creating coach. |
| Owner view-as-coach | No. Synthetic owner sessions are explicitly denied. |
| Coach dashboard/practice analytics | No. Analytics services do not load private-note tables. |
| Generated Download PDF coach-review report | No. Its activity text reads type details and family note only. |
| Live coach portal session-prep report | Yes, for the genuine coach. It is an editable coach-only column. |
| Browser Print of that live report | Currently yes. Print CSS does not hide the private-note column. |
Do not give a browser-printed session-prep page to a family or third party without reviewing it. Use the generated Download PDF for the current private-note-free export, or remove/redact the column before printing.
The browser-print behavior is a current implementation caveat, not a relaxation of the API privacy model.
Audit behavior
Create, update and delete write coachPrivateNote.created, .updated or .deleted events. Audit metadata identifies note ID, target type, baby/activity, family and organization context. The note body is intentionally not copied into audit metadata.
Platform API contract
| Method and path | Role |
|---|---|
GET /coach/families/:familyId/babies/:babyId/private-note | Return the current coach's baby note or null. |
PUT /coach/families/:familyId/babies/:babyId/private-note | Create/update/delete with { body, expectedUpdatedAt? }. |
POST /coach/families/:familyId/activity-private-notes/query | Return current coach's notes for 1–100 authorized activity IDs. |
PUT /coach/families/:familyId/activities/:activityId/private-note | Create/update/delete one activity note. |
Validation and user-visible states
| State | Expected behavior |
|---|---|
| Note loading | Keep an editor disabled/loading; do not show another target's prior draft. |
| No note | Start with an empty draft and expectedUpdatedAt: null. |
| Saved | Update local note/timestamp and show concise confirmation. |
| Blank saved | Delete, return null and close/reset the editor appropriately. |
| Conflict | Keep the draft and ask the coach to reload/reconcile rather than silently overwrite. |
| Dirty baby note | Disable minimize and confirm before changing family/baby. |
| Private-note side-load failure | Keep the underlying family/report data usable and show a dedicated warning. |
| Access expired | Deny without returning whether a note exists. |
Acceptance checklist
- Baby and activity uniqueness scopes prevent duplicate durable notes for the same coach target.
- Body validation enforces 10,000 characters and blank save deletes.
- Current editors send
expectedUpdatedAtand a stale save returns 409 without losing the draft. - Baby/activity identifiers must belong to the authorized family.
- Family users, other coaches and owner view-as cannot read or mutate note bodies.
- Expired or revoked record access denies notes even from a saved route.
- Audit events never copy the note body.
- Analytics and generated PDF exclude note bodies.
- Browser print is recognized as currently capable of including the visible private-note column.
- Batch callers respect the 100-activity maximum or fail non-fatally.
Source evidence
Coach portal
admin-web/src/routes/CoachBabyPrivateNotePanel.tsxadmin-web/src/routes/ActivityPrivateNoteCell.tsxadmin-web/src/routes/SessionPrepPanel.tsxadmin-web/src/routes/SessionPrepCoachReport.tsxadmin-web/src/styles.css
Mobile web
mobile-web/src/components/CoachPrivateNotes.tsxmobile-web/src/components/ActivityHistoryEditSheet.tsxmobile-web/src/routes/FamilyDashboard.tsxmobile-web/src/routes/ActivityTrackingScreen.tsxmobile-web/src/routes/AnalysisScreen.tsx
Platform and contracts
platform/apps/api/src/routes/coach-private-note-routes.tsplatform/apps/api/src/services/coach-private-note-service.tsplatform/packages/shared/src/coach-private-notes.tsplatform/packages/shared/src/enums.ts
