# Conflicts: # e2e/__screenshots__/dashboard/dashboard.visual.spec.ts/dashboard-admin.png # e2e/__screenshots__/dashboard/dashboard.visual.spec.ts/dashboard-dispatcher.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-add.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-detail.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-edit.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-empty.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-error.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-filter.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-inactive.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-list.png # e2e/__screenshots__/vendors/vendors.visual.spec.ts/vendor-mobile-navigation.png # e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-detail.png # e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-empty.png # e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-error.png # e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-filters.png # e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-list.png # e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-mobile-navigation.png # e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-new.png |
||
|---|---|---|
| .cursor/rules | ||
| .github | ||
| .husky | ||
| config | ||
| docs | ||
| e2e | ||
| eslint-rules | ||
| public | ||
| scripts | ||
| src | ||
| terraform | ||
| .env | ||
| .env.development | ||
| .env.example | ||
| .env.production | ||
| .gitignore | ||
| .prettierignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| ARCHITECTURE_AND_CODE_QUALITY.md | ||
| commitlint.config.js | ||
| eslint.config.js | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| playwright.visual.config.ts | ||
| QUALITY_GATES.md | ||
| README.md | ||
| REVIEW_AND_PR_FRAMEWORK.md | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
SHOC Frontend (shoc-frontend-new)
Vite + React SPA for Sea Haven facility management (SHOC): work orders, vendor
portal, uplifts, and related admin features. This is the selective rebuild of
the legacy SHOC frontend — new code follows the IrisLoan.Admin conventions
documented in docs/ARCHITECTURE_PLAN.md.
- GitHub:
Sea-Haven-Industries/shoc-frontend-new - Hosted at: https://dev.seahaven.com (dev, deployed from
main) and https://staging.seahaven.com (staging, deployed fromvX.Y.Z-stagingtags) - Backend API:
https://api.dev.seahaven.com/api(called directly, cross-origin) — source:Sea-Haven-Industries/shoc-backend
Architecture
Static SPA hosting on AWS, owned by HCP Terraform
(terraform/README.md). CloudFront serves the built
dist/ from a private S3 bucket at the bucket root;
the SPA calls the backend directly over HTTPS at VITE_API_URL (no /api
proxy at the CDN — the backend allows CORS).
graph LR
U[Browser] -->|HTTPS dev.seahaven.com| CF[CloudFront]
CF -->|OAC bucket root| S3[S3 seahaven-shoc-frontend-dev]
CF -.->|viewer-request fn| FN[SPA rewrite → /index.html]
U -->|HTTPS api.dev.seahaven.com/api CORS| API[SHOC backend API]
GH[GitHub Actions] -->|OIDC s3 sync dist/| S3
TF[HCP Terraform] -->|bucket CloudFront IAM SSM| CF
Dev and staging hosting live in terraform/live/dev and
terraform/live/staging. GitHub .github/workflows/deploy-web.yaml syncs
content.
Frontend stack: React 19, TypeScript, Vite, Tailwind CSS 4 + MUI, TanStack
Query, React Router (via @generouted/react-router), React Hook Form + Zod,
Ky HTTP client. Source layout: src/api/, src/domain/, src/app/ (see the
architecture plan for the keep/discard migration matrix).
AWS Resources
HCP workspace shoc-frontend-new-dev — account 396287094661, region
us-east-1. Defined in terraform/live/dev.
| Resource | Name | Purpose |
|---|---|---|
| S3 bucket | seahaven-shoc-frontend-dev |
Private origin (BLOCK_ALL, SSE, versioned; OAC-only reads) |
| CloudFront distribution | (stack output DistributionId) |
HTTPS static hosting on dev.seahaven.com, ACM *.seahaven.com |
| CloudFront Function | SpaRewrite |
Viewer-request rewrite of extensionless paths to /index.html (deep links) |
| IAM role | githubdeploy-shoc-frontend-new-dev |
GitHub Actions OIDC deploy role, trust scoped to Environment dev plus deploy-web.yaml |
| Route 53 records | A/AAAA apex alias in zone dev.seahaven.com (Z07671212N75U4YLPWZR8) |
Points the custom domain at CloudFront |
No Lambdas, queues, or databases — this stack is static hosting only.
Configuration
Secrets
No Secrets Manager. Deploy looks up /shoc-frontend-new/<env>/deploy/{bucket,distribution-id}
after assuming DEPLOY_ROLE_ARN. AWS access is OIDC only. The one GitHub Actions
repo secret is:
| Secret | Purpose |
|---|---|
SENTRY_AUTH_TOKEN |
Source-map upload by scripts/upload-sourcemaps.sh after a deploy |
Environment variables (build-time, VITE_*)
| Variable | Description | Dev value |
|---|---|---|
VITE_API_URL |
Ky API base prefix, baked into the build at vite build |
/api (dev server) / https://api.dev.seahaven.com/api (production build) |
VITE_API_TARGET |
Dev-proxy target for /api (Vite dev server only) |
http://localhost:5141 |
VITE_API_URL supplies the full API prefix — route paths in API_PATHS do
not include /api. Absolute values must end with /api;
vite.config.ts enforces this via config/api-url-contract.ts and fails the
build otherwise. See .env.example,
.env.development, and .env.production.
Pinned hosting constants (domain, certificate ARN, hosted zone) live in
terraform/live/dev/main.tf.
Local Development
Requirements: Node.js ≥ 22.22.1 (CI/CD run Node 24), npm 11.16.0 (pinned via
packageManager).
npm ci
cp .env.example .env # then set VITE_API_URL=/api for local dev
npm run dev # Vite dev server on port 3000, proxies /api → VITE_API_TARGET
The dev proxy expects the shoc-backend API at http://localhost:5141;
override with VITE_API_TARGET (e.g. https://api.dev.seahaven.com to use
the deployed dev API).
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server on port 3000 |
npm run build |
Type-check (tsc -b) and production build to dist/ |
npm run preview |
Preview the production build locally |
npm test / npm run test:watch |
Vitest unit tests (once / watch) |
npm run test:e2e / npm run test:e2e:ui |
Playwright e2e tests (headless / UI mode) |
npm run lint / npm run lint:fix |
ESLint (check / auto-fix) |
npm run format / npm run format:check |
Prettier (write / check) |
npm run governance |
Governance checks (godfile, maintainability, Terraform, CD guards) |
npm run verify |
All gates: format + lint + build + test + governance |
npm run governance needs terraform and python3 on PATH for the
Terraform and content-CD gates.
Husky + lint-staged run ESLint and Prettier on staged files at commit;
commitlint enforces conventional commit messages. Run npx tsc --noEmit (or
npm run build) before pushing to catch type errors early.
Contributing
- Branch from
mainwith a kebab-case description and a prefix matching the work:feature/,fix/,hotfix/,chore/,docs/, orrefactor/(e.g.feature/vendor-portal-filters,chore/sea-haven-branding). - Commit messages follow Conventional Commits — commitlint rejects anything else at commit time.
- Open PRs against
main. The PR body uses the three-section layout the template pre-fills: Summary, Changes and value, Ticket.mainneeds theci-completecheck and an approving review from a code owner (@Sea-Haven-Industries/internal-dev); new pushes dismiss stale approvals. PRs merge through the merge queue, so a branch does not need to be updated withmainbefore it merges. Merged branches are deleted automatically. - A change set cannot mix
terraform/with deployable application files (G13), including each queued PR on the merge-group check. Workflow, docs, and gate-script changes may travel with either side.deploy-web.yamlstill ignoresterraform/**so a Terraform-only merge does not sync the bucket. - Promotion flow: merge to
maindeploysdev.seahaven.com. A person cutsvX.Y.Z-stagingforstaging.seahaven.com. CorevX.Y.Zwaits until a prod distribution exists.
Deployment
No stored AWS keys — OIDC only. Infrastructure and content deploy separately:
- CI (
.github/workflows/ci.yaml) — on push and PRs tomain, runs format, lint, build, sharded unit tests, visual regression, Playwright smoke, andnpm run governanceas parallel jobs, thenci-complete. Conventions and gates are documented underAGENTS.md,QUALITY_GATES.md,ARCHITECTURE_AND_CODE_QUALITY.md, andREVIEW_AND_PR_FRAMEWORK.md. - SPA content (
.github/workflows/deploy-web.yaml) — push tomaindeploysdev; a publishedvX.Y.Z-stagingrelease deploysstaging. Syncsdist/to the bucket root and invalidates/*. - Infrastructure — HCP workspaces
shoc-frontend-new-devandshoc-frontend-new-staging(terraform/README.md).
Operations
- Verify: open https://dev.seahaven.com after a green Deploy Web run in the Actions tab; confirm a deep link (e.g. a work-orders route) loads directly and API calls succeed.
- Logs: deploy logs live in GitHub Actions (CI + Deploy workflows). There are no CloudWatch application logs — the stack is static hosting; runtime errors surface in the browser and on the backend API's side.
- Common failure modes:
- Stale content after deploy — CloudFront is still
InProgressor an edge still serves the previousindex.htmlhash. Read the live-state summary before assuming the site is down. - OIDC
AssumeRoleerrors — the trust policy is scoped to Environmentdevorstagingplusdeploy-web.yaml. A job withoutenvironment:cannot assume the role. - Broken API requests after a build —
VITE_API_URLmissing the/apisuffix or carrying the wrong environment's host (it is baked in at build time). - CORS errors — the backend must allow the frontend origin; CloudFront does
not proxy
/api. - Non-empty origin path —
deploy-web.yamlrefuses to sync until Terraform has moved every origin to the bucket root.
- Stale content after deploy — CloudFront is still
Documentation
- Terraform runbook:
terraform/README.md - Rebuild strategy and conventions:
docs/ARCHITECTURE_PLAN.md; design system and UI docs underdocs/