Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesDashboardBehavior — dashboard overview

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.

SurfaceGate / ownership
Action CenterStaff HTTP feed: full 20-rule catalog for owner/admin, seven operations/engagement rules for coach
Today scheduleOrg has schedule mode, or has no coaching mode; the latter retains the scheduling empty state
Pending taskstasks:view; existing task detail sheet handles actions
Member growth trendanalytics:view
Revenue trendanalytics:view + paymentAnalytics:view + active payment-provider configuration
Leads trendleads:manage
Coaching rosterOrg 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_overdue
  • operations.sessions_missing_workout
  • operations.waitlisted_bookings
  • engagement.checkin_missed
  • engagement.checkins_to_review
  • engagement.forms_missing
  • engagement.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 OutstandingFormsQueue when meta.actionable is 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:

ActionActual 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=day returns the whole org-local day (limit 20); callers without scope=day retain 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_completed queue ordered by due date, with up to 25 rows. A row opens TaskDetailSheet, 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 keyed withPlans, overdue, inDebt, ending, invited, noPlan, and former.
  • trainedThisWeek from roster summary, plus newThisMonth and churnedThisMonth from 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; noPlan is 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.