Closes WEB-M5 (web hand-duplicated wire types, standing drift risk):
- shared/api-contracts: rewritten as the authoritative superset of the
.NET DTOs (ProposalListItem/ProposalDetail with poNumber and
submittedByName, line item requests, customers, pricing library,
dashboard, audit, sites, auth, presigned upload, ApiProblem); stale
Proposal/UpdateLineItemsRequest shapes removed
- shared/api-contracts/src/schemas.ts: zod runtime schemas coupled to
every wire type via `satisfies z.ZodType<T>` (schema/type drift is now
a compile error); separate entrypoint so type-only consumers (mobile)
never pull zod
- web: imports @proposal-system/api-contracts (file: dep + tsconfig
paths + vite preserveSymlinks); all 7 lib/api modules re-export shared
types so page imports stay stable; enum unions tightened
(PricingLibraryPage form state now ServiceCategory-typed)
- fix(web): customer create/update sent a singular `address` field the
API silently dropped (contract is addresses: string[], CustomerDtos.cs)
- addresses now round-trip, extra addresses preserved on edit
- api: ProblemDetails responses carry a machine-readable top-level
`code` (SHOC error-code vocabulary): ValidationFailed,
InvalidStateTransition, NotFound, Unauthorized, InternalError; new
BusinessRuleException(code, message) maps to 422 with its code;
GlobalExceptionHandlerTests cover the full mapping (wire contract)
Cross-checked .NET DTOs vs TS types vs zod schemas with the
orchestrator scanner (Gemini): core domains consistent; internal-only
DTOs (FileDtos vendor/lambda surface, SimilarProposalDtos, UserDtos
admin surface) intentionally uncovered.
Verify: dotnet 166/166, web tsc + vitest 26/26 + build, mobile tsc,
shared tsc all green.
Bump @mui/material and @mui/icons-material 7.3.1 -> ^9.1.1 together in
/web. They must move in lockstep (icons peer-depends on material), so
Dependabot's separate per-package PRs (#145/#146) could never go green
individually (ERESOLVE). Adds a 'mui' Dependabot group so future @mui/*
updates — including majors — are raised as one PR.
MUI v9 migration fixes (v7->v9 spans two majors):
- ListItemText: primaryTypographyProps -> slotProps.primary; fontSize
moved into sx (Typography system props removed in v9).
- Autocomplete renderInput: params.InputProps -> params.slotProps.input;
spread ...params.slotProps to retain inputLabel/htmlInput slots.
- Icons: @mui/icons-material/ErrorOutline -> ErrorOutlined (the plain
ErrorOutline export was removed in v9).
- vitest: inline @mui/material + react-transition-group so Vite resolves
v9's Transition.mjs directory import (native ESM loader can't).
Verified locally on Node 24: npm ci, npm run build, and npm test
(26 tests, 3 suites) all pass.
* feat: proposal delivery — email customers the PDF on "Mark as Sent"
Makes the system's namesake feature real: marking a proposal Sent now emails the
customer an expiring link to the branded PDF, and customers are managed (with
contact emails) instead of hardcoded. v1 PR4.
API:
- Customer.ContactEmail + migration; Customer list/update endpoints. Search stays
additive at GET /api/customers?query= (frozen-mobile + web compat); new paginated
list at GET /api/customers/list (admin).
- IEmailService (SesEmailService v2 / DevEmailService, dev-gated). MarkSentAsync
resolves the customer's email, presigns the latest PDF (7d), and sends via SES.
Email/presign failures are caught + audited and NEVER roll back the Sent transition.
- Startup EF migration guarded by a Postgres advisory lock (concurrency-safe).
Infra:
- SES email identity (proposals@seahavenind.com); least-privilege ses:SendEmail/
SendRawEmail scoped to the identity ARN + ses:FromAddress condition; SES_FROM_ADDRESS
env. SES starts in sandbox — production access needed for unverified recipients.
Web:
- Customer management page (/admin/customers): list / create / edit incl. contact email.
- New-proposal form searches real customers (free-solo) instead of a hardcoded value.
- Mark-as-Sent dialog notes the PDF will be emailed to the customer.
GPT-4.1 cross-review (SES IAM): no BLOCK (ses:FromAddress condition applied).
Verified: api build + 121 tests; web tsc + 26 tests; infra tsc; ruff clean.
* Potential fix for pull request finding 'CodeQL / Exposure of private information'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* Potential fix for pull request finding 'CodeQL / Exposure of private information'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* Potential fix for pull request finding 'CodeQL / Exposure of private information'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>