workspace-shellrun.md00_intake/stub.mdThere is no single page that answers "where is this lead and what can I do next?" — the lead is scattered
across service-leads/[id], projects/[id], expert/bids/[id] and several satellite pages, each
re-rendering the same header/journey/activity. Before any data or actions can be unified, the platform
needs the shell: one new page whose layout is stable and persona-aware. This first feature lands the
design (ported from apps/demo/workspace) so the team can see and agree the surface early.
Add a brand-new, additive route in apps/web (e.g. app/(app)/workspace/[id]) — leaving
service-leads, projects and every existing detail page untouched and functional. Port the design spine
from apps/demo/workspace as real apps/web components (server-by-default, @sustentus/ui primitives):
workflows.json order), with the lead's current stage
highlighted. Read the real status via the workflow engine accessors; not the demo's click-to-preview.apps/web/lib/auth.ts), never from the URL.Sections render as scaffolding (titles + empty/placeholder states); real data is stub 2, actions are stubs 4–6. The page should render for a single real lead id at its true status.
…/workspace/[id] for a valid lead id; existing routes are unchanged and still work.service-leads/[id], projects/[id], or any existing page.apps/demo/components/workspace/* + apps/demo/lib/workspace/{journey,views}.ts. Port
the structure/look; do not import from apps/demo.@sustentus/services workflow accessors
(packages/services/src/db/workflows/) — never a local status map (avoids the status-name drift the brief warns of).apps/web/app/(app)/workspace/[id]/**, apps/web/components/workspace/**, @sustentus/ui.route-policies.ts.01_define/output/spec.mdA lead has no single home. Today it is scattered across service-leads/[id], projects/[id],
expert/bids/[id] and several satellite pages, each re-rendering its own header, journey and activity —
so no one page answers "where is this lead and what can I do next?". This is the first feature of the
lead-workspace epic (Refine the Bridge / Q2 2026 Objective 3 — Validate Technical Infrastructure &
Payout Flow: leads completing end-to-end and lead-to-payout cycle time). Before any data or actions can
be unified onto one page, the platform needs the shell: one stable, persona-aware layout. This feature
lands the design — ported from apps/demo/workspace as real apps/web components — read-only, so the
team can see and agree the surface early before data (stub 2) and action levers (stubs 4–6) are wired on.
Add a brand-new, additive route at apps/web/app/(app)/workspace/[id] — leaving service-leads/[id],
projects/[id], expert/bids/[id] and every other detail page untouched and fully functional. Port the
design spine from apps/demo/workspace as real, server-by-default apps/web components built on
@sustentus/ui primitives (structure/look ported; nothing imported from apps/demo):
resolveStatusLabel(status, persona)), never a local map.@sustentus/services
(getStatusesByWorkflow("lead"), declared order), with the lead's real current stage highlighted via
getStatus(lead.status). Read-only — not the demo's client-side click-to-preview.apps/web/lib/auth.ts getRole()), never from the URL.leadService.findById) for the header
identity fields and status only; satellite data is stub 2. An unknown/invalid id renders notFound().service-leads/[id] and projects/[id] detail pages each gain a
top-right "Go to workspace" link to /workspace/<id> (both pages already load the same lead by the
same id). This is the only change to those pages.The page renders for a single real lead id at its true status. Sections render as scaffolding (titles + empty/placeholder states); the levers rail and the header CTA slot are empty placeholders.
Decisions settled this Define: route segment is workspace/[id] (matches the demo source and the epic
breakdown); access is open to all six personas via a route-policies.ts entry for /workspace. (The
apps/web proxy middleware is deny-by-default — an unlisted (app) route redirects to the role home —
so a policy entry is required for the route to be reachable at all; persona is still resolved from auth in
the page via getRole().)
/workspace/<id> for a valid lead id; expert/bids/[id] is unchanged, and
service-leads/[id] / projects/[id] keep working with only a "Go to workspace" link added./workspace/<id> (the route is allowed for every role in route-policies.ts).service-leads/[id] and projects/[id] detail pages each show a top-right "Go to workspace"
link to /workspace/<id>.@sustentus/ui primitives.workflows.json order via getStatusesByWorkflow("lead")
and highlights the lead's real current stage resolved through getStatus(lead.status) — not a
hardcoded or locally-duplicated status map.getRole()); the header status chip uses
resolveStatusLabel(status, persona).notFound()).@sustentus/services workflow accessors.service-leads/[id], projects/[id], expert/bids/[id],
proposals/[id], the finance pages or the BRD page. The only change to a detail page is the
"Go to workspace" link added to service-leads/[id] and projects/[id].role === … checks or pages; importing anything from apps/demo.workspace/[id]) and the gating approach (page-level auth only) were the two
items the stub deferred to Define, and both are now decided above.Per the epic breakdown's recorded note, this Define read apps/demo/components/workspace/** and
apps/demo/lib/workspace/** as the design source (code, not docs) — beyond the docs-only intake band but
necessary because the design to port is code, not prose.
02_build/output/notes.mdfeat: workspace-shell — lead workspace route + design spineapps/web/app/(app)/workspace/[id]/page.tsx: new additive route. Resolves persona via
getRole() and tenant via getTenantOrNull() (redirect to /sign-in if missing), loads the lead
with leadService.findById, and renders notFound() for a missing or invalid id (CastError /
BSONError caught → not-found). Server component.apps/web/components/workspace/workspace-view.tsx: composes the shell — header, journey strip,
section column and levers-rail frame — in the demo's two-column layout. Derives the status name
from lead.status and the per-persona chip label via statusService.resolveLabel(name, persona).apps/web/components/workspace/workspace-header.tsx: request ref (#<requestId>), service,
customer and the per-persona status chip, plus the next-best-action CTA slot — an empty placeholder.apps/web/components/workspace/journey-strip.tsx: renders the lead spine from
statusService.getDisplayOrder("lead") (= getStatusesByWorkflow("lead")) in workflows.json order,
highlighting the lead's real current status. The spine is built by walking positive transitions from
the first status; off-spine statuses (e.g. qualified_out) render as a drop-off chip. No local
status map; read-only (nothing clickable).apps/web/components/workspace/section-registry.ts + section-frame.tsx: the section list and the
card frame each renders — title + empty placeholder. Per-persona section lists and real data are
later features, so the shell shows the full set as scaffolding.apps/web/components/workspace/levers-rail.tsx: an empty placeholder rail (no levers yet).apps/web/lib/route-policies.ts: added a /workspace policy allowing all six roles. The apps/web
proxy middleware (proxy.ts) is deny-by-default — an unlisted (app) route redirects to the
role home — so this entry is what makes the route reachable for every persona. (The earlier
"page-level auth only" note was a misread of the middleware and is corrected here.)apps/web/components/workspace/workspace-link-button.tsx: a shared "Go to workspace" link button.apps/web/app/(app)/service-leads/[id]/page.tsx and apps/web/app/(app)/projects/[id]/page.tsx:
wrapped the existing grid in a flex column and added a top-right "Go to workspace" link. Both pages
already load the same lead by the same id, so the link target is /workspace/<id>; no other
behaviour on those pages changed./workspace/<id> for a valid lead id; expert/bids/[id] is unchanged,
and service-leads/[id] / projects/[id] keep working with only a "Go to workspace" link added./workspace/<id> — route-policies.ts allows every role (the proxy
middleware is deny-by-default, so the entry is required).service-leads/[id] and projects/[id] detail pages each show a top-right "Go to workspace"
link to /workspace/<id>.@sustentus/ui primitives.workflows.json order via getStatusesByWorkflow("lead")
and highlights the lead's real current stage — not a hardcoded or locally-duplicated status map.getRole()); the header status chip uses
resolveStatusLabel(status, persona) (via statusService.resolveLabel).notFound()).@sustentus/services workflow accessors (statusService).workflows.json order changes and keeps qualified_out (and any
future drop-off) off the linear spine without naming it.lead.status (a StatusView with .name); the per-persona
chip label comes only from statusService.resolveLabel.requestId, service.name (falling back to title), and a customer
display-name helper (company name → full name → email).03_release/output/investor-update.mdWho it's for: All six roles — customer, expert, CSM, SDM, admin, vendor. What shipped: A new per-lead workspace page, reachable from the lead and project views, that shows a lead's real journey stage on one persona-aware screen. Why it matters: It's the design foundation for bringing a lead's whole journey onto a single view — groundwork toward a shorter lead-to-payout cycle (Refine the Bridge · Q2 Objective 3).
Dig deeper: <merged-PR URL> · <no changelog — early-preview shell>
03_release/output/release.md/code-review medium (standard complexity), two finder angles + manual verification on the git diff 771d676..HEAD apps/web scope:
!onSpine.has(cursor) + if (!status) break) and degrades gracefully when the lead's status is off-spine/unknown (no highlight, no crash); the service-leads/[id] and projects/[id] JSX restructure keeps div nesting balanced (confirmed by the green Vercel build) with every prior child in its original column. Null-safety on lead.status?.name and the customer/service fallbacks is sound.Requirements (BRD) / Proposals were false positives (those are sentence case — first word capitalised, BRD an acronym); the customerName/loadLead helper notes were "no rule broken" and loadLead deliberately mirrors the existing loadLeadForDetail pattern in the service-leads page./workspace/<id> for a valid lead id; existing detail pages keep working — additive route; only a link added to service-leads/projects./workspace/<id> — route-policies.ts /workspace policy lists every role (middleware is deny-by-default).service-leads/[id] and projects/[id] show a top-right "Go to workspace" link — shared WorkspaceLinkButton.@sustentus/ui.getStatusesByWorkflow("lead") order and highlights the lead's real current stage — no local status map.getRole(); status chip via statusService.resolveLabel(status, persona).notFound().@sustentus/services accessors.p-6 on the workspace container) landed in this PR so the shell isn't flush to the screen edges.