From c78b17eb58ed268282215b0ca8684aa79399e1d7 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sun, 18 Jan 2026 23:55:26 +0100 Subject: [PATCH] website refactor --- docs/architecture/core/DOMAIN_OBJECTS.md | 10 ++++ docs/architecture/website/DISPLAY_OBJECTS.md | 57 ++++++++++++++++---- docs/architecture/website/VIEW_DATA.md | 13 +++-- docs/architecture/website/VIEW_MODELS.md | 9 +++- 4 files changed, 75 insertions(+), 14 deletions(-) diff --git a/docs/architecture/core/DOMAIN_OBJECTS.md b/docs/architecture/core/DOMAIN_OBJECTS.md index 597702c79..4bee38191 100644 --- a/docs/architecture/core/DOMAIN_OBJECTS.md +++ b/docs/architecture/core/DOMAIN_OBJECTS.md @@ -93,9 +93,19 @@ Value Objects MUST NOT: • contain business workflows • reference entities • perform IO + • **contain presentation or formatting logic** (e.g., `formatForDisplay()`) ⸻ +### Domain vs. Presentation Value Objects + +It is critical to distinguish between **Domain Value Objects** and **Presentation Value Objects** (Displays): + +- **Domain Value Objects (Core):** Protect business meaning and invariants. They are pure and know nothing about how data is shown to users. +- **Display Objects (Website):** Protect presentation logic and formatting. They are used by the Website (both server and client) to ensure consistent display. + +If the API needs to return formatted strings, this logic belongs in **API Presenters**, not in Domain Value Objects. + Creation Rules • create() for new domain meaning • fromX() for interpreting external formats diff --git a/docs/architecture/website/DISPLAY_OBJECTS.md b/docs/architecture/website/DISPLAY_OBJECTS.md index 0fd4ccfed..523bb172b 100644 --- a/docs/architecture/website/DISPLAY_OBJECTS.md +++ b/docs/architecture/website/DISPLAY_OBJECTS.md @@ -11,6 +11,13 @@ In this codebase, a Display is a **Frontend Value Object**: - 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?” @@ -65,6 +72,8 @@ A Display MUST NOT: - 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: @@ -73,6 +82,25 @@ In this repository, a Display MUST NOT: 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. @@ -105,16 +133,17 @@ Placement rule (strict): --- -## Relationship to View Models +## Relationship to View Models and ViewData Builders -- View Models MAY use Displays -- Displays MUST NOT depend on View Models -- Displays represent **parts** -- View Models represent **screens** +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. @@ -138,9 +167,17 @@ Additionally: ## Summary -- Displays encapsulate **how something looks** -- View Models encapsulate **what a screen needs** -- Both are presentation concerns -- Neither contains business truth +- 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 **Value Objects for UI display**, not utility functions. \ No newline at end of file +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). \ No newline at end of file diff --git a/docs/architecture/website/VIEW_DATA.md b/docs/architecture/website/VIEW_DATA.md index 413792736..fed684e72 100644 --- a/docs/architecture/website/VIEW_DATA.md +++ b/docs/architecture/website/VIEW_DATA.md @@ -43,15 +43,22 @@ useEffect(() => { setViewModel(vm); }, []); -// Template receives ViewModel, not ViewData -return viewModel ?