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

14 KiB

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:

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:

API/Data Access -> Business Logic
API/Data Access -> View
Business Logic -> View

Feature Structure

For new or updated product modules, use this shape:

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:

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.