Skip to Content

← All archived runs

Run: project-change-control

run.md

Run: project-change-control

  • branch: claude/zen-dijkstra-ntxevp
  • pr: #505

00_intake/stub.md

Stub: Customer dashboard — change control log

  • feature-slug: project-change-control
  • epic: customer-dashboard-data
  • personas: Customer (view); delivery team (Expert, CSM, SDM) record changes
  • initiative: Build the Bridge / objective: Q2 2026 Objective 3 — Validate Technical Infrastructure & Payout Flow
  • depends-on: customer-project-foundation
  • sequence: 8 of 9

Problem

The Change control section (change, timelineImpact, costImpact — e.g. "API integration scope expanded … +2 days … +£500") is dummy. There is no change-control model. Scope changes that move the timeline or cost are recorded nowhere, so the customer has no transparent log of how/why the plan shifted — a trust-critical surface during delivery.

Proposed change

  • Define a change-control entry per project: change (description), timelineImpact (e.g. "+2 days" / "No change"), costImpact (e.g. "+£500" / "No change"), and an at date.
  • Build the minimal capture for the delivery team to log a change-control entry on a project.
  • Wire the ChangeControlSection to the real entries for the customer's active project.

Acceptance criteria (rough)

  • Delivery team can record a change with timeline + cost impact; it appears on the customer dashboard, newest first.
  • Entries are project- and customer-scoped.
  • Empty state renders cleanly when no changes have been logged.
  • Cost impact respects the project currency rather than a hard-coded symbol.

Out of scope (this feature)

  • Auto-deriving change entries from milestone/scope edits — entries are recorded explicitly this round.
  • Customer approval workflow on a change (acknowledge/approve) — view-only here.

Notes for Define

  • Decide whether timelineImpact/costImpact are free text (as the mock implies) or structured (signed numbers) — structured would let finance/health react to them later.
  • touches: packages/services/src/db/models (new change-control model), packages/services/src/server, apps/web/components/dashboard/customer/change-control-section.tsx.

01_define/output/spec.md

Spec: Project change-control log

  • slug: project-change-control
  • personas: Customer (view); Expert, CSM, SDM (record)
  • touches: packages/services/src/db/models (new change-control model), packages/services/src/db/services/change-control, packages/services/src/db/services/customer-project, apps/web/app/(app)/projects/[id] (team capture), apps/web/components/dashboard/customer/change-control-section.tsx, apps/web/app/(app)/customer/dashboard/page.tsx
  • complexity: standard

Problem

During delivery, a project's scope sometimes shifts in ways that move the timeline or cost (e.g. "API integration scope expanded … +2 days … +£500"). Today the customer dashboard's "Changes to your project" section reads entirely from dummy data (apps/web/lib/mock/customer.tschangeControl): there is no change-control model, so those shifts are recorded nowhere and the customer has no transparent log of how or why the plan changed. That transparency is trust-critical during delivery — the customer-facing window onto the delivery step of the service journey — and is part of the Build the Bridge initiative's Q2 2026 Objective 3 (validate the technical infrastructure that backs the live delivery surfaces) by replacing the last mock section of the customer dashboard with real, per-tenant, per-customer data.

Proposed change

  • Add a change-control entry model, one per project (a lead with isActiveProject = true), tenant-scoped and lead-scoped, mirroring the existing blocker model's shape and plugins (tenantPlugin, softDeletePlugin, schemaPlugin). Fields:
    • change — short description of what changed (required text).
    • timelineImpactDays — signed integer; 0 means no timeline change (e.g. 2 → "+2 days").
    • costImpactAmount — signed number; 0 means no cost change (e.g. 500 → "+£500").
    • at — the date the change was recorded (defaults to now).
    • createdBy — the team member who logged it (optional user ref, as on blocker).
  • The cost impact carries no currency of its own — it is rendered in the project's currency, which is already resolved from the accepted quote by customerProjectService (FinanceCurrency: EUR/USD/GBP, defaulting to EUR). No hard-coded £.
  • Add a ChangeControlService (change-control/index.ts + instance.ts) following the BlockerService pattern, with record(tenantId, leadId, input) and listByLead(tenantId, leadId) (newest first by at).
  • Minimal team-side capture: a server action on the internal project detail page (apps/web/app/(app)/projects/[id]/actions.ts, alongside raiseBlockerAction) plus a small form on that page, so the delivery team (the project's manager/CSM/SDM/expert) can log a change with a description, a timeline-day impact, and a cost-amount impact. No standalone management console.
  • Wire the customer dashboard: surface the project's change-control entries through the CustomerProjectEnvelope returned by customerProjectService (the typed envelope every other customer-dashboard section already extends), including the resolved currency, and replace d.changeControl in apps/web/app/(app)/customer/dashboard/page.tsx with the real entries.
  • Update ChangeControlSection to take structured entries { change, timelineImpactDays, costImpactAmount } plus the project currency, and format the badges ("+2 days" / "No change", "+£500" / "No change") from those values — keeping the existing card layout and the secondary-vs-warning badge styling (no change = secondary, any non-zero impact = warning).

Acceptance criteria

  • A delivery-team member can record a change-control entry (description + timeline-day impact + cost-amount impact) on a project from the internal project detail page, and it persists.
  • The recorded entry appears in the customer dashboard's "Changes to your project" section, newest first, with no mock data left in that section.
  • Entries are tenant- and project (lead)-scoped: a customer only ever sees change-control entries for their own active project.
  • Cost impact is rendered in the project's currency (from the accepted quote, EUR/USD/GBP) — no hard-coded currency symbol.
  • A timeline or cost impact of 0 renders as a "No change" (secondary) badge; any non-zero impact renders as a signed value in a warning badge.
  • When no change-control entries exist for the project, the section renders cleanly (the current component returns nothing for an empty list — that behaviour is preserved).

Out of scope

  • Auto-deriving change entries from milestone or scope edits — entries are recorded explicitly this round.
  • A customer approval / acknowledgement workflow on a change (the section stays view-only for the customer).
  • A rich team-side change-control management console (edit/delete/history) beyond the minimal capture form — a later epic, consistent with the customer-dashboard-data breakdown.
  • Feeding change-control impacts into the computed health engine or the finance summary — the structured fields make that possible later, but no health/finance wiring is built this round.
  • Editing or soft-deleting an entry from the UI (the model carries softDeletePlugin, but no UI path is built this round).

Open questions

  • none

02_build/output/notes.md

Build notes: project-change-control

  • commits: feat: project-change-control — model + service + capture + dashboard wiring

What changed

  • packages/services/src/db/models/change-control.ts: new tenant/lead-scoped change-control model (change, timelineImpactDays, costImpactAmount, at, createdBy), mirroring blocker's shape and plugins. Index { tenantId, lead, at: -1 } for the newest-first dashboard read.
  • packages/services/src/db/services/change-control/{index,instance}.ts: ChangeControlService with record() and listByLead() (newest first), following BlockerService.
  • packages/services/src/db/{models,services}/index.ts: export the model, service, instance, and RecordChangeControlInput.
  • packages/services/src/db/services/customer-project/index.ts: added CustomerProjectChangeEntry and a changeControl field on CustomerProjectEnvelope; getActiveProjectForCustomer now fetches the project's change-control entries and toEnvelope maps them (date → ISO string).
  • apps/web/app/(app)/projects/[id]/actions.ts: new recordChangeControlAction (delivery roles only), composing the explicit zodToActionError/resolveActionContext/runActionBody pipeline.
  • apps/web/components/service-leads/lead-change-control/*: minimal team-side capture (server fetch wrapper + client form/list + loading), wired into the project detail page's delivery-only column alongside blockers and action items.
  • apps/web/components/dashboard/customer/change-control-section.tsx: now takes structured entries { change, timelineImpactDays, costImpactAmount } + project currency; formats the timeline/cost badges ("+2 days" / "No change", "+£500" / "No change") from the structured values. Empty-list behaviour (renders nothing) preserved.
  • apps/web/lib/dashboard-utils.ts: exported currencySymbol(currency) (reusing the existing private symbol map) so the section renders cost in the project currency, no hard-coded symbol.
  • apps/web/app/(app)/customer/dashboard/page.tsx: ChangeControlSection now reads project.changeControl + project.finance.currency instead of the mock.
  • apps/web/lib/mock/customer.ts: removed the dummy changeControl array.

Acceptance criteria status

  • Delivery team can record a change with timeline + cost impact from the project detail page — recordChangeControlAction + LeadChangeControl capture (gated to admin/csm/sdm/expert).
  • Entry appears on the customer dashboard, newest first, no mock left — listByLead sorts at: -1; page reads project.changeControl; mock array deleted.
  • Tenant- and project-scoped — service queries by tenantId + lead; envelope is resolved from the signed-in customer's own active project.
  • Cost rendered in project currency, no hard-coded symbol — costLabel uses currencySymbol(project.finance.currency).
  • 0 impact → "No change" (secondary) badge; non-zero → signed warning badge — impactBadge.
  • Empty list renders cleanly — section returns nothing for changes.length === 0 (preserved).

Verify result

  • Mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview, not here. One thing I fixed locally to avoid a known type error: the capture form converts the numeric inputs with Number(...) (with a Number.isFinite guard) before calling the action, so the call site matches the action's inferred number params.

Notes for review

  • The team-side capture is intentionally minimal (record only — no edit/delete), per the spec's Out of scope. Demo-data seeding was deliberately not added: sibling concepts (blockers, action items) aren't seeded either, and the empty state is a valid acceptance criterion.
  • Impacts are stored as signed numbers (0 = no change); the UI owns all formatting and supplies the currency, keeping the model currency-agnostic.

03_release/output/changelog.md

Changelog: project-change-control

Published as apps/help/app/changelog/2026-06-22-project-change-control/page.mdx.


See how changes to your project affect timeline and cost

The "Changes to your project" section of your dashboard now shows the real scope changes on your project instead of placeholder text, so you always know how and why the plan has moved:

  • See each recorded change with its effect on your timeline and on your cost, newest first.
  • Timeline and cost impacts are shown clearly — "+2 days", "+£500", or "No change" — with the cost in your project's own currency.
  • Your delivery team (expert, CSM, or SDM) records these changes from the project page as they happen.
  • A project with nothing logged shows a clean, empty section, so you're never left guessing.

03_release/output/investor-update.md

Customers can now see exactly how their project plan changes

Who it's for: Customers (delivery teams record the changes) What shipped: A live change-control log on the customer dashboard — every scope change with its timeline and cost impact, shown in the project's currency. Why it matters: Builds delivery trust, advancing Build the Bridge's Q2 objective to validate technical infrastructure.

Replaces the last placeholder section of the customer dashboard with real per-project data.

Dig deeper: https://github.com/sustentus/sustentus/pull/505 · https://help.sustentus.com/changelog/2026-06-22-project-change-control

03_release/output/release.md

Release: project-change-control

  • pr: #505 · merged: yes — squash d05eb948 on 2026-06-22
  • CI: green (Typecheck, Lint, Format, Migrate preview database, Audit database, Vercel) on the merged commit
  • technical docs: no technical docs impact (models/services are not individually enumerated in apps/docs/technical)
  • business docs: updated in this PR — business/feature-role-matrix/projects (new "Record change control on project" row) and business/service-journey/delivery (new "Change control" section)
  • release notes: both — changelog entry (apps/help/app/changelog/2026-06-22-project-change-control) + investor draft, both in this PR
  • deploy: READY — production deploys green for web (platform.sustentus.com) and help-centre (help.sustentus.com)
  • sent: investor update sent to 2 recipients on 2026-06-22, after green deploy

Review summary

  • /code-review (correctness pass): no bugs found.
  • Accuracy: the customer dashboard card subtitle read "Approved changes…", but there is no approval workflow (explicitly out of scope; the docs describe a read-only log) — fixed to "Recorded changes…" on the branch.
  • Conventions + docs accuracy: clean — documented field names/constraints match the model; investor update is within the 60-word body cap and names the real "Build the Bridge" initiative + Q2 2026 Objective 3 (Validate Technical Infrastructure & Payout Flow); changelog is sentence-case and jargon-free.

Acceptance check (vs spec)

  • Delivery team can record a change (description + timeline-day + cost-amount) from the project detail page — recordChangeControlAction + LeadChangeControl capture (delivery roles only).
  • Entry appears on the customer dashboard, newest first, no mock — listByLead sorts at: -1; page reads project.changeControl; mock array removed.
  • Tenant- and project-scoped — service queries by tenantId + lead; envelope resolved from the customer's own active project.
  • Cost rendered in project currency, no hard-coded symbol — currencySymbol(finance.currency).
  • 0 impact → "No change" (secondary) badge; non-zero → signed warning badge.
  • Empty list renders cleanly — section returns nothing for an empty list.