Changelog
Unreleased
Changes that have landed since the last release.
Added
- Admin — send the welcome email to members who have never signed in, either the ones you select or everyone at once. On Admin → Users, selecting members now offers "Send invitation", and the "Never logged in" tab has "Invite everyone who has never signed in", which first shows how many members will be emailed and roughly how long it will take, and asks again if that number changes before you confirm; inviting everyone asks for a fresh second-factor code, like the member import. Both only queue the emails: they go out in the background at about one a second, each with a link to set a password. Members who have signed in, were invited in the last 24 hours, are already waiting, are held or suspended, or whose address cannot receive mail are skipped, and so are staff accounts (invite those one at a time). The result says how many were queued and how many were skipped. Administrators only, not brokers; each request is recorded in the audit log with its counts. Server:
POST /api/v2/admin/members/invitations,GET …/never-signed-in-count,POST …/never-signed-in. - Admin — export every member in the same columns the member import reads, to move a community's members to another community. On Admin → Users, "Export all members" is now a menu with two choices: "Members report (all details)", which is the report it always was, and "File for importing elsewhere", which downloads
members-for-import-YYYY-MM-DD.csvwith the import template's columns (first_name,last_name,email,phone,location,balance) and one row for every member who has not been deleted or erased, staff accounts included (the import file has no role column, so anyone imported elsewhere becomes an ordinary member). It is the "file ready to re-import" that the 3.1.0 import entry listed as not yet built; asking members for their town at onboarding is still not built. Administrators only, not brokers; 10 downloads per 5 minutes per administrator; each download is recorded in the audit log (member_export, with the row count) and the activity log before any data is sent, and nothing is sent if that record cannot be written. Balances are written with two decimals, a negative one is escaped like any other value that starts with-(the importer undoes that and starts the member at 0, with a warning), and values that start with=,+,-or@are escaped against spreadsheet formulas. Known limits: there is no second-factor prompt on this download yet (the existing members report has none either, and the download button cannot show the prompt); a community with more than 5,000 members exports fine but the importer refuses a file over 5,000 rows or 2 MB, so the file must be split first; and a member stored with a one-word name exports with an empty last name, which the importer refuses, so those rows need a last name added before the file can be imported. Server:GET /api/v2/admin/members/export. - Welcome and invitation emails go out through a stored, paced outbox that survives restarts. The emails queued by the member import's "email each new member" option and by "Send invitation" / "Invite everyone who has never signed in" are rows in a database table (
member_invitation_outbox), not jobs that can be lost with a restart. A scheduled command (members:send-invitations, every minute, run in the background so its 50-second runs do not hold up other scheduled jobs) sends them at about one email a second (the time estimates assume about 40 a minute, allowing for the time each send takes), each with a fresh 7-day set-password link made at the moment it is sent. A send that fails is tried again after a growing wait (5, 20, 45 and 80 minutes) and is given up on after five tries; a send that was interrupted part-way is not repeated, and a member is not sent a second invitation within 24 hours. In rare cases where the mail service accepts a message but does not confirm it, the message may be retried and arrive twice. At the moment of sending, a member is skipped, and the reason is recorded, if they have already signed in, are waiting for an identity check or are held or suspended, were invited by any route in the last 24 hours, are no longer in the community, or have an address that cannot receive mail. A record holds who asked, when, the status and, for a failed send, the kind of error and a short message with any email address removed (for a database error, only its error codes), never the text of an email; finished records (sent, skipped or given up) are deleted 30 days after they last changed. The 30 days is a working choice that the Data Protection Officer has yet to confirm. First shipped with the import's email option in 3.1.0; these details were not described then.
Fixed
- Sign-up and password reset can no longer get stuck on "Checking against known data breaches…". The new-password check asks Have I Been Pwned (
api.pwnedpasswords.com) from the browser whether the password is in a known breach. It already let the member continue when that request failed, but it had no time limit, so a request that hung instead of failing — seen on 9 Oct 2026 in a sandboxed browser, and possible behind some firewalls, captive portals and privacy extensions — left the check running for ever and the form could never be sent.usePasswordChecknow aborts the request after 4 seconds (HIBP_TIMEOUT_MS) and lets the member continue, saying "Long enough. We could not check known data breaches just now, so this will be checked when you submit." The server still checks every new password against the same breach list on registration and reset (PwnedPasswordService), so that remains the real gate. The hook's messages, which were hard-coded English, now use translation keys (auth:password_check.*) translated into all 11 languages. Change password in Settings does not use this check and was not affected. - Admin — the member import no longer shows a "minutes to send" estimate when nobody will be emailed. In a community that requires an identity check, until the administrator ticks that they have checked each person's identity, every imported member is held and none is emailed. The "Email each new member…" option still showed "about N minutes for this file" beside it, which was untrue; it now shows only the note that members waiting for an identity check are not emailed now. The estimate returns as soon as the identity box is ticked.
Internal
- Cutting a release now refreshes the accessible site's changelog pages, and CI fails if a released version's page is missing or out of date. The accessible frontend serves
/changelogfrom pages pre-rendered intoweb-uk/src/lib/generated/changelog/and committed (its Docker build cannot seeCHANGELOG.md). Nothing regenerated them, so 2.1.0, 3.0.0 and 3.1.0 were missing until9764ad880.scripts/release.mjsnow runsweb-uk/scripts/build-changelog.jsaftercopy-changelogand commits that directory with the release (still by explicit pathspec); it refuses before writing anything,--dry-runincluded, whenmarked/sanitize-htmlare not installed underweb-uk/. Newnpm --prefix web-uk run check:changelog:released(--check-released) compares every released version's page and the released entries ofindex.json, exits 2 when it cannot run, and is BLOCKING in the always-onDocumentation, Version, and Changelog Hygienejob (which now installs web-uk's dependencies with--ignore-scripts) and inscripts/preflight.mjs.unreleased.jsonis deliberately not gated: nearly every commit edits[Unreleased], so gating it would demand a regeneration on all of them; each release cut refreshes it, and the fullcheck:changelogstill reports it.