From bf796e83ea25647fd6554df4b0455300b4531523 Mon Sep 17 00:00:00 2001 From: Satyam Rastogi Date: Fri, 29 May 2026 17:07:30 +0530 Subject: [PATCH] docs: add Plan 4 (lifecycle state machine + inspections + estimate versions) --- docs/superpowers/2026-05-29-MASTER-PLAN.md | 3 +- ...9-plan4-lifecycle-inspections-estimates.md | 257 ++++++++++++++++++ 2 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-05-29-plan4-lifecycle-inspections-estimates.md diff --git a/docs/superpowers/2026-05-29-MASTER-PLAN.md b/docs/superpowers/2026-05-29-MASTER-PLAN.md index 3e6ee8e..c70cb92 100644 --- a/docs/superpowers/2026-05-29-MASTER-PLAN.md +++ b/docs/superpowers/2026-05-29-MASTER-PLAN.md @@ -509,7 +509,8 @@ Each plan = one phase, produces working/verifiable software on its own, and is e ### Plan 4 — Project lifecycle + inspections + estimates - **What:** make the existing PROJECT PROGRESSION bar an **interactive state machine** with progress % tied to the stage (§9); the two-touchpoint **inspection model** with an issues/rework loop + session photo/note uploads (§11, A.5); the **claimed-vs-verified scope-variance** badge; and the **estimate-versions** section (v1 claim → v2 verified → signed; full document view). - **Why:** the static progression bar already disagrees with completion % (a visible defect); interactivity + the inspection loop + scope-variance is the core sales narrative (a professional inspection turns a $200 self-report into an insurance-backed job). -- **How:** add the lifecycle/inspection/estimate fields to projects in `mockStore.jsx` (sequential) per Appendix A; add store mutators (advance stage, add inspection/issue, upload photo→object URL, add estimate version); build the UI in `OwnerProjectDetail.jsx` (progression control, inspection panel + issues list, scope-variance badge, estimate-versions tab) — these UI pieces in the one file run sequentially, but are independent of the data tasks. Verify: advancing a stage updates %, logging an issue loops to rework then re-inspection, estimate versions render. +- **How:** add the lifecycle/inspection/estimate fields to projects in `mockStore.jsx` (sequential) per Appendix A; add store mutators (advance stage, add inspection/issue, upload photo→object URL, add estimate version); build the UI in `OwnerProjectDetail.jsx` (progression control, inspection panel + issues list, scope-variance badge, estimate-versions tab) — these UI pieces in the one file run sequentially, but are independent of the data tasks. A new pure `src/data/lifecycle.js` (stage set, stage→% map, next/prev helpers) is the one parallel-safe carve-out; everything else is concentrated in `mockStore.jsx` + `OwnerProjectDetail.jsx`, so Plan 4 is mostly sequential. Verify: advancing a stage updates %, logging an issue loops to rework then re-inspection, estimate versions render. +- **Plan file:** `docs/superpowers/plans/2026-05-29-plan4-lifecycle-inspections-estimates.md`. ### Plan 5 — Subcontractor task state machine - **What:** the §10 stage machine (Not Assigned → Assigned → Pre-Work Inspection → Work In Progress → On Hold ↔ → Post-Work Review → Pass→Completed / Fail→Rework), per-transition notes + actor + timestamp, pre/post-inspection attribution; owner table gains Task Category + Assigned Date; richer task modal (stage timeline + issues with photos). diff --git a/docs/superpowers/plans/2026-05-29-plan4-lifecycle-inspections-estimates.md b/docs/superpowers/plans/2026-05-29-plan4-lifecycle-inspections-estimates.md new file mode 100644 index 0000000..daac146 --- /dev/null +++ b/docs/superpowers/plans/2026-05-29-plan4-lifecycle-inspections-estimates.md @@ -0,0 +1,257 @@ +# Plan 4 — Project Lifecycle State Machine + Inspections + Estimate Versions + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`). Follow the execution protocol (`2026-05-29-execution-protocol.md`) — sequential on shared files, parallel only where lanes are disjoint. + +**Goal:** Turn the static project-progression bar into an **interactive lifecycle state machine** (stage → progress %, with advance/regress + note + actor), add the **two-touchpoint inspection model** (damage verification + external final inspection, with an issues→rework→re-inspection loop and photo uploads), the **claimed-vs-verified scope-variance badge**, and an **estimate-versions** tab (v1 claim → v2 verified → signed; opens the full estimate document). This makes the Plan-3-tagged scenarios #2 (insurance up-scope), #5 (revisions), #8 (final inspection), and #10 (dispute) fully playable. + +**Architecture:** A new pure module `src/data/lifecycle.js` defines the stage set, the stage→% map, and next/prev helpers (reused by both the store mutator and the UI). `mockStore.jsx` gains `lifecycleStage` + `stageHistory` on every project, `inspections[]` / `estimateVersions[]` / `verifiedScope` on the scenario projects, and new mutators (advance stage, add inspection, add/resolve issue, add estimate version) following the existing `updateProject`/`addJobTeamMember` pattern. `OwnerProjectDetail.jsx` gets an interactive progression control, an Inspections tab, an Estimate Versions tab, and a scope-variance badge — reusing the existing `EstimateDetailModal` and the `URL.createObjectURL` photo pattern. + +**Tech stack:** React 18, Vite 7, mock React-context store, pnpm. No test harness → verify via `npm run build`, throwaway `node` checks for pure logic, and dev-run of each interaction. Nothing test-related is committed. + +**Spec/master:** `docs/superpowers/2026-05-29-MASTER-PLAN.md` §9 (lifecycle), §11 (estimate/inspection/scope-variance), Appendix A.5 (inspection), A.6 (estimate version), Appendix G Plan 4. Builds on Plan 3's `demoScenario` tags. + +--- + +## Current state (verified) + +- `OwnerProjectDetail.jsx` has **10 tabs** (`overview, budget, team, changeOrders, rfis, milestones, invoices, risks, activity, docs`, array at lines 67-78). **No Inspections or Estimate Versions tab.** +- The **"Project Progression"** block (lines 555-592, inside `overview`) is **static**: five hardcoded steps `["Lead","Inspection Scheduled","Damage Verified","Scope Approved","Project Completed"]` whose active state is `(i*25) <= project.completionPercentage`. No `onClick`, no advance control. **No `lifecycleStage` field exists** on any project. +- Store mutator pattern (`mockStore.jsx`): `const [projects, setProjects] = useState(MOCK_PROJECTS)` (line ~7438); mutators are inline arrows on the context `value` object (~line 8036+): `updateProject(projectId, data)` (shallow-merge, line ~8181), `addJobTeamMember(projectId, member)` (nested-append with `id: \`jtm_${Date.now()}\``, ~8184). New mutators follow this exactly. +- Estimate document renderer: **`EstimateDetailModal`** (`src/components/estimates/EstimateDetailModal.jsx`), props `{ isOpen, onClose, estimate }`. `estimate` = `{ id, clientName, address, date, status, total, templateId, templateName, estimatedBy, roofArea, notes }`. Line items are **computed** via `computeLineItems(template, roofArea)` (`src/utils/estimateExport.js`); a `!template` fallback (lines ~389-394) renders a stored-total-only view. Versions can therefore reuse this modal with either a real `templateId` or just a `total`. +- Photo upload: copy **Pattern A** — `URL.createObjectURL(file)` into a local state array (`PropertyDetailDrawer.jsx` lines 235-246) — the only pattern that yields a displayable image URL. (The `CreateItemModal` `type:'file'` path captures a `File` but its submit is a stub.) +- 29 projects (`PRJ-2026-001…029`); Plan 3 tagged 10 with `demoScenario:1..10`. + +## Scope & parallelism note (read first) + +Plan 4 is **concentrated in two files** — `src/data/mockStore.jsx` (data + mutators) and `src/pages/owner/OwnerProjectDetail.jsx` (all UI). The only parallel-safe carve-out is the new pure `src/data/lifecycle.js`. Therefore **Phase A (data/store, mockStore.jsx) and Phase B (UI, OwnerProjectDetail.jsx) are each internally sequential**; Phase B depends on Phase A (it consumes the new fields + mutators). Don't fabricate parallelism here — run it sequentially and commit between tasks. + +--- + +## Lane / ownership map + +| Task | Lane (exclusive files) | Phase | +|------|------------------------|-------| +| T1 `lifecycle.js` (new) | `src/data/lifecycle.js` | A (first; independent) | +| T2 lifecycleStage + stageHistory on all 29 projects | `src/data/mockStore.jsx` | A (seq) | +| T3 inspections / estimateVersions / verifiedScope on scenario projects | `src/data/mockStore.jsx` | A (seq, after T2) | +| T4 store mutators | `src/data/mockStore.jsx` | A (seq, after T3) | +| T5 interactive progression control | `src/pages/owner/OwnerProjectDetail.jsx` | B (seq) | +| T6 Inspections tab | `src/pages/owner/OwnerProjectDetail.jsx` | B (seq) | +| T7 Estimate Versions tab | `src/pages/owner/OwnerProjectDetail.jsx` | B (seq) | +| T8 Scope-variance badge | `src/pages/owner/OwnerProjectDetail.jsx` | B (seq) | +| T9 Verify | (read-only) | C | + +--- + +## Task 1: Create `src/data/lifecycle.js` (stage model — pure) + +**Files:** Create `src/data/lifecycle.js`. Independent; do first. + +- [ ] **Step 1:** Create the file: + +```js +// src/data/lifecycle.js +// The project lifecycle state machine (Master §9). Pure constants + helpers; no React. + +// Ordered linear stages. 'Rework In Progress' is an off-line regression state (entered on a failed +// final inspection), so it is NOT in the linear order array — handle it explicitly. +export const LIFECYCLE_STAGES = [ + 'Lead', + 'Damage Inspection Scheduled', + 'Damage Verified', + 'Scope Approved', + 'Contract Signed', + 'Work In Progress', + 'Work Complete — Awaiting Inspection', + 'Final Inspection Scheduled', + 'Final Inspection In Progress', + 'Project Completed', + 'Final Payment Received — Closed', +]; + +export const REWORK_STAGE = 'Rework In Progress'; + +// Stage → canonical progress %. Advancing to a stage sets the project's completionPercentage to this. +export const STAGE_PROGRESS = { + 'Lead': 5, + 'Damage Inspection Scheduled': 10, + 'Damage Verified': 20, + 'Scope Approved': 30, + 'Contract Signed': 40, + 'Work In Progress': 55, + 'Work Complete — Awaiting Inspection': 80, + 'Final Inspection Scheduled': 85, + 'Final Inspection In Progress': 90, + 'Rework In Progress': 75, + 'Project Completed': 95, + 'Final Payment Received — Closed': 100, +}; + +export const stageToPct = (stage) => STAGE_PROGRESS[stage] ?? 0; + +export const nextStage = (stage) => { + if (stage === REWORK_STAGE) return 'Final Inspection Scheduled'; // rework re-enters inspection + const i = LIFECYCLE_STAGES.indexOf(stage); + return i >= 0 && i < LIFECYCLE_STAGES.length - 1 ? LIFECYCLE_STAGES[i + 1] : stage; +}; + +export const prevStage = (stage) => { + const i = LIFECYCLE_STAGES.indexOf(stage); + return i > 0 ? LIFECYCLE_STAGES[i - 1] : stage; +}; + +// Is this stage at/after a reference stage? (for gating UI like "can schedule final inspection") +export const isAtOrAfter = (stage, ref) => + LIFECYCLE_STAGES.indexOf(stage) >= LIFECYCLE_STAGES.indexOf(ref); +``` + +- [ ] **Step 2 (verify):** throwaway `node` check (temp dir, deleted): assert `nextStage('Damage Verified')==='Scope Approved'`, `nextStage(REWORK_STAGE)==='Final Inspection Scheduled'`, `stageToPct('Contract Signed')===40`, `prevStage('Lead')==='Lead'`, `isAtOrAfter('Project Completed','Contract Signed')===true`. Then `node -e "import('./src/data/lifecycle.js').then(m=>console.log(Object.keys(m)))"` proves it loads. +- [ ] **Step 3:** commit `git commit -m "feat(lifecycle): add pure project lifecycle stage model (stages, progress map, helpers)"` (after `git add src/data/lifecycle.js`). + +## Task 2: Add `lifecycleStage` + `stageHistory` to all 29 projects + +**Files:** `src/data/mockStore.jsx` only. + +- [ ] **Step 1:** For EACH of the 29 projects, add a `lifecycleStage` (a value from `LIFECYCLE_STAGES`/`REWORK_STAGE`) derived from its current `status`/`phase`/`completionPercentage`: + - `status:'completed'` & all paid → `'Final Payment Received — Closed'`; completed but not all paid → `'Project Completed'`. + - active near-done (~80–90%) → `'Work Complete — Awaiting Inspection'` or `'Final Inspection Scheduled'`. + - active mid (40–70%) → `'Work In Progress'`. + - signed/pre-construction (the converted signed leads, ~0–30%) → `'Contract Signed'` or `'Scope Approved'`. + - the disputed/on-hold scenario (#10) → keep its existing stage (e.g. `'Work In Progress'`) — On-Hold is represented by `status:'disputed'` + the dispute data; do NOT add an on-hold lifecycle stage (the §10 sub-task machine has On Hold; the project lifecycle does not). + - Set `completionPercentage` to remain consistent with `stageToPct(lifecycleStage)` for discrete stages; for `'Work In Progress'` keep the project's existing milestone-driven % (40–70). +- [ ] **Step 2:** Add a `stageHistory: [{ stage, date, by, note }]` to each project — at minimum the current stage as the last entry; for the scenario projects, author 2–4 prior transitions with real dates/actors (owner or rep names) telling the job's story. +- [ ] **Step 3 (verify):** `npm run build` → `✓ built`. `git grep -c "lifecycleStage:" -- src/data/mockStore.jsx` → 29. Spot-check 3 projects that `completionPercentage` is consistent with the stage. +- [ ] **Step 4:** commit `git commit -am "feat(data): add lifecycleStage + stageHistory to all projects"`. + +## Task 3: Add inspections / estimateVersions / verifiedScope to scenario projects + +**Files:** `src/data/mockStore.jsx` only. After T2. + +Author rich data on the tagged scenarios (find them via `demoScenario`), per schemas A.5 / A.6 and §11. + +- [ ] **Step 1 — Damage inspection + scope variance (scenario #2, the hero up-scope).** On the `demoScenario:2` project add: + - `reportedScope: { summary: 'Small roof leak near chimney', estTotal: 200, estDays: 1 }` (the customer's claim — also mirror onto its `sourceLeadId` lead as `reportedScope`). + - `verifiedScope: { summary: 'Widespread hail bruising across north + west slopes; decking damage', estTotal: 15400, estDays: 5 }`. + - `scopeVariance: true` (drives the badge). + - `inspections: [{ id:'insp__1', kind:'damage', round:1, inspectorName:, inspectorRole:'Field Agent', inspectionDate:, result:'pass', feedbackNote:'Verified major hail damage far exceeding reported leak; photos attached.', issues:[] }]`. + - `estimateVersions: [ { version:1, date:, total:200, note:'Initial — homeowner-reported leak', status:'sent' }, { version:2, date:, total:15400, note:'Revised after damage verification (insurance supplement)', status:'revised' }, { version:3, date:, total:15400, note:'Approved + signed after adjuster supplement', status:'signed', templateId: } ]`. +- [ ] **Step 2 — Estimate revisions (scenario #5).** On `demoScenario:5` add `estimateVersions` of 3–4 versions showing a negotiation (v1 sent → v2 revised → v3 revised → v4 signed), each with a `note` and changing `total`; set `verifiedScope` if relevant. +- [ ] **Step 3 — Final inspection + rework loop (scenario #8, near-complete).** On `demoScenario:8` add a final inspection round per A.5: `{ id, kind:'final', round:1, inspectorName:, inspectorContact:{ phone:, email: }, inspectionDate:, result:'fail', feedbackNote:'Flashing detail at the valley needs rework.', issues:[{ id, description:'Valley flashing not sealed to spec', note:'Re-seal and re-inspect', photos:[], status:'open' }] }`. Set its `lifecycleStage` to `'Final Inspection In Progress'` (or `REWORK_STAGE` to demo the regression). This gives the demo a live issues→rework loop. +- [ ] **Step 4 — Closed, all-passed (scenario #9).** On `demoScenario:9` add a passing final inspection: `{ kind:'final', round:1, inspectorName, inspectorContact, inspectionDate, result:'pass', feedbackNote:'Passed — no punch items.', issues:[] }` and a signed `estimateVersions` entry, so a fully-closed job shows the complete paper trail. +- [ ] **Step 5 — Dispute context (scenario #10).** Ensure the disputed project's `verifiedScope`/`estimateVersions` reflect the change-order dispute (a `status:'revised'` version the customer is contesting). (The paused payments + dispute timeline already exist from Plan 3.) +- [ ] **Step 6 (verify):** `npm run build` → `✓ built`. `git grep -c "inspections:" -- src/data/mockStore.jsx` ≥ 4; `git grep -c "estimateVersions:" ` ≥ 4; `git grep -c "scopeVariance: true"` ≥ 1. Confirm any `templateId` used exists in the templates store (else omit it — the modal's no-template fallback handles it). +- [ ] **Step 7:** commit `git commit -am "feat(data): add inspections, estimate versions, and scope variance to demo scenarios"`. + +## Task 4: Store mutators + +**Files:** `src/data/mockStore.jsx` only. After T3. Add these inline arrows to the context `value` object, next to `updateProject` (follow that exact pattern): + +- [ ] **Step 1:** Add: + +```js +// Lifecycle: set stage, log the transition, sync completionPercentage from the stage map. +advanceLifecycleStage: (projectId, stage, note, by) => { + setProjects(prev => prev.map(p => p.id === projectId ? { + ...p, + lifecycleStage: stage, + completionPercentage: stage === 'Work In Progress' + ? Math.max(p.completionPercentage || 0, 55) // WIP is milestone-driven; never regress within it + : STAGE_PROGRESS[stage] ?? p.completionPercentage, + stageHistory: [...(p.stageHistory || []), { stage, date: new Date().toISOString().slice(0, 10), by: by || 'Owner', note: note || '' }], + } : p)); +}, +addInspection: (projectId, inspection) => { + setProjects(prev => prev.map(p => p.id === projectId + ? { ...p, inspections: [...(p.inspections || []), { ...inspection, id: inspection.id || `insp_${Date.now()}` }] } + : p)); +}, +addInspectionIssue: (projectId, inspectionId, issue) => { + setProjects(prev => prev.map(p => p.id === projectId ? { + ...p, + inspections: (p.inspections || []).map(ins => ins.id === inspectionId + ? { ...ins, issues: [...(ins.issues || []), { ...issue, id: issue.id || `iss_${Date.now()}`, status: 'open' }] } + : ins), + } : p)); +}, +resolveInspectionIssue: (projectId, inspectionId, issueId) => { + setProjects(prev => prev.map(p => p.id === projectId ? { + ...p, + inspections: (p.inspections || []).map(ins => ins.id === inspectionId + ? { ...ins, issues: (ins.issues || []).map(iss => iss.id === issueId ? { ...iss, status: 'fixed' } : iss) } + : ins), + } : p)); +}, +addEstimateVersion: (projectId, version) => { + setProjects(prev => prev.map(p => p.id === projectId + ? { ...p, estimateVersions: [...(p.estimateVersions || []), version] } + : p)); +}, +``` + +- [ ] **Step 2:** At the top of `mockStore.jsx`, import the progress map used by the mutator: `import { STAGE_PROGRESS } from './lifecycle';` (place with the other imports). +- [ ] **Step 3 (verify):** `npm run build` → `✓ built`. `git grep -n "advanceLifecycleStage\|addInspection\|addEstimateVersion" -- src/data/mockStore.jsx` shows the new keys on the context value. +- [ ] **Step 4:** commit `git commit -am "feat(store): add lifecycle/inspection/estimate-version mutators"`. + +## Task 5: Interactive progression control + +**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. Phase B. + +- [ ] **Step 1:** Import the lifecycle model + mutator: `import { LIFECYCLE_STAGES, REWORK_STAGE, nextStage, prevStage, stageToPct } from '../../data/lifecycle';` and pull `advanceLifecycleStage` from `useMockStore()`. +- [ ] **Step 2:** Replace the static progression block (lines ~555-592) so it renders from `project.lifecycleStage` (fall back to deriving from `completionPercentage` if a project somehow lacks the field). Render the stage label + the progress bar driven by `project.completionPercentage` (which now tracks the stage). +- [ ] **Step 3:** Add an owner/admin-only **Advance Stage** control: a button "Advance to: {nextStage(project.lifecycleStage)}" that opens the existing `CreateItemModal` (or a small inline prompt) capturing an optional note, then calls `advanceLifecycleStage(project.id, nextStage(project.lifecycleStage), note, user.name)`. Add a secondary "Regress / Send to Rework" affordance for the failed-inspection case (advance to `REWORK_STAGE` when at a Final Inspection stage). Gate the controls with `['OWNER','ADMIN'].includes(user?.role)`. +- [ ] **Step 4:** Show the `stageHistory` as a compact timeline beneath the bar (stage · date · by · note). +- [ ] **Step 5 (verify):** `npm run build` → `✓ built`. Dev-run: advancing a stage updates the bar + % live and appends to the history. +- [ ] **Step 6:** commit `git commit -am "feat(projects): interactive lifecycle progression control with stage history"`. + +## Task 6: Inspections tab + +**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. After T5. + +- [ ] **Step 1:** Add `{ id: 'inspections', label: 'Inspections', icon: }` to the `tabs` array. +- [ ] **Step 2:** Add the `activeTab === 'inspections'` render block (follow the existing `NeoCard`/table card patterns). Render `project.inspections` grouped by `kind` (Damage Verification vs Final Inspection). For each inspection show: kind, round, inspector name (+ role for damage, + contact phone/email for final), date, a pass/fail badge, and the `feedbackNote`. Below each, list its `issues[]` with an open/fixed badge. +- [ ] **Step 3:** Interactions (owner/admin): **Add Issue** to an inspection (calls `addInspectionIssue`), **Mark Fixed** on an open issue (calls `resolveInspectionIssue`), and **Attach Photo** to an issue using Pattern A — a hidden `` whose `onChange` does `URL.createObjectURL(file)` and stores `{ id, url, caption }` into the issue's `photos[]` (local component state mirror is fine for session display; persist via a small `updateProject` if you want it sticky). Render thumbnails from `issue.photos[].url`. +- [ ] **Step 4:** Add a **"Schedule Re-inspection"** action that appends a new final-inspection round (`round: prevRound+1`, `result` unset/pending) via `addInspection` — demonstrating the rework→re-inspection loop; older rounds stay visible. +- [ ] **Step 5 (verify):** `npm run build` → `✓ built`. Dev-run on scenario #8: the failed final inspection shows its open issue; marking it fixed + scheduling re-inspection works; photo thumbnail appears. +- [ ] **Step 6:** commit `git commit -am "feat(projects): Inspections tab (damage + final, issues, rework loop, photo upload)"`. + +## Task 7: Estimate Versions tab + +**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. After T6. + +- [ ] **Step 1:** Add `{ id: 'estimates', label: 'Estimates', icon: FileText }` to the `tabs` array. +- [ ] **Step 2:** Import the estimate modal: `import EstimateDetailModal from '../../components/estimates/EstimateDetailModal';` and add a `const [selectedVersion, setSelectedVersion] = useState(null);`. +- [ ] **Step 3:** Add the `activeTab === 'estimates'` block: a list of `project.estimateVersions` (version, date, `formatCurrency(total)`, note, and a status badge — green "Signed" for `status:'signed'`). Each row is clickable. +- [ ] **Step 4:** On click, open `EstimateDetailModal` with an estimate object built from the version + project: `{ id: \`${project.id}-v${v.version}\`, clientName: project.customerName, address: project.address, date: v.date, status: v.status, total: v.total, templateId: v.templateId, templateName: v.templateName, estimatedBy: , roofArea: v.roofArea, notes: v.note }`. If the version has no `templateId`, the modal's no-template fallback renders the stored total — that's expected. +- [ ] **Step 5 (verify):** `npm run build` → `✓ built`. Dev-run on scenario #2 and #5: the version history renders with a Signed badge on the final version; clicking opens the full estimate document. +- [ ] **Step 6:** commit `git commit -am "feat(projects): Estimate Versions tab (history + full document view)"`. + +## Task 8: Scope-variance badge + +**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. After T7. + +- [ ] **Step 1:** In the `overview` tab header area (near the project title/metrics), when `project.scopeVariance` is truthy and both `reportedScope` and `verifiedScope` exist, render a visible badge, e.g.: + `Reported ~${formatCurrency(project.reportedScope.estTotal)} / ${project.reportedScope.estDays}d → Verified ${formatCurrency(project.verifiedScope.estTotal)} / ${project.verifiedScope.estDays}d` + styled as an attention badge (amber), with a short tooltip/subtext summarizing the discrepancy story. +- [ ] **Step 2 (verify):** `npm run build` → `✓ built`. Dev-run scenario #2: the badge shows the "~$200 → $15,400" jump. +- [ ] **Step 3:** commit `git commit -am "feat(projects): claimed-vs-verified scope-variance badge"`. + +## Task 9: Verify (read-only) + +- [ ] `npm run build` clean. +- [ ] `git grep -c "lifecycleStage:" -- src/data/mockStore.jsx` → 29; every project's `completionPercentage` is consistent with `stageToPct(lifecycleStage)` for discrete stages (WIP exempt). +- [ ] Scenario coverage: #2 has reportedScope+verifiedScope+scopeVariance+3 estimate versions+a damage inspection; #5 has multi-version estimate history; #8 has a failed final inspection with an open issue; #9 has a passing final inspection; #10's dispute reflected in a contested estimate version. +- [ ] Dev-run the four interaction loops: **advance stage** (bar + % + history update); **log + resolve an inspection issue**; **schedule a re-inspection round**; **open an estimate version document**; **scope-variance badge** visible on #2. +- [ ] No financial invariants broken (this plan adds lifecycle/inspection/estimate data; it must not change budgetBreakdown/committedCost/actualCost — confirm Plan 1/2 invariants still hold on any project touched). + +--- + +## Self-review checklist +- [ ] `lifecycle.js` pure, exports stages/progress/helpers; fixture-verified. +- [ ] All 29 projects have `lifecycleStage` + `stageHistory`; % consistent with stage. +- [ ] Scenario projects carry inspections / estimateVersions / scope-variance per §11. +- [ ] 5 mutators added following the established `setProjects` pattern; `STAGE_PROGRESS` imported. +- [ ] OwnerProjectDetail: progression control interactive (advance/regress + note + history); Inspections tab (issues + rework + photos); Estimates tab (versions + modal); scope-variance badge. +- [ ] Owner/admin gate on mutating controls; build clean; no financial invariant regressions. + +## Notes for Plan 5 +Plan 5 (subcontractor task state machine, Master §10) is independent of these project-level changes and operates on `MOCK_SUBCONTRACTOR_TASKS` + `SubcontractorTasksPage.jsx` + `TaskViewModal.jsx`. The inspection/rework UI patterns built here (issues list, photo upload, status badges, re-review rounds) are the model to reuse for the sub-task Pre-Work/Post-Work review loop.