diff --git a/docs/superpowers/plans/2026-05-29-plan2-unify-routing.md b/docs/superpowers/plans/2026-05-29-plan2-unify-routing.md new file mode 100644 index 0000000..0fc6ac6 --- /dev/null +++ b/docs/superpowers/plans/2026-05-29-plan2-unify-routing.md @@ -0,0 +1,121 @@ +# 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).