This commit is contained in:
2026-01-11 14:42:54 +01:00
parent 2f0b83f030
commit 90b6e73a22
27 changed files with 980 additions and 2513 deletions

View File

@@ -0,0 +1,51 @@
# Blockers (Website UX)
This document defines **Blockers** as UX-only prevention mechanisms in the website.
Shared contract: [`docs/architecture/shared/BLOCKERS_AND_GUARDS.md`](docs/architecture/shared/BLOCKERS_AND_GUARDS.md:1)
## 1) Definition
A Blocker is a website mechanism that prevents an action from being executed.
Blockers exist solely to improve UX and reduce unnecessary requests.
Blockers are not security.
## 2) Responsibilities
Blockers MAY:
- prevent multiple submissions
- disable actions temporarily
- debounce or throttle interactions
- hide or disable UI elements
- prevent navigation under certain conditions
Blockers MUST:
- be reversible
- be local to the website
- be treated as best-effort helpers
## 3) Restrictions
Blockers MUST NOT:
- enforce security
- claim authorization
- block access permanently
- replace API Guards
- make assumptions about backend state
## 4) Common Blockers
- SubmitBlocker
- ThrottleBlocker
- NavigationBlocker
- FeatureBlocker
## 5) Canonical placement
- `apps/website/lib/blockers/**`

View File

@@ -1,154 +0,0 @@
Blockers & Guards
This document defines clear, non-overlapping responsibilities for Blockers (frontend) and Guards (backend).
The goal is to prevent semantic drift, security confusion, and inconsistent implementations.
Core Principle
Guards enforce. Blockers prevent.
• Guards protect the system.
• Blockers protect the UX.
There are no exceptions to this rule.
Backend — Guards (NestJS)
Definition
A Guard is a backend mechanism that enforces access or execution rules.
If a Guard denies execution, the request does not reach the application logic.
In NestJS, Guards implement CanActivate.
Responsibilities
Guards MAY:
• block requests entirely
• return HTTP errors (401, 403, 429)
• enforce authentication and authorization
• enforce rate limits
• enforce feature availability
• protect against abuse and attacks
Guards MUST:
• be deterministic
• be authoritative
• be security-relevant
Restrictions
Guards MUST NOT:
• depend on frontend state
• contain UI logic
• attempt to improve UX
• assume the client behaved correctly
Common Backend Guards
• AuthGuard
• RolesGuard
• PermissionsGuard
• ThrottlerGuard (NestJS)
• RateLimitGuard
• CsrfGuard
• FeatureFlagGuard
Summary (Backend)
• Guards decide
• Guards enforce
• Guards secure the system
Frontend — Blockers
Definition
A Blocker is a frontend mechanism that prevents an action from being executed.
Blockers exist solely to improve UX and reduce unnecessary requests.
Blockers are not security mechanisms.
Responsibilities
Blockers MAY:
• prevent multiple submissions
• disable actions temporarily
• debounce or throttle interactions
• hide or disable UI elements
• prevent navigation under certain conditions
Blockers MUST:
• be reversible
• be local to the frontend
• be treated as best-effort helpers
Restrictions
Blockers MUST NOT:
• enforce security
• claim authorization
• block access permanently
• replace backend Guards
• make assumptions about backend state
Common Frontend Blockers
• SubmitBlocker
• AuthBlocker
• RoleBlocker
• ThrottleBlocker
• NavigationBlocker
• FeatureBlocker
Summary (Frontend)
• Blockers prevent execution
• Blockers improve UX
• Blockers reduce mistakes and load
Clear Separation
Aspect Blocker (Frontend) Guard (Backend)
Purpose Prevent execution Enforce rules
Security ❌ No ✅ Yes
Authority ❌ Best-effort ✅ Final
Reversible ✅ Yes ❌ No
Failure effect UI feedback HTTP error
Naming Rules (Hard)
• Frontend uses *Blocker
• Backend uses *Guard
• Never mix the terms
• Never implement Guards in the frontend
• Never implement Blockers in the backend
Final Rule
If it must be enforced, it is a Guard.
If it only prevents UX mistakes, it is a Blocker.

View File

@@ -50,11 +50,10 @@ Blockers exist to prevent UX mistakes.
- Blockers may reduce unnecessary requests.
- The API still enforces rules.
See [`BLOCKER_GUARDS.md`](docs/architecture/website/BLOCKER_GUARDS.md:1).
See [`docs/architecture/shared/BLOCKERS_AND_GUARDS.md`](docs/architecture/shared/BLOCKERS_AND_GUARDS.md:1) and [`docs/architecture/website/BLOCKERS.md`](docs/architecture/website/BLOCKERS.md:1).
## 6) Canonical placement in this repo
- `apps/website/lib/blockers/**`
- `apps/website/lib/hooks/**`
- `apps/website/lib/command-models/**`

View File

@@ -1,14 +1,20 @@
# Login Flow State Machine Architecture
# Login Flow State Machine (Strict)
## Problem
The current login page has unpredictable behavior due to:
- Multiple useEffect runs with different session states
- Race conditions between session loading and redirect logic
- Client-side redirects that interfere with test expectations
This document defines the canonical, deterministic login flow controller for the website.
## Solution: State Machine Pattern
Authoritative website contract:
### State Definitions
- [`docs/architecture/website/WEBSITE_CONTRACT.md`](docs/architecture/website/WEBSITE_CONTRACT.md:1)
## 1) Core rule
Login flow logic MUST be deterministic.
The same inputs MUST produce the same state and the same next action.
## 2) State machine definition (strict)
### 2.1 State definitions
```typescript
enum LoginState {
@@ -19,7 +25,7 @@ enum LoginState {
}
```
### State Transition Table
### 2.2 State transition table
| Current State | Session | ReturnTo | Next State | Action |
|---------------|---------|----------|------------|--------|
@@ -29,7 +35,7 @@ enum LoginState {
| UNAUTHENTICATED | exists | any | POST_AUTH_REDIRECT | Redirect to returnTo |
| AUTHENTICATED_WITHOUT_PERMISSIONS | exists | any | POST_AUTH_REDIRECT | Redirect to returnTo |
### Class-Based Controller
### 2.3 Class-based controller
```typescript
class LoginFlowController {
@@ -57,7 +63,7 @@ class LoginFlowController {
return this.state;
}
// Pure function - returns action, doesn't execute
// Pure function - returns action, does not execute
getNextAction(): LoginAction {
switch (this.state) {
case LoginState.UNAUTHENTICATED:
@@ -71,7 +77,7 @@ class LoginFlowController {
}
}
// Called after authentication
// Transition called after authentication
transitionToPostAuth(): void {
if (this.session) {
this.state = LoginState.POST_AUTH_REDIRECT;
@@ -80,15 +86,14 @@ class LoginFlowController {
}
```
### Benefits
## 3) Non-negotiable rules
1. **Predictable**: Same inputs always produce same outputs
2. **Testable**: Can test each state transition independently
3. **No Race Conditions**: State determined once at construction
4. **Clear Intent**: Each state has a single purpose
5. **Maintainable**: Easy to add new states or modify transitions
1. The controller MUST be constructed from explicit inputs only.
2. The controller MUST NOT perform side effects.
3. Side effects (routing) MUST be executed outside the controller.
4. The controller MUST be unit-tested per transition.
### Usage in Login Page
## 4) Usage in login page (example)
```typescript
export default function LoginPage() {
@@ -129,4 +134,4 @@ export default function LoginPage() {
}
```
This eliminates all the unpredictable behavior and makes the flow testable and maintainable.
This pattern ensures deterministic behavior and makes the flow testable.

View File

@@ -0,0 +1,46 @@
# Authentication UX Flow (Website)
This document defines how the website handles authentication from a UX perspective.
Shared contract:
- [`docs/architecture/shared/AUTH_CONTRACT.md`](docs/architecture/shared/AUTH_CONTRACT.md:1)
Authoritative website contract:
- [`docs/architecture/website/WEBSITE_CONTRACT.md`](docs/architecture/website/WEBSITE_CONTRACT.md:1)
## 1) Website role (strict)
The website:
- redirects unauthenticated users to login
- hides or disables UI based on best-effort session knowledge
The website does not enforce security.
## 2) Canonical website flow
```text
Request
Website routing
API requests with credentials
API enforces authentication and authorization
Website renders result or redirects
```
## 3) Non-negotiable rules
1. The website MUST NOT claim authorization.
2. The website MUST NOT trust client state for enforcement.
3. Every write still relies on the API to accept or reject.
Related:
- Website blockers: [`docs/architecture/website/BLOCKERS.md`](docs/architecture/website/BLOCKERS.md:1)
- Client state rules: [`docs/architecture/website/CLIENT_STATE.md`](docs/architecture/website/CLIENT_STATE.md:1)

View File

@@ -189,7 +189,7 @@ See [`FORM_SUBMISSION.md`](docs/architecture/website/FORM_SUBMISSION.md:1).
- The website MUST NOT enforce security.
- The API enforces authentication and authorization.
See [`BLOCKER_GUARDS.md`](docs/architecture/website/BLOCKER_GUARDS.md:1).
See [`docs/architecture/shared/BLOCKERS_AND_GUARDS.md`](docs/architecture/shared/BLOCKERS_AND_GUARDS.md:1) and [`docs/architecture/website/BLOCKERS.md`](docs/architecture/website/BLOCKERS.md:1).
## 7.1) Client state (strict)

View File

@@ -0,0 +1,65 @@
# Website Data Flow (Strict)
This document defines the **apps/website** data flow.
Authoritative contract: [`docs/architecture/website/WEBSITE_CONTRACT.md`](docs/architecture/website/WEBSITE_CONTRACT.md:1).
Website scope:
- `apps/website/**`
## 1) Website role
The website is a **delivery layer**.
It renders truth from the API and forwards user intent to the API.
## 2) Read flow
```text
RSC page.tsx
PageQuery
API client (infra)
API Transport DTO
Page DTO
Presenter (client)
ViewModel (optional)
Presenter (client)
ViewData
Template
```
## 3) Write flow
All writes enter through **Server Actions**.
```text
User intent
Server Action
Command Model / Request DTO
API
Revalidation
RSC reload
```
## 4) Non-negotiable rules
1. Templates accept ViewData only.
2. Page Queries do not format.
3. Presenters do not call the API.
4. Client state is UI-only.

View File

@@ -0,0 +1,50 @@
# Website File Structure (Strict)
This document defines the canonical **physical** structure for `apps/website/**`.
It describes where code lives, not the full behavioral rules.
Authoritative contract:
- [`docs/architecture/website/WEBSITE_CONTRACT.md`](docs/architecture/website/WEBSITE_CONTRACT.md:1)
## 1) High-level layout
```text
apps/website/
app/  Next.js routes (RSC pages, layouts, server actions)
templates/  template components (ViewData only)
lib/  website code (clients, services, view-models, etc.)
```
## 2) `apps/website/app/` (routing)
Routes are implemented via Next.js App Router.
Rules:
- server `page.tsx` does composition only
- templates are pure
- writes enter via server actions
See [`docs/architecture/website/WEBSITE_RSC_PRESENTATION.md`](docs/architecture/website/WEBSITE_RSC_PRESENTATION.md:1).
## 3) `apps/website/lib/` (website internals)
Canonical folders (existing in this repo):
```text
apps/website/lib/
api/  API clients
infrastructure/  technical concerns
services/  UI orchestration (read-only and write orchestration)
page-queries/  server composition
types/  API transport DTOs
view-models/  client-only classes
display-objects/  deterministic formatting helpers
command-models/  transient form models
blockers/  UX-only prevention
hooks/  React-only helpers
di/  client-first DI integration
```