# 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/ ``` 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.