Part 3 of the split of #41 (see that issue for background). Depends on #465 (slugify fallback); can land in parallel with #466.
Problem
Renaming a set or artist silently rewrites its slug client-side (src/api/sets/useUpdateSet.ts:36, src/api/artists/useUpdateArtist.ts:37 call generateSlug(updates.name)). This breaks bookmarked URLs and, for non-Latin names, writes an empty slug (no trigger runs on UPDATE).
Design (decided in triage)
- Edit views for sets and artists show an explicit slug field, pre-filled with the current slug.
- The user may edit it. Client validates format with
isValidSlug / sanitizeSlug from src/lib/slug.ts and runs a lightweight uniqueness query (within the entity's uniqueness scope — global for artists, per-edition for sets) to warn before submit.
- Empty is forbidden client-side (form validation error; react-hook-form per repo conventions).
- Rename alone no longer changes the slug — the slug only changes when the user edits the slug field.
- The DB stays the authority: unique constraints remain the backstop; map a constraint-violation error to a friendly form message.
- Backstop: add BEFORE UPDATE triggers on
sets and artists that re-derive via public.slugify(NEW.name) (with dedupe) if NEW.slug arrives empty/NULL, mirroring the existing insert triggers in 20260804101202_add_slug_dedupe_triggers.sql.
Work
- Migration: extend
sets_dedupe_slug / artists_dedupe_slug triggers to BEFORE INSERT OR UPDATE (guard so an unchanged slug on update is a no-op and doesn't self-collide — the existing id IS DISTINCT FROM NEW.id check covers this).
- Remove auto
generateSlug from useUpdateSet / useUpdateArtist; accept an optional explicit slug in the mutation input instead.
- Add the slug field to the set and artist edit forms (find them under
src/components/Admin/ / ArtistDetail/), with validation as above. Follow repo form conventions (react-hook-form, DialogTitle + DialogDescription).
Acceptance
- Renaming a set/artist leaves its slug (and URLs) unchanged.
- Editing the slug to a taken value shows a friendly error (both from the pre-check and from a raced constraint violation).
- Submitting an empty slug is blocked by form validation.
- An UPDATE that somehow arrives with an empty slug gets a trigger-derived non-empty slug.
Part 3 of the split of #41 (see that issue for background). Depends on #465 (slugify fallback); can land in parallel with #466.
Problem
Renaming a set or artist silently rewrites its slug client-side (
src/api/sets/useUpdateSet.ts:36,src/api/artists/useUpdateArtist.ts:37callgenerateSlug(updates.name)). This breaks bookmarked URLs and, for non-Latin names, writes an empty slug (no trigger runs on UPDATE).Design (decided in triage)
isValidSlug/sanitizeSlugfromsrc/lib/slug.tsand runs a lightweight uniqueness query (within the entity's uniqueness scope — global for artists, per-edition for sets) to warn before submit.setsandartiststhat re-derive viapublic.slugify(NEW.name)(with dedupe) ifNEW.slugarrives empty/NULL, mirroring the existing insert triggers in20260804101202_add_slug_dedupe_triggers.sql.Work
sets_dedupe_slug/artists_dedupe_slugtriggers toBEFORE INSERT OR UPDATE(guard so an unchanged slug on update is a no-op and doesn't self-collide — the existingid IS DISTINCT FROM NEW.idcheck covers this).generateSlugfromuseUpdateSet/useUpdateArtist; accept an optional explicitslugin the mutation input instead.src/components/Admin//ArtistDetail/), with validation as above. Follow repo form conventions (react-hook-form,DialogTitle+DialogDescription).Acceptance