diff --git a/.env.example b/.env.example
index 23efff3..8480039 100644
--- a/.env.example
+++ b/.env.example
@@ -5,7 +5,14 @@
#
# The domain must already resolve to this server, and ports 80 and 443 must be
# reachable from the internet — port 80 is what the ACME challenge uses.
-SITE_ADDRESS=timelog.example.com
+SITE_ADDRESS=pardis.example.com
# Notified by Let's Encrypt before a certificate expires. Optional but advised.
ACME_EMAIL=you@example.com
+
+# Adresse locale d’écoute ; laisser 0.0.0.0 pour toutes les interfaces.
+LISTEN_IP=0.0.0.0
+
+# Adresse/CIDR du proxy qui transmet le protocole PROXY à Caddy.
+# Remplacer cette adresse d’exemple par celle du proxy réellement utilisé.
+TRUST_PROXY=192.0.2.10/32
diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml
index 8da1b41..5776f7f 100644
--- a/.gitea/workflows/deploy.yml
+++ b/.gitea/workflows/deploy.yml
@@ -23,9 +23,9 @@ jobs:
run: |
set -eu
npm ci
- npm test
- npm run check
- npm run build
+ npm run validate
+ npx playwright install --with-deps chromium
+ npm run test:e2e
deploy:
needs: validate
diff --git a/.gitignore b/.gitignore
index 9d60660..76672d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+
+# Browser test artifacts
+playwright-report/
+test-results/
diff --git a/README.md b/README.md
index bc2db79..e16fd97 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,9 @@ npm test # unit and IndexedDB regression tests
npm run check # svelte-check
npm run build # production build into dist/
npm run validate # tests, Svelte check and full PWA build
+npx playwright install chromium # install the browser for UI tests
+npm run test:e2e # fresh profiles, synthetic fixtures and offline PWA test
+npm run validate:all # include browser tests in full validation
```
## Exporter et importer les données
@@ -229,7 +232,7 @@ the static output, obtaining and renewing a Let's Encrypt certificate on its own
```sh
cp .env.example .env
-$EDITOR .env # set SITE_ADDRESS and ACME_EMAIL
+$EDITOR .env # set SITE_ADDRESS, ACME_EMAIL and TRUST_PROXY
docker compose up -d --build
```
@@ -239,7 +242,7 @@ Requirements:
- Ports **80 and 443** must be reachable from the internet. Port 80 is not optional —
it is what the ACME challenge uses to prove domain ownership.
-Certificates are stored in the `caddy_data` volume. Keep it: without it Caddy re-issues
+Certificates are stored in the bind-mounted `./caddy/data` directory. Keep it: without it Caddy re-issues
on every recreate and will hit the Let's Encrypt rate limit after a handful of restarts.
To update, pull and rebuild:
@@ -261,9 +264,12 @@ the only exception, which is why `npm run dev` works without certificates.
### Behind an existing reverse proxy
-If something upstream already terminates TLS, set `SITE_ADDRESS=:80`, drop the `443`
-port mappings, and let the proxy forward to port 80. `ACME_EMAIL` is then unused but
-must still be non-empty — Caddy will not parse a blank value.
+The current Caddy configuration terminates TLS itself and accepts PROXY protocol
+from the address or CIDR in `TRUST_PROXY`. Set this value to the real upstream
+proxy address; `192.0.2.10/32` in `.env.example` is a documentation placeholder.
+`LISTEN_IP` selects the local interface for the published ports and defaults to
+`0.0.0.0`. A deployment that terminates TLS elsewhere needs its own Caddy and
+port configuration; changing `SITE_ADDRESS` alone does not configure that mode.
The app must be served from the root of its domain. `start_url` and the service worker
scope are both `/`, so hosting it under a subpath will not work without changing the
@@ -315,3 +321,23 @@ actualisation sont affichées avec une action Réessayer.
La checklist avant production et les validations restant à exécuter sont suivies
dans [PRODUCTION.md](PRODUCTION.md).
+
+## Sauvegarde vérifiée et validation navigateur
+
+**Exporter les données** prépare le fichier et enregistre son empreinte complète.
+**Vérifier le fichier exporté…** relit le fichier téléchargé, contrôle qu’il est
+importable et compare son contenu à l’export. La date de la dernière sauvegarde
+vérifiée est distincte de celle du dernier export préparé. Après sept jours sans
+export vérifié, Pardis affiche un rappel pendant son utilisation. Aucune donnée
+n’est envoyée à un serveur ; conservez le fichier hors de cet appareil.
+
+La purge affiche une liste figée de journées. Sauvegarde et suppression partagent
+une transaction ; une correction concurrente bloque la purge et demande une
+nouvelle consultation. Les autres journées récemment importées restent intactes.
+
+Les tests Playwright utilisent des contextes neufs et des données synthétiques,
+à 320 px. Ils vérifient consultation/correction, focus, Retour, reprise à deux
+onglets, export vérifié, purge/restauration et rechargement hors ligne. Traces,
+vidéos et captures sont désactivées. Pour un Chrome déjà installé, définir
+`PARDIS_CHROME_PATH` avec son chemin ; sinon installer Chromium avec Playwright.
+La CI installe Chromium et ses dépendances avant de lancer ces tests.
diff --git a/docker-compose.yml b/docker-compose.yml
index 829880f..53deaf2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,10 +12,10 @@ services:
TRUST_PROXY: ${TRUST_PROXY:?set TRUST_PROXY in .env}
ports:
- - ${LISTEN_IP:?0.0.0.0}:80:80
- - ${LISTEN_IP:?0.0.0.0}:443:443
- - ${LISTEN_IP:?0.0.0.0}:443:443/udp
+ - "${LISTEN_IP:-0.0.0.0}:80:80"
+ - "${LISTEN_IP:-0.0.0.0}:443:443"
+ - "${LISTEN_IP:-0.0.0.0}:443:443/udp"
volumes:
- ./caddy/data:/data
- - ./caddy/config:/config
\ No newline at end of file
+ - ./caddy/config:/config
diff --git a/package-lock.json b/package-lock.json
index 933e646..fec420c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,17 @@
{
- "name": "app",
- "version": "0.0.0",
+ "name": "pardis",
+ "version": "0.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "app",
- "version": "0.0.0",
+ "name": "pardis",
+ "version": "0.8.0",
"dependencies": {
"dexie": "^4.4.4"
},
"devDependencies": {
+ "@playwright/test": "^1.64.0",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@vite-pwa/assets-generator": "^1.0.2",
"fake-indexeddb": "^6.2.5",
@@ -2126,6 +2127,22 @@
"url": "https://github.com/sponsors/oxc-project"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.64.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.0.tgz",
+ "integrity": "sha512-oTN9FNYuN6bqQMwiho6jJKLCG2/QOYCixGXuCjRmzOq23eODeukuY2XiAVoVpAECbuezMt1MHQjAiQxK8F+Y+g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.64.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/@quansync/fs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz",
@@ -5583,6 +5600,35 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.64.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.0.tgz",
+ "integrity": "sha512-kVzTnYFYEyQ9RGzBjNCd2+yEqWmVbxl3AHPynv3qQ6JTjv4a+kwRcnqQwIpuP3NnrnIrE/Q6wWAIAvMfIE2/Yg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.64.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.64.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.0.tgz",
+ "integrity": "sha512-T9r+MZkTECl2+oUcZ26YgZzTrYidTsIhYYkJix+6iDOymOb1FB2RLLSOxRGnZGv5DStt+aduIVbt+E8WygXYpg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
diff --git a/package.json b/package.json
index d925b18..f8364d8 100644
--- a/package.json
+++ b/package.json
@@ -10,9 +10,12 @@
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
- "check": "svelte-check --tsconfig ./jsconfig.json"
+ "check": "svelte-check --tsconfig ./jsconfig.json",
+ "test:e2e": "playwright test",
+ "validate:all": "npm run validate && npm run test:e2e"
},
"devDependencies": {
+ "@playwright/test": "^1.64.0",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@vite-pwa/assets-generator": "^1.0.2",
"fake-indexeddb": "^6.2.5",
diff --git a/playwright.config.js b/playwright.config.js
new file mode 100644
index 0000000..8366e98
--- /dev/null
+++ b/playwright.config.js
@@ -0,0 +1,22 @@
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ testDir: './tests/e2e',
+ fullyParallel: false,
+ workers: 1,
+ timeout: 30_000,
+ reporter: 'list',
+ use: {
+ baseURL: 'http://127.0.0.1:4183',
+ timezoneId: 'Europe/Paris',
+ viewport: { width: 320, height: 640 },
+ // These tests use fresh contexts and synthetic fixtures; no user profile.
+ trace: 'off', screenshot: 'off', video: 'off',
+ launchOptions: process.env.PARDIS_CHROME_PATH ? { executablePath: process.env.PARDIS_CHROME_PATH } : {},
+ },
+ webServer: {
+ command: 'npm run preview -- --host 127.0.0.1 --port 4183 --strictPort',
+ url: 'http://127.0.0.1:4183',
+ reuseExistingServer: false,
+ },
+});
diff --git a/src/App.svelte b/src/App.svelte
index a761f57..ed17a07 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -1,5 +1,8 @@
{ if (!busy) onclose(); }}>
-