Files
gridpilot.gg/docs/architecture/website/WEBSITE_CONTRACT.md
2026-01-11 14:42:54 +01:00

6.0 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 Page DTO

Definition: the website-owned, server-to-client payload for a route.

Rules:

  • JSON-serializable only.
  • Contains raw values only (IDs, ISO strings, numbers, codes).
  • MUST NOT contain class instances.
  • Created by Page Queries.
  • Passed from server routes into client code.

Canonical placement in this repo:

  • apps/website/lib/page-queries/** (composition and Page DTO construction)

3.3 ViewModel

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

Rules:

  • Instantiated only in 'use client' modules.
  • Never serialized.
  • MUST NOT be passed into Templates.

See VIEW_MODELS.md.

Canonical placement in this repo:

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

3.4 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)

4) Presentation helpers (strict)

4.1 Presenter

Definition: a deterministic, side-effect free transformation.

Presenters map between website presentation models:

  • Page DTO → ViewData
  • Page DTO → ViewModel
  • ViewModel → ViewData

Rules:

  • MUST be deterministic.
  • MUST be side-effect free.
  • MUST NOT call HTTP.
  • MUST NOT call the API.
  • MAY use Display Objects.

See PRESENTERS.md.

Canonical placement in this repo:

  • colocated with ViewModels in apps/website/lib/view-models/**

4.2 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)

RSC page.tsx
  ↓
PageQuery (server)
  ↓
API service / API client (infra)
  ↓
API Transport DTO
  ↓
Page DTO
  ↓
Presenter (client)
  ↓
ViewModel (optional, client)
  ↓
Presenter (client)
  ↓
ViewData
  ↓
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 the API

See FORM_SUBMISSION.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. ViewModels never go to the API.
  5. Templates accept ViewData only.
  6. Page Queries do not format; they only compose.
  7. Presenters are pure and deterministic.
  8. Server Actions are the only write entry point.
  9. Authorization always belongs to the API.