- AUTH-L1 (confirmed medium): logout() now clears the react-query cache —
the singleton cache survived SPA logout, serving the previous
principal's cached GETs to the next login in the same tab for up to
staleTime with no server round-trip.
- AUTH-L3 (confirmed low): isTokenValid decodes base64url before atob —
valid Cognito JWTs containing '-'/'_' in the payload segment were
misclassified as expired (login lockout/loop; inherited from the old
authSlice).
- AUTH-L2 (unverified, hardened anyway): 401 interceptor broadcasts
AUTH_SESSION_CLEARED_EVENT so AuthProvider drops in-memory state
synchronously, restoring the old Redux atomic-clear semantics.
- INJ-1 (unverified, hardened anyway): Authorization header only set
when the stored token is a string.
Each fix pinned by a test; 63 vitest green, tsc clean.
Correctness:
- State-transition mutations now invalidate every cached view via
invalidateProposalViews (detail + line items + lists + stats + admin
dashboard) — approving no longer leaves a stale queue for the
5-minute staleTime
- Presigned S3 PUT moved to proposals/api.ts with res.ok check — a
rejected upload is no longer confirmed as uploaded
- toCustomerRequest always sends contactEmail ('' clears); API create
path normalizes empty->null to match the update path — customer
emails can now be cleared from the UI
- Shared Number-based numeric form fields (domain/shared/formFields):
'12abc' no longer silently coerces to 12 in the pricing library
- Customer create/update invalidate customersKeys.all so cached search
autocompletes see new customers
- AdminWorkspace clears dirty right after a successful implicit save,
before approve — no false unsaved-changes prompt when approve fails
- ProposalFormPage submit gate and missing-fields caption derive from
ONE checks list (missing customer is now listed)
- Empty states gated on !err in ProposalListPage/AdminDashboard — no
contradictory error + 'no proposals' UI
- VendorDataPanel migrated to useVendorProposals (kills the divergent
['vendorProposals', id] cache key and the inline apiClient query)
- useCustomerList/usePricingLibraryList get keepPreviousData — no
TablePagination out-of-range flash on page change
Cleanup:
- Dead speculative hooks removed (useCreate/BulkUpdate/DeleteLineItem,
useUpdateProposal, useProposalHistory/Audit, lineItemRowFormSchema,
toUpdateLineItemEntry); tests moved to the live save path
(useSaveProposalWorkspace)
- Shared useDebouncedValue hook replaces 4 drifted inline debounce
copies (one leaked its timer on unmount, two hardcoded 300ms);
DEBOUNCE_AUTOCOMPLETE=300 named
- Fix: WEB-H5 / WEB-H6 finding-ID markers restored at the relocated
onError handlers (CLAUDE.md traceability)
- shared/api-contracts gains an exports map; /schemas resolver alias
deduplicated from 3 copies to the tsconfig paths mapping
Verify: tsc clean, vitest 51/51 (tests updated to pin the new
invalidation/mapper behavior + new '12abc' rejection test),
vite build OK, dotnet 166/166.
Additive-only: pages still use lib/api/* and constants/queryKeys.ts until
the page-migration agents run. Each domain ships api.ts (HTTP moved from
lib/api), types.ts (contract re-exports + view types), schemas.ts (contract
schema re-exports + form schemas with toRequest mappers), and use-cases.ts
(TanStack Query v5 hooks + hierarchical query keys, mirroring current page
invalidations and toast-on-error behavior).
Adds an explicit vite/vitest alias for the
@proposal-system/api-contracts/schemas subpath (package has no exports map)
plus a schema/mapper smoke test suite.
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.
Moves proof-or-kill-verified false positives (Fastfile runtime PEM-assembly
boilerplate; Podfile.lock CocoaPods SPEC CHECKSUMs) from machine-level to a
tracked repo-local .security-review/suppressions.json so the Open SWE
daily-report automation resolves them. Justifications sanitized to avoid
reproducing the begin-marker literal. Machine-level copy retained until merge.
TypeScript 7.0.2 (the native-port build) no longer exposes the internal
compiler API (ts.sys) that ts-node@10.9.2 depends on, so
`npx ts-node bin/app.ts` fails during `cdk synth` with
"Cannot read properties of undefined (reading 'fileExists')".
Switch the CDK app runner to tsx (esbuild-based, version-agnostic — it
does not consume the typescript package's programmatic API), and pin tsx
as an infra devDependency. Verified `cdk synth` succeeds locally with
typescript 7.0.2 installed.
Update both packages together so peer dependencies are compatible:
@vitejs/plugin-react 6.x supports Vite 8, resolving the ERESOLVE
conflict from #191 where vite was upgraded alone.