{"slug":"architecture/PROJECTION_CONTEXT_REFACTOR","title":"Projection Context Refactor — Implementation Note","section":"architecture","sectionLabel":"Architecture","status":"unclassified","type":"document","path":"architecture/PROJECTION_CONTEXT_REFACTOR.mdx","bytes":4921,"description":null,"updated":null,"source":"integratewise/Integratewise-docs-wiki","content":"\n> **Tier:** B (Implementation plan). Audit: `CURRENT_CONTEXT_FLOW.md`. Canon: `PROJECTION_MODEL.md` §5.\n> **Date:** June 12, 2026 | **Status:** PLANNED — not started. Gated on `apps/web` WIP ownership.\n> **Scope:** Frontend context-consistency fix. No architecture change; the backend is already correct.\n\n---\n\n## The rule (single sentence)\n\n```text\nProjectionContext is authoritative.\nComponents may consume it. Components may NOT recompute it.\n```\n\n---\n\n## Current (dual authority — the bug)\n\n```text\nGateway /api/v1/workspace/profile  ──▶  domain prop  ──▶  Header · Workbench · Content\n                                   ╲\ntenant_users / profiles  ──▶ useProjection().user.department (default \"bizops\") ──▶ Left Nav\n\nTwo authorities. Whichever resolves a given surface wins → Header=CUSTOMER_SUCCESS, Nav=BizOps.\n```\n\n## Target (single authority)\n\n```text\nGateway /api/v1/workspace/profile\n        ↓\n   ProjectionContext   (one object: user · tenant · department · domain · capabilities · entitlements)\n        ↓\n   Header → Nav → Workbench → Twin     (all read the same object; none recompute)\n```\n\n---\n\n## Files (in change order)\n\n| #   | File                                                           | Change                                                                                                                                                                          | Risk                        |\n| --- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |\n| 1   | `apps/web/src/lib/projection/use-projection.ts`                | Seed `UserContext.department`/domain from the gateway profile value (the same one AppShell already has). Remove the independent `profiles` re-query and the `\"bizops\"` default. | Med — central hook          |\n| 2   | `apps/web/src/components/l1/workspace/workspace-shell-new.tsx` | Derive department/domain from the single projection context. Stop mixing a `domain` prop with a separately-resolved `useProjection()` department.                               | **High — foreign WIP here** |\n| 3   | `apps/web/src/components/l1/workspace/data-density-scorer.ts`  | Remove `\"CUSTOMER_SUCCESS\"` default; require explicit domain from caller.                                                                                                       | Low                         |\n| 4   | `apps/web/src/components/l1/l1-module-content.tsx`             | Remove hardcoded `const domain = \"unified\"`; take domain from context.                                                                                                          | Low                         |\n| 5   | `services/gateway/src/twin-handoff.ts`                         | `\"bizops\"` fallback only when truly unknown; prefer resolved department.                                                                                                        | Low                         |\n| 6   | `services/gateway/src/customer-zero.ts`                        | Keep `\"bizops\"` (Customer Zero is genuinely BizOps) — document, don't change.                                                                                                   | None                        |\n\nMap consolidation (follow-up, not blocking the bug): collapse `DOMAIN_TO_CTX` (content-router) and\nthe shell `workbenchConfig` keys onto the shared `packages/types` resolver\n(`DOMAIN_SPINE_CONFIG` / `DEPARTMENT_TO_CONFIG_KEY`).\n\n---\n\n## Acceptance check\n\n1. A user with `department = CUSTOMER_SUCCESS` sees **Account Success** in header, nav, and\n   workbench — no surface shows Business Operations.\n2. A user with unset department gets **one** deterministic result (no surface-dependent default).\n3. `grep` shows no component computing department/domain except the single projection resolver.\n4. Playwright onboarding/workspace E2E (live-verification) passes for a CS persona.\n\n---\n\n## Preconditions (do NOT start until these are true)\n\n- `apps/web/src/components/l1/workspace/workspace-shell-new.tsx` — **foreign WIP committed or discarded**\n- `apps/web/src/AppShell.tsx` — foreign WIP committed or discarded\n- `apps/web/src/utils/spine/gateway-adapter.ts` — foreign WIP committed or discarded\n\nRationale: files #1–#2 overlap with in-flight uncommitted work from other agents. Editing on top of\nthat risks clobbering it. The diagnosis is complete; this is a controlled implementation against a\nclean working tree, not more discovery.\n\n---\n\n_When the working tree is clean, this is a small, surgical change — the canonical behavior already\nexists in the gateway; the work is making the client consume it once._\n"}