Settings gains a "Dark mode" switch. Theme preference is device-global
(localStorage), independent of accounts. With no explicit choice the app keeps
following the OS via prefers-color-scheme; picking a mode sets data-theme on
<html>, which the CSS treats as an override (attribute selector beats the media
query). A tiny <head> script applies a saved choice before first paint to avoid
a light/dark flash. Toggling previews live, independent of Save/Cancel.
Bumps the service-worker cache. Verified in a headless-browser run: default
follows OS, enabling dark swaps the palette, the choice persists across reload,
and toggling back restores light.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Settings → Delete account removes the signed-in account and everything it
owns. DELETE /api/me re-checks the password (guarding an unattended session),
then wipes the user's events, config, sessions and user row in one transaction
and removes their photos/<user_id>/ directory. The client clears the account's
local cache and returns to the login screen.
Bumps the service-worker cache so clients pick up the new UI.
Verified: wrong password is rejected (401, data intact); correct password
returns 204, invalidates the session, drops all rows to zero and removes the
photo dir; the email can be re-registered afterwards. Confirmed end to end in
a headless-browser run of the Settings → delete flow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every event, profile and photo is now scoped to a signed-in account, so
separate people can track separate puppies on one server.
Server:
- users + sessions tables; bcrypt passwords; random session tokens stored
hashed and set as an HttpOnly cookie. Middleware gates /api/* behind a
valid session.
- register/login/logout/me endpoints. Registration requires a shared invite
code (-invite-code / PUPPY_INVITE_CODE); empty disables it.
- events, config and photos are keyed by user_id; the sync upsert guards
against cross-user overwrites and reads are scoped, so accounts are isolated.
Photos live under photos/<user_id>/ and are only served to their owner.
- in-place schema migration adds user_id and reshapes config; legacy
single-tenant data (including imported events.json) is parked ownerless and
adopted by the first account to register.
Client:
- login/register gate in front of the app; the tracker only boots once the
session check resolves. localStorage is namespaced per user.
- 401s bounce back to login; an offline reload falls back to the last cached
session so offline-first still works. Logout clears the session and reloads.
Deployment:
- module.nix gains inviteCodeFile (secret via EnvironmentFile) and
secureCookies options.
Verified end to end (curl + a headless-browser run of the auth flow):
isolation between accounts, invite enforcement, first-user adoption, photo
ownership, and session persistence across reload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the JSON-file event and config stores with a SQLite database
(modernc.org/sqlite, pure-Go so the static build keeps CGO_ENABLED=0).
Last-write-wins now rides on the upsert's WHERE clause rather than a
Go-side map compare; the sync protocol and HTTP handlers are unchanged.
On first start the server auto-imports any legacy events.json/config.json
sitting in the data dir, renaming them to *.imported. The -data flag now
points at puppy.db; photos still live on the filesystem alongside it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>