Files
gridpilot.gg/docs/architecture/website/DISPLAY_OBJECTS.md
2026-01-18 23:55:26 +01:00

6.3 KiB

Displays

Definition

A Display encapsulates reusable, UI-only display logic.

In this codebase, a Display is a Frontend Value Object:

  • class-based
  • immutable
  • deterministic
  • side-effect free

Distinction from Domain Value Objects

While both are "Value Objects", they serve different layers:

  1. Domain Value Objects (Core): Encapsulate business truth and invariants (e.g., Money, EmailAddress). They are pure and never contain formatting logic.
  2. Display Objects (Website): Encapsulate presentation truth and formatting (e.g., PriceDisplay, DateDisplay). They are used to transform raw data or Domain Value Objects into user-ready strings.

It answers the question:

“How should this specific piece of information be shown?”

Displays are not screen-specific. They exist to avoid duplicating presentation logic across View Models.

Naming Convention:

  • Displays MUST end with Display suffix
  • Displays MUST be reusable across multiple screens
  • Valid examples: PriceDisplay, EmailDisplay, RatingDisplay
  • Invalid examples: DashboardRatingDisplay, UserProfileDisplay

Responsibilities

A Display MAY:

  • format values (money, dates, durations)
  • handle localization only when localization inputs are deterministic (for example: mapping stable codes to stable labels)
  • map codes to labels
  • encapsulate UI display conventions
  • be reused across multiple View Models

In addition, a Display MAY:

  • normalize presentation inputs (for example trimming/casing)
  • expose multiple explicit display variants (for example shortLabel, longLabel)

A Display MUST:

  • be deterministic
  • be side-effect free
  • operate only on presentation data

A Display MUST:

  • be implemented as a class with a small, explicit API
  • accept only primitives/plain data in its constructor (or static factory)
  • expose only primitive outputs (strings/numbers/booleans)

Restrictions

A Display MUST NOT:

  • contain business logic
  • enforce domain invariants
  • perform validation
  • influence system behavior
  • be sent back to the server
  • depend on backend or infrastructure concerns
  • depend on environment-specific APIs (e.g., window, document, navigator)
  • be serialized (they are classes; only their primitive outputs are stored in ViewData)

In this repository, a Display MUST NOT:

  • call Intl.*
  • call Date.toLocaleString() / Date.toLocaleDateString() / Date.toLocaleTimeString()

Reason: these are runtime-locale/timezone dependent and cause SSR/hydration mismatches.

Handling Client-Only Formatting

If a formatting requirement strictly requires client-only APIs (e.g., browser-native relative time or local timezone detection):

  1. It MUST NOT live in a Display Object.
  2. It SHOULD live in a View Model (which is client-only).
  3. The Template should handle the transition from server-provided ViewData to client-updated ViewData.

Best Practices for Time/Date Formatting

To avoid hydration mismatches while still providing good SEO and UX:

  1. Use UTC methods for Determinism: In Display Objects, prefer getUTCDate(), getUTCMonth(), etc., over their local counterparts. This ensures the server and client produce the exact same string regardless of their local timezones.
  2. Hardcoded Arrays: Use hardcoded arrays for month/day names instead of Intl to ensure consistency across environments.
  3. Pass "Now" as an Argument: For relative time (e.g., "time ago"), pass the reference "now" timestamp as an argument to the Display Object instead of calling Date.now() inside it.
  4. The "Upgrade" Pattern:
    • Server: ViewData Builder uses a Display Object to produce a deterministic UTC-based string (e.g., "2024-01-18 15:00 UTC").
    • Client: View Model uses client APIs (Intl, toLocale*) to produce a localized string (e.g., "3:00 PM") and updates the ViewData.

Localization rule (strict)

Localization MUST NOT depend on runtime locale APIs.

Allowed approaches:

  • API returns the exact labels/strings for the current user context.
  • Website maps stable codes to stable labels using a deterministic table.

Forbidden approaches:

  • any usage of Intl.*
  • any usage of toLocale*

If a rule affects system correctness or persistence, it does not belong in a Display.


Ownership & Placement

  • Displays belong to the presentation layer
  • They are frontend-only
  • They are not shared with the backend or core

Placement rule (strict):

  • Displays live under apps/website/lib/display-objects/*.
  • Filenames MUST match the class name with .tsx extension (e.g., RatingDisplay.tsx contains class RatingDisplay)

Relationship to View Models and ViewData Builders

Displays are the shared source of truth for formatting logic across the website:

  • ViewData Builders (Server): Use Displays to produce deterministic, formatted strings for SEO and initial SSR.
  • View Models (Client): Use Displays to produce formatted strings for interactive UI and client-specific context.

Additional strict rules:

  • View Models SHOULD compose Displays.
  • ViewData Builders SHOULD use Displays for all formatting.
  • Displays MUST NOT be serialized or passed across boundaries.
    • They must not appear in server-to-client DTOs.
    • Templates should receive primitive display outputs, not Display instances.

Testing

Displays SHOULD be tested because they often contain:

  • locale-specific behavior
  • formatting rules
  • edge cases visible to users

Additionally:

  • test determinism by running the same inputs under Node and browser contexts (where applicable)
  • test boundary rules (no Intl.*, no toLocale*)

Summary

  • Displays encapsulate how something looks (the single source of truth for formatting logic).
  • View Models encapsulate what a screen needs (including client-specific "last mile" formatting).
  • Both are presentation concerns.
  • Neither contains business truth.

In one sentence: Displays are the shared source of truth for deterministic formatting logic, used by both the server and the client.


Final Rule: Where does formatting go?

  1. Is it deterministic? (e.g., currency symbols, fixed date formats, labels) → Display Object.
  2. Is it client-only? (e.g., Intl.*, toLocale*, browser timezone) → View Model.
  3. Is it for SEO?ViewData Builder (using a Display Object).