* feat(web): adopt SHOC design system and shell layout (ADR 0003)
Port shoc-frontend-new dev's design system with its CSS-variable
single-token-source mechanism:
- src/styles/theme.css: SHOC token file ported verbatim (Montserrat/
DM Sans/JetBrains Mono, primary #1c75bc, navy #262262, full radius/
shadow/sidebar/header token layers); fonts self-hosted via @fontsource
- src/lib/theme/{css-vars,mui-theme}.ts: getCssVar -> createTheme
adapter mirroring SHOC's mui-theme.ts (palette, typography, shadows
tuple, component overrides; MUI v9 slot renames expressed as class
selectors); theme.ts is now a re-export
- Shell: SHOC composition (sidebar column + sticky gradient topbar +
scrolling main); sidebar 244px/76px collapse with brand header row,
grouped nav, SHOC active treatment (white card + 3px accent bar);
topbar 100-degree gradient, surface hamburger, gradient avatar pill
- Brand: SeahavenMark + BrandLockup ported (Tailwind re-expressed as
sx; wordmark subtitle localized to PROPOSAL SYSTEM)
- Login: SHOC auth-card treatment (centered 384px card on #f9fafb)
- Old "Sea Haven Ops" Inter/#2563EB theme and Nunito remnants removed;
remaining hardcoded hexes replaced with tokens; lucide-react for
shell/nav icons per SHOC convention
Verify: tsc clean, 26/26 vitest, vite build OK; Playwright screenshots
pixel-sampled against the extracted SHOC spec (all hard values exact,
no blocking deviations).
* feat(contracts): adopt shared api-contracts in web, add zod schemas and ProblemDetails codes
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.
* feat(web): domain-layer conventions doc + react-hook-form deps (Phase 4 prep)
* feat(web): scaffold domain module layer (proposals, lineItems, customers, pricingLibrary, admin, sites)
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.
* refactor(web): proposal pages on domain layer, proposal form on react-hook-form
* refactor(web): admin workspace on domain layer
* refactor(web): customer management on domain layer + react-hook-form
* refactor(web): pricing library on domain layer + react-hook-form
* refactor(web): dashboards on domain layer
* test(web): domain use-case hook coverage
* refactor(web): finish domain-layer integration — migrate straggler components, delete legacy api modules
- SimilarProposalsPanel -> useSimilarProposals (domain/admin); inline
SimilarProposal type replaced by domain/admin/types (identical shape);
query key joins the admin hierarchical key space
- LineItemEditor type imports -> domain/lineItems/types
- Delete now-orphaned lib/api/{proposals,lineItems,customers,
pricingLibrary,admin,sites}.ts, constants/queryKeys.ts,
hooks/usePaginatedList.ts (lib/api/client.ts + auth.ts stay per
domain README rule 5)
Verify: tsc clean, vitest 52/52, build OK, Playwright smoke of the
authed shell renders on domain hooks.
* fix(web): apply Phase 4 code-review findings (10 correctness + 4 cleanup)
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.
* refactor(web): fold Redux auth/ui slices into SHOC-shape auth context + storage module
Phase 4 tail of the SHOC-alignment plan. Matches SHOC's auth shape
(lib/auth storage module + providers/ context split) while keeping the
deliberate divergences:
- sessionStorage, not localStorage (WEB-C1 stands; SHOC's localStorage
is on the SHOULD-NOT-ALIGN list)
- token acquisition stays in the auth pages (Cognito code exchange /
dev-login) — the provider only owns session state
- 401 interceptor clears storage directly (WEB-M2 behavior preserved;
full-page redirect resets provider state)
Sidebar open state moves to plain layout state in App passed down as
props (SHOC (protected)/_layout.tsx pattern), keeping localStorage
persistence. Drops @reduxjs/toolkit and react-redux.
Tests: authSlice tests replaced by authStorage + AuthProvider suites
(QA-C5 coverage preserved); client interceptor tests updated for the
storage-based 401 path. 59 vitest green, tsc clean, vite build OK.
Verified end-to-end headless: login redirect, seeded-session shell,
sidebar toggle persistence, logout, expired/malformed token handling,
RoleGuard bounce; recipe persisted as web/.claude/skills/verify.
* fix(web): harden auth session teardown per /sh-security-review findings
- 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.
* docs: web stack row reflects auth-context refactor (Redux removed, MUI v9)
- AUDIT-REPORT.md: mark all Phase 6 findings fixed (API-M2/M5/M7/M9/M10/M12/M13,
WEB-M3/M4/M8/M9/M11, LAM-M2/M3/M6/M9, INF-M1/M2/M9), update test count to 149
- README.md: Function URL NONE→AWS_IAM, add Testing and Security sections,
expand CI table with test jobs, note SQS encryption/OpenSearch VPC/access logging
- Remove stale session docs (AUDIT-2026-05-20, HANDOFF, RETROSPECTIVE, CHATGPT prompt)
- Add .claude/agents/ to .gitignore
- Remove empty-state placeholder from SimilarProposalsPanel
BLOCK-01: Add API Gateway JWT authorizer with Cognito, route internal
Lambda calls through Function URL to bypass gateway auth
BLOCK-02/03: Prevent proposal number race condition with pg_advisory_xact_lock
and filter revision numbers from max-number query
BLOCK-04: Restrict VendorProposals and GeneratedPdfs to admins/sysadmins
BLOCK-05: Sum all vendor costs instead of overwriting with single vendor
BLOCK-06: Enable ValidateAudience on JWT, add Auth env vars to API Lambda
BLOCK-07: Validate ID token signature in AuthController via OIDC discovery
BLOCK-08: Use batchItemFailures in all Lambda SQS handlers
BLOCK-09: Increase SQS visibility timeout from 180s to 720s
FIX-10: Scope dispatcher queries to own proposals (IDOR fix)
Apple review requires a test account login path that doesn't depend on
Google OAuth. Add amazon-cognito-identity-js for direct SRP auth with a
native email/password form on the login screen. Fill in the empty Cognito
client ID and pool ID, fix the Cognito domain prefix, and align CDK
callback URLs with the app's actual URL scheme. Enable push-triggered
mobile deploys, add CDK outputs for client IDs, fix stale README
references, and add mobile/README.md.
- Remove verbose print statements from Lambda handler
- Add dependabot pip entry for oss-index-creator
- Update README with new Lambda and deployed stack state
* Add iOS native project for React Native mobile app
Xcode project with bundle ID com.seahavenind.proposals,
CocoaPods configuration, and app scaffolding.
* Add Fastlane configuration for iOS builds and TestFlight distribution
Configures match with S3 storage (seahaven-ios-certificates bucket)
for code signing and a beta lane for automated TestFlight uploads.
* Add mobile CI job and iOS CD workflow (disabled)
CI: adds mobile typecheck job on PRs.
CD: deploy-mobile.yaml builds and uploads to TestFlight via
Fastlane on a macOS runner with OIDC auth for match S3 access.
Currently workflow_dispatch only — activate for V1 release.
* Refactor workflows to thin wrappers calling org reusable workflows
CI jobs now call ci-dotnet, ci-typescript-cdk, and ci-python-sam
from the org repo. Deploy calls cd-cdk with post-deploy script
for web build/S3/CloudFront. Mobile deploy calls cd-mobile-ios.
Adds deploy concurrency groups to both deploy workflows.
* Add mobile Dependabot entries and remove assignees
Add npm and bundler ecosystems for mobile/. Remove assignees
from all entries — convention no longer in use.
* Add comprehensive README for the proposal-system monorepo
* Fix mobile TypeScript errors and add package-lock.json
Fix tsconfig.json (remove rootDir/outDir, add noEmit), fix useRef
type error, fix navigation type cast, add @types/react-native-vector-icons,
and generate package-lock.json for CI.
* Add .npmrc for mobile to resolve peer dependency conflicts
react-native-screens@4.x requires react-native >= 0.82 but the
project uses 0.79. legacy-peer-deps allows installation until
the next React Native upgrade.