Repository navigation
Releases: rbenzing/SlimBooks
Release list
Slimbooks v3.1.0
Release v3.1.0
Upgrading from 3.0.0 asks nothing of you. No environment variable changed, no
migration was added, and nothing needs a manual step — pull the image or the
tag, restart, and you are done. If 3.0.0 is running, your .env is already
correct for this release.
What changed
Every dialog in the application is now a real one. All thirteen modals were
rebuilt on the browser's native element, so each now traps focus,
closes on Escape, renders above the rest of the page, makes the content behind
it inert, and hands focus back to whatever opened it. Eleven of the thirteen
had none of that before — they were plain overlays a keyboard user could tab
straight out of, with no way to close them from the keyboard at all. The
connection-lost dialog is the deliberate exception to Escape: it stays put,
because a dropped connection is not something you can dismiss your way out of.
React was upgraded to 19, and @types/express from 4 to 5 — the latter matching
the Express 5.2.1 runtime the server has actually been running since 3.0.0, so
the old types were the thing that was wrong.
Five frontend dependencies were removed, roughly 1.1 MB: the four @radix-ui
packages and class-variance-authority. The native dialog work replaced
everything they were still doing.
Fixed
A clean checkout could not install. react-dom had been raised to 19 while react
stayed at 18 — an unsatisfiable peer dependency that made npm ci fail outright,
breaking CI, the release workflow and both Docker builds. If you build from
source, this is the release that repairs it.
Known issue
A toast raised from inside an open dialog is currently painted behind it and
cannot be clicked. This affects the error shown for a malformed CSV in the
client, expense and payment import dialogs, and the failure notice when a PDF
cannot be generated — in those cases the operation fails silently rather than
telling you why. The import itself is unaffected; only the message is lost. A
fix is planned for the next release.
Slimbooks v3.0.0
Slimbooks v2.5.0
Release 2.5.0
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
v2.4.0
Release 2.4.0
Bumped with npm version, so package.json and both package-lock.json
entries move together. They had disagreed since 2.2.0, which edited one by
hand and left the lockfile saying 2.1.1 — and the lockfile travels inside
the artifact the release workflow ships.
Also writes down what a version number promises here, because 2.2.0 was
tagged locally and never pushed: the commits were live on main while
GitHub's latest release stayed 2.1.1 for eleven days. Pushing a branch and
pushing a tag are separate commands and only the second one releases.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Slimbooks v2.3.0
Release 2.3.0
Bumped with npm version, so package.json and both package-lock.json
entries move together. They had disagreed since 2.2.0, which edited one by
hand and left the lockfile saying 2.1.1 — and the lockfile travels inside
the artifact the release workflow ships.
Also writes down what a version number promises here, because 2.2.0 was
tagged locally and never pushed: the commits were live on main while
GitHub's latest release stayed 2.1.1 for eleven days. Pushing a branch and
pushing a tag are separate commands and only the second one releases.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Slimbooks v2.2.0
Merge epoch-timestamps: store instants as epoch milliseconds (2.2.0)
Slimbooks v2.1.1
Store one timestamp format, UTC, and render it on the viewer's clock
Two shapes were being written into the same TEXT columns:
2026-08-12T13:54:13.241Z insertRecord/updateRecord, via toISOString()
2026-08-12 13:54:13 the column defaults and the dialect
Both are UTC, so this was never a timezone bug. It was a comparison bug:
these columns are TEXT on both backends, every comparison is
lexicographic, and a space sorts below T — so 2026-08-12 23:00:00
sorted before 2026-08-12T01:00:00.241Z. Any window query spanning the
two formats returned the wrong rows, silently and only sometimes.
Everything now stores YYYY-MM-DDTHH:MM:SSZ — 20 characters, fixed
width, whole seconds. The SQL spelling was the smaller change but is not
a format JavaScript parses to a defined instant: the standard covers the
T/Z form and says nothing about the space form, so V8 reads it as
local time. The database moves to the format its only consumer can read.
Calendar-day columns are narrowed back to YYYY-MM-DD. A due date
written as an instant rendered as one day or the next depending on where
the reader was sitting; a due date is a day.
Migration 014 rewrites existing rows, in JavaScript rather than SQL —
the obvious statement needs || on SQLite and CONCAT on MySQL, and a
migration that branches on the engine is what this codebase avoids. It
is idempotent per row, so a second run touches nothing.
On the display side, eleven screens imported a formatDateSync that
hard-coded MM/DD/YYYY in en-US and parsed with a bare new Date(...),
so the format chosen in Settings applied on some screens and not others,
and a bare yyyy-MM-dd due date showed as the previous day for every
viewer west of UTC. That copy is gone; the import path now resolves to
the implementation in date.util.ts, which reads the user's settings and
shares one formatter with the async path. formatTimeSync and
formatDateTimeSync join it, and the settings are mirrored into
localStorage so the first paint is already in the chosen format.
Two standing guards, since a one-time sweep is what missed the last
round: portableSql.test.ts now also fails on toISOString() written to
a timestamp column, and the dialect tests evaluate their expressions
against a real engine instead of pinning the text — a pinned string is
equally happy with a format that does not parse.
Verified against MySQL 9.7 and SQLite: 2363 tests, both drivers, nothing
skipped. A real upgrade of a database carrying both legacy shapes
reorders correctly afterwards.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Slimbooks v2.1.0
Release 2.1.0
Minor rather than major: everything since 2.0.0 is additive. DB_DRIVER defaults
to sqlite and STORAGE_DRIVER to disk, so an existing install upgrades with no
configuration change, no data migration and no behaviour change. Nothing was
removed and no environment variable was retired.
The version is read from package.json at runtime by APP_VERSION, so this one
bump reaches the API and the seeded app_version setting.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Slimbooks v2.0.0
Package dist/ alone, and back up the database from where it now lives
The release job still tar'd server/dist, which 2.0.0 removed, so packaging
failed with "Cannot stat: No such file or directory" after a green build. The
build emits dist/client and dist/server, so dist alone covers both halves.
The Raspberry Pi backup script had the same stale path. That one failed
quietly: it tested for a file that no longer exists and skipped the backup, so
an operator would have been told nothing while getting no database backup at
all. It now reads DATA_DIR, warns when it finds nothing, and prefers
sqlite3 .backup over cp — copying a WAL database out from under a running
server can capture a torn state.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Slimbooks v1.1.0
Track src/utils/data, wrongly excluded by an unanchored gitignore rule
.gitignore carried data as a bare pattern. Git matches that against any file
or directory of that name at any depth, so alongside the runtime SQLite
directories it also caught src/utils/data/ — filtering, validation,
import-export and their index, 555 lines imported by 15 modules.
Those four files were never in the repository. Nothing locally could notice:
they are present on disk, so typecheck, tests and build all passed. It
surfaced the moment CI cloned the repo and could not resolve @/utils/data,
which is precisely the class of bug CI exists to catch.
The database directories are now named explicitly and anchored, and the log
pattern is directory-only for the same reason.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com