Files
pardis/README.fr.md
Adrien W. 64f61004d1
All checks were successful
Tests et déploiement / validate (push) Successful in 2m43s
Tests et déploiement / deploy (push) Successful in 1m21s
version 1.0.0
2026-10-11 12:51:47 +02:00

421 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pardis
PWA hors ligne de suivi des heures de chantier. Tant que l’application est ouverte,
elle peut démarrer automatiquement la journée et proposer sa clôture. Chaque tâche
commence là où la précédente s’est terminée. Les journées clôturées ont un
récapitulatif concis, prêt à être recopié dans un registre papier.
Version actuelle de l’application : **1.0.0**.
Conçue pour l’extérieur : contraste élevé, grandes zones tactiles et utilisable
avec des gants. Les données sont enregistrées localement dans IndexedDB. Une
synchronisation facultative chiffrée de bout en bout les partage entre appareils
autorisés, sans inscription par e-mail ni mot de passe.
Version anglaise : [README.md](README.md).
## Développement
```sh
npm install
npm run dev # http://localhost:5173
npm test # tests unitaires et de régression IndexedDB
npm run check # vérification Svelte
npm run build # build de production dans dist/
npm run validate # tests, vérification Svelte et build complet de la PWA
npx playwright install chromium # installer le navigateur pour les tests UI
npm run test:e2e # profils vierges, données synthétiques et test PWA hors ligne
npm run validate:all # inclure les tests navigateur dans la validation complète
```
## Exporter et importer les données
Dans **Réglages → Gérer les données et sauvegardes**, l’écran **Données et
sauvegardes** regroupe l’export, l’import, la vérification des sauvegardes et la
durée de conservation. Le rappel **Sauvegarder** ouvre aussi cet écran.
Les données restent disponibles dans le navigateur sur cet appareil. La synchronisation
chiffrée peut être activée séparément pour les partager avec vos autres appareils.
Effacer les données du site ou perdre l’appareil peut faire perdre l’historique :
gardez une copie vérifiée du fichier exporté sur un autre support.
**Exporter les données** télécharge un fichier
`pardis-AAAA-MM-JJ.json`. Il contient toutes les journées conservées, leurs postes
et les réglages enregistrés. L'export fonctionne hors ligne et ne modifie pas les
données de l'appareil. Les identifiants internes des postes sont recréés à l'import.
Pour restaurer une sauvegarde, choisissez **Importer un fichier…** puis vérifiez
le nom du fichier, les compteurs et les éventuels ajustements signalés. Deux modes
sont disponibles :
- **Fusionner** ajoute seulement les dates absentes. Une date déjà présente est
conservée intégralement, même si le fichier contient une autre version de ses
postes. Les réglages locaux sont conservés. Importer deux fois le même fichier
ne crée pas de doublons.
- **Remplacer tout** demande une confirmation, puis remplace toutes les journées,
tous les postes et les réglages, y compris une journée locale en cours. Si le
fichier ne contient pas de réglages, les valeurs par défaut sont rétablies.
Les journées importées sont enregistrées comme terminées. Pour une journée
exportée en cours, le dernier poste est arrêté à l'heure de l'export : le temps
écoulé est conservé. Les intervalles sans travail déjà présents sont conservés.
Une sauvegarde vide est valide et peut servir à restaurer un appareil vide.
Le format JSON est identifié par `format: "timelog-export"` et `version: 2`. Le format v1 reste importable. Les anciennes applications refusent le format v2 pour éviter de transformer les pauses en travail.
Les fichiers étrangers, versions non prises en charge, dates en double et réglages
invalides sont refusés. Les postes inutilisables et les corrections de plages
horaires sont signalés avant confirmation. La limite de taille est de 20 Mo.
L'écriture est transactionnelle : un échec annule le remplacement ou la fusion
dans son intégralité. Le récapitulatif, le thème et les suggestions sont ensuite
actualisés sans recharger la page.
Dans **Durée de conservation**, **Conserver (jours)** détermine les journées
anciennes proposées à la purge. Modifier cette durée ne supprime rien ; la purge
automatique est désactivée. L’historique conservé alimente les suggestions et les
statistiques. La dernière journée terminée et les journées actives sont conservées.
**Purger maintenant** présente les dates concernées et demande confirmation.
Une sauvegarde locale est conservée avant la suppression ; **Récupérer la
sauvegarde avant purge** permet de la fusionner ou de remplacer les données,
puis de l’exporter après restauration. Cette copie est remplacée à la prochaine
purge et disparaît si les données du site sont effacées : un fichier externe
reste nécessaire.
Les tests d'import/export couvrent le format, les réglages, la fidélité des durées,
les fichiers invalides, la fusion, le remplacement et l'annulation transactionnelle
sur une IndexedDB simulée. `src/lib/fixtures/backup.json` est le jeu d'essai fourni ;
il n'est pas publié avec l'application.
## Journées, reprise et historique
- Une journée oubliée est clôturée au retour dans l'application, à son heure de
fin prévue (prolongations comprises), sans jamais précéder la dernière activité
enregistrée. Cette heure est une estimation : l'application ne mesure pas le
travail lorsqu'elle est fermée. Le dernier poste conserve ainsi sa durée jusqu'à
cette limite, au lieu d'être ramené à zéro.
- Une journée terminée aujourd’hui propose **Reprendre la journée** avec confirmation : prolonger le dernier poste, ou créer une pause jusqu’à maintenant (choix initial). Un dernier repas ou une pause garde sa nature ; le travail reprend dans un nouveau poste. Les journées passées se corrigent par **Voir les détails → Corriger cette journée**.
- La création, la clôture et la reprise sont transactionnelles. Les actions
simultanées sur le même poste ne créent pas de doublons ; une journée clôturée
ne peut pas recevoir un nouveau poste actif. La purge épargne les journées
actives et conserve toujours la dernière journée terminée.
- Les horaires de déjeuner affichent un rappel puis une heure indicative de
reprise. Les transitions restent manuelles sauf si l'option **Pause déjeuner**
est activée dans les automatismes.
- Dans **Récap**, le sélecteur **Journée à consulter** permet d'afficher et de copier
chaque journée terminée encore conservée, y compris les journées importées.
Les tests couvrent notamment les clôtures oubliées, les reprises, les appels
concurrents, les retours d'horloge, les échecs transactionnels et la conservation.
## Automatismes
Dans **Réglages → Automatismes**, chaque comportement a son interrupteur :
| Option | Comportement |
| --- | --- |
| Ouverture | Démarrer aux horaires et jours prévus. Activée par défaut, comme auparavant. |
| Début de journée | Au bouton « Arrivé sur site », proposer la dernière tâche de travail de la précédente journée enregistrée, hors repas et transport. |
| Pause déjeuner | Ouvrir la pause à l'heure habituelle et reprendre la tâche précédente après la durée réglée. Reprendre le type de repas et le restaurant de la précédente journée enregistrée. |
| Arrivée sur chantier | Lors du repas automatique, transformer un transport initial non renseigné en un transport de 1/5/15/30 minutes, selon l'arrondi, suivi de la tâche précédente. |
| Fin de demi-journée | À la pause ou à la clôture, remplacer une plage automatique non renseignée par la dernière tâche de travail connue. Conserver tout poste manuel. |
| Fin de journée | Clôturer à l'heure prévue sans confirmation. |
Les cinq nouvelles options sont désactivées par défaut. Elles sont conservées
dans les sauvegardes et rétablies lors d'un import en remplacement. L'option
**Arrivée sur chantier** intervient seulement avec un repas automatique.
Sans cette estimation de transport, **Fin de demi-journée** peut remplacer le
transport initial non renseigné par du travail sur toute la plage. Une tâche de
travail déjà présente suffit : aucun doublon n'est ajouté à la demi-journée.
« Précédente » désigne la dernière journée terminée enregistrée avant la date
courante, y compris après un week-end. La complétion utilise la dernière tâche
de travail connue ; sans historique de travail, elle utilise **Travaux sur site**.
Les postes produits automatiquement portent la mention **Auto**. Les corriger
les rend manuels et les protège des déductions suivantes. Les saisies anciennes
sans cette information sont considérées comme manuelles.
Une pause commencée ou corrigée manuellement n'est pas terminée par l'automatisme
de déjeuner. Un choix manuel du type ou du restaurant est conservé. Une pause
supprimée n'est pas recréée. Désactiver l'option arrête les transitions suivantes ;
cela ne supprime pas les postes déjà enregistrés. Une durée de déjeuner nulle
n'ajoute aucune pause automatique. La durée d'une pause automatique déjà commencée
est conservée si le réglage change ensuite.
L'app vérifie l'horloge toutes les 30 secondes et au retour à l'écran. Une PWA
fermée ou suspendue ne peut pas exécuter du code à heure fixe : à la réouverture,
les étapes manquées sont rattrapées avec leurs horaires prévus, sans écraser une
action manuelle plus tardive. Les journées déjà clôturées restent intactes et les
dates entièrement absentes ne sont pas créées. Pour une journée sans intervention,
activer les options voulues et ouvrir l'app au moins une fois pour cette journée.
Les prolongations et la reprise d'une journée sont respectées. Un démarrage
manuel laisse au moins une heure avant une clôture automatique. Les transitions
automatiques sont transactionnelles : une erreur annule toute l'opération et
des appels simultanés ne créent pas de doublons.
## Jours non travaillés
Dans **Réglages → Jours non travaillés**, ajouter les dates à bloquer à l'avance.
La liste est triée par date ; chaque date peut être modifiée ou supprimée.
Les doublons sont refusés. Ces exceptions priment sur les jours de semaine
habituellement travaillés et empêchent l'ouverture automatique, y compris lors
du rattrapage à la réouverture de l'app ou hors ligne.
Aucune journée ni aucun poste n'est créé pour une date bloquée. Les journées
déjà commencées ou terminées restent conservées. Dans **Journée**, le bouton
**Démarrer quand même** permet un démarrage manuel exceptionnel.
Retirer une date rétablit les règles habituelles de démarrage automatique.
La liste est enregistrée avec les réglages et incluse dans l'export des données.
Un import en remplacement rétablit la liste de la sauvegarde ; un import en
fusion conserve la liste locale, comme les autres réglages. Une ancienne
sauvegarde sans cette liste la réinitialise à vide lors d'un remplacement.
## Corriger les postes et les journées terminées
Le **Total des heures de présence** exclut les pauses et le repas. Le repas reste affiché
séparément dans le récapitulatif et le texte copié. Les totaux suivent le réglage
d’arrondi existant.
Dans **Journée**, touchez un poste dans **Postes de la journée** pour modifier
son début, sa fin, sa tâche ou son client. Les boutons **Poste ordinaire**,
**Repas** et **Pause** permettent de choisir la nature du poste. Le type interne
d'un transport déjà enregistré est conservé quand on corrige ses horaires ou
son libellé.
Les sélecteurs d'heure ne proposent pas de date : ils conservent la date du
poste. Les minutes et les boutons d'ajustement suivent l'arrondi choisi dans
les réglages (1, 5, 15 ou 30 minutes). Les tâches, clients et restaurants utilisent
les mêmes suggestions et la même saisie libre que l'écran de la journée.
Les corrections sont enregistrées ensemble avec **Enregistrer**. Annuler ne
modifie rien ; ouvrir puis valider un horaire inchangé conserve sa valeur
enregistrée. Le début du premier
poste et la fin du dernier définissent les bornes de la journée. Modifier une
limite intermédiaire ajuste la limite du poste voisin, sans chevauchement.
La fin d'un poste encore en cours reste pilotée par les boutons de changement
de tâche ou de clôture.
**Ajouter un poste oublié** permet de choisir une plage existante puis les
horaires du poste à insérer dans cette plage. Le début et la fin sont préremplis
avec les deux bornes connues (jusqu'à l'heure actuelle pour un poste en cours).
Il suffit de changer une seule borne pour corriger le début ou la fin de la plage.
Les portions avant et après sont
conservées avec leur tâche et leur client. Par exemple, un poste 08:00–12:00
peut devenir 08:00–09:00, une tâche oubliée 09:00–10:00, puis 10:00–12:00.
Le bouton **Repas** permet aussi d’ajouter un déjeuner oublié et de renseigner
panier ou restaurant ; **Pause** ajoute une pause hors repas. Pour ajouter juste
après une frontière existante, choisissez le poste qui commence à cette frontière.
Dans **Récap**, choisissez la date puis **Voir les détails → Corriger cette
journée** : les mêmes actions sont disponibles sans réouverture, même si une autre journée est en
cours. Le récapitulatif et son texte à copier sont recalculés après chaque
enregistrement. La suppression d'un poste demande confirmation ; le voisin
absorbe sa durée, et le dernier poste d'une journée ne peut pas être supprimé.
Supprimer un repas réaffecte sa durée au poste voisin et peut donc changer le
total des heures de présence.
Chaque correction est transactionnelle. Les horaires inversés, les insertions
hors de la plage choisie et les formulaires devenus périmés après une autre
modification sont refusés sans écriture partielle.
## Statistiques
Ouvrez **Réglages → Consulter les statistiques**. Le bouton **Stats** de la barre
du bas n'apparaît que sur cette page ; les autres écrans gardent leurs trois
boutons habituels. La page fonctionne hors ligne. Les calculs de statistiques restent sur l’appareil ;
la synchronisation facultative peut partager leur historique source chiffré.
Les filtres couvrent tout l'historique, le mois, l'année, les 30 derniers jours
ou une plage de dates inclusive. Les journées en cours sont exclues par défaut.
L'option pour les inclure affiche des cumuls et détails provisoires. Les moyennes
de « Repères et observations », la médiane, les extrêmes et les profils par jour
de semaine restent calculés sur les seules journées terminées enregistrées.
Les statistiques présentent :
- Les heures de présence hors repas et pauses, les pauses, le transport identifié, les nombres de
journées, postes, tâches et clients.
- Les heures de présence par client et par tâche, leurs parts des heures de présence, les nombres de
journées concernées et les détails croisés, accessibles en ouvrant une ligne.
- L'évolution par mois ou semaine, sans inventer des heures pour les jours absents.
- Les journées avec repas panier ou restaurant, les restaurants fréquentés,
les durées de pause et les journées sans pause enregistrée. Plusieurs fragments
de repas le même jour ne créent pas plusieurs journées de repas.
- Les moyennes, la médiane, les journées extrêmes, les principales concentrations
de temps par client et tâche, et le détail quotidien.
- Les clients ou restaurants non renseignés, les postes à zéro après arrondi,
les journées vides, les plages non couvertes et les chevauchements éventuels.
Le calcul reprend l'arrondi du récapitulatif et exclut toujours le repas et les pauses des heures de présence.
Seules les données encore conservées sont analysées : les jours absents ou purgés
ne permettent pas de conclure à une absence de travail. Le transport identifié
repose sur le type enregistré, pas sur une déduction à partir du nom de la tâche.
En développement, la console expose `timelog.seed()` pour générer 20 jours
d’historique réaliste (utile pour tester le moteur de suggestions) et
`timelog.wipe()` pour l’effacer.
## Déploiement
La pile comprend l’application statique servie par Caddy et une API de synchronisation
facultative adossée à SQLite dans un volume persistant. Caddy obtient et renouvelle
le certificat HTTPS. La synchronisation reste désactivée dans chaque client jusqu’à
son activation explicite. Voir [SYNC.md](SYNC.md) pour l’association, le chiffrement,
la récupération, les quotas et les sauvegardes du serveur.
```sh
cp .env.example .env
$EDITOR .env # renseigner SITE_ADDRESS, ACME_EMAIL et TRUST_PROXY
docker compose up -d --build
```
Prérequis :
- `SITE_ADDRESS` doit déjà pointer vers le serveur.
- `ACME_EMAIL` est obligatoire et doit être une adresse de contact valide.
- Les ports **80 et 443** doivent être accessibles depuis Internet. Le port 80 est
obligatoire : le défi ACME l’utilise pour vérifier la propriété du domaine.
Les certificats sont stockés dans le répertoire monté `./caddy/data`. Conservez-le :
sans lui, Caddy redemande un certificat à chaque recréation et atteindra la limite
de requêtes de Let's Encrypt après quelques redémarrages.
Pour mettre à jour, récupérez les changements et reconstruisez l’image :
```sh
git pull && docker compose up -d --build
```
Les clients déjà ouverts ne se rechargent pas seuls. Chaque application vérifie
toutes les heures si une nouvelle version est disponible et affiche une bannière
**« Installer »**, uniquement lorsqu’aucune journée n’est en cours, afin qu’une
mise à jour n’interrompe jamais une journée de travail.
### HTTPS est obligatoire
Les navigateurs n’enregistrent un service worker que dans un contexte sécurisé. En
`http://` simple, sur une adresse IP ou un nom d’hôte, l’application se charge mais
n’a **pas de fonctionnement hors ligne, ne peut pas être installée et ne se met jamais
à jour** - ce qui annule l’intérêt de sa conception. `localhost` est la seule
exception, d’où le fonctionnement de `npm run dev` sans certificat.
### Derrière un reverse proxy existant
La configuration Caddy actuelle termine elle-même TLS et accepte le protocole PROXY
depuis l’adresse ou le CIDR défini dans `TRUST_PROXY`. Indiquez l’adresse réelle du
proxy amont ; `192.0.2.10/32` dans `.env.example` est une valeur fictive de documentation.
`LISTEN_IP` sélectionne l’interface locale des ports publiés et vaut `0.0.0.0` par
défaut. Un déploiement qui termine TLS ailleurs doit disposer de sa propre configuration
Caddy et de ports ; modifier `SITE_ADDRESS` seul ne configure pas ce mode.
L’application doit être servie à la racine de son domaine. `start_url` et la portée
du service worker valent tous deux `/` : un hébergement dans un sous-répertoire ne
fonctionnera pas sans modifier le manifeste et `base` dans Vite.
## Architecture
Deux décisions structurent le reste de l’application.
**Les horodatages bruts sont enregistrés ; l’arrondi n’est appliqué qu’à l’affichage.**
Modifier l’arrondi corrige donc aussi les journées passées. Deux postes adjacents
partagent le même instant enregistré - la fin du poste *N* est le début du poste
*N+1* - afin que l’arrondi ne puisse jamais créer de trou entre eux. Les sélecteurs
d’heure suivent la grille d’arrondi choisie ; ouvrir puis valider un horaire inchangé
préserve son horodatage. Les récapitulatifs appliquent le même arrondi à l’affichage.
**Les suggestions sont déduites de l’historique, pas de tables de compteurs.** Rien
n’est à synchroniser : le modèle est reconstruit en mémoire à partir des postes.
En contrepartie, une durée de conservation courte dégrade les prédictions ; c’est
pourquoi elle est de deux ans par défaut.
```
src/lib/
time.js dates locales, arrondi, formatage (pur)
predict.js moteur de suggestions (pur)
resume.js génération du texte du registre (pur)
db.js schéma Dexie et invariant de chaîne
day.svelte.js machine à états du cycle de journée
```
Les fonctions pures et les opérations de base de données et de cycle de vie ont des
tests de régression. `db.js` regroupe les modifications des journées et des postes
dans des transactions communes, notamment la création, la clôture, la reprise et la
correction des bornes.
## Pauses et nouveaux parcours
**Journée → Commencer une pause** ferme le poste courant. **Reprendre le travail**
propose la dernière tâche de travail et son client, immédiatement modifiables.
Une pause a ses propres horaires et son libellé, sans client ni type de repas.
Le détail montre tous les postes ; la consultation reste séparée de la correction.
Le récapitulatif et sa copie excluent les pauses. Le transport entre dans le total
des heures de présence, les repas et pauses en sont exclus. Seules les statistiques montrent le
cumul des pauses, séparément des repas. Les intervalles vides importés sont préservés.
**Réglages → Gérer les automatismes** et **Gérer les jours non travaillés** ouvrent
des pages dédiées ; le bouton Retour du navigateur fonctionne via les routes hash.
**Gérer les données et sauvegardes** ouvre aussi une page dédiée, avec retour aux
réglages, conservation de la durée après rechargement et accès hors ligne.
Les réglages sont écrits par transaction sur la dernière valeur persistée pour
préserver les modifications d’autres onglets. Les erreurs de démarrage et
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. L’export reste local ; conservez le fichier hors de cet appareil. La synchronisation
facultative utilise un coffre distinct et ne remplace pas cette sauvegarde.
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. Ils couvrent
aussi le nouvel écran Données, le retour du champ de conservation à sa valeur
persistée après un échec, le refus d’un export modifié et l’annulation complète
d’une purge en erreur, avec préservation de la sauvegarde précédente. 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 Headless Shell et ses dépendances avant de lancer ces
tests. Elle télécharge uniquement la variante utilisée par les tests sans interface
et accorde 180 secondes à chaque connexion de téléchargement, avec les nouvelles
tentatives intégrées à Playwright. L’installation dispose de 20 minutes et le job
de validation de 30 minutes. Une erreur d’installation bloque le déploiement.
## Synchronisation facultative entre appareils
**Données et sauvegardes → Synchronisation et appareils** permet de créer un coffre
chiffré sans e-mail ni mot de passe, puis de lier des appareils avec un code temporaire
ou un QR code et une confirmation sur l’écran dédié. La saisie reste locale et
fonctionne hors ligne. Les journées concurrentes sont conservées pour résolution.
Un document de secours permet de retrouver l’accès après perte des appareils.
Les règles métier sont partagées ; le thème et le suivi des sauvegardes restent
locaux. Un appareil est responsable des automatismes, avec proposition de transfert
à l’ouverture sur un appareil secondaire. Les demandes d’association ne produisent
aucune alerte ailleurs dans l’application.
Le protocole, les limites de sécurité, le fonctionnement des suppressions et les
commandes de sauvegarde/restauration du serveur sont documentés dans [SYNC.md](SYNC.md).
Pour le développement, lancer aussi `npm run sync:server` avec Node.js 24.
## Licence et crédits typographiques
Le code source de Pardis est distribué sous la licence GNU General Public License,
version 3 uniquement (GPL-3.0-only). Le texte intégral se trouve dans
[LICENSE.md](LICENSE.md). Les fichiers de polices fournis sont des œuvres distinctes,
distribuées sous la SIL Open Font License, version 1.1. Leurs mentions de droits
d’auteur et le texte de cette licence figurent dans [public/fonts/OFL.txt](public/fonts/OFL.txt).
Copyright (C) 2026 EI Adrien Weiskopff.
- **Bebas Neue**, par Ryoichi Tsunekawa - SIL Open Font License 1.1.
- **Charis SIL** - SIL Open Font License 1.1. Voir le [site officiel de Charis SIL](https://software.sil.org/fr/charis/).