Project Charter and Architecture Decisions
This document formalizes the structural architecture decisions of the Racines project. These rules must not be circumvented without prior discussion with the team.
Environment variables — critical rules
AUDIO_CDN_URL (mandatory at runtime)
- Full URL of the MinIO audio CDN — container environment variable (no more
--build-arg) - Read by
RootLayout(Server Component,force-dynamic) on every SSR request - Injected into
window.__RACINES_CDN__as the first<script>in<head>, before any client chunk - The Docker entrypoint (
docker/entrypoint.sh) fails if this variable is empty at container startup - Never leave it empty:
getAudioUrl()would return'', causing spurious requests to the current page - Example:
AUDIO_CDN_URL=https://racines-s3.id2real.net/audios
AUDIO_PROXY_DISABLED (runtime, default: false)
- Disables the
/api/audio/serve/fallback proxy whenAUDIO_CDN_URLis set truein production,falsein local development without an external CDN- If
trueandAUDIO_CDN_URLis empty → empty audio URL → silent bug. Both variables must be consistent.
AUDIO_CDN_DOMAIN (runtime, auto-derived)
- Hostname extracted from
AUDIO_CDN_URL, used in the CSP (src/proxy.ts) - If absent,
docker/entrypoint.shderives it automatically - Can be provided explicitly to override the derivation
.app_env file format (Docker Compose)
- All values must be in double quotes:
VAR="value" - Mandatory for values containing special characters (hash
#, spaces,=, etc.)
Offline architecture — intentional behaviors
Network circuit breaker (src/lib/network-state.ts)
navigator.onLinealone is unreliable (WiFi connected but internet down →true)- States:
CLOSED→OPEN→HALF_OPEN→CLOSED - Switches to
OPENafter 3 failures in a 60s window - Recovery:
window.onlineevent (immediate probe) + periodic probeHEAD /api/health/livewith exponential backoff 30s→300s HALF_OPEN: the probe succeeded but recovery is only confirmed by the first successful real requestrecordFetchFailure()only for network errors and codes 502/503/504. 4xx and 500 do not mean the server is unreachable
Audio cache (racines-audio-cache-v3)
- Responses are stored without the
Varyheader (the CDN sendsVary: Accept-Encodingwhich breakscache.match()) - Use
cache.match(url, { ignoreVary: true })for reads
IndexedDB — current version: 3
itemsstore:by_card_idindex added in v3 (migration inopenDB())- Any addition of a store or index must increment the version and handle
onupgradeneeded
Audio — priority rule
The application never uses TTS (Text-to-Speech). If no native audio exists, the button is absent.
Audio playback priority order
- IndexedDB: locally downloaded audio (offline-first)
- MinIO CDN (
AUDIO_CDN_URL): audio served directly from the CDN - Next.js proxy (
/api/audio/serve/): fallback in development or if CDN is unavailable - ~~TTS~~: never used
Condition for displaying the audio button
The 🔊 button is displayed only if item.audio_url is non-null in the database. A TTS recording is not an acceptable alternative.
UI patterns — user feedback
States and notifications must always be visible without scrolling.
Sticky banner — persistent state
- Usage: offline mode, update available
- Existing implementation:
src/components/ServiceWorkerRegistration.tsx - Fixed position below the header, permanently visible
- Do not use for ephemeral messages
Toast — ephemeral confirmation
- Usage: success of a user action (e.g. contact form submitted)
position: fixed, corner or bottom of screen, disappears after ~15s- Must include a manual close button (×)
- Existing implementation:
src/app/contact/page.tsx
Modal / Dialog — critical action
- Usage: destructive confirmation, blocking error
- Avoid for informational messages — prefer toast or banner
PWA / Application icon
Rules
- The file
public/apple-touch-icon.png(180×180 px) must always be present - Do not add a manual
<link rel="apple-touch-icon">tag — Next.js generates it fromsrc/app/apple-icon.png NEXT_PUBLIC_PWA_ENABLED=truemust be set in production- Any icon modifications must be verified by installing the application on iOS and Android
Service Worker — rules
- The SW is disabled on
/adminand/speaker(avoids cache conflicts in these stateful interfaces) - Admin pages are never cached by the SW
- After each deployment, the SW automatically detects the new version and displays an update banner
Code conventions
TypeScript
- No
anywithout a commented justification - Types centralized in
src/types/index.ts - Server Components by default —
'use client'only when necessary
SQL
- No string concatenation in queries — always use parameters (
$1,$2…) - Shared pool via
src/lib/postgres.ts— never a direct new connection
Logs
- Never log sensitive user data (emails, names, message content)
- Server errors are logged with stack traces but without personal data
This document is authoritative
In case of doubt between this document and a code comment, this document takes precedence. If a rule is circumvented for a valid reason, document it in the code and in this file.