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:
- Domain Value Objects (Core): Encapsulate business truth and invariants (e.g.,
Money,EmailAddress). They are pure and never contain formatting logic. - 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
Displaysuffix - 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):
- It MUST NOT live in a
Display Object. - It SHOULD live in a View Model (which is client-only).
- The Template should handle the transition from server-provided
ViewDatato client-updatedViewData.
Best Practices for Time/Date Formatting
To avoid hydration mismatches while still providing good SEO and UX:
- Use UTC methods for Determinism: In
Display Objects, prefergetUTCDate(),getUTCMonth(), etc., over their local counterparts. This ensures the server and client produce the exact same string regardless of their local timezones. - Hardcoded Arrays: Use hardcoded arrays for month/day names instead of
Intlto ensure consistency across environments. - Pass "Now" as an Argument: For relative time (e.g., "time ago"), pass the reference "now" timestamp as an argument to the
Display Objectinstead of callingDate.now()inside it. - The "Upgrade" Pattern:
- Server:
ViewData Builderuses aDisplay Objectto produce a deterministic UTC-based string (e.g., "2024-01-18 15:00 UTC"). - Client:
View Modeluses client APIs (Intl,toLocale*) to produce a localized string (e.g., "3:00 PM") and updates theViewData.
- Server:
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
.tsxextension (e.g.,RatingDisplay.tsxcontainsclass 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.*, notoLocale*)
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?
- Is it deterministic? (e.g., currency symbols, fixed date formats, labels) → Display Object.
- Is it client-only? (e.g.,
Intl.*,toLocale*, browser timezone) → View Model. - Is it for SEO? → ViewData Builder (using a Display Object).