Added fonts
Added license file and updated README
This commit is contained in:
613
README.md
613
README.md
@@ -1,395 +1,394 @@
|
||||
# Pardis
|
||||
|
||||
Offline-first PWA for tracking hours on site. While the app is running, it can start
|
||||
the day automatically and prompt for its end. Each task begins where the previous
|
||||
one ended. Closed days have concise summaries ready to copy into a paper logbook.
|
||||
An offline-first progressive web app (PWA) for tracking on-site working hours. While
|
||||
the app is open, it can start a workday automatically and prompt to close it. Each
|
||||
task starts when the previous one ends. Closed days have concise summaries ready to
|
||||
copy into a paper logbook.
|
||||
|
||||
Built for use outdoors: high contrast, large targets, usable with gloves on. Data
|
||||
is saved locally in IndexedDB. Optional end-to-end encrypted synchronization shares
|
||||
it between authorized devices without e-mail or password registration.
|
||||
Designed for outdoor use: high contrast, large touch targets, and usable with gloves.
|
||||
Data is stored locally in IndexedDB. Optional end-to-end encrypted synchronization
|
||||
shares it between authorized devices without requiring an email address or password.
|
||||
|
||||
French documentation: [README.fr.md](README.fr.md).
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run dev # http://localhost:5173
|
||||
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
|
||||
```
|
||||
npm install
|
||||
npm run dev # http://localhost:5173
|
||||
npm test # unit and IndexedDB regression tests
|
||||
npm run check # Svelte diagnostics
|
||||
npm run build # production build into dist/
|
||||
npm run validate # tests, Svelte diagnostics 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
|
||||
## Exporting and importing data
|
||||
|
||||
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.
|
||||
In **Settings (Réglages) → Manage data and backups (Gérer les données et
|
||||
sauvegardes)**, the **Data and backups (Données et sauvegardes)** screen groups
|
||||
export, import, backup verification, and retention settings. The **Back up
|
||||
(Sauvegarder)** reminder also opens this screen.
|
||||
|
||||
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.
|
||||
Data remains available in the browser on this device. Optional encrypted
|
||||
synchronization can share it with your other devices. Clearing site data or losing
|
||||
the device can result in lost history: keep a verified copy of the exported file on
|
||||
another storage device.
|
||||
|
||||
**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.
|
||||
**Export data (Exporter les données)** downloads a file named
|
||||
pardis-YYYY-MM-DD.json. It contains all retained days, their entries, and saved
|
||||
settings. Export works offline and does not modify the device's data. Internal entry
|
||||
identifiers are regenerated on 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 :
|
||||
To restore a backup, choose **Import file… (Importer un fichier…)**, then review the
|
||||
file name, counts, and any reported adjustments. Two modes are available:
|
||||
|
||||
- **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.
|
||||
- **Merge (Fusionner)** adds only dates that are not already present. An existing
|
||||
date is kept in full even if the file contains a different version of its entries.
|
||||
Local settings are preserved. Importing the same file twice creates no duplicates.
|
||||
- **Replace all (Remplacer tout)** asks for confirmation, then replaces every day,
|
||||
entry, and setting, including a local day currently in progress. If the file has no
|
||||
settings, defaults are restored.
|
||||
|
||||
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.
|
||||
Imported days are marked as closed. For a day exported while in progress, the last
|
||||
entry is stopped at the export time, preserving elapsed time. Existing gaps between
|
||||
work periods are preserved. An empty backup is valid and can restore an empty device.
|
||||
|
||||
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.
|
||||
The JSON format is identified by format: "timelog-export" and version: 2. Version 1
|
||||
files remain importable. Older app versions reject version 2 to avoid turning breaks
|
||||
into work. Foreign files, unsupported versions, duplicate dates, and invalid
|
||||
settings are rejected. Unusable entries and time-range corrections are reported
|
||||
before confirmation. The size limit is 20 MB. Writes are transactional: a failure
|
||||
cancels the entire merge or replacement. The summary, theme, and suggestions then
|
||||
refresh without reloading the 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.
|
||||
Under **Retention (Durée de conservation)**, **Keep (days) (Conserver (jours))**
|
||||
determines which older days are offered for deletion. Changing this setting does not
|
||||
delete anything; automatic deletion is disabled. Retained history feeds suggestions
|
||||
and statistics. The latest closed day and active days are always kept.
|
||||
|
||||
**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.
|
||||
**Delete now (Purger maintenant)** lists affected dates and asks for confirmation.
|
||||
A local backup is kept before deletion. **Recover pre-deletion backup (Récupérer la
|
||||
sauvegarde avant purge)** can merge or replace data, then export it after recovery.
|
||||
This copy is replaced by the next deletion and is removed if site data is cleared; an
|
||||
external file is still necessary.
|
||||
|
||||
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.
|
||||
Import/export tests cover the format, settings, duration fidelity, invalid files,
|
||||
merge, replacement, and transaction rollback using a simulated IndexedDB.
|
||||
src/lib/fixtures/backup.json is the supplied test fixture; it is not published with
|
||||
the app.
|
||||
|
||||
## Journées, reprise et historique
|
||||
## Workdays, resuming, and history
|
||||
|
||||
- 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.
|
||||
- If a workday was left open, it is closed when the app is next opened, at its planned
|
||||
end time (including overtime), but never before the last recorded activity. This is
|
||||
an estimate: the app does not measure work while it is closed. The final entry
|
||||
keeps its duration up to that limit instead of being reset to zero.
|
||||
- A day closed today offers **Resume workday (Reprendre la journée)** with
|
||||
confirmation: continue the last entry, or create a break up to now (the default).
|
||||
A final meal or break keeps its type; work resumes in a new entry. Past days can be
|
||||
corrected via **View details → Edit this day (Voir les détails → Corriger cette
|
||||
journée)**.
|
||||
- Creating, closing, and resuming a day are transactional. Simultaneous actions on
|
||||
the same entry cannot create duplicates; a closed day cannot receive a new active
|
||||
entry. Deletion skips active days and always keeps the latest closed day.
|
||||
- Lunch schedules show a reminder and an indicative return time. Transitions remain
|
||||
manual unless **Lunch break (Pause déjeuner)** is enabled under automations.
|
||||
- In **Summary (Récap)**, the **Day to view (Journée à consulter)** selector shows
|
||||
and copies every retained closed day, including imported days.
|
||||
|
||||
Les tests couvrent notamment les clôtures oubliées, les reprises, les appels
|
||||
concurrents, les retours d'horloge, les échecs transactionnels et la conservation.
|
||||
Tests cover missed closures, resuming, concurrent calls, clock changes, transaction
|
||||
failures, and data retention.
|
||||
|
||||
## Automatismes
|
||||
## Automations
|
||||
|
||||
Dans **Réglages → Automatismes**, chaque comportement a son interrupteur :
|
||||
In **Settings (Réglages) → Automations (Automatismes)**, each behavior has its own
|
||||
switch:
|
||||
|
||||
| Option | Comportement |
|
||||
| Option | Behavior |
|
||||
| --- | --- |
|
||||
| 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. |
|
||||
| Opening | Start at the scheduled times and on scheduled days. Enabled by default, as before. |
|
||||
| Start of day | When tapping “Arrived on site” (Arrivé sur site), suggest the last work task from the previous recorded day, excluding meals and travel. |
|
||||
| Lunch break | Start a break at the usual time and resume the previous task after the configured duration. Reuse the meal type and restaurant from the previous recorded day. |
|
||||
| Site arrival | During an automatic meal, turn an unspecified initial travel period into a travel entry of 1/5/15/30 minutes, according to the rounding setting, followed by the previous task. |
|
||||
| Half-day end | At a break or day closure, replace an unspecified automatic period with the last known work task. Preserve any manual entry. |
|
||||
| End of day | Close the day at the scheduled time without 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.
|
||||
The five newer options are disabled by default. They are included in backups and are
|
||||
restored by replacement imports. **Site arrival** only applies with an automatic
|
||||
meal. Without this travel estimate, **Half-day end** may replace unspecified initial
|
||||
travel with work for the entire period. If a work task is already present, no
|
||||
duplicate is added to the half-day.
|
||||
|
||||
« 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.
|
||||
“Previous” means the latest recorded closed day before the current date, including
|
||||
after a weekend. Completion reuses the most recent known work task; when there is no
|
||||
work history, it uses “On-site work” (Travaux sur site). Automatically created
|
||||
entries are marked **Auto**. Editing one makes it manual and protects it from later
|
||||
inference. Older entries without this marker are treated as manual.
|
||||
|
||||
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.
|
||||
A manually started or edited break is not ended by the lunch automation. A manually
|
||||
chosen meal type or restaurant is preserved. A deleted break is not recreated.
|
||||
Disabling an option stops future transitions; it does not remove entries already
|
||||
saved. A zero lunch duration does not add an automatic break. The duration of an
|
||||
automatic break already in progress is preserved if the setting changes later.
|
||||
|
||||
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.
|
||||
The app checks the clock every 30 seconds and when it returns to the foreground.
|
||||
A closed or suspended PWA cannot run code at a scheduled time: on reopening, missed
|
||||
steps are caught up at their planned times without overwriting a later manual action.
|
||||
Already closed days remain unchanged, and entirely missing dates are not created.
|
||||
For a day with no intervention, enable the desired options and open the app at least
|
||||
once that day.
|
||||
|
||||
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.
|
||||
Overtime and resumed days are respected. A manual start leaves at least one hour
|
||||
before an automatic closure. Automatic transitions are transactional: an error
|
||||
cancels the whole operation, and simultaneous calls cannot create duplicates.
|
||||
|
||||
## Jours non travaillés
|
||||
## Non-working days
|
||||
|
||||
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.
|
||||
In **Settings (Réglages) → Non-working days (Jours non travaillés)**, add dates to
|
||||
block in advance. The list is sorted by date; each date can be edited or removed.
|
||||
Duplicates are rejected. These exceptions take precedence over the usual weekdays
|
||||
and prevent automatic opening, including catch-up when the app is reopened or used
|
||||
offline.
|
||||
|
||||
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.
|
||||
No day or entry is created for a blocked date. Days already started or closed are
|
||||
preserved. **Start anyway (Démarrer quand même)** in **Workday (Journée)** allows an
|
||||
exceptional manual start. Removing a date restores the usual automatic start rules.
|
||||
|
||||
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.
|
||||
The list is saved with settings and included in data exports. A replacement import
|
||||
restores the backup's list; a merge keeps the local list, like other settings. An
|
||||
older backup without this list resets it to empty on replacement.
|
||||
|
||||
## Corriger les postes et les journées terminées
|
||||
## Editing entries and closed days
|
||||
|
||||
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.
|
||||
The **Total hours present (Total des heures de présence)** excludes breaks and meals.
|
||||
Meals remain separate in the summary and copied text. Totals use the selected
|
||||
rounding setting.
|
||||
|
||||
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.
|
||||
In **Workday (Journée)**, tap an entry under **Day entries (Postes de la journée)**
|
||||
to edit its start, end, task, or client. **Regular entry (Poste ordinaire)**, **Meal
|
||||
(Repas)**, and **Break (Pause)** buttons select the entry type. When editing the
|
||||
times or label of an existing travel entry, its internal type is preserved.
|
||||
|
||||
**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.
|
||||
Time selectors do not include a date: they keep the entry's date. Minutes and
|
||||
adjustment buttons follow the selected rounding interval (1, 5, 15, or 30 minutes).
|
||||
Tasks, clients, and restaurants use the same suggestions and free-text entry as the
|
||||
workday screen.
|
||||
|
||||
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.
|
||||
Corrections are saved together with **Save (Enregistrer)**. Cancel makes no changes;
|
||||
opening and saving an unchanged time preserves its stored value. The first entry's
|
||||
start and last entry's end define the day boundaries. Editing an intermediate
|
||||
boundary adjusts the adjacent entry's boundary without overlap. The end of an active
|
||||
entry remains controlled by task changes or day closure.
|
||||
|
||||
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.
|
||||
**Add a missed entry (Ajouter un poste oublié)** lets you choose an existing period,
|
||||
then the times for an entry to insert into it. The start and end are prefilled with
|
||||
the known boundaries (up to now for an active entry). Change either boundary to
|
||||
adjust one side of the period. The portions before and after are preserved with
|
||||
their original task and client. For example, 08:00–12:00 can become 08:00–09:00,
|
||||
a missed task from 09:00–10:00, then 10:00–12:00. **Meal (Repas)** can add a missed
|
||||
meal and set its type to packed or restaurant; **Break (Pause)** adds a non-meal
|
||||
break. To add an entry immediately after an existing boundary, choose the entry
|
||||
that starts at that boundary.
|
||||
|
||||
## Statistiques
|
||||
In **Summary (Récap)**, choose a date, then **View details → Edit this day (Voir les
|
||||
détails → Corriger cette journée)**. The same actions are available without resuming
|
||||
the day, even while another day is in progress. The summary and copied text are
|
||||
recalculated after each save. Deleting an entry requires confirmation; its neighbor
|
||||
absorbs its duration, and the final entry of a day cannot be deleted. Deleting a meal
|
||||
assigns its duration to the neighboring entry and can therefore change total hours
|
||||
present.
|
||||
|
||||
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é.
|
||||
Every correction is transactional. Reversed times, insertions outside the selected
|
||||
period, and forms made stale by another edit are rejected without a partial write.
|
||||
|
||||
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.
|
||||
## Statistics
|
||||
|
||||
Les statistiques présentent :
|
||||
Open **Settings (Réglages) → View statistics (Consulter les statistiques)**. The
|
||||
**Stats** button appears in the bottom bar only on this page; other screens keep
|
||||
their usual three buttons. The page works offline. Statistics are calculated on the
|
||||
device; optional synchronization may share their source history in encrypted form.
|
||||
|
||||
- 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.
|
||||
Filters cover all history, the month, the year, the last 30 days, or an inclusive
|
||||
date range. Days in progress are excluded by default. Enabling them shows
|
||||
provisional totals and details. Averages under **Highlights and observations
|
||||
(Repères et observations)**, the median, extremes, and weekday profiles use only
|
||||
recorded closed days.
|
||||
|
||||
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.
|
||||
Statistics include:
|
||||
|
||||
In dev the console exposes `timelog.seed()` to generate 20 days of realistic history
|
||||
(useful for exercising the suggestion engine) and `timelog.wipe()` to clear it.
|
||||
- Hours present excluding meals and breaks, breaks, identified travel, and counts of
|
||||
days, entries, tasks, and clients.
|
||||
- Hours present by client and task, their shares of hours present, the number of
|
||||
affected days, and cross-details available by opening a row.
|
||||
- Monthly or weekly trends without inventing hours for missing days.
|
||||
- Days with packed or restaurant meals, restaurants visited, break durations, and
|
||||
days with no recorded break. Multiple meal fragments on one day count as one
|
||||
meal day.
|
||||
- Averages, median, extreme days, top client and task time concentrations, and
|
||||
daily details.
|
||||
- Unspecified clients or restaurants, entries rounded to zero, empty days, uncovered
|
||||
periods, and possible overlaps.
|
||||
|
||||
Calculations use the summary's rounding and always exclude meals and breaks from
|
||||
hours present. Only retained data is analyzed: missing or deleted days cannot be
|
||||
taken as proof that no work happened. Identified travel uses the recorded entry type,
|
||||
not an inference from the task name.
|
||||
|
||||
In development, the console exposes `timelog.seed()` to generate 20 days of realistic
|
||||
history (useful for exercising suggestions) and `timelog.wipe()` to clear it.
|
||||
|
||||
## Deployment
|
||||
|
||||
The stack contains the static app served by Caddy and an optional-use synchronization
|
||||
The stack consists of the static app served by Caddy and an optional synchronization
|
||||
API backed by SQLite in a persistent volume. Caddy obtains and renews the HTTPS
|
||||
certificate. Synchronization stays disabled in each client until explicitly activated.
|
||||
See [SYNC.md](SYNC.md) for pairing, encryption, recovery, quotas and server backups.
|
||||
certificate. Synchronization remains disabled in each client until explicitly
|
||||
activated. See [SYNC.md](SYNC.md) for pairing, encryption, recovery, quotas, and
|
||||
server backups.
|
||||
|
||||
```sh
|
||||
cp .env.example .env
|
||||
$EDITOR .env # set SITE_ADDRESS, ACME_EMAIL and TRUST_PROXY
|
||||
docker compose up -d --build
|
||||
```
|
||||
cp .env.example .env
|
||||
$EDITOR .env # set SITE_ADDRESS, ACME_EMAIL and TRUST_PROXY
|
||||
docker compose up -d --build
|
||||
|
||||
Requirements:
|
||||
|
||||
- `SITE_ADDRESS` must already resolve to the server.
|
||||
- `ACME_EMAIL` is required and must be a valid contact address.
|
||||
- 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.
|
||||
- Ports **80 and 443** must be reachable from the internet. Port 80 is mandatory:
|
||||
the ACME challenge uses it to prove domain ownership.
|
||||
|
||||
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.
|
||||
Certificates are stored in the bind-mounted `./caddy/data` directory. Keep it:
|
||||
without it, Caddy requests a new certificate after every recreation and will hit
|
||||
Let's Encrypt rate limits after only a handful of restarts.
|
||||
|
||||
To update, pull and rebuild:
|
||||
|
||||
```sh
|
||||
git pull && docker compose up -d --build
|
||||
```
|
||||
git pull && docker compose up -d --build
|
||||
|
||||
Running clients do not reload on their own. Each app checks hourly for a new build and
|
||||
offers an **"Installer"** banner — but only while no working day is in progress, so an
|
||||
update can never interrupt someone mid-day.
|
||||
Running clients do not reload automatically. Each app checks hourly for a new build
|
||||
and offers an **Install (Installer)** banner, but only when no workday is in progress,
|
||||
so an update cannot interrupt a workday.
|
||||
|
||||
### HTTPS is not optional
|
||||
### HTTPS is required
|
||||
|
||||
Browsers only register a service worker in a secure context. Served over plain `http://`
|
||||
to an IP or hostname, the app still loads but has **no offline support, cannot be
|
||||
installed, and never updates** — which removes the point of the design. `localhost` is
|
||||
the only exception, which is why `npm run dev` works without certificates.
|
||||
Browsers only register a service worker in a secure context. Served over plain
|
||||
`http://` at an IP address or hostname, the app still loads but has **no offline
|
||||
support, cannot be installed, and never updates**, defeating the design. `localhost`
|
||||
is the only exception, which is why `npm run dev` works without certificates.
|
||||
|
||||
### Behind an existing reverse proxy
|
||||
|
||||
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 current Caddy configuration terminates TLS itself and accepts the PROXY protocol
|
||||
from the address or CIDR in `TRUST_PROXY`. Set this to the actual upstream proxy
|
||||
address; `192.0.2.10/32` in `.env.example` is a documentation placeholder.
|
||||
`LISTEN_IP` selects the local interface for 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
|
||||
manifest and Vite's `base`.
|
||||
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 manifest and Vite's `base`.
|
||||
|
||||
## Architecture
|
||||
|
||||
Two decisions shape everything else.
|
||||
|
||||
**Raw timestamps are stored; rounding is applied only at render.** Changing the rounding
|
||||
setting therefore corrects past days too. Adjacent entries share one stored instant —
|
||||
entry *N*'s end *is* entry *N+1*'s start — so rounding can never open a gap between
|
||||
them. Time pickers follow the selected rounding grid; opening an unchanged time
|
||||
preserves its stored timestamp. Summaries apply the same rounding at render.
|
||||
**Raw timestamps are stored; rounding is applied only when rendering.** Changing
|
||||
the rounding setting therefore corrects past days too. Adjacent entries share one
|
||||
stored instant - entry *N*'s end is entry *N+1*'s start - so rounding can never open
|
||||
a gap between them. Time pickers follow the selected rounding grid; opening an
|
||||
unchanged time preserves its stored timestamp. Summaries apply the same rounding
|
||||
when rendered.
|
||||
|
||||
**Suggestions are derived from history, not from counter tables.** There is nothing to
|
||||
keep in sync; the model is rebuilt in memory from the entries. The tradeoff is that a
|
||||
short retention window degrades predictions, which is why retention defaults to two
|
||||
years.
|
||||
**Suggestions are derived from history, not counter tables.** Nothing needs to be
|
||||
kept in sync; the model is rebuilt in memory from entries. The tradeoff is that a
|
||||
short retention window degrades predictions, which is why retention defaults to
|
||||
two years.
|
||||
|
||||
```
|
||||
src/lib/
|
||||
time.js local dates, rounding, formatting (pure)
|
||||
predict.js the suggestion engine (pure)
|
||||
resume.js logbook text builder (pure)
|
||||
db.js Dexie schema and the chain invariant
|
||||
day.svelte.js day lifecycle state machine
|
||||
```
|
||||
src/lib/
|
||||
time.js local dates, rounding, formatting (pure)
|
||||
predict.js suggestion engine (pure)
|
||||
resume.js logbook text builder (pure)
|
||||
db.js Dexie schema and chain invariant
|
||||
day.svelte.js day lifecycle state machine
|
||||
|
||||
Pure helpers and database/lifecycle operations have regression tests. `db.js`
|
||||
keeps day and entry changes in shared transactions, including creation, closure,
|
||||
reopening and boundary corrections.
|
||||
Pure helpers and database/lifecycle operations have regression tests. `db.js` keeps
|
||||
day and entry changes in shared transactions, including creation, closure, resuming,
|
||||
and boundary corrections.
|
||||
|
||||
## Pauses et nouveaux parcours
|
||||
## Breaks and newer workflows
|
||||
|
||||
**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.
|
||||
**Workday → Start a break (Journée → Commencer une pause)** closes the current entry.
|
||||
**Resume work (Reprendre le travail)** suggests the last work task and its client,
|
||||
which can be edited immediately. A break has its own times and label, without a
|
||||
client or meal type. Details show every entry; viewing is separate from editing.
|
||||
Summaries and copied text exclude breaks. Travel is included in total hours present;
|
||||
meals and breaks are excluded. Statistics show total break time separately from meals.
|
||||
Imported gaps are preserved.
|
||||
|
||||
**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.
|
||||
**Settings (Réglages) → Manage automations (Gérer les automatismes)** and
|
||||
**Manage non-working days (Gérer les jours non travaillés)** open dedicated pages;
|
||||
the browser Back button works through hash routes. **Manage data and backups (Gérer
|
||||
les données et sauvegardes)** also opens a dedicated page, with a return to settings,
|
||||
retention persistence after reload, and offline access.
|
||||
|
||||
La checklist avant production et les validations restant à exécuter sont suivies
|
||||
dans [PRODUCTION.md](PRODUCTION.md).
|
||||
Settings are written transactionally against the latest persisted value to preserve
|
||||
changes made in other tabs. Startup and refresh errors are shown with a Retry action.
|
||||
|
||||
## Sauvegarde vérifiée et validation navigateur
|
||||
The production checklist and remaining validation steps are tracked in
|
||||
[PRODUCTION.md](PRODUCTION.md).
|
||||
|
||||
**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.
|
||||
## Verified backups and browser validation
|
||||
|
||||
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.
|
||||
**Export data (Exporter les données)** prepares a file and saves its full fingerprint.
|
||||
**Verify exported file… (Vérifier le fichier exporté…)** reads the downloaded file,
|
||||
checks that it can be imported, and compares its contents with the prepared export.
|
||||
The date of the last verified backup is separate from the most recent prepared export.
|
||||
After seven days without a verified export, Pardis shows a reminder while in use.
|
||||
Export remains local; keep the file outside this device. Optional synchronization
|
||||
uses a separate vault and does not replace backups.
|
||||
|
||||
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.
|
||||
Deletion displays a fixed list of days. Backup and deletion share a transaction; a
|
||||
concurrent correction blocks deletion and requires reviewing the list again. Other
|
||||
recently imported days remain untouched.
|
||||
|
||||
Playwright tests use fresh contexts and synthetic data at 320 px. They cover
|
||||
viewing/editing, focus, Back navigation, resuming in two tabs, verified export,
|
||||
deletion/recovery, and offline reload. They also cover the dedicated Data screen,
|
||||
restoring a retention field after a save failure, rejecting a modified export, and
|
||||
fully rolling back a failed deletion while preserving the previous backup. Traces,
|
||||
videos, and screenshots are disabled. To use an installed Chrome, set
|
||||
`PARDIS_CHROME_PATH` to its path; otherwise install Chromium with Playwright.
|
||||
CI installs Chromium Headless Shell and its dependencies before running the tests.
|
||||
It downloads only the headless browser build used by the tests and gives each
|
||||
download attempt 180 seconds, with Playwright's built-in retries. Browser setup has
|
||||
a 20-minute limit and the validation job has a 30-minute limit. An installation
|
||||
failure blocks deployment.
|
||||
|
||||
## Synchronisation facultative entre appareils
|
||||
## Optional device synchronization
|
||||
|
||||
**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.
|
||||
**Data and backups (Données et sauvegardes) → Synchronization and devices
|
||||
(Synchronisation et appareils)** creates an encrypted vault without an email or
|
||||
password, then links devices using a temporary code or QR code and confirmation
|
||||
on the dedicated screen. Data entry stays local and works offline. Concurrent days
|
||||
are retained for conflict resolution. A recovery document restores access after
|
||||
losing the devices.
|
||||
|
||||
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.
|
||||
Business rules are shared; the theme and backup tracking stay local. One device is
|
||||
responsible for automations, with a transfer prompt when opening the app on a
|
||||
secondary device. Pairing requests do not create alerts elsewhere in the app.
|
||||
|
||||
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.
|
||||
The protocol, security limits, deletion behavior, and server backup/restore commands
|
||||
are documented in [SYNC.md](SYNC.md). For development, also run `npm run sync:server`
|
||||
with Node.js 24.
|
||||
|
||||
## License and font credits
|
||||
|
||||
Pardis is distributed under the GNU General Public License, version 3 only
|
||||
(GPL-3.0-only). See [LICENSE.md](LICENSE.md) for the full license text. The bundled font
|
||||
files are separate works and remain under the SIL Open Font License, version 1.1;
|
||||
their license and copyright notices are included in [public/fonts/OFL.txt](public/fonts/OFL.txt).
|
||||
Copyright (C) 2026 EI Adrien Weiskopff.
|
||||
|
||||
- **Bebas Neue**, by Ryoichi Tsunekawa - SIL Open Font License 1.1.
|
||||
- **Charis SIL** - SIL Open Font License 1.1. See the [official Charis SIL site](https://software.sil.org/fr/charis/).
|
||||
|
||||
Reference in New Issue
Block a user