Files
pardis/README.md
2026-09-06 13:50:58 +02:00

97 lines
3.8 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/
```
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.