website refactor

This commit is contained in:
2026-01-18 23:55:26 +01:00
parent c0559d8b48
commit c78b17eb58
4 changed files with 75 additions and 14 deletions

View File

@@ -93,9 +93,19 @@ Value Objects MUST NOT:
• contain business workflows • contain business workflows
• reference entities • reference entities
• perform IO • 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 Creation Rules
• create() for new domain meaning • create() for new domain meaning
• fromX() for interpreting external formats • fromX() for interpreting external formats

View File

@@ -11,6 +11,13 @@ In this codebase, a Display is a **Frontend Value Object**:
- deterministic - deterministic
- side-effect free - 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: It answers the question:
> “How should this specific piece of information be shown?” > “How should this specific piece of information be shown?”
@@ -65,6 +72,8 @@ A Display MUST NOT:
- influence system behavior - influence system behavior
- be sent back to the server - be sent back to the server
- depend on backend or infrastructure concerns - 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: 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. 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 rule (strict)
Localization MUST NOT depend on runtime locale APIs. 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 are the **shared source of truth** for formatting logic across the website:
- Displays MUST NOT depend on View Models
- Displays represent **parts** - **ViewData Builders (Server):** Use Displays to produce deterministic, formatted strings for SEO and initial SSR.
- View Models represent **screens** - **View Models (Client):** Use Displays to produce formatted strings for interactive UI and client-specific context.
Additional strict rules: Additional strict rules:
- View Models SHOULD compose Displays. - View Models SHOULD compose Displays.
- ViewData Builders SHOULD use Displays for all formatting.
- Displays MUST NOT be serialized or passed across boundaries. - Displays MUST NOT be serialized or passed across boundaries.
- They must not appear in server-to-client DTOs. - They must not appear in server-to-client DTOs.
- Templates should receive primitive display outputs, not Display instances. - Templates should receive primitive display outputs, not Display instances.
@@ -138,9 +167,17 @@ Additionally:
## Summary ## Summary
- Displays encapsulate **how something looks** - Displays encapsulate **how something looks** (the single source of truth for formatting logic).
- View Models encapsulate **what a screen needs** - View Models encapsulate **what a screen needs** (including client-specific "last mile" formatting).
- Both are presentation concerns - Both are presentation concerns.
- Neither contains business truth - Neither contains business truth.
In one sentence: Displays are **Value Objects for UI display**, not utility functions. 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).

View File

@@ -43,15 +43,22 @@ useEffect(() => {
setViewModel(vm); setViewModel(vm);
}, []); }, []);
// Template receives ViewModel, not ViewData // Template receives ViewData from ViewModel
return viewModel ? <Template viewModel={viewModel} /> : null; return viewModel ? <Template viewData={viewModel.viewData} /> : null;
``` ```
Templates MUST NOT compute derived values. Templates MUST NOT compute derived values.
ViewData Builders MUST NOT call the API. ViewData Builders MUST NOT call the API.
## 4) Determinism rules ## 4) Formatting and SEO
ViewData is responsible for providing **fully formatted strings** to Templates for Server-Side Rendering (SSR).
- **SEO Requirement:** All data required for search engines (prices, dates, counts, labels) MUST be formatted in the `ViewData Builder` on the server.
- **Template Simplicity:** Templates should simply render the strings provided in `ViewData` without further processing.
## 5) Determinism rules
Any formatting used to produce ViewData MUST be deterministic. Any formatting used to produce ViewData MUST be deterministic.

View File

@@ -20,10 +20,17 @@ A View Model MAY:
- accept an API DTO as input - accept an API DTO as input
- derive UI-specific fields - derive UI-specific fields
- combine or reshape data for rendering - combine or reshape data for rendering
- perform formatting (dates, numbers, labels) - perform formatting (dates, numbers, labels), especially for **client-only context** (e.g., local timezone, relative "time ago")
- handle localization and presentation logic - handle localization and presentation logic
- use Display Objects for reusable UI concerns - use Display Objects for reusable UI concerns
### Formatting Responsibility
While `ViewData Builders` handle formatting for SEO and initial render, `View Models` are responsible for:
- **Client-specific formatting:** Data that depends on the browser's locale, timezone, or precise location.
- **Interactive formatting:** Updating display values in response to user input or state changes.
- **Consistency:** Using the same `Display Objects` as the server to ensure a consistent look and feel.
In the website SSR/RSC architecture, View Models MAY compute view-only derived values, but MUST NOT be the type passed into Templates. In the website SSR/RSC architecture, View Models MAY compute view-only derived values, but MUST NOT be the type passed into Templates.
A View Model MUST: A View Model MUST: