- Stale-task section in the daily digest email (close named rev-47 follow-up)Rev 47's running state explicitly named 'stale-task auto-flag email digest' as the rev-48 candidate, citing the new dashboard panel as a diagnostic surface that only fires when an operator is looking at the dashboard. Rev 48 closes the loop for solo founders and email-first operators who don't have the dashboard tab open. New buildStaleTasksSection() helper renders up to 5 stale tasks with day-counts, status pills, and inline blocker call-outs into the existing daily digest email body. Stale tasks are workspace-shared (every owner/admin recipient sees the same list, since 'this work is rotting' is workspace-level diagnostic context) โ distinct from the rev-25 per-recipient assignee section and the rev-31 per-recipient mentions section which are personal-inbox state. Pure derived state โ reuses the rev-47 getStaleTasks() helper verbatim, no schema change.
- Stale-task daily Slack push + new task.stale_warning outbound webhook eventCron sweep added at the end of runDailyDigest() โ pingStaleTasks(). For every onboarded workspace whose Slack webhook is set, fetches getStaleTasks() and (a) pings Slack via the new buildStaleTasksSlackPayload() block (header + section listing up to 5 stale tasks with N-d quiet pills + bulb context CTA), (b) dispatches the new task.stale_warning outbound webhook event via dispatchStaleTasksWebhook(). Rate-limited via a stale_tasks activity-log entry to once per workspace per 24h with the same dead-Slack-webhook auto-clear path as pingStuckLoops() and pingCostSpikes(). New OutboundEvent value task.stale_warning + matching dispatcher in src/lib/outbound.ts. Pairs with the rev-47 dashboard panel + rev-48 digest section as the third stale-task push channel โ the same trio shape as the rev-32 cost-spike alarm (in-app banner + Slack push + outbound). External automations (CRM, project tool, board-status integration) can now mirror 'this work is rotting' as a workspace-level signal.
- GET /api/v1/tasks/stale endpoint (v1 parity for rev-47 dashboard primitive)New bearer-auth endpoint mirrors the rev-47 dashboard StaleTasksPanel exactly. Accepts optional thresholdDays (1-60, default 5) and limit (1-50, default 10). Delegates to the same getStaleTasks() helper the dashboard uses so the two surfaces share one server-side implementation. Until rev 48 an MCP host driving the desk could read every task field but couldn't answer the diagnostic question 'which tasks are silently rotting in the queue?' without enumerating /api/v1/tasks and computing staleness client-side. Rev 48 closes that gap. Pairs with the rev-46 /api/v1/decisions endpoint and the rev-37 task.unblocked outbound event as the full task-state observability surface on the protocol-bound side.
- In-line staleness pill on every active-work cardCumulative dashboard polish โ until rev 48, the staleness signal was only visible in the dedicated rev-47 StaleTasksPanel sidebar. Operators triaging a long active-work queue had to cross-reference the dedicated panel against each card by title to know which were stale. Rev 48 surfaces a small โณ N-d quiet pill in the active-work card pill row (alongside the rev-21 priority + rev-22 due + rev-23 pinned + rev-16 assignee + rev-26 comment-count pills) for any queued/in_progress task aged 5+ days that isn't pinned. Pure derived state โ same staleness rules as the rev-47 server-side helper. The pill row now reads at-a-glance: triage state (priority/due/assignee), discussion state (comments + mentions), and rotting state (staleness) all in one row. Closes the visual-hierarchy gap that the rev-47 StaleTasksPanel didn't address.