10 KiB
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.jsxto enumerate everyproj_###— both theprojects[].iddefinitions 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. EveryprojectIdFK now readsPRJ-2026-###. - Step 4:
npm run build→✓ built, zero errors. - Step 5 (reconcile FKs): confirm no subcontractor task / schedule entry references a
projectIdthat doesn't exist inprojects(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_INITIALfor kl_023–kl_038 and theirKANBAN_PROJECT_DATAentries. - Step 2: For each of the 16, append a new object to the
projectsarray with idPRJ-2026-013…PRJ-2026-028, mapping fields per the Normalization Map and back-filling per the Back-fill list. AddsourceLeadId: '<kl_id>'to the project, andprojectId: '<PRJ id>'to the corresponding kanban lead (so the board links to the project). Keep the kanban lead'scolumnId(it still shows in Signed/Progress/Complete). - Step 3: Assign realistic
ownerId(split across own_001/own_002),teamMembers(canonical people), and acontractorId(con_001 unless the data names another). Varyphase/statusto 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 whenuser.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 the unified /projects/:projectId route (Option B)
Files: src/App.jsx only. Phase B (parallel).
We adopt the final Option-B scheme for the projects route now — a single, role-prefix-free URL — so won leads and projects share one page. The attribute-based access guard arrives in Phase 8; for the interim, gate with a coarse allowedRoles.
- Step 1: Add a route
/projects/:projectId→OwnerProjectDetail, gatedallowedRoles={['OWNER','ADMIN','FIELD_AGENT']}(interim coarse guard — Phase 8 replaces it with the ABAC policy guard). Leave the existing/owner/projects/:projectIdroute in place for now (Phase 8 removes the duplicate). Do NOT add/admin/projects//emp/fa/projectsrole-prefixed copies. - Step 2:
npm run build→✓ built. - Step 3: commit
git commit -m "feat(routes): add unified /projects/:id route for the shared 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 doesnavigate(\${basePath}/leads/${lead.id}`). Change it so: if the lead has aprojectId(won), navigate to the unified`/projects/${lead.projectId}`(no basePath); else keep`${basePath}/leads/${lead.id}`` (pre-sale leads stay role-prefixed until Phase 8). - Step 2: Check
KanbanPage.jsxfor 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 and pipeline leads (links ~lines 280/366 and 499/565). Point ALL project links AND won pipeline leads (those with a
projectId) at the unified/projects/:id. 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
projectIdresolving to a real project; every new project has asourceLeadIdresolving 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 buildclean.- 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 insrc. - 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).