Skip to Content

← All archived runs

Run: expert-work-queue

run.md

Run: expert-work-queue

  • branch: claude/youthful-rubin-m7pgkv
  • pr: #508

00_intake/stub.md

Stub: Expert work queue + action required

  • feature-slug: expert-work-queue
  • epic: expert-dashboard-data
  • personas: Expert
  • initiative: Build the Bridge / objective: Q2 2026 Objective 3 — Validate Technical Infrastructure & Payout Flow
  • depends-on: expert-workbench-foundation, sla-stage-targets, work-blockers-escalations
  • sequence: 4 of 8

Problem

The 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.

Proposed change

  • Build the read query: the signed-in expert's owned leads needing attention (active, with an open blocker or an SLA-pressured stage), joined to customer + contact, CSM (lead.manager), value (accepted quote/proposal), stage + SLA timing (from sla-stage-targets), and blocker/escalation data (from work-blockers-escalations).
  • Compute the Action required summary: 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).
  • Wire WorkQueueTable + the Action required summary to this real data; honour the existing status-band colours (red / amber / soft).

Acceptance criteria (rough)

  • Work queue lists the expert's real attention-needing leads with stage, SLA/overdue, blocker, who, suggested contact, escalation count, CSM and value — no mock rows.
  • Action required counts (overdue, escalated, due-today, customers-waiting, going-live-this-week) are computed from the same data and reconcile with the rows.
  • Status bands derive from the SLA timing engine, not hard-coded.
  • Empty queue renders a clean "nothing needs attention" state.

Out of scope (this feature)

  • Defining SLA targets or the blocker model (consumed from features 2 and 3).
  • The message-compose / contact action behind suggestedContact beyond surfacing the suggestion (wiring an actual send is a later concern unless trivially present).
  • Work-in-progress / move-to-completion tables (feature 5).

Notes for Define

  • goingLiveThisWeek needs a go-live date source — reconcile lead.endDate vs milestone due dates; pick one definition and note it.
  • Value resolution (accepted quote 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.md

Spec: Expert work queue + action required

  • slug: expert-work-queue
  • personas: Expert
  • touches: packages/services/src/db/services/expert-workbench/work-queue.ts (new), packages/services/src/db/services/expert-workbench/value.ts (new — shared value helper), packages/services/src/db/services/expert-workbench/active-projects.ts (consume shared value helper), packages/services/src/db/services/expert-workbench/index.ts (fill workQueue + actionRequired slots), apps/web/components/dashboard/expert/work-queue-table.tsx (align customers-waiting filter)
  • complexity: standard

Problem

The 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.

Proposed change

  • Build the work-queue read (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.
  • Compose each row (WorkItem shape, unchanged) from the existing layers, reusing the active-projects.ts label helpers (customer/contact/work/CSM) rather than re-deriving them:
    • stagetiming.currentStatus.displayName; sla ← stage target label; dueIntiming.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).
  • Extract one shared value helper (expert-workbench/value.ts) from the valueLabel currently inlined in active-projects.ts — a batched resolver over a set of lead ids (accepted proposal priceformatCurrency, 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").
  • Compute the 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).
  • Sort the work queue by SLA pressure: overdue first, then ascending dueIn ("sorted by due date", matching the table's stated order).
  • Align the client 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.

Acceptance criteria

  • The Work queue lists the signed-in expert's real attention-needing leads (open blocker or SLA pressure), each with stage, SLA target, due/overdue, blocker, blocking party (who), suggested contact, escalation count, CSM and value — no mock rows; lib/mock/expert.ts is not read.
  • Each row's status band, due/overdue and SLA labels derive from the sla-stage-targets timing engine; blocker/who/suggested-contact/escalation come from the work-blockers-escalations model.
  • Lead value is resolved through a single shared helper consumed by both the work-queue read and active-projects.ts (no second value-resolution path).
  • The 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).
  • Leads in terminal statuses, and leads with neither an open blocker nor SLA pressure, are excluded from the queue; the work queue is sorted overdue-first then by ascending due.
  • When the expert has no attention-needing leads, the table renders its existing clean empty state (no mock rows, no error).

Out of scope

  • Defining or editing the SLA target model, the blocker/escalation model, or the milestone model — all consumed from features 2 / 3 (and the customer epic); this feature reads them.
  • Milestone due dates as a go-live source — lead.endDate is the chosen single definition for goingLiveThisWeek; reconciling milestone-derived go-live dates is a later concern.
  • The Work-in-progress / Move-to-completion tables (expert-active-projects, feature 5) and earnings / quality slots — only the workQueue + actionRequired slots are filled here.
  • Wiring an actual send behind the suggestedContact "Respond" action / message compose — the suggestion is surfaced (recipient pre-filled) but the send stays the existing client stub.
  • The raise / escalate / clear blocker buttons and their server actions — already delivered by work-blockers-escalations; this feature only reads open blockers.
  • Realtime updates — the section is server-fetched on load like the rest of the workbench.

Open questions

  • none — the go-live source (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.md

Build notes: expert-work-queue

  • commits: feat: expert-work-queue — wire Work queue + Action required from leads/SLA/blockers

What changed

  • packages/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.tsgetWorkbench 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.

Acceptance criteria status

  • Work queue lists real attention-needing leads with stage/SLA/overdue/blocker/who/suggested contact/escalation/CSM/value — no mock rows (deriveWorkQueue; page reads the envelope, not the mock).
  • Row status band, due/overdue, SLA labels derive from sla-stage-targets; blocker fields from work-blockers-escalations.
  • Lead value resolved through one shared helper (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).
  • Terminal-status leads and no-blocker/no-pressure leads excluded; queue sorted overdue-first then ascending due.
  • Empty queue renders the table's existing clean empty state.

Verify result

  • Mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview, not here. Release reads them back from the PR's check runs.

Notes for review

  • Terminal statuses are matched against a small normalised name set (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.
  • No-SLA-target rows (a blocker on a stage with no configured target) use a dueIn sentinel (9999) so they never read as "due today" and sort last; the Deadline cell shows "No SLA target".
  • The representative-blocker choice (most-escalated, tie newest) is what drives a row's single who/blocker/escalation; the row-level escalated/customersWaiting tallies count rows by that representative so they reconcile with the table filters.

03_release/output/changelog.md

Changelog: expert-work-queue

Audience: 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.md

Experts now see their live work queue

Who 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

Release: expert-work-queue

  • pr: #508 · merged: pending
  • CI: green (Format, Lint, Typecheck, Audit database, Migrate preview database all success; Migrate production database skipped)
  • technical docs: no technical docs impact — the change adds internal service modules within the existing @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.
  • business docs: no business docs impact — the expert workbench Work queue / Action required is not enumerated as a feature-role-matrix row or service-journey step; this wires existing dashboard UI to real data, so no documented capability changes.
  • release notes: both — changelog entry (apps/help/app/changelog/2026-06-23-expert-work-queue/page.mdx) + investor draft, both in this PR
  • deploy: pending — Vercel production deploy gate (web + help) to be confirmed after merge
  • sent: pending — investor update fires after green deploy

Review summary

  • /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-projectsindex 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.
  • By-design notes (documented in build notes, not defects): the representative-blocker drives a row's single who/escalation, and goingLiveThisWeek dedups customer labels.

Acceptance check (vs spec)

  • Work queue lists real attention-needing leads (open blocker or SLA pressure) with all columns, no mock — deriveWorkQueue; page reads the envelope, not lib/mock/expert.ts.
  • Status band / due / SLA from sla-stage-targets; blocker/who/contact/escalation from work-blockers-escalations.
  • Lead value via one shared helper (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).
  • Terminal-status and no-attention leads excluded; sorted overdue-first then ascending due.
  • Empty queue renders the table's existing clean empty state.