138 lines
6.2 KiB
Markdown
138 lines
6.2 KiB
Markdown
# TimeLog
|
|
|
|
Offline-first PWA for tracking hours on site. The day starts and ends on its own, each
|
|
task begins where the previous one ended, and the app's main output is a concise
|
|
summary of the last working day, ready to copy into a paper logbook.
|
|
|
|
Built for use outdoors: high contrast, large targets, usable with gloves on. All data
|
|
stays on the device (IndexedDB); there is no backend and nothing is uploaded.
|
|
|
|
## Development
|
|
|
|
```sh
|
|
npm install
|
|
npm run dev # http://localhost:5173
|
|
npm test # unit tests for the time, prediction, resume and boundary logic
|
|
npm run check # svelte-check
|
|
npm run build # production build into dist/
|
|
```
|
|
|
|
## Exporter et importer les données
|
|
|
|
Dans **Réglages → Données**, **Exporter les données** télécharge un fichier
|
|
`timelog-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: 1`.
|
|
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.
|
|
|
|
L'import ne déclenche pas immédiatement de purge. La durée de conservation
|
|
s'applique de nouveau à la prochaine ouverture ou au changement de jour ; le
|
|
bouton **Purger maintenant** reste disponible.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
## Deployment
|
|
|
|
The stack is a single container: a multi-stage build compiles the app and Caddy serves
|
|
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
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Requirements:
|
|
|
|
- `SITE_ADDRESS` must already resolve to the server.
|
|
- 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
|
|
on every recreate and will hit the Let's Encrypt rate limit after a handful of restarts.
|
|
|
|
To update, pull and rebuild:
|
|
|
|
```sh
|
|
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.
|
|
|
|
### HTTPS is not optional
|
|
|
|
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.
|
|
|
|
### 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 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. Times a worker sets by hand are snapped to the grid on write, so a chosen value
|
|
reads back exactly as chosen.
|
|
|
|
**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.
|
|
|
|
```
|
|
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
|
|
```
|
|
|
|
The four pure modules hold the logic worth testing and carry the unit tests. `db.js`
|
|
concentrates every write that touches a boundary into `closeAndStart` and
|
|
`setEntryStart`, so the timeline cannot drift out of order from anywhere else.
|