Files
gridpilot.gg/docs/architecture/website/FORMATTERS.md
2026-01-24 01:22:43 +01:00

3.2 KiB

Displays & Formatters

Definition

A Display encapsulates reusable, UI-only display logic.

In this codebase, we distinguish between Formatters (Stateless Logic) and Display Objects (Rich Value Objects).

1. Formatters (The "Mouth")

Formatters are pure, stateless utilities. They are the "Experts" on how to transform a raw value into a primitive string/number.

  • Usage: Used by ViewDataBuilders (Server) and ViewModels (Client).
  • Output: MUST return primitive values only (string, number, boolean, null).
  • Uncle Bob says: "Data structures (ViewData) should not have behavior. Keep logic in stateless utilities."

2. Display Objects (The "Rich API")

Display Objects are logic-rich Value Objects that live only on the client. They wrap data and provide multiple ways to look at it.

  • Usage: Used by ViewModels (Client) to provide a rich API to the UI.
  • Output: Can return complex objects or variants.
  • Uncle Bob says: "Objects expose behavior, not data. Use them to hide the complexity of the UI."

Responsibilities

A Display/Formatter MAY:

  • format values (money, dates, durations)
  • handle deterministic localization (mapping stable codes to labels)
  • encapsulate UI display conventions

A Display/Formatter MUST:

  • be deterministic
  • be side-effect free
  • be implemented as a class with static methods (Formatters) or as immutable classes (Display Objects)

Restrictions

A Display/Formatter MUST NOT:

  • contain business logic (e.g., "Team is full if count > 10")
  • enforce domain invariants
  • perform validation
  • be serialized (only their primitive outputs are stored in ViewData)
  • call Intl.* or toLocale* (unless explicitly marked for client-only ViewModels)

Relationship to ViewData and ViewModels

The "Primitive Compact" (Server-Side)

ViewDataBuilders MUST use Formatters to produce flat, serializable ViewData.

  • Rule: ViewData properties assigned from a Display/Formatter MUST be primitives.
  • Reason: Ensures ViewData remains a "dumb" JSON structure for SSR.

The "Rich API" (Client-Side)

ViewModels MAY use Display Objects to provide interactive formatting.

  • Rule: ViewModels can return Display Object instances to the UI.
  • Reason: Allows the UI to access multiple variants (e.g., date.short, date.relative) without re-fetching data.

Summary of the Flow

graph TD
    DTO[Raw DTO] -->|ViewDataBuilder| VD[ViewData]
    subgraph "Server: The Formatter Compact"
    VD -->|Uses| F[Stateless Formatter]
    F -->|Returns| S[Primitive string/number]
    end
    
    VD -->|SSR Boundary| T[Template]
    
    subgraph "Client: The DisplayObject Richness"
    T -->|Props| CW[ClientWrapper]
    CW -->|new| VM[ViewModel]
    VM -->|Wraps| DO[Rich Display Object]
    DO -->|Provides| R[Rich API: .time, .relative, .date]
    end

Final Rule: Where does logic live?

  1. Is it a business rule? (e.g., "Can join?") → ViewModel.
  2. Is it a formatting rule? (e.g., "How to show date?") → Formatter/Display.
  3. Is it for SEO/SSR?ViewDataBuilder (using a Formatter).
  4. Is it for interaction?ViewModel (using a Display Object).