# Plan 2 — Unify Lead↔Project + Routing/Naming > **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`). Follow the execution protocol (`2026-05-29-execution-protocol.md`) — parallel only where lanes are disjoint. **Goal:** Make a *won* kanban lead the same entity as a project — one data model, one rich detail page, clean `/…/projects/:id` routes, and unified `PRJ-2026-###` ids. **Architecture:** Migrate `proj_###`→`PRJ-2026-###` (with inbound FKs), convert the 16 won kanban leads (kl_023–kl_038) into full `projects` records (normalizing field names + back-filling missing project fields) linked back to their kanban lead, route all won jobs to the existing rich `OwnerProjectDetail` under owner/admin/field-agent prefixes, and relink the kanban drawer + project list. Pre-sale leads (kl_001–022, 039–044) stay as leads on the lighter `LeadProjectPage`. **Tech stack:** React 18, Vite 7, mock context store, pnpm. No test harness → verify via `npm run build` + grep/reconciliation + dev-run. **Spec/master:** `docs/superpowers/specs/2026-05-29-demo-data-coherence-design.md`, `docs/superpowers/2026-05-29-MASTER-PLAN.md`. --- ## Lane / ownership map (for safe parallelism) | Task | Lane (exclusive files) | Phase | |------|------------------------|-------| | T1 Id migration | `src/data/mockStore.jsx` | A (sequential) | | T2 Convert won leads → projects | `src/data/mockStore.jsx` | A (sequential, after T1) | | T3 Detail page role access | `src/pages/owner/OwnerProjectDetail.jsx` | B (parallel) | | T4 Routes | `src/App.jsx` | B (parallel) | | T5 Kanban relink | `src/components/kanban/LeadInfoDrawer.jsx`, `src/pages/KanbanPage.jsx` | B (parallel) | | T6 Project list relink | `src/pages/owner/OwnerProjectList.jsx` | B (parallel) | | T7 Verify | (read-only) | C | **Phase A (T1, T2) is strictly sequential** — both edit `mockStore.jsx`. **Phase B (T3–T6) may run in parallel** — disjoint files, and all depend only on Phase A being committed. **T7** runs last. --- ## Field normalization map (T2 — kanban project data → project schema) | Kanban (`KANBAN_PROJECT_DATA` / lead) | Project schema | |----|----| | `estimatedAmount` | `budget` and `approvedBudget` | | `completionPct` | `completionPercentage` | | `workTimeline[]` | `activityTimeline[]` (same item shape) | | milestone `{date}` | milestone `{dueDate}` (keep id/name/status; `assignedTo` = contractor id) | | invoice `{description, paidDate}` | invoice `{submittedBy, datePaid}` (keep id/amount/status/dueDate; submittedBy = contractor/sub id) | | rfi `{openedDate, closedDate}` | rfi `{dateOpened, dateClosed}` | | changeOrder `{requestedBy, date}` | changeOrder `{dateSubmitted}` (keep id/title/amount/status/description) | | `contractorName`/`contractorPhone` | `contractorId` (map "Mike Brennan"→`con_001`; keep others if a different contractor) | | lead `name`/`address`/`phone`/`email` | becomes the project's customer/`address`; keep a `customer` block | **Back-fill (required so the rich tabs aren't broken/empty):** `ownerId` (assign own_001 or own_002), `phase`, `committedCost` (≈ between actualCost and budget), `spent`=`actualCost`, `variancePercent`=round((actualCost−budget)/budget×100,1), `margin`, `commissionType`/`commissionRate`/`commission`, `teamMembers[]` (≥ sales rep p1/p2 + contractor con_001), `subcontractorIds[]`, `riskLog`/`issueLog` (may be `[]`), `paymentSchedule[]`, `openRFIs`/`changeOrderCount`/`pendingInvoiceCount` (computed from the arrays), `budgetBreakdown[]` (Plan-1 invariants: allocated→budget, committed→committedCost, actual→actualCost, committed≥actual), `startDate`/`endDate`, `healthScore`. **Reuse the existing `KANBAN_PROJECT_DATA` numbers; only fill genuine gaps.** --- ## Task 1: Migrate project ids `proj_###` → `PRJ-2026-###` **Files:** `src/data/mockStore.jsx` only. - [ ] **Step 1:** `git grep -n "proj_0" -- src/data/mockStore.jsx` to enumerate every `proj_###` — both the `projects[].id` definitions AND inbound FKs (`projectId: 'proj_###'`) in tasks, schedule, `MOCK_SUBCONTRACTOR_TASKS`, and anywhere else. - [ ] **Step 2:** Replace each `proj_001`→`PRJ-2026-001`, … `proj_012`→`PRJ-2026-012`, **consistently across the whole file** (definitions + all FKs) so every reference still resolves. Use replace_all per id. - [ ] **Step 3 (verify):** `git grep -n "proj_0" -- src/data/mockStore.jsx` → NO matches. Every `projectId` FK now reads `PRJ-2026-###`. - [ ] **Step 4:** `npm run build` → `✓ built`, zero errors. - [ ] **Step 5 (reconcile FKs):** confirm no subcontractor task / schedule entry references a `projectId` that doesn't exist in `projects` (grep the new ids; each FK must match a project id). - [ ] **Step 6:** commit `git commit -m "refactor(data): migrate project ids to PRJ-2026-### (with inbound FKs)"`. ## Task 2: Convert the 16 won kanban leads into project records **Files:** `src/data/mockStore.jsx` only. **Runs after T1.** - [ ] **Step 1:** Read `KANBAN_LEADS_INITIAL` for kl_023–kl_038 and their `KANBAN_PROJECT_DATA` entries. - [ ] **Step 2:** For each of the 16, append a new object to the `projects` array with id `PRJ-2026-013`…`PRJ-2026-028`, mapping fields per the Normalization Map and back-filling per the Back-fill list. Add `sourceLeadId: ''` to the project, and `projectId: ''` to the corresponding kanban lead (so the board links to the project). Keep the kanban lead's `columnId` (it still shows in Signed/Progress/Complete). - [ ] **Step 3:** Assign realistic `ownerId` (split across own_001/own_002), `teamMembers` (canonical people), and a `contractorId` (con_001 unless the data names another). Vary `phase`/`status` to match the lead's stage (signed→Scope Approved/Contract Signed, progress→Work In Progress, complete→Completed). - [ ] **Step 4 (reconcile):** every new project satisfies Plan-1 financial invariants (Σbreakdown columns == totals; committed≥actual; spent==actualCost; variancePercent sign correct). Write a throwaway node check to /tmp if helpful; do NOT commit it. - [ ] **Step 5:** `npm run build` → `✓ built`, zero errors. - [ ] **Step 6:** commit `git commit -m "feat(data): convert won kanban leads (kl_023-038) into unified project records"`. ## Task 3: Let owner/admin/field-agent view a project (relax owner-only lookup) **Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. **Phase B (parallel).** - [ ] **Step 1:** Find the lookup `projects.find(p => p.id === projectId && p.ownerId === user?.id)` (~line 296). Relax it so ADMIN/OWNER/FIELD_AGENT can view: match by id, and only enforce ownerId when `user.role === 'OWNER'` (owners see their own; admin/field-agent see any). Keep the not-found fallback. - [ ] **Step 2:** `npm run build` → `✓ built`. - [ ] **Step 3:** commit `git commit -m "fix(projects): allow admin/field-agent to open project detail, not just owner"`. ## Task 4: Add `/…/projects/:projectId` routes for all three role prefixes **Files:** `src/App.jsx` only. **Phase B (parallel).** - [ ] **Step 1:** Add routes rendering `OwnerProjectDetail`: `/admin/projects/:projectId` (ADMIN, OWNER) and `/emp/fa/projects/:projectId` (FIELD_AGENT, ADMIN, OWNER), mirroring the existing `/owner/projects/:projectId`. (Optionally also `/admin/projects` and `/emp/fa/projects` → `OwnerProjectList`.) - [ ] **Step 2:** `npm run build` → `✓ built`. - [ ] **Step 3:** commit `git commit -m "feat(routes): add /admin and /emp/fa projects routes for the unified detail page"`. ## Task 5: Relink the kanban drawer so won leads open the project page **Files:** `src/components/kanban/LeadInfoDrawer.jsx`, `src/pages/KanbanPage.jsx`. **Phase B (parallel).** - [ ] **Step 1:** In `LeadInfoDrawer.jsx` (~line 302) the "More Details" button does `navigate(\`${basePath}/leads/${lead.id}\`)`. Change it so: if the lead has a `projectId` (won), navigate to `\`${basePath}/projects/${lead.projectId}\``; else keep `\`${basePath}/leads/${lead.id}\``. - [ ] **Step 2:** Check `KanbanPage.jsx` for any other lead→detail navigation and apply the same conditional. - [ ] **Step 3:** `npm run build` → `✓ built`. - [ ] **Step 4:** commit `git commit -m "feat(kanban): won leads open the unified project page; pre-sale leads stay on lead page"`. ## Task 6: Relink the project list's pipeline entries **Files:** `src/pages/owner/OwnerProjectList.jsx` only. **Phase B (parallel).** - [ ] **Step 1:** This page lists both construction projects (→ `/owner/projects/:id`) and pipeline leads (→ `/owner/leads/:id`, lines ~499/565). For pipeline leads that now have a `projectId` (won), link to `/owner/projects/:projectId` instead. Pre-sale leads keep `/owner/leads/:id`. - [ ] **Step 2:** `npm run build` → `✓ built`. - [ ] **Step 3:** commit `git commit -m "feat(projects): project list links won pipeline jobs to the unified project page"`. ## Task 7: Verify (read-only) - [ ] `git grep -n "proj_0" -- src` → none (ids fully migrated). - [ ] Every kanban lead kl_023–038 has a `projectId` resolving to a real project; every new project has a `sourceLeadId` resolving to a kanban lead. - [ ] All projects (old + new) satisfy Plan-1 financial invariants (dispatch a read-only reviewer to re-check sums, as in Plan 1). - [ ] `npm run build` clean. - [ ] Dev run: from the kanban board, opening a Signed/In-Progress/Complete lead's "More Details" lands on `/…/projects/PRJ-2026-###` showing the full rich detail (10 tabs); opening a pre-sale lead still shows the lighter lead page. Owner, admin, and field-agent can each open a project. --- ## Self-review checklist - [ ] No `proj_###` left anywhere in `src`. - [ ] 16 new projects appended; ids `PRJ-2026-013..028`; each linked to its kanban lead both ways. - [ ] Financial invariants hold for all ~28 projects. - [ ] Build clean; won leads route to project page; pre-sale leads route to lead page; all three roles can view. ## Notes for Plan 3 After this lands, Plan 3 targets the unified model: kanban depth (realistic details on the pre-sale leads), the 10 curated threaded demo-case scenarios (chosen from the ~28 projects), and computed `selectors.js` dashboards. The lighter `LeadProjectPage` is now pre-sale-only; converging it fully into one component is deferred (Phase 4 adds the lead-side extras — stage mover, hail history, progression — onto the unified page).