Slug disambiguation, nationality universe grouping, curator search relevance, and SEO duplicate content fix
Slug disambiguation on create - when creating a character with a duplicate name, the form now shows a suffix input (e.g. "farseer") that produces meaningful slugs like patience-farseer instead of patience-2. The suffix is passed as slugSuffix to the API and used as contextSuffix in withSlugRetry.
Manual slug editing - all seven entity edit forms (character, book, series, author, universe, group, genre) now include a URL Slug field. When a curator changes the slug, old slugs are preserved as redirects via saveSlugRedirect. Curator search results and entity list tables display slugs for disambiguation when entities share the same name.
Slug collision handling - when a curator explicitly sets a slug, withSlugRetry now receives maxRetries: 0 so it fails immediately on collision instead of silently appending -2. A new SlugCollisionError is caught by each PATCH route and returned as a 409.
Curator search relevance - search results now prioritise entities whose name starts with the query over substring matches (e.g. "Patience" ranks above "Illyrio Mopatis" when searching "pati").
Nationality universe grouping - added optional universeId column to lookup_values table with foreign key to universes. 113 nationality values associated with their correct universe. 25 display names cleaned by stripping parenthetical disambiguation now redundant with universe grouping (e.g. "Dor-lomin (Middle-earth)" to "Dor-lomin"). Public lookup API includes universeId in response.
Admin lookup universe column - the nationality category in the admin lookup table now shows a Universe column with the universe name, editable via dropdown in inline edit mode. Universe selector also added to the create value form. Colour and icon columns removed from all lookup tables to reduce clutter.
Grouped nationality picker - character create and edit forms now group nationality options by universe: "Real World" first, then universe names alphabetically, then "Other" for unassociated values. SearchableMultiSelect component extended with optional optionGroups prop for grouped display with headings. Falls back to flat filtered list when searching.
Audit log sources fix - the previous fix normalised oldValue sources but newValue sources still used null for URL and label. Both sides now normalise to '' consistently across all seven entity PATCH routes.
Social post generator - removed X/Twitter from the social post generator.
SEO duplicate content fix - nested hierarchical entity URLs (e.g. /universes/.../series/.../books/{id}/{slug}) were rendering full page content alongside the canonical flat URLs (/books/{id}/{slug}), creating duplicate content that could dilute PageRank and confuse search engine indexing. All 18 nested route pages now issue 308 permanent redirects to their canonical flat URL. Internal links from detail components updated to generate flat URLs directly, avoiding unnecessary redirect hops. 13 dead loading skeleton files removed from redirected routes.