Skip to content

v0.29.2 - Hotfix: missing slug column migration on prod

  • 2026-06-08

Bugs

  • [x] All /api/campaigns and /api/characters calls failed in production with 500 Failed to fetch campaigns immediately after v0.29.0 deploy. Root cause: migrations/0060_add_slugs.sql shipped with v0.29.0 but was inert — the platform doesn't run drizzle journal migrations in prod, it uses an in-process idempotent ALTER TABLE … ADD COLUMN IF NOT EXISTS … runner in packages/server-core/src/db.ts (postgresApplyColumnMigrations / sqliteApplyColumnMigrations), and that runner had no entries for campaigns.slug / maps.slug / characters.slug, so prod's table never got the column. Drizzle then raised column "slug" does not exist on every campaign query. (Fixed: added the three ALTER TABLE … ADD COLUMN IF NOT EXISTS slug TEXT statements + the three CREATE UNIQUE INDEX IF NOT EXISTS … indexes to both runtime paths in db.ts. Same pattern as the v0.27.3 hotfix for map timestamps. Ran the SQL by hand on prod RDS to restore service; this commit prevents the next cold-start / fresh deploy from re-introducing the gap. New regression test dbSchema.slugs.test.ts (3 tests) pins the runtime ALTERs to the migration file.)
  • [x] A mysterious dropdown was fixed to the lower-left of the map showing the impersonated token's name and not responding to clicks (right-click on the map was hijacked, and DevTools' element-picker selected the canvas underneath). Root cause: a <Select> inside CurrentTokenBarContainer (apps/web/src/components/UI.tsx) — the "player character selector" — was wrapped in a Box with pointerEvents: 'none', so it rendered visibly but couldn't be interacted with. The "switch impersonated token" UI is already covered by Character Context (CharacterContextBreadcrumb), so the broken mini-selector was redundant. (Fixed: removed the playerCharacterSelector definition, its mobile fallthrough render block, and its desktop fragment render. Updated source-pin tests UI.hideImpersonatedBar.test.ts and UI.mobileImpersonate.test.ts to assert the new contract — the impersonated-token selector is no longer present anywhere, and selectedToken bars render in the shared mobile/desktop fragment.)