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
• 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

View File

@@ -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.
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);
}, []);
// Template receives ViewModel, not ViewData
return viewModel ? <Template viewModel={viewModel} /> : null;
// Template receives ViewData from ViewModel
return viewModel ? <Template viewData={viewModel.viewData} /> : null;
```
Templates MUST NOT compute derived values.
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.

View File

@@ -20,10 +20,17 @@ A View Model MAY:
- accept an API DTO as input
- derive UI-specific fields
- 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
- 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.
A View Model MUST: