40227-vm/frontend/docs/frontend-architecture.md
2026-06-12 06:55:35 +02:00

225 lines
14 KiB
Markdown

# Frontend Architecture
## Purpose
The frontend uses a three-layer architecture:
- View layer
- Business logic layer
- API/data access layer
The goal is to keep UI components thin, keep business rules testable, and keep all backend communication centralized.
## Layer 1: View
Location:
- `frontend/src/components/`
- `frontend/src/pages/`
Responsibilities:
- Render UI.
- Own visual composition and layout.
- Call business-layer hooks/actions.
- Show loading, empty, and error states from business-layer state.
- Keep form markup and user interaction wiring close to the component.
- Compose shared UI primitives from `frontend/src/components/ui/` for common controls and repeated states.
View components must not:
- Call `fetch` directly.
- Import backend API modules directly.
- Contain tenant, role, permission, or workflow rules.
- Transform backend DTOs into product state when that transformation belongs to business logic.
- Hide failed persisted workflows with static data.
## Layer 2: Business Logic
Location:
- `frontend/src/business/`
- `frontend/src/shared/business/`
Responsibilities:
- Own React Query hooks for server state.
- Own local workflow state when it is not purely visual.
- Transform backend DTOs into UI-facing view models.
- Apply role, permission, tenant, campus, and workflow rules received from backend contracts.
- Own calculations, filters, sorting, validation helpers, and derived state.
- Coordinate multiple API calls for one user workflow.
Business logic may import:
- `frontend/src/shared/api/`
- `frontend/src/shared/types/`
- `frontend/src/shared/constants/`
- `frontend/src/shared/business/`
Business logic must not:
- Render JSX.
- Read or write secrets.
- Duplicate backend authorization or tenant enforcement.
- Create another HTTP client.
Shared business helpers should be used for repeated cross-module mechanics. `frontend/src/shared/business/queryMutations.ts` centralizes React Query mutation invalidation, `frontend/src/shared/business/apiListRows.ts` centralizes `ApiListResponse.rows` extraction and mapping, and `frontend/src/shared/business/queryState.ts` centralizes multi-query loading/error aggregation.
## Layer 3: API/Data Access
Location:
- `frontend/src/shared/api/`
- `frontend/src/shared/types/`
Responsibilities:
- Own all HTTP calls to `backend/`.
- Send backend-owned auth cookies through the shared HTTP client.
- Define request and response types.
- Preserve backend errors and expose them to business logic.
- Keep endpoint paths and payload shapes in one place.
API/data access modules must not:
- Render UI.
- Own component state.
- Apply product workflow decisions that belong to business logic.
- Return mock/static data for persisted workflows.
- Swallow backend failures.
## Import Direction
Allowed direction:
```text
View -> Business Logic -> API/Data Access -> Backend
```
Shared constants and shared types may be imported by any layer.
## Error Handling
Backend/API errors are parsed in `frontend/src/shared/api/httpClient.ts` and normalized for UI rendering through `frontend/src/shared/errors/errorMessages.ts`.
Feature hooks and components must use the shared error formatter instead of reading `.message` directly or creating local formatter helpers. Detailed rules live in `frontend/docs/error-handling.md`.
`npm run test` includes `frontend/src/shared/architecture/import-boundaries.test.ts`, which enforces these import boundaries and keeps runtime data access centralized in the shared API layer.
Disallowed direction:
```text
API/Data Access -> Business Logic
API/Data Access -> View
Business Logic -> View
```
## Feature Structure
For new or updated product modules, use this shape:
```text
frontend/src/business/<module>/
hooks.ts
mappers.ts
selectors.ts
validators.ts
types.ts
frontend/src/shared/api/<module>.ts
frontend/src/shared/types/<module>.ts
frontend/src/components/<module>/
```
Only create files that are actually needed for the module.
## Routing
The frontend uses React Router. Top-level URL route declarations live in a typed object route config instead of inline JSX in `App.tsx`.
Target structure:
```text
frontend/src/app/AppProviders.tsx
frontend/src/app/AppRouter.tsx
frontend/src/app/appRoutes.tsx
frontend/src/app/ModuleRouteGuard.tsx
frontend/src/app/shellOutletContext.ts
frontend/src/shared/constants/routes.ts
frontend/src/shared/constants/moduleRoutes.ts
```
Rules:
- `App.tsx` composes providers and renders the router only.
- `appRoutes.tsx` owns top-level and product URL route objects.
- Product routes are lazy-loaded route elements under the shared app shell layout.
- Route page adapters stay thin and delegate product behavior to business hooks.
- `AppRouter.tsx` uses `useRoutes(appRoutes)`.
- `APP_ROUTE_PATHS` owns path constants and module route metadata maps each `ModuleId` to exactly one route path.
- The browser URL is the source of truth for the active product module.
- Sidebar, footer, dashboard actions, and other module navigation should navigate by route path instead of storing active module state.
- `/login` remains the deterministic destination for expired access plus expired refresh sessions.
- Restricted module routes redirect to `/dashboard`.
## Update Rule
When updating an existing module:
1. Add or update backend API endpoints first.
2. Add typed API functions in `frontend/src/shared/api/`.
3. Add business hooks and mappers in `frontend/src/business/<module>/`.
4. Update view components to call the business layer.
5. Remove direct data access from the component.
6. Add or update module documentation.
## Current Baseline
The active frontend already has:
- React 19, Vite 8, TypeScript 6, Tailwind 4, Vitest 4, and ESLint 10 as the current active tooling baseline.
- Current top-level URL routes are `/`, `/login`, and `*`; product module routes are nested under the shared shell layout and lazy-loaded from `frontend/src/pages/modules/`.
- View components under `frontend/src/components/` and `frontend/src/pages/`.
- Shared backend API foundation under `frontend/src/shared/api/`.
- Shared frontend constants and types under `frontend/src/shared/`.
- Cross-module UI-facing product types under `frontend/src/shared/types/app.ts`.
- Static app navigation/media config under `frontend/src/shared/constants/appData.ts` and static personality catalog metadata under `frontend/src/shared/constants/personalityCatalog.ts`.
- Backend-owned campus records and branding load through `frontend/src/shared/api/campuses.ts` and `frontend/src/business/campuses/`; frontend keeps only generic campus helpers and allowed Tailwind branding tokens.
- Test-only seed records under `frontend/src/test-seeds/`; runtime code must not import that directory.
- Theme names, default theme values, CSS class names, and media query constants under `frontend/src/shared/constants/theme.ts`; global light/dark CSS tokens remain in `frontend/src/index.css` and Tailwind maps to those variables in `frontend/tailwind.config.ts`.
- React Query keys, UI timing values, storage keys, and sidebar runtime constants live in dedicated files under `frontend/src/shared/constants/`.
- Auth/profile session logic under `frontend/src/business/auth/`, with `AuthContext` acting as a thin provider. The auth transport is backend-owned HttpOnly cookie auth documented in `backend/docs/cookie-auth.md` and `frontend/docs/auth-integration.md`.
- Frontend authorization is permission-aware and UX-only (the backend is the sole authority): a typed permission catalog (`frontend/src/shared/auth/permissions.ts`), `globalAccess`-aware selectors + the `usePermissions()` hook (`frontend/src/hooks/usePermissions.ts`), and the `<PermissionGate>` affordance gate (`frontend/src/components/auth/PermissionGate.tsx`). The UI role derives from `app_role.name` (the 11 first-class roles); there is no `productRole`.
- An `AuthGuard` (`frontend/src/app/AuthGuard.tsx`) gates the shell — unauthenticated users redirect to `/login`; `ModuleRouteGuard` renders the 404 page for a forbidden direct URL; `IndexRedirect` lands each role on its first accessible module. The previous in-shell guest-preview experience was removed.
- App shell state, access selection, campus display lookup, mobile overlay visibility, shell outlet context, and prepared Sidebar/TopBar/Footer props live under `frontend/src/business/app-shell/`. The shared shell layout remains a thin view composition in `frontend/src/components/AppLayout.tsx`.
- Top bar shell state under `frontend/src/business/top-bar/`, with search, badges, notifications, and profile menu composition split under `frontend/src/components/top-bar/`.
- FRAME entries under `frontend/src/business/frame/`, with typed API calls in `frontend/src/shared/api/frame.ts` and explicit empty/error states in the view.
- Current-user progress under `frontend/src/business/user-progress/`, with typed API calls in `frontend/src/shared/api/userProgress.ts` for learned signs and zone check-ins.
- Safety quiz results under `frontend/src/business/safety-quiz/`, with typed API calls in `frontend/src/shared/api/safetyQuizResults.ts`.
- Walk-through check-ins under `frontend/src/business/walkthrough/`, with typed API calls in `frontend/src/shared/api/walkthrough.ts`, shared constants in `frontend/src/shared/constants/walkthrough.ts`, and summary calculations in typed selectors.
- Communications under `frontend/src/business/communications/`, with typed API calls in `frontend/src/shared/api/communications.ts` for parent messages, internal alerts, and dashboard upcoming events.
- EI/personality results under `frontend/src/business/personality/`, with typed API calls in `frontend/src/shared/api/personality.ts`, DTO mappers, distribution selectors, workflow-specific hook files, and explicit loading/error states in the view.
- Campus attendance config and daily summaries under `frontend/src/business/campus-attendance/`, with typed API calls in `frontend/src/shared/api/campusAttendance.ts`, DTO mappers, summary selectors, and explicit loading/error states in the view.
- Staff attendance snapshot and director staff counts under `frontend/src/business/staff-attendance/`, with typed API calls in `frontend/src/shared/api/staffAttendance.ts`, DTO mappers, rollup selectors, and explicit loading/error states in the view.
- Handbook policies and safety protocols under `frontend/src/business/policies/` and `frontend/src/business/safety-protocols/`, backed by the unified `policy_documents` store (`frontend/src/shared/api/policyDocuments.ts` + `policyAcknowledgments.ts`), with DTO mappers, selectors, persistent acknowledgment, and explicit loading/error states in the view.
- Classroom timer sounds are a unified library: hardcoded built-ins (local Web Audio), generated `recipe` rows, and uploaded `file`/`url` rows from `frontend/src/business/audio-files/`. Generation uses a local stub (`business/audio-files/generate.ts`) pending an AI key; playback branches by kind.
- UI component variants live in dedicated non-component files such as `frontend/src/components/ui/button-variants.ts`, `badge-variants.ts`, `toggle-variants.ts`, and `navigation-menu-variants.ts`.
- Loading, empty, and error state panels are centralized through `frontend/src/components/ui/state-panel.tsx`, with tone/size/alignment variants in `frontend/src/components/ui/state-panel-variants.ts`.
- Repeated module headings use `frontend/src/components/ui/module-header.tsx`; simple native dropdowns use `frontend/src/components/ui/native-select.tsx`.
- Reusable UI/context hooks live outside provider component files, including `frontend/src/components/theme-context.ts`, `frontend/src/components/ui/form-field-context.ts`, `frontend/src/components/ui/sidebar-context.ts`, and `frontend/src/contexts/auth-context.ts`.
- The tracked large framework components have been split into thin wrappers and focused view pieces backed by business hooks/selectors.
- Frontend TypeScript runs in strict mode through `npm run typecheck`; `npm run build` runs typecheck before Vite.
- Frontend unit tests run through `npm run test` with Vitest. Current coverage includes business-layer selectors and mappers for app-shell/sidebar, auth, campuses, campus attendance, classroom support, classroom timer, communications, community, dashboard, director dashboard, ESA funding, FRAME, personality, policies, safety quiz, sign language, staff attendance, top bar, user progress, vocational, walk-through check-in/summary/form workflows, and zones.
- `npm run test` also enforces API/business/view import boundaries through `frontend/src/shared/architecture/import-boundaries.test.ts`.
- Frontend backend-free smoke tests run through `npm run test:e2e` with Playwright.
- Frontend backend-seeded tests run through `npm run test:e2e:content` with Playwright after backend migrations, seeders, and the backend server are running — covering the content catalog and authenticated **RBAC access** (`tests/e2e/rbac-access.seeded.e2e.ts`: per-role route access plus API enforcement of the permission/relational policy, using the seeded fixture users).
- Frontend dependency verification is clean: `npm run lint`, `npm run test`, `npm run build`, `npm audit --audit-level=low`, and `npm outdated` pass for stable package releases.
## Known Remaining Gaps
- New or changed framework wrappers should follow the same thin-view plus business-hook pattern.
- New product routes should be added to module route metadata, `frontend/src/app/appRoutes.tsx`, and covered by route metadata tests.
- TypeScript compiler strictness is enabled for the current baseline. Keep future slices compatible with `strict`, `noUnusedLocals`, and `noUnusedParameters`.
- Unit test coverage exists for route config, module route metadata (incl. role-aware landing), permission selectors, API/data-access behavior, auth refresh/retry behavior, business-layer selector/mapper/report slices, and import-boundary guardrails. Backend-seeded Playwright tests cover authenticated per-role RBAC access and API enforcement.