c18abf84c6
Editing an existing share alias (e.g. slug `te` -> `ted`) failed to update the displayed `/l/<alias>` link: `setAlias()` looked the requested slug up by name and, if free, INSERTed a brand-new row, leaving the page with multiple alias rows. The modal then read via `findByPageId().executeTakeFirst()` with no `ORDER BY`, so Postgres returned an arbitrary (in practice the oldest, stale) row. Every edit also spawned an orphan row that kept a live `/l/<old>` link forever. Regression of #205. Enforce the invariant "a page has EXACTLY ONE custom address": - `setAlias()` now resolves the page's current alias row and RENAMES it in place when the requested name is free (insert only when the page has none), keeps the same-name no-op and the cross-page 409 `ALIAS_REASSIGN_REQUIRED` + confirmed-retarget flow, and after any successful write DELETEs all other alias rows for the page (self-heal). Runs in one transaction so the page is never transiently empty or duplicated. - repo: add `updateAlias` (rename) and `deleteOthersForPage`; make `findByPageId` deterministic with `ORDER BY created_at DESC, id DESC`. - migration: dedup existing rows (keep newest per page) + a PARTIAL unique index `(workspace_id, page_id) WHERE page_id IS NOT NULL` so dangling aliases still coexist while live ones are one-per-page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>