diff --git a/CHANGELOG.md b/CHANGELOG.md
index 656cd66..98e7472 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,19 @@ All notable changes to **Tiger Core** (`webtigers/tiger-core`). Format follows
## [Unreleased]
+## [1.19.0] — 2026-10-01
+
+### Changed
+
+- **Active-only UNIQUE indexes on `user.email` + `user.username` — soft-delete now frees the value
+ (TIGER-274).** A plain `UNIQUE(col)` spans soft-deleted (`deleted=1`) rows, so soft-deleting a user
+ never freed their email/username and a re-signup with that address collided on the index. Migration
+ `0053` moves each unique index onto a generated `
_active = IF(deleted=0, col, NULL)` column —
+ MySQL/MariaDB allow multiple NULLs in a unique index, so soft-deleted rows drop out and the value is
+ reusable, while live rows stay unique. The real `email`/`username` columns are untouched and the
+ finders already filter `deleted=0`, so there is no behavioral change for live rows. This is the
+ platform-wide convention for every unique + soft-deletable column (TIGER-274).
+
## [1.18.1] — 2026-09-27
### Fixed
diff --git a/library/Tiger/Version.php b/library/Tiger/Version.php
index ab5241b..90367db 100644
--- a/library/Tiger/Version.php
+++ b/library/Tiger/Version.php
@@ -9,5 +9,5 @@
class Tiger_Version
{
/** Current Tiger Core version. Keep in lockstep with the git tag cut for a release. */
- const VERSION = '1.18.1';
+ const VERSION = '1.19.0';
}
diff --git a/migrations/0053_active_only_user_unique.php b/migrations/0053_active_only_user_unique.php
new file mode 100644
index 0000000..914e7d9
--- /dev/null
+++ b/migrations/0053_active_only_user_unique.php
@@ -0,0 +1,33 @@
+_active = IF(deleted=0, col, NULL)` instead: MySQL/MariaDB allow multiple NULLs in a unique
+ * index, so deleted rows drop out (NULL) and free the value, while live rows stay unique. The real
+ * `email`/`username` columns are untouched — `Tiger_Model_User::findByEmail`/finders already filter
+ * `deleted=0`, so no application code changes. This is the convention for every unique + soft-deletable
+ * column platform-wide (see TIGER-274).
+ */
+return [
+ 'up' => [
+ "ALTER TABLE `user` DROP INDEX `uq_user_email`",
+ "ALTER TABLE `user` ADD COLUMN `email_active` VARCHAR(191) GENERATED ALWAYS AS (IF(`deleted`=0, `email`, NULL)) VIRTUAL",
+ "ALTER TABLE `user` ADD UNIQUE KEY `uq_user_email_active` (`email_active`)",
+ "ALTER TABLE `user` DROP INDEX `uq_user_username`",
+ "ALTER TABLE `user` ADD COLUMN `username_active` VARCHAR(64) GENERATED ALWAYS AS (IF(`deleted`=0, `username`, NULL)) VIRTUAL",
+ "ALTER TABLE `user` ADD UNIQUE KEY `uq_user_username_active` (`username_active`)",
+ ],
+ 'down' => [
+ "ALTER TABLE `user` DROP INDEX `uq_user_email_active`",
+ "ALTER TABLE `user` DROP COLUMN `email_active`",
+ "ALTER TABLE `user` ADD UNIQUE KEY `uq_user_email` (`email`)",
+ "ALTER TABLE `user` DROP INDEX `uq_user_username_active`",
+ "ALTER TABLE `user` DROP COLUMN `username_active`",
+ "ALTER TABLE `user` ADD UNIQUE KEY `uq_user_username` (`username`)",
+ ],
+];