Repository navigation
[1.3.2] - 2026-10-25
Bug Fixes
- Share modal silently saved an empty user list by @John710
The user picked a person in the dropdown and hit Save, expecting the
selection to be stored — but the two-step UX required clicking the tiny
'+' button first, so the PUT went out with users: [] and returned
success while nothing was shared. Selecting a user in the dropdown now
adds them immediately (Save stays disabled while the picker still holds
an uncommitted user). Verified against production for all four entity
types. - Exempt refresh/setup-required via allowList (max:0 blocks, not exempts) by @John710
@fastify/rate-limit treats a max of 0 as 'zero requests allowed' —
setup-required got 429'd on every call. Route exemption belongs in
allowList. - Session recovery path — refresh grace, rate-limit exemptions, client storm guard by @John710
Multi-tab usage made sessions self-destruct: two tabs refreshing within
the rotation window tripped the replay guard, which revoked every
session; the logout burst then exhausted the 10/min auth rate limit,
so the login page itself threw raw 429 toasts.- refresh rotation now grants the consumed token a 30-second grace
window instead of deleting it: concurrent tab refreshes are benign,
a token reused beyond the grace is still treated as stolen - /auth/refresh and /auth/setup-required are exempt from the rate
limit (they are the recovery path); /auth/login stays at 10/min - api.js: after a failed refresh a latch stops the queued-request
refresh storm; cleared on successful login - 429 responses surface as a localized 'too many requests' message
- refresh rotation now grants the consumed token a 30-second grace
- Fresh installs made the first user a non-admin by @John710
Migration 27 defaulted users.role to 'owner' and migration 28 converted
existing rows to 'admin' — but users registered AFTER 28 got the column
default again, and the auth plugin maps 'owner' to 'user', locking the
instance owner out of admin endpoints on any fresh install. Migration 30
sets the default to 'user'; /auth/register now inserts role='admin'
explicitly (registration is only open for the first user). - Trim unused SQL parameters left by the globalization pass by @John710
Postgres rejects bound-but-unreferenced parameters with 42P18 — the
globalization commit removed user scoping from queries but left the
trailing userId in the args arrays (transactions tag map and tag checks,
budgets category/tag checks, recurring category check, debts
autocategory, categories parent checks/list/delete/merge, tags
list/create/delete, import categories). Found by tracing the CI 500s
via the container-log dump; parameter usage audited against every
touched query. - Drop per-user indexes IF EXISTS (naming differs across pg-migrate versions) by @John710- Dedupe transaction_tags via ctid (no id column) by @John710
transaction_tags has a composite PK (transaction_id, tag_id) — the
duplicate cleanup referenced a non-existent id column and migration 29
rolled back on start (caught by CI). - Family sharing feedback + global categories and tags by @John710
- accounts: ShareModal was never rendered (silent template replace
failure) — the share button did nothing - budgets: refetch after save (the raw create response lacks the joined
category name, cards showed nameless chips) - universal button hover/active feedback (main.css) + primary Shared
badge: an entity that is already shared shows 'Shared' instead of
'Share' (is_shared now returned for debts and recurring too) - rate limits raised for real family usage: GET 240/min, mutations
90/min (auth stays 10/min) - migration 29: categories and tags are now GLOBAL — same-name
duplicates across users are merged (oldest wins), user scoping dropped,
unique constraints become (name, type) / (name); all backend queries
unscoped (transactions, budgets, debts, recurring, import, demodata,
debt category autogeneration)
- accounts: ShareModal was never rendered (silent template replace
- Drop unused SQL parameters after visibility rework by @John710
Postgres errors with 42P18 when a bound parameter is never referenced
(net-worth passed userId as $1 that the query no longer used). Visible
account arrays are now $1 everywhere and filter placeholders start at $2;
the same latent collision in the report helpers is fixed too. - Transactions list param order broke visibility scoping by @John710
The list query passed userId as $1 while the WHERE clause expected the
visible-account array — every list came back empty (caught by the
blackbox suite's paired-leg check).
CI/CD
- Changelog range starts at the previous PUBLISHED release by @John710
Failed builds leave tags without releases; counting them as range
boundaries dropped their commits from the next release's changelog
(happened three times across 1.3.2 betas). The range now starts at the
most recent tag with a published release, via gh release list - Dump app container logs when blackbox fails by @John710
Documentation
- Family sharing guide, per-role feature list, changelog range fix by @John710
- README (EN+RU): Family Sharing section — admin user management, share
dialog, view/edit permissions, global categories/tags, created_by - Features list now reflects sharing, admin users, 2FA and API tokens
- workflow: stable releases compute the range from the previous STABLE
tag (the published-release logic stays for betas) — otherwise a stable
after a beta series would only contain the version bump
- README (EN+RU): Family Sharing section — admin user management, share
Features
- Family sharing on entity level, admin user management by @John710
Testing
- Dump raw net-worth response on failure by @John710- Tighter pacing thresholds + GET-only 429 retry by @John710
The server limiter uses fixed windows — a rolling 25/min burst can
straddle two windows and still exceed 30. Thresholds 12/60 keep worst-case
straddles under both limits; stray GET 429s (outside /auth) retry once. - Pace GET and non-GET blackbox requests against separate limits by @John710- Pace blackbox requests instead of retrying 429s by @John710
A rolling-window pacifier keeps non-auth requests under the 100/min GET
limit (the report burst tripped it and net-worth read a 429 error body
as an empty series). Auth requests stay bursty so the rate-limit test
still observes its expected 429. - Retry 429 in blackbox for GET requests too by @John710
The report burst can exceed the 100/min GET limit mid-suite; the net-worth
assert read a 429 error body as an empty series (got=undefined) and failed
non-deterministically in CI. All methods now wait out the window and retry.
Full Changelog: v1.3.1...v1.3.2