Behavior — dashboard overview
Composition and gates
The page renders its greeting/onboarding, then Action Center → Today/tasks → trends → coaching roster. There is no Pulse strip, standalone KPI strip, or Reports snapshot on Overview.
| Surface | Gate / ownership |
|---|---|
| Action Center | Staff HTTP feed: full 20-rule catalog for owner/admin, seven operations/engagement rules for coach |
| Today schedule | Org has schedule mode, or has no coaching mode; the latter retains the scheduling empty state |
| Pending tasks | tasks:view; existing task detail sheet handles actions |
| Member growth trend | analytics:view |
| Revenue trend | analytics:view + paymentAnalytics:view + active payment-provider configuration |
| Leads trend | leads:manage |
| Coaching roster | Org has coaching mode; data scoped server-side to active members enrolled in active coaching programs |
Delivery modes come from active programs, not organization_type. A
coaching-only org has tasks rather than classes in its working row. The trend
row disappears if none of its panels apply. The Today/tasks tiles use bounded
tracks with internal scrolling; this is layout behavior, not a claim of
verified coverage at every breakpoint.
Action Center populations and Reports
Insight rows count each person once, use org-local day cutoffs, and carry
either an exact destination or bounded actions; see
Insights behavior. The Reports
workspace, its sidebar entry, and the report KPI snapshot require
analytics:view; unauthorized roles do not request report data.
The snapshot is rendered only inside Reports, at
/[lang]/dashboard/analytics. That workspace uses
tab=attendance|revenue|retention and URL filters
from, to, classTypeId, method, months, and asOf; its seven API/CSV
projection identifiers are unchanged. This is not seven Overview tabs.
See analytics behavior for report definitions,
currency separation, and the lifecycle-cohort estimate. Revenue/report
readers retain canonical receipts.sql semantics; this work adds no accounting
definition.
Action Center catalog and actions
The Insights behavior catalog is the
rule-by-rule reference for all 20 IDs, counts, severity, and exact hrefs.
There are four finance, eight members, three operations, four engagement, and
one leads rule. The web hides every finance category rule for an org without
billing (hasBilling() false; see payments behavior),
whatever the feed returns; the same holds for the overview revenue tile and the
payments KPI cards. The coach’s seven-rule subset is:
operations.tasks_overdueoperations.sessions_missing_workoutoperations.waitlisted_bookingsengagement.checkin_missedengagement.checkins_to_reviewengagement.forms_missingengagement.birthdays
Owner/admin-only rules are not evaluated for coach HTTP requests. Ordinary
members cannot use the staff feed. Spotter’s analytics.org_insights tool
currently has its own owner/admin scope; it is not the coach HTTP endpoint.
Results sort urgent → warning → info, then count descending. Empty rules drop out; an empty feed renders all-clear. Counts retain their actual units: notices count cases; waitlist counts distinct members; check-in review counts occurrences. Per-member dedup/current-roster predicates are rule-specific, not a global rewrite of finance or membership populations. Samples contain at most three entries and do not limit headline totals.
drilldownHref: string | null is data from the server:
- A real root href navigates with
/{lang}prefixed. - Null renders a non-navigating row. Named member/session samples can provide real actions; unsupported generic list filters are not fabricated.
- Check-ins to review expand the existing
CheckInReviewQueue. - Missing forms expand
OutstandingFormsQueuewhenmeta.actionableis true. - Overdue-task samples remain informational. There is no supported overdue-only open-task filter or per-task route; the root href opens the open queue sorted by due date.
Owning destination URLs
Destination hydration is unconditional:
| Action | Actual destination / behavior |
|---|---|
| Pending refunds | /dashboard/tasks?tab=not_completed&type=manual_refund |
| Failed join checkout | /dashboard/tasks?tab=not_completed&type=payment_follow_up |
| Open retention cases | /dashboard/tasks?tab=not_completed&type=cancellation_review |
| Stored debt | /dashboard/payments?tab=debt; the page hydrates Debt and browser history |
| Named workout/waitlist session sample | /dashboard/schedule?programId=<uuid>&weekStart=<org-local session date>&session=<uuid> → real program schedule tab |
Tasks independently validate supported listTasksQuerySchema fields and
hydrate the URL/history. tab=overdue is unsupported and falls back to Today;
date bounds only apply to date_range. The real task links above are not a
new task-filter API. Payments validates its existing tab allowlist and falls
back to Transactions for unknown tabs; no failed-charge status/date filter
has been invented.
Task tabs: the date tabs (today, tomorrow, yesterday, date_range)
list completed tasks (drawn ticked) but hide cancelled ones — the rows stay in
the database, and an explicit status=cancelled filter still returns them.
today also carries open overdue tasks. not_completed excludes completed and
cancelled; completed lists only completed. The summary’s todayCount counts
open tasks due today. A row’s checkbox completes an open task; on a completed
task it un-ticks it (PATCH …/tasks/:id { status: pending }, clearing
completedAt / completedBy). A cancelled row is never drawn ticked, shows a
“Cancelled” badge, and its checkbox reopens it the same way. A lead’s contact
task that cannot reopen because the lead has another open one (409) shows that
reason in a toast.
Schedule validates program/session UUIDs and real YYYY-MM-DD dates before
redirecting to /dashboard/programs/<id>?tab=schedule. Missing/invalid program
context falls back to Programs; invalid optional week/session values are
dropped. Existing program calendar/week state and session detail own the
result. There is no new global schedule or waitlist filter.
Today and task workflows
GET /sessions/upcoming-today?scope=dayreturns the whole org-local day (limit 20); callers withoutscope=dayretain the from-now window (limit 5). Past sessions appear dimmed with an ended label; missing workouts have an amber chip. Staff, including coaches, can fetch today’s sessions.- Today “view all” opens the first schedule program’s schedule tab, or the Programs list when no schedule program exists.
- Pending tasks use the existing
not_completedqueue ordered by due date, with up to 25 rows. A row opensTaskDetailSheet, not a fabricated task URL. - The coaching roster appears after trends, not as a second header or a KPI
strip. Its own header carries workouts due today, seven-day compliance,
and needs-attention count from
/assignments/coach-overview.
Retention work and members context
members.notices_open includes unresolved retention-case kinds request and
notice on live, nondeleted subscriptions. It counts cases, while only
the bounded sampled people are deduplicated. Severity uses acknowledgement SLA and uncontacted effective
dates; see Insights behavior.
The row opens the filtered cancellation_review task queue. Requests awaiting notice issuance are obligations, not
members already scheduled to end. The Members
?paymentStatus=ending filter remains a separate ending-roster view, with its
Notice/Ends on/Case/Owner context; its count need not equal open cases.
See memberships behavior and
subscriptions behavior.
The actual Members ribbon is
apps/web/src/app/[lang]/(protected)/dashboard/members/members-ribbon.tsx.
It is analytics:view-gated and owns:
- Total = active member records (
roster-summary.total), with pending invitations (invited) and former members (former) printed beside it, not inside it. The commercial donut covers all three populations; slices apply the roster filter in place and are keyedwithPlans,overdue,inDebt,ending,invited,noPlan, andformer. trainedThisWeekfrom roster summary, plusnewThisMonthandchurnedThisMonthfrom analytics summary.- Imported-member and at-risk callouts when their populations are nonzero.
- An org without billing (
hasBilling()false, see payments behavior) gets no money slices (withPlans,overdue,inDebt,ending;noPlanis labelled “Members”) and no at-risk callout. The churn tile stays.
The org-wide ribbon is independent of the table’s active filter. There is no
Ending soon tile; ending is a commercial donut bucket and separate roster
filter, not the Action Center case queue.
RetentionCasePanel remains the per-subscription case working surface under
the member’s membership tab: acknowledgement, contact, notice/outcome actions,
and available save tools belong there. Existing task details provide case
context/navigation; the Action Center does not create a second case model.
Ask Spotter and limits
Every feed row can offer Ask Spotter when an agent provider is present.
openWith receives localized copy, { insightId, insightCount } selection
context, and an insight subject; it is distinct from navigating a root/sample
link. The selection context belongs to that send, not later unrelated text.
There is no per-org threshold setting, dismissal, or snooze. Missing-workout insights count sessions still ahead of now, not sessions that already ran. No latency or scale guarantee is asserted by this documentation.