/{slug}/workboard) is the coach’s task list. Automations (/{slug}/automations) define which events create those tasks and what else happens.
In the API a task is an inbox item (InboxItem), and its type is InboxItemType.
Work board
Code lives in[organizationSlug]/workboard/:
Server load
workboard/page.tsx loads everything the board needs up front:
- All open and snoozed tasks:
/inboxwithstatusOPENandSNOOZED.readEveryPageininbox-pages.tswalks the pages (INBOX_PAGE_SIZE500, at mostINBOX_MAX_PAGES20) andmergeInboxItemsdeduplicates by id. /coaches(100) and/clients(500).- The SmartSend chat embed config and the studio record. Both are optional. The studio feeds the portion unit label on plan stats and the studio name in the push preview.
?task= in the URL preselects a task. The page renders inside PageShell with the full height board layout.
Filtering is done in the browser. task-filtering.ts exports deriveTaskView(...), matchesScope and matchesType. The scope filter has two sentinels: ALL_COACHES ("ALL") and MY_TASKS.
Task types
task-meta.ts maps every InboxItemType to a translation key under workboard.taskType.*, an icon and a colour in TASK_META. Tasks are grouped by priority into PRIORITY_GROUPS: urgent, today, later.
Layouts
A task’s panel is not hand written per type.task-layouts.ts describes it as data:
TASK_LAYOUTSholds a layout for 18 types:FORM_FILLED,FORM_SENT,FORM_RATING_BELOW,FORM_RESPONSE,INACTIVE,NO_WORKOUT,PLAN_EXPIRING,TECHNIQUE_VIDEO,NEW_CLIENT_IN_PLAN,SUBSCRIPTION_ASSIGNED,MILESTONE,CALORIE_UNDER,CALORIE_OVER,AEROBIC_UNDER,PAUSED_DURATION,TRAINING_PLAN_STALE,NUTRITION_PLAN_STALEandMESSAGE.LAYOUT_ALIASESpoints legacy types at a live layout:FORM_FILLED_OVERDUEandFORM_FEEDBACK_SENTuseFORM_FILLED,CHURN_RISKandMISSED_CHECK_INuseINACTIVE,MISSED_STREAKusesNO_WORKOUT.layoutFor(type)resolves alias, then layout, thenFALLBACK_LAYOUT. A type the web app has never heard of still renders.visibleBlocks(layout, args)filters by each block’swhen. Awhenthat throws counts as false, so one bad condition cannot break the panel.
LayoutArgs gives every selector the task, its loaded TaskContext, the current time and the trainee’s other open tasks (siblings).
Quick actions are one of push, smartsendTab, editProgram, reviewCheckin, cancelForm, sendForm and renew.
Blocks
detail/blocks/ has one block per kind of evidence: AerobicBlock, EngagementBlock, FormResponseBlock, FormsBlock, MessageBlock, MilestoneBlock, NutritionBlock, PlansBlock, SubscriptionsBlock, TechniqueVideoBlock and WorkoutsBlock.
Each follows the same split:
BlockRenderer.tsx picks the component for a BlockInstance. BlockSection.tsx and BlockState.tsx are the shared frame and the loading, empty and forbidden states. use-client-resource.ts is the hook a block uses to read a trainee resource.
How the panel loads data
The panel reads through GET route handlers, not server actions. Next.js runs a page’s actions one at a time, and a slow read was blocking the next task’s context and the “mark done” write. The reasoning and the endpoints are in Route handlers. On selection:staticPrefetch(layout)lists the resources whose range is fixed. They start loading at once.- The task context loads from
/api/workboard/{slug}/tasks/{taskId}/context. contextPrefetch(layout, args)lists the resources whose range depends on the context. For example a stale nutrition plan reads 30 days, or 3 months once the plan is older than that (nutritionStaleRange).
/api/workboard/{slug}/clients/{clientId}/{kind}. loadClientResource returns { status: "forbidden" } when the API answers 404, which happens when a coach may not see that trainee. The block renders its forbidden state instead of an error.
Each request carries an AbortSignal. Moving to another task cancels the reads of the previous one.
Writes
Inworkboard/actions.ts:
/{slug}/workboard/automations is an old URL. It redirects to /{slug}/automations. The button in the board’s header still links to the old path and relies on that redirect.
Automations
Code lives in[organizationSlug]/automations/:
This is a canvas route. The sidebar collapses when the coach opens it.
The flow schema
TRIGGER_ORDER lists the 18 triggers in display order. They are the same names as the task types above, from FORM_FILLED to NUTRITION_PLAN_STALE.
FlowTrigger carries the trigger’s parameters: time and unit, pct, an optional score, programs, and for form triggers formTypes (INTAKE, CHECK_IN, ONE_TIME, PDF_SIGNATURE) and formIds. triggerFormScope(trigger) returns all form types when none are set.
A step is a FlowNode, a union keyed on kind:
A
FlowRule compares a field with gt, lt or eq. Nested steps are addressed with a FlowLoc, a list of [stepIndex, branchIndex] hops.
mkNode(kind), mkBranch(label, fallback) and mkRule() build defaults. freshId(prefix) generates ids.
Saving
automations/actions.ts:
The name token inserted into messages is
NAME_TOKEN in trigger-meta.ts.
trigger-meta.ts also exports WA_TEMPLATES, CONTENT_ITEMS and DEMO_TRAINEES. Whether those constants still feed the live UI or only the test run preview was not confirmed while writing this page.
Home screen tasks
The dashboard shows a short task list too.dashboard-task-href.ts builds the link from a dashboard task into the board, and markInboxStatus and bulkInbox in the root actions.ts update status from there.