expert-work-queuerun.md00_intake/stub.mdThe Work queue table + Action required summary
(apps/web/components/dashboard/expert/work-queue-table.tsx)
is the centrepiece of the workbench and is entirely dummy. Each row needs customer, contact, work,
stage, dueIn/sla/overdue/timeStuck, status band, blocker + who + suggested contact,
escalationCount, CSM and value. The Action required summary needs counts: overdue, escalated,
dueToday, customersWaiting, and goingLiveThisWeek. All the inputs now exist (owned leads, the SLA
timing engine, the blocker/escalation model) — this feature composes them.
lead.manager), value
(accepted quote/proposal), stage + SLA timing (from sla-stage-targets), and blocker/escalation data
(from work-blockers-escalations).overdue (SLA-breached), escalated (open escalated
blockers), dueToday, customersWaiting (open blockers where who = Customer), goingLiveThisWeek
(leads with a go-live/end date inside the current week).WorkQueueTable + the Action required summary to this real data; honour the existing
status-band colours (red / amber / soft).suggestedContact beyond surfacing the suggestion
(wiring an actual send is a later concern unless trivially present).goingLiveThisWeek needs a go-live date source — reconcile lead.endDate vs milestone due dates;
pick one definition and note it.total vs proposal price) should match how
expert-active-projects and expert-earnings-summary resolve it — keep one shared helper.touches: packages/services/src/server (work-queue read), apps/web/components/dashboard/expert/
work-queue-table.tsx + action-required-filters.tsx, apps/web/app/(app)/expert/dashboard/page.tsx.
</content>
</invoke>01_define/output/spec.mdThe Work queue table and the "What needs my action now" summary are the centrepiece of the expert Go
live workbench (/expert/dashboard), and they are the last two slots in the
expert-workbench-foundation envelope still returned empty (workQueue: [],
actionRequired: EMPTY_ACTION_REQUIRED in
packages/services/src/db/services/expert-workbench/index.ts). The table renders nothing real: an
expert cannot see which of their owned leads need attention, why, who is blocking, how overdue they
are, or what they are worth. This is feature 4 of 8 in the expert-dashboard-data epic, advancing
Build the Bridge → Q2 2026 Objective 3 (Validate Technical Infrastructure & Payout Flow) — the
objective only holds once the workbench shows real, per-tenant, per-expert work instead of mock rows.
The three inputs this feature composes have all merged: the per-stage SLA timing engine
(sla-stage-targets), the blocker + escalation model (work-blockers-escalations), and the
server-fetched workbench envelope with its shared lead/customer/CSM/value derivations
(expert-workbench-foundation / active-projects.ts). This feature joins them; it defines no new
model.
expert-workbench/work-queue.ts,
deriveWorkQueue(tenantId, expertUserId)) returning the filled workQueue and actionRequired
slots. The candidate set is the signed-in expert's owned, in-flight leads needing attention:
lead.expert = me, current tenant, not soft-deleted, excluding terminal (won/lost/cancelled/closed)
statuses, that satisfy the attention predicate — at least one open blocker, OR an SLA band of
at-risk/red (i.e. SLA pressure / overdue). Leads with no pressure and no open blocker do not
appear.WorkItem shape, unchanged) from the existing layers, reusing the
active-projects.ts label helpers (customer/contact/work/CSM) rather than re-deriving them:stage ← timing.currentStatus.displayName; sla ← stage target label; dueIn ←
timing.remainingDays; overdue ← a "<n>d" label when overdue else null; status ← the SLA
band mapped to the status-dot token the row already renders (red for breached, the at-risk
token — soft/amber — for at-risk, a neutral token for on-track), all from the
sla-stage-targets engine.who ← the lead's representative open blocker's owner; blocker ← its label;
suggestedContact ← its suggestedContact (mapping the blocker model's Customer | CSM onto the
UI MessageRecipient, defaulting to All when unset); escalationCount ← its escalationCount;
timeStuck ← business days since that blocker was raised (or since lastEscalatedAt). The
representative open blocker is the most-escalated one, tie-broken by newest. When a lead is in
the set on SLA pressure alone (no open blocker), who/blocker/escalationCount/timeStuck
degrade gracefully (e.g. who empty, blocker an SLA-pressure label, escalationCount 0).value ← the accepted-proposal price, resolved via the shared value helper (below).expert-workbench/value.ts) from the valueLabel currently
inlined in active-projects.ts — a batched resolver over a set of lead ids
(accepted proposal price → formatCurrency, else "—") — and have both the active-projects
read and this new work-queue read consume it, so lead value is resolved one way across the workbench
(the stub's "keep one shared helper").actionRequired summary at the row level so the counts reconcile with the rows
(the table filters rows; the badges count them — they must agree):overdue = rows with an overdue label; escalated = rows with escalationCount > 0; dueToday =
rows with dueIn === 0; customersWaiting = rows whose representative who === "Customer".goingLiveThisWeek = the customer labels of the expert's in-flight leads whose lead.endDate
falls within the current ISO week (Monday–Sunday). lead.endDate is the single chosen go-live
source — milestone due dates are not used for this signal (decision recorded; see Out of scope).
This set is computed over the expert's in-flight leads, independent of the attention predicate (a
lead can be going live this week without an open blocker).dueIn ("sorted by due date",
matching the table's stated order).customersWaiting filter in work-queue-table.tsx from its current
who === "Expert" predicate to who === "Customer", so the filtered rows match the
customersWaiting badge and the "Customer waiting / Unblock" label. No other UI change — the page
already passes workbench.workQueue / workbench.actionRequired into WorkQueueTable, and the
table renders a built empty state.who), suggested
contact, escalation count, CSM and value — no mock rows; lib/mock/expert.ts is not read.sla-stage-targets timing
engine; blocker/who/suggested-contact/escalation come from the work-blockers-escalations model.active-projects.ts (no second value-resolution path).actionRequired counts — overdue, escalated, due-today, customers-waiting — are computed
from the same rows and equal the number of rows shown when that filter is active (counts
reconcile with rows), and the client customersWaiting filter selects who === "Customer".goingLiveThisWeek lists the customer labels of the expert's in-flight leads whose lead.endDate
is within the current ISO week (Mon–Sun).lead.endDate is the chosen single definition for
goingLiveThisWeek; reconciling milestone-derived go-live dates is a later concern.expert-active-projects, feature 5) and earnings /
quality slots — only the workQueue + actionRequired slots are filled here.suggestedContact "Respond" action / message compose — the
suggestion is surfaced (recipient pre-filled) but the send stays the existing client stub.work-blockers-escalations; this feature only reads open blockers.lead.endDate), the shared value helper, the attention predicate, and
row-level summary tallies (so counts reconcile with the rows) were all resolved at Define.
</content>
</invoke>02_build/output/notes.mdfeat: expert-work-queue — wire Work queue + Action required from leads/SLA/blockerspackages/services/src/db/services/expert-workbench/work-queue.ts (new) —
deriveWorkQueue(tenantId, expertUserId). Loads the expert's owned leads (populated
customer/manager/product), derives SLA timing for all of them in one batch
(sla-stage-targets), drops terminal-status leads, then keeps the attention set (an open
blocker from work-blockers-escalations, or SLA pressure). Each row maps stage/SLA/due/overdue
from the timing engine and blocker/who/suggested-contact/escalation from the representative open
blocker (most-escalated, tie-broken newest). Rows are sorted overdue-first then by ascending due.
The actionRequired summary is computed at the row level (overdue / escalated / due-today /
customers-waiting) so the badge counts equal the table's filtered rows; goingLiveThisWeek is the
in-flight leads whose lead.endDate falls in the current ISO week (Mon–Sun, UTC).packages/services/src/db/services/expert-workbench/value.ts (new) — the single lead-value
convention (formatLeadValue: accepted-proposal price → formatCurrency, else —) plus a batched
resolveValueLabelsForLeads. Both the work-queue read and active-projects.ts resolve value
through formatLeadValue — one value path across the workbench.packages/services/src/db/services/expert-workbench/labels.ts (new) — extracted the shared
customer/contact/work/CSM column helpers so both reads use one derivation rather than duplicating it.packages/services/src/db/services/expert-workbench/active-projects.ts — now consumes the
shared labels.ts helpers and formatLeadValue instead of its own inlined copies (no behaviour
change; same labels/value).packages/services/src/db/services/expert-workbench/index.ts — getWorkbench now fills the
workQueue and actionRequired slots from deriveWorkQueue (was typed-empty). Removed the
now-unused EMPTY_ACTION_REQUIRED.apps/web/components/dashboard/expert/work-queue-table.tsx — aligned the client
customersWaiting filter from who === "Expert" to who === "Customer", so the filtered rows
match the badge and the "Customer waiting / Unblock" label.The /expert/dashboard page already passed workbench.workQueue / workbench.actionRequired into
WorkQueueTable, so filling the envelope slots wires the section with no page change. The table's
built empty state renders when the expert has no attention items.
deriveWorkQueue; page reads the envelope, not the mock).sla-stage-targets; blocker fields from
work-blockers-escalations.formatLeadValue) consumed by both reads.actionRequired counts computed from the same rows (row-level tallies) and reconcile with the
filters; client customersWaiting selects who === "Customer".goingLiveThisWeek = in-flight leads with lead.endDate in the current ISO week (Mon–Sun).completed, qualified out,
rejected, …) since the workflow JSON exposes no terminal flag — mirrors the static stage-map
pattern. Extend the set if new terminal statuses are added.dueIn sentinel
(9999) so they never read as "due today" and sort last; the Deadline cell shows "No SLA target".escalated/customersWaiting tallies count rows by that
representative so they reconcile with the table filters.03_release/output/changelog.mdAudience: Expert. Published as apps/help/app/changelog/2026-06-23-expert-work-queue/page.mdx
(goes live on merge).
Heading: Your work queue now shows the leads that need you
Benefit: The expert go-live workbench Work queue lists the expert's real attention-needing leads (open blocker or SLA pressure) — customer, stage, blocker, who's blocking, overdue, escalation count, CSM and value — and the "what needs my action now" counters are derived from the same rows so they reconcile with the list. Leads with no blocker and no SLA pressure stay out; go-lives this week are flagged.
03_release/output/investor-update.mdWho it's for: Experts What shipped: The expert workbench work queue and "action required" counts now show each expert's real attention-needing leads — stage, blocker, who's blocking, how overdue, and value — instead of placeholder rows. Why it matters: Another step in Build the Bridge → Validate Technical Infrastructure & Payout Flow: experts can act on their real delivery work.
Dig deeper: <merged-PR URL> · <changelog entry URL>
03_release/output/release.md@sustentus/services package (no new app, route, env var, package entrypoint, or build/CI step); nothing in apps/docs/app/technical/** describes the expert workbench internals.feature-role-matrix row or service-journey step; this wires existing dashboard UI to real data, so no documented capability changes.apps/help/app/changelog/2026-06-23-expert-work-queue/page.mdx) + investor draft, both in this PR/code-review on the feature diff (work-queue.ts, value.ts, labels.ts, index.ts, active-projects.ts, work-queue-table.tsx): no correctness findings. The circular type-import pattern mirrors the established active-projects ↔ index precedent; the value/label extraction is behaviour-preserving; the NO_DUE sentinel cannot collide with a real remaining-days value; row-level tallies reconcile with the client filters by construction.goingLiveThisWeek dedups customer labels.deriveWorkQueue; page reads the envelope, not lib/mock/expert.ts.sla-stage-targets; blocker/who/contact/escalation from work-blockers-escalations.formatLeadValue) consumed by both reads.actionRequired counts computed row-level and reconcile with the filters; client customersWaiting selects who === "Customer".goingLiveThisWeek from lead.endDate in the current ISO week (Mon–Sun).