expert-workbench-foundationrun.md00_intake/stub.mdThe 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.
/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.@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.user.fullName) and a derived specialisation string
(from position / seniority / top skills or products — no new field unless Define decides one is
warranted).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)./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.specialisation field unless Define concludes derivation is insufficient.customer-project-foundation shape — same server-fetch + envelope
pattern, scoped to expert instead of customer."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.mdThe 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.
/expert/dashboard to a server component scoped to the signed-in expert, mirroring the
sibling vendor/dashboard / customer/satisfaction pattern: resolve the tenant
(getTenantOrNull → redirect("/sign-in") if absent), then resolve the Clerk user to the local
expert record. Keep any interactive bits as client leaf components.@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.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.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./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./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.@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.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.specialisation field — derivation from existing profile data is sufficient.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.02_build/output/notes.mdfeat: expert-workbench-foundation — server-fetched per-expert workbench envelope + headerpackages/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 (getTenantOrNull → redirect("/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./expert/dashboard is a server component rendering the signed-in expert's real name +
derived specialisation; no mock import in the page./sign-in (via getTenantOrNull); signed-in non-expert / no expert record →
/no-role. No mock data shown in either case.@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.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>".gh pr checks. No check is expected to fail.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).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.md03_ship/output/investor-update.mdExperts 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.
03_ship/output/pr.mddb/services; the
package's documented entrypoints, source tree, and architecture are unchanged/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.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./expert/dashboard is a server component rendering the signed-in expert's real name + derived
specialisation; no mock import in the page./sign-in; signed-in non-expert / no expert record → /no-role; no mock data.@sustentus/services/server
(expertWorkbenchService.getWorkbench) exported from the server barrel — the one entry point
the other seven features extend.— metrics), not mock entries.specialisation: position → else seniority + top skill/product → else generic "Expert".gate:merge-approved)