Skip to Content

← All archived runs

Run: expert-workbench-foundation

run.md

Run: expert-workbench-foundation

  • issue: #459 # canonical home of the spec + state (labels, comments)
  • branch: claude/sleepy-ptolemy-pouhob
  • pr: #460

00_intake/stub.md

Stub: Expert workbench — foundation + header

  • feature-slug: expert-workbench-foundation
  • epic: expert-dashboard-data
  • personas: Expert
  • initiative: Build the Bridge / objective: Q2 2026 Objective 3 — Validate Technical Infrastructure & Payout Flow
  • depends-on: none
  • sequence: 1 of 8

Problem

The whole /expert/dashboard page is a client component reading a single hard-coded workbenchData object (apps/web/lib/mock/expert.ts). There is no server fetch, no scoping to the signed-in expert, and the header (expertName, specialisation) is invented. Every other section needs a real per-expert data envelope to hang off, so the foundation has to exist first.

Proposed change

  • Convert /expert/dashboard to a server-fetched page scoped to the signed-in expert (resolve the Clerk user → expert via userService.findByClerkUserId, like the existing bid-pool page), within the current tenant.
  • Stand up a single read-service envelope in @sustentus/services/server that returns the expert's workbench data; this round it populates the header and leaves typed-but-empty slots the later features fill, so each section ships independently.
  • Wire the header: real expert name (user.fullName) and a derived specialisation string (from position / seniority / top skills or products — no new field unless Define decides one is warranted).
  • Keep the existing apps/web/components/dashboard/expert/* components; feed them real data instead of the mock (sections not yet wired keep rendering empty/loading states, not mock entries).

Acceptance criteria (rough)

  • /expert/dashboard renders for the signed-in expert with their real name + a derived specialisation; no lib/mock/expert.ts import remains in the page.
  • A logged-out / non-expert user is handled (redirect or empty state), not shown mock data.
  • The read-service envelope is callable per-expert/per-tenant and is the single entry point the other seven features extend.
  • Sections without real data yet render a clean empty/loading state.

Out of scope (this feature)

  • Any of the six section bodies (work queue, bid pool, WIP, move-to-completion, earnings, quality) — those are their own features that extend this envelope.
  • A new persisted specialisation field unless Define concludes derivation is insufficient.

Notes for Define

  • Mirror the customer sibling customer-project-foundation shape — same server-fetch + envelope pattern, scoped to expert instead of customer.
  • The page is currently "use client"; moving to a server component (with client leaf components for any interactivity) is the main structural change.
  • touches: apps/web/app/(app)/expert/dashboard/page.tsx, apps/web/lib/mock/expert.ts (remove), packages/services/src/server (new expert-workbench read service), packages/services/src/db/services/users. </content> </invoke>

01_define/output/spec.md

Spec: Expert workbench — foundation + header

  • slug: expert-workbench-foundation
  • issue: #459
  • personas: Expert
  • touches: apps/web/app/(app)/expert/dashboard/page.tsx, apps/web/lib/mock/expert.ts (remove from page), packages/services/src/db/services/expert-workbench (new read service + instance), packages/services/src/db/services/index.ts, packages/services/src/db/services/users
  • complexity: standard

Problem

The whole /expert/dashboard page is a single "use client" component reading one hard-coded workbenchData object from apps/web/lib/mock/expert.ts. There is no server fetch, no scoping to the signed-in expert, and the header (expertName, specialisation) is invented. This is the first of eight features in the expert-dashboard-data epic (Build the Bridge / Q2 2026 Objective 3 — Validate Technical Infrastructure & Payout Flow): the objective is to prove the expert can sign in and see real, per-expert data flow end to end. Every later section (work queue, bid pool, WIP, move-to-completion, earnings, quality) needs a real per-expert data envelope to hang off, so that foundation — a server-fetched page scoped to the signed-in expert, plus a single read-service entry point — has to exist first.

Proposed change

  • Convert /expert/dashboard to a server component scoped to the signed-in expert, mirroring the sibling vendor/dashboard / customer/satisfaction pattern: resolve the tenant (getTenantOrNullredirect("/sign-in") if absent), then resolve the Clerk user to the local expert record. Keep any interactive bits as client leaf components.
  • Stand up a single expert-workbench read service in @sustentus/services/server (e.g. expertWorkbenchService.getWorkbench(tenantId, expertUserId)) that returns the expert's workbench envelope. This round it populates the header and returns typed-but-empty slots for the six sections, so each later feature fills its own slot independently. This service is the single entry point the other seven features extend.
  • Wire the header from real data: expert name from user.fullName, and a derived specialisation string — prefer the expert's position if set; otherwise compose from seniority + top populated skill/product name(s) (e.g. Senior · Salesforce, Data migration); fall back to a generic label (e.g. Expert) when the profile is sparse. No new persisted field.
  • Feed the existing apps/web/components/dashboard/expert/* components from the envelope instead of the mock. Sections whose real data isn't wired yet render their empty/loading state off typed-empty slots — never mock entries. The page no longer imports lib/mock/expert.ts.

Acceptance criteria

  • /expert/dashboard is a server component that renders for the signed-in expert with their real fullName and a derived specialisation; no lib/mock/expert.ts import remains in the page.
  • A logged-out visitor is redirected to /sign-in; a signed-in user with no expert record (wrong role) is redirected to their own area (/no-role) — neither is shown mock data.
  • A single read service in @sustentus/services/server returns the workbench envelope for a given (tenantId, expertUserId), exported from the services server barrel, and is the one entry point the other seven features extend.
  • The envelope populates the header fields and exposes typed-but-empty slots for the six sections (work queue, bid pool, WIP, move-to-completion, earnings, quality).
  • Sections without real data yet render a clean empty/loading state from those typed-empty slots, not mock entries.
  • specialisation derivation follows: position → else seniority + top skill/product name(s) → else generic fallback; verifiable for a sparse profile (falls back) and a rich one.

Out of scope

  • Any of the six section bodies (work queue, bid pool, WIP, move-to-completion, earnings, quality) — each is its own downstream feature that extends this envelope.
  • A new persisted specialisation field — derivation from existing profile data is sufficient.
  • Removing/deleting apps/web/lib/mock/expert.ts outright — only the page's dependence on it is removed this round; the file can be retired once the last section is wired.

Open questions

  • none

02_build/output/notes.md

Build notes: expert-workbench-foundation

  • branch: claude/sleepy-ptolemy-pouhob
  • commits:
    • feat: expert-workbench-foundation — server-fetched per-expert workbench envelope + header

What changed

  • packages/services/src/db/services/expert-workbench/index.ts (new): ExpertWorkbenchService with getWorkbench(tenantId, expertUserId). Loads the expert (role: "expert", tenant-scoped), derives the header, and returns the envelope with typed-but-empty section slots. Defines the canonical envelope + slot types (ExpertWorkbench, ExpertWorkbench*Item, etc.) that the other seven expert-dashboard features extend.
  • packages/services/src/db/services/expert-workbench/instance.ts (new): expertWorkbenchService singleton, matching the per-service instance pattern.
  • packages/services/src/db/services/index.ts: export the instance + envelope types so they flow through @sustentus/services/server.
  • apps/web/app/(app)/expert/dashboard/page.tsx: converted from a "use client" mock-reading component to a server component. Resolves tenant (getTenantOrNullredirect("/sign-in")), resolves the Clerk user to the local app user (resolveAppUserForTenant); a non-expert or missing record → redirect("/no-role"). Feeds the existing client section components from the envelope. No lib/mock/expert.ts import remains.

Acceptance criteria status

  • /expert/dashboard is a server component rendering the signed-in expert's real name + derived specialisation; no mock import in the page.
  • Logged-out → /sign-in (via getTenantOrNull); signed-in non-expert / no expert record → /no-role. No mock data shown in either case.
  • Single read service in @sustentus/services/server (expertWorkbenchService.getWorkbench(tenantId, expertUserId)) returns the envelope, exported from the services server barrel — the one entry point the other seven features extend.
  • Envelope populates header fields and exposes typed-but-empty slots for all six sections (work queue, bid pool, WIP, move-to-completion, earnings, quality).
  • Sections without data render clean empty states off the typed-empty slots (empty tables with zero-count badges; earnings/quality show "—"/0), not mock entries.
  • specialisation derivation: position → else seniority + top skill/product name(s) → else generic "Expert". Sparse profile falls back to "Expert"; a profile with seniority + skills yields e.g. "Senior · <skill>, <skill>".

Verify result

  • Mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview, not here. Ship reads them back from gh pr checks. No check is expected to fail.

Notes for review

  • Envelope slot types are deliberately defined in the service (not imported from apps/web), so @sustentus/services stays app-independent and the types are the canonical contract downstream features fill. They mirror the existing dashboard component prop shapes, so the page passes them straight through (empty arrays this round).
  • The header name mirrors the User schema's fullName virtual (capitalise + trim), reimplemented in the service because .lean() reads drop virtuals; falls back to email if name is blank.
  • apps/web/lib/mock/expert.ts is intentionally left in place (still referenced by the dashboards app's own copy); only the web page's dependence on it was removed, per spec Out of scope.

03_ship/output/changelog.md

Your workbench is now yours

  • When you sign in, your go live workbench shows your real name and a specialisation drawn from your own profile, in place of the sample details shown before.
  • Your workbench is scoped to your account, so you only ever see your own view.

03_ship/output/investor-update.md

The expert workbench now runs on real, per-expert data

Experts now open a workbench that is genuinely theirs. When an expert signs in, the platform resolves their identity and serves a workbench scoped to their own account — their real name and a specialisation derived from their profile — in place of the placeholder content that stood in before. It is a quiet change on the surface, but it lays the real data path every expert-facing screen now builds on, and it is a concrete step toward experts delivering live transactions end to end.

  • Advances Build the Bridge: the groundwork for our Validate Technical Infrastructure & Payout Flow objective this quarter — the path experts will use to pick up and deliver real work.
  • Every expert sees only their own workbench, scoped to their account and organisation — the access model the rest of the expert experience extends.
  • Establishes the single data entry point the remaining expert-dashboard features plug into, so each ships independently on a proven foundation rather than re-inventing it.

03_ship/output/pr.md

Ship: expert-workbench-foundation

  • PR: #460 — https://github.com/sustentus/sustentus/pull/460
  • branch: claude/sleepy-ptolemy-pouhob
  • CI: format pass · lint pass · typecheck pass · preview build pass (build commit e5c3900)
  • technical docs: no technical docs impact — adds one read service under db/services; the package's documented entrypoints, source tree, and architecture are unchanged
  • business docs: no business docs impact — rewires the expert dashboard's data source (mock → real per-expert); adds no capability/permission row to the feature-role matrices and changes no documented service-journey/platform-overview behaviour
  • release notes: both — investor draft + changelog entry included in this PR

Review summary

  • /code-review (high effort) on the feature files (expert-workbench/index.ts, instance.ts, expert/dashboard/page.tsx) → no findings. Verified: role: "expert" discriminator filter + string tenantId cast match the established userService convention; skillService/productService findById(tenantId, id) signatures + .name return shape match; client-component prop types are structurally identical to the service envelope types (literal unions included); redirect() narrowing correct; soft-delete plugin auto-excludes deleted experts. — resolved, no changes needed.
  • Branch scope: clean. Verified git diff origin/main...HEAD after fetching fresh — PR #460 contains only this feature (3 code files, the changelog entry, the services barrel export, the stub moved to _done/, and the run paper trail). The prior admin PRs (#443–#458) are already on main; an earlier read against a stale local main ref made them look in-scope — they are not.

Acceptance check (vs spec)

  • /expert/dashboard is a server component rendering the signed-in expert's real name + derived specialisation; no mock import in the page.
  • Logged-out → /sign-in; signed-in non-expert / no expert record → /no-role; no mock data.
  • Single read service in @sustentus/services/server (expertWorkbenchService.getWorkbench) exported from the server barrel — the one entry point the other seven features extend.
  • Envelope populates the header and exposes typed-but-empty slots for all six sections.
  • Empty slots render clean empty states (zero-count tables, metrics), not mock entries.
  • specialisation: position → else seniority + top skill/product → else generic "Expert".

Merge & deploy

  • merged: no — awaiting explicit human merge approval (gate:merge-approved)
  • deploy: pending merge