Files
gridpilot.gg/docs/architecture/website/WEBSITE_CONTRACT.md
2026-01-12 19:24:59 +01:00

8.9 KiB

Website Architecture Contract (Strict)

This document is the authoritative contract for apps/website.

If any other website document conflicts with this one, this one wins.

1) Purpose (non-negotiable)

The website is a delivery layer.

It does not:

  • contain business rules
  • make authorization decisions
  • own or persist business truth

It only:

  • renders truth from apps/api
  • collects user intent
  • forwards user intent to apps/api

The API is the single source of truth.

2) System context (hard boundary)

The website never bypasses the API.

Browser
  ↓
Next.js App Router (RSC + Server Actions)
  ↓
HTTP
  ↓
Backend API (Use Cases, Domain, Database)

3) Website presentation model types (strict)

3.1 API Transport DTO

Definition: the shape returned by the backend API over HTTP.

Rules:

  • API Transport DTOs MUST be contained inside infrastructure.
  • API Transport DTOs MUST NOT be imported by Templates.

Canonical placement in this repo:

  • apps/website/lib/types/** (transport DTOs consumed by services and page queries)

3.2 API Transport DTO

Definition: the shape returned by the backend API over HTTP.

Rules:

  • API Transport DTOs MUST be contained inside infrastructure.
  • API Transport DTOs MUST NOT be imported by Templates.

Canonical placement in this repo:

  • apps/website/lib/types/** (transport DTOs consumed by services and page queries)

3.3 ViewData

Definition: the only allowed input type for Templates.

Rules:

  • JSON-serializable only.
  • Contains only template-ready values (mostly strings/numbers/booleans).
  • MUST NOT contain class instances.

See VIEW_DATA.md.

Canonical placement in this repo:

  • apps/website/templates/** (Templates that accept ViewData only)

3.3 ViewModel

Definition: the client-only, UI-owned class representing fully prepared UI state.

Rules:

  • Instantiated only in 'use client' modules.
  • Never serialized.
  • Used for client components that need state management.

See VIEW_MODELS.md.

Canonical placement in this repo:

  • apps/website/lib/view-models/**

4) Data transformation helpers (strict)

4.1 ViewModel Builder

Definition: transforms API Transport DTOs into ViewModels.

Purpose: prepare raw API data for client-side state management.

Rules:

  • MUST be deterministic.
  • MUST be side-effect free.
  • MUST NOT call HTTP.
  • MUST NOT call the API.
  • Input: API Transport DTO
  • Output: ViewModel

See BUILDERS.md.

Canonical placement in this repo:

  • apps/website/lib/builders/view-models/**

4.2 ViewData Builder

Definition: transforms API DTOs directly into ViewData for templates.

Purpose: prepare API data for server-side rendering.

Rules:

  • MUST be deterministic.
  • MUST be side-effect free.
  • MUST NOT call HTTP.
  • MUST NOT call the API.
  • Input: API Transport DTO
  • Output: ViewData

See BUILDERS.md.

Canonical placement in this repo:

  • apps/website/lib/builders/view-data/**

4.3 Display Object

Definition: deterministic, reusable, UI-only formatting/mapping logic.

Rules:

  • Class-based, immutable, deterministic.
  • MUST NOT call Intl.*.
  • MUST NOT call any toLocale*.
  • MUST NOT implement business rules.

See DISPLAY_OBJECTS.md.

Canonical placement in this repo:

  • apps/website/lib/display-objects/**

5) Read flow (strict)

Server Components (RSC)

RSC page.tsx
  ↓
PageQuery
  ↓
API client (infra)
  ↓
API Transport DTO
  ↓
ViewData Builder (lib/builders/view-data/)
  ↓
ViewData
  ↓
Template

Client Components

Client Component
  ↓
API client (useEffect)
  ↓
API Transport DTO
  ↓
ViewModel Builder (lib/builders/view-models/)
  ↓
ViewModel (lib/view-models/)
  ↓
Client State (useState)
  ↓
Template

6) Write flow (strict)

All writes MUST enter through Next.js Server Actions.

Forbidden:

  • client components performing write HTTP requests
  • client components calling API clients for mutations

Allowed:

  • client submits intent (FormData, button action)
  • server action performs UX validation
  • server action calls a service (not API clients directly)
  • service orchestrates API calls and business logic

Server Actions must use Services:

// ❌ WRONG - Direct API client usage
'use server';
import { AdminApiClient } from '@/lib/api/admin/AdminApiClient';

export async function updateUserStatus(userId: string, status: string) {
  const apiClient = new AdminApiClient(...);
  await apiClient.updateUserStatus(userId, status); // ❌ Should use service
}

// ✅ CORRECT - Service usage
'use server';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { AdminApiClient } from '@/lib/api/admin/AdminApiClient';
import { AdminService } from '@/lib/services/admin/AdminService';
import { revalidatePath } from 'next/cache';

export async function updateUserStatus(userId: string, status: string) {
  try {
    // Create infrastructure
    const logger = new ConsoleLogger();
    const errorReporter = new EnhancedErrorReporter(logger, {
      showUserNotifications: true,
      logToConsole: true,
      reportToExternal: process.env.NODE_ENV === 'production',
    });
    
    const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
    const apiClient = new AdminApiClient(baseUrl, errorReporter, logger);
    const service = new AdminService(apiClient);
    
    // Use service
    await service.updateUserStatus(userId, status);
    
    // Revalidate
    revalidatePath('/admin/users');
  } catch (error) {
    console.error('updateUserStatus failed:', error);
    throw new Error('Failed to update user status');
  }
}

Pattern:

  1. Server action creates infrastructure (logger, errorReporter, apiClient)
  2. Server action creates service with infrastructure
  3. Server action calls service method
  4. Server action handles revalidation and returns

Rationale:

  • Services orchestrate API calls (can grow to multiple calls)
  • Keeps server actions consistent with PageQueries
  • Makes infrastructure explicit and testable
  • Services can add caching, retries, transformations

See FORM_SUBMISSION.md and SERVICES.md.

7) Authorization (strict)

  • The website may hide/disable UI for UX.
  • The website MUST NOT enforce security.
  • The API enforces authentication and authorization.

See docs/architecture/shared/BLOCKERS_AND_GUARDS.md and docs/architecture/website/BLOCKERS.md.

7.1) Client state (strict)

Client-side state is allowed only for UI concerns.

Allowed:

  • selection
  • open/closed dialogs
  • transient form state
  • optimistic flags and loading spinners

Forbidden:

  • treating client state as business truth
  • using client state as an authorization decision
  • persisting client state as the source of truth

Hard rule:

  • any truth returned by the API MUST overwrite client assumptions.

Canonical placement in this repo:

  • apps/website/lib/blockers/** for UX-only prevention helpers
  • apps/website/lib/hooks/** for React-only utilities
  • apps/website/lib/command-models/** for transient form models

See CLIENT_STATE.md.

8) DI contract (Inversify) (strict)

The DI system under apps/website/lib/di/index.ts is client-first.

Server execution is concurrent. Any shared singleton container can leak cross-request state.

Rules:

  1. Server app/**/page.tsx MUST NOT access the container.
  2. Page Queries SHOULD prefer manual wiring.
  3. Client modules MAY use DI via ContainerProvider and hooks.
  4. ContainerManager.getContainer() is client-only.
  5. Any server DI usage MUST be request-scoped (a fresh container per request).

Hard constraint:

  • A singleton Inversify container MUST NOT be used to serve concurrent server requests.

See WEBSITE_DI_RULES.md.

9) Non-negotiable rules (final)

  1. The API is the brain.
  2. The website is a terminal.
  3. API Transport DTOs never reach Templates.
  4. Templates accept ViewData only.
  5. Page Queries do not format; they only compose.
  6. ViewData Builders transform API DTO → ViewData (RSC).
  7. ViewModel Builders transform API DTO → ViewModel (Client).
  8. Builders are pure and deterministic.
  9. Server Actions are the only write entry point.
  10. Server Actions must use Mutations (not Services directly).
  11. Mutations orchestrate Services for writes.
  12. Authorization always belongs to the API.