# Changelog All notable changes to librenotes are recorded here. The format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] ## [0.1.0] — TBD The initial public release: a fork of Notesium turned into a multi-tenant SaaS, plus the surrounding infrastructure. ### Added - **Fork.** Forked [alonswartz/notesium](https://github.com/alonswartz/notesium) at `aff9f460c2d864112db7f0935b4168b107289d91`, restructured into the standard Go layout (`cmd/`, `internal/`), renamed the module to `git.librete.ch/public/librenotes`, preserved the MIT license alongside librenotes copyright, documented the upstream remote and cherry-pick workflow. - **User model + SQLite storage** (`internal/storage`). UUIDv4 IDs, email uniqueness, WAL mode, embedded migrations. - **Magic-link authentication** (`internal/auth`). 32-byte cryptographically random tokens, SHA-256-hashed at rest, single-use, 15-minute expiry. HS256 JWT sessions with 24-hour lifetime, `jwt.WithValidMethods` to reject `alg=none`. Pluggable `Mailer` interface with `SMTPMailer` and dev-friendly `LogMailer`. DB-backed per-email rate limiting. - **Per-tenant filesystem isolation** (`internal/tenant`). Each user gets a sandboxed directory; all reads and writes go through `os.Root` so path traversal and symlink escapes are rejected at the syscall layer. - **Tenant-aware HTTP API** (`internal/httpapi`). JWT middleware, tenant context, `/api/whoami`, `/api/notes` CRUD with optimistic-locking conflict detection (`?base=`), `/healthz`. - **`librenotes serve`** command wires storage, auth, tenants, notes, and a static frontend into one binary. Configuration via `LIBRENOTES_*` env vars or flags. Background goroutine purges expired magic tokens. - **Frontend** (`cmd/librenotes/web/public/`): mobile-first landing page, login + verify pages, app shell with sync badge and conflict dialog. Vanilla JS, no build step. - **JWT session client** with sessionStorage + automatic `Authorization: Bearer` injection and 401-redirects-to-login. - **Tenant-scoped localStorage** wrapper so two users on the same browser have isolated UI state; cleared on logout. - **Offline cache** in IndexedDB (`notes-cache.js`) with dirty tracking and tombstones for deletes. - **Background sync** (`sync.js`) — pushes dirty rows on reconnect, pulls remote changes, surfaces conflicts to the UI via custom events. - **PWA support**: manifest, service worker (cache-first shell, network-first API), 192/512/maskable icons, install prompt. - **Responsive CSS** with explicit breakpoints from 320px to 2560px, including a 3-column ultrawide layout primitive. - **Pointer events** on the resize handle so touch and pen work identically to mouse, including pointer capture for drag. - **Containerised deployment**: multi-stage Dockerfile producing a distroless `nonroot` image, dev + prod Compose stacks, in-binary `librenotes healthcheck` for distroless HEALTHCHECK. - **CI**: Gitea Actions workflow runs `make lint`, `make build`, `make test` on every push and PR. - **Deploy workflow** (gated on `DEPLOY_ENABLED=true`) builds and pushes images, SSHes to the host, runs `compose pull && up -d`, polls `/healthz`. - **Backup tooling** (`scripts/`): SQLite online snapshot + tar of notes, optional rclone off-site copy, retention pruning, weekly automated restore-test, systemd timer units. - **Documentation**: user guide, self-hosting, API reference, operations, contributing. - **Community infrastructure**: bug-report and feature-request Gitea issue templates, PR template, Code of Conduct. - **First-run onboarding**: welcome dialog + seeded sample note, dismissal persisted per tenant. ### Notes - The bundled notes engine (the original Notesium UI under `internal/notesium/web/app/`) is not yet wired into the multi-tenant frontend. Phase 7 will replace or wrap it. - SMTP delivery has been exercised against the LogMailer only; real-provider integration is part of self-hosting validation. [0.1.0]: https://git.librete.ch/public/librenotes/releases/tag/v0.1.0