Skip to main content

Changelog

Recent updates and changes to the Sanctum platform.

February 21, 2026โ€‹

๐ŸŒ Blizzard API Localizationโ€‹

Multi-locale support for journal game data (expansions, instances, encounters).

Backendโ€‹

  • โœ… Added JournalExpansionLocale, JournalInstanceLocale, JournalEncounterLocale Prisma models with composite (entityId, locale) keys
  • โœ… Prisma migration: add_journal_locale_tables
  • โœ… BattleNetAdapter โ€” added locale parameter to journal methods + text-only fetch methods (getJournalInstanceText, getJournalEncounterText)
  • โœ… JournalInstanceSyncService โ€” fetches all 6 Blizzard locales during sync (5 non-English locales fetched in parallel via Promise.all, ~70s for locale phase)
  • โœ… GameDataService โ€” locale-aware queries with English fallback; cache keys include locale
  • โœ… GameDataController โ€” optional ?locale= query parameter on all endpoints
  • โœ… JournalInstanceSyncProcessor โ€” cache invalidation clears all locale-scoped keys
  • โœ… Shared locale utilities (apps/api/src/common/locale.ts)

Frontendโ€‹

  • โœ… useBlizzardLocale() hook โ€” maps i18next language to Blizzard locale code
  • โœ… appLocaleToBlizzard() utility (shared between API and web)
  • โœ… RTK Query endpoints (gameDataApi) accept and pass locale parameter
  • โœ… All call sites updated: EventCard, EventDetail, EventActivityDetailsSection, useEventFormState

Testsโ€‹

  • โœ… locale.spec.ts โ€” backend locale utilities
  • โœ… game-data.service.spec.ts โ€” locale-aware data retrieval and cache keys
  • โœ… locale.test.ts โ€” frontend locale mapping

๐ŸŒ Mobile Nav i18nโ€‹

  • โœ… DrawerGuildSection โ€” replaced 14 hardcoded strings with t() calls using existing guild:nav.*, guild:settingsNav.*, and guild:members keys
  • โœ… MobileNavDrawer โ€” replaced "Menu", "Admin", and aria-label strings with t() calls
  • โœ… Added navigation.menu and navigation.mainNavigation keys to all 7 locale files

๐Ÿ“ฆ Dependency Updatesโ€‹

  • dotenv v17 โ€” bumped from v16. Only behavioral change: startup log now shows by default ([dotenv] injecting env (N) from .env). Suppress with DOTENV_CONFIG_QUIET=true if needed.
  • ESLint v10 โ€” bumped from v9. Already on flat config so migration was minimal. Wrapped eslint-plugin-react with @eslint/compat's fixupPluginRules() to bridge the removed context.getFilename() API. Added @eslint/compat to root devDependencies.
  • cache-manager-redis-yet โ†’ @keyv/redis โ€” replaced the old Redis store adapter with @keyv/redis (Keyv-native). CacheModule factory is now synchronous; uses stores: [createKeyv(options)] instead of the old store + async redisStore(). Cache invalidation handler updated to access stores[0].store.client for pattern-based key deletion.
  • Tailwind CSS v4 โ€” upgraded from v3. Switched from PostCSS plugin to @tailwindcss/vite (faster Vite-native processing). Removed autoprefixer (v4 handles vendor prefixes natively). index.css updated from @tailwind directives to @import "tailwindcss" + @config to keep the existing HeroUI theme config in place.

February 20, 2026โ€‹

โšก Session Check Performance Fixโ€‹

Fixed redundant GET /api/v1/auth/get-session calls on every client-side navigation.

  • Problem: requireAuth() in TanStack Router beforeLoad guards called authClient.getSession() (a direct fetch) on every navigation to a protected route (~15 routes). This was independent of the authClient.useSession() nanostores atom used in components, resulting in unnecessary server round-trips on every page change.
  • Fix: Added a 60-second TTL cache and request deduplication to auth.getSession() in apps/web/src/lib/auth.ts. Subsequent navigations within the window return the cached session. signOut() and invalidateSession() clear the cache immediately.

February 16, 2026โ€‹

๐Ÿ“… Events Calendar UI Improvementsโ€‹

Event Card Layoutโ€‹

  • โœ… Two-row layout โ€” Event cards restructured for better readability
    • Row 1: Status icons + Title + Time
    • Row 2: Event type icon + Recurring icon + Signup count
  • โœ… Accessibility โ€” Changed raid event color from red to primary blue for better contrast
  • โœ… Today button โ€” Moved between month navigation arrows for better UX (< Today >)

Week Overview on Guild Overview Pageโ€‹

  • โœ… Next 7 Days grid โ€” Shows upcoming events in 7-day grid view
  • โœ… Responsive layout โ€” Single column on mobile, single row on desktop
  • โœ… Quick navigation โ€” "View calendar" button links to full events page
  • โœ… Event details โ€” Each day shows up to 3 events with time, "+X more" indicator

๐Ÿ“… Events UI Enhancementsโ€‹

View Modes & Filtersโ€‹

  • โœ… Month / Week / List โ€” ButtonGroup to switch between calendar month, week (current week grid), and list views
  • โœ… Search โ€” Filter events by title or creator name (client-side on loaded data)
  • โœ… Event type filters โ€” Toggle raid, dungeon, social, pvp chips to show/hide types
  • โœ… Date range โ€” Calendar and week views use startDate/endDate on GET /guilds/:guildId/events for efficient loading; list view uses pagination

Calendar UXโ€‹

  • โœ… Week view โ€” Current week grid with day cells; same event cards and date selection as month
  • โœ… Today โ€” "Today" button resets month/year and clears selected date
  • โœ… Prev/Next month โ€” Icon buttons with aria-label="Previous month" / "Next month"
  • โœ… Date selection โ€” Click day to filter events to that date; click again to clear

Event Cards & Navigationโ€‹

  • โœ… Prefetch โ€” Event detail route prefetched on card hover/focus for faster navigation
  • โœ… Child route โ€” Events page renders <Outlet /> when on /events/$eventId so list/calendar donโ€™t flash during navigation

Accessibilityโ€‹

  • โœ… EventSlotsSection โ€” Select (role) and Input (max count) have aria-label per slot (e.g. "Role for slot 1", "Max count for slot 1") for screen readers

๐Ÿ”ง Organization Detail API Fixesโ€‹

Roster Members in Organization Detail Responseโ€‹

  • โœ… GET /organizations/:id now accepts page, pageSize, sortBy, sortOrder query params
  • โœ… Returns rosterMembers (full guild roster from Blizzard sync) with claimedByCharacter relations
  • โœ… Returns rosterPagination (page, pageSize, total, totalPages) for infinite scroll
  • โœ… Previously only returned memberships (user's own synced characters), now returns the full guild roster

CASL Ability Rules & Permission Fixโ€‹

  • โœ… GetOrganizationDetailUseCase now generates real CASL ability rules via AbilityFactory
  • โœ… Membership lookup uses orderBy: { role: { wowRank: 'asc' } } to pick highest-ranked character
  • โœ… Fixed "Guild Roster is Private" showing for guild masters due to empty abilityRules: []

WowGuild Data in Organization Responsesโ€‹

  • โœ… Organization detail and list endpoints now include WowGuild fields (realm, faction, emblemData, logoUrl, etc.)
  • โœ… currentLogoUrl computed from useCustomLogo preference
  • โœ… roles array included in organization detail response

Guild Text Content Endpointsโ€‹

  • โœ… GET /organizations/:id/text-content โ€” fetch all text content types (motd, rules, loot_rules, etc.)
  • โœ… PATCH /organizations/:id/text-content โ€” update text content with version history tracking

๐Ÿ• Last Login & Character Sync UXโ€‹

Last Login Persistenceโ€‹

  • โœ… Character.lastLoginAt โ€” when character was last played (from Blizzard profile)
  • โœ… GuildMember.lastLoginAt โ€” persisted during roster sync avatar fetch
  • โœ… Character enrichment processor persists lastLoginAt from last_login_timestamp
  • โœ… Avatar fetch processor parses and persists lastLoginAt for roster members
  • โœ… When claimed, roster sync also updates linked Character's lastLoginAt

Display & Sortโ€‹

  • โœ… Guild roster "Last seen" โ€” uses lastLoginAt when available, else N/A
  • โœ… Character cards show both "Last synced" and "Last login"
  • โœ… Characters page sort by "Last login" (most recent first, nulls last)
  • โœ… Guild detail sort by last seen uses lastLoginAt with fallback to lastSeenInRoster

Role & Cache Fixesโ€‹

  • โœ… Role endpoints resolve guild by ID or alias (fixes 404 when using alias in URL)
  • โœ… Role updates (create, update, delete, permissions) bust guild cache
  • โœ… Guild cache invalidation by both ID and alias

Character Sync UXโ€‹

  • โœ… Dismiss button on sync progress when complete or failed (desktop + mobile)
  • โœ… HeroUI onPress replaces deprecated onClick on Button components

๐Ÿ“… Recurring Events & Event Scopeโ€‹

Recurring Event Seriesโ€‹

  • โœ… Create multiple events at once with recurringDates array
  • โœ… Events share recurringSeriesId for batch operations
  • โœ… POST /events/:id/cancel-series โ€” cancel all events in series
  • โœ… DELETE /events/:id/delete-series โ€” delete all events in series
  • โœ… Use cases: CancelRecurringSeriesUseCase, DeleteRecurringSeriesUseCase

Event Scope (guild vs group)โ€‹

  • โœ… scope: guild โ€” full guild event, officers only can create
  • โœ… scope: group โ€” member event, any guild member can create
  • โœ… Default: group
  • โœ… CASL ability checks enforce scope on create

Event Enhancementsโ€‹

  • โœ… imageUrl โ€” custom or journal instance image
  • โœ… POST /events/:id/image โ€” upload event image (5MB max, moderation)
  • โœ… journalInstanceId โ€” link to Blizzard instance for auto metadata/image
  • โœ… status โ€” draft, published, cancelled, completed

๐Ÿ›ก๏ธ Admin Panelโ€‹

  • โœ… Admin-only routes (/admin) โ€” requires isAdmin (Manage All)
  • โœ… User.isAdmin โ€” platform admin flag
  • โœ… GET /admin/admins โ€” list admins
  • โœ… GET /admin/users?q= โ€” search users for promotion
  • โœ… POST /admin/admins/:userId โ€” promote/demote admin
  • โœ… GET /admin/stats โ€” platform usage (guilds, users, events, etc.)
  • โœ… POST /admin/sync-instances โ€” trigger journal sync
  • โœ… GET /admin/sync-instances/active โ€” active sync session
  • โœ… GET /admin/sync-status โ€” recent roster + instance syncs
  • โœ… GET /admin/jobs โ€” queue health

๐Ÿ“– Game Data (Journal Instances)โ€‹

  • โœ… JournalExpansion, JournalInstance, JournalEncounter โ€” Blizzard-synced raid/dungeon metadata
  • โœ… GET /game-data/expansions โ€” WoW expansions
  • โœ… GET /game-data/instances?category=raid|dungeon โ€” raids and dungeons
  • โœ… GET /game-data/encounters?journalInstanceId= โ€” bosses per instance
  • โœ… Public endpoints (no auth)

๐Ÿ”„ Journal Instance Syncโ€‹

  • โœ… BullMQ queue: journal-instance-sync
  • โœ… Processor: JournalInstanceSyncProcessor
  • โœ… WebSocket: InstanceSyncGateway โ€” real-time progress
  • โœ… JournalSyncSession โ€” tracks expansions, instances, encounters progress
  • โœ… useInstanceSync hook + InstanceSyncProgress component
  • โœ… Admin panel: trigger sync, view progress

๐Ÿ“ฆ Storage & Image Moderationโ€‹

  • โœ… StorageService โ€” S3 or local filesystem for event images
  • โœ… ImageModerationService โ€” moderation on upload
  • โœ… Event image upload with validation (5MB, JPEG/PNG/GIF/WebP)

๐Ÿ“ Participation Updatesโ€‹

  • โœ… characterId โ€” which character user brings
  • โœ… roleKey โ€” links to EventSlot
  • โœ… Status: waitlist, declined added

๐Ÿ“š Documentationโ€‹

  • โœ… Domain Schema โ€” updated Event, User, Participation, Journal entities
  • โœ… API Endpoints โ€” admin, events, game-data, recurring series
  • โœ… Background Jobs โ€” journal-instance-sync
  • โœ… WebSocket Pattern โ€” instance sync example

February 15, 2026โ€‹

๐Ÿ“ Guild Posts Visibility Fixโ€‹

Fixed guild posts with visibility: guild not appearing for authenticated members.

  • Cache: Added Cache-Control: no-store and Vary: Authorization to posts endpoints so responses are not cached by auth state (was causing 304 with stale unauthenticated data)
  • Facade: Added canManageGuild to getGuildPosts options type so it is passed correctly to the use case
  • Frontend: skip: !guild ensures posts are fetched only after guild (and auth) loads

๐Ÿ“ฑ Characters Mobile UX & API Fixesโ€‹

Mobileโ€‹

  • โœ… Floating bottom action bar (iOS-style island) for characters page
  • โœ… Sync progress shown inline in floating bar when syncing
  • โœ… CharactersFilterSheet (bottom sheet) for mobile filters
  • โœ… CharactersMobileBar with Create, Sync, Filters

APIโ€‹

  • โœ… GET /characters/templates โ€“ Fixed to return templates (was returning [])
  • โœ… POST /characters โ€“ Added endpoint for manual character creation (was 404)
  • โœ… DTO validation for create (uses dto/create-manual-character.dto.ts)

๐Ÿ”— Guild Alias Supportโ€‹

Guilds can now be accessed by UUID or URL-friendly alias.

Backendโ€‹

  • โœ… Added alias field to Guild model (optional, unique)
  • โœ… findGuildByIdOrAlias resolves both UUID and alias across all guild endpoints
  • โœ… Alias lookup for guild detail, logo, roster, and abilities guard
  • โœ… alias included in guild detail API responses

Frontendโ€‹

  • โœ… Guild alias editor in settings page
  • โœ… URL navigation supports /guilds/:alias for clean URLs
  • โœ… Auto-generated aliases for Blizzard-synced guilds
  • โœ… Cache invalidation for guild updates

๐Ÿš€ CI/CD Workflow Refactorโ€‹

GitHub Actions test workflow updated for full E2E support.

  • โœ… Postgres 16 service for E2E tests
  • โœ… Redis 7 service for E2E tests (cache, BullMQ)
  • โœ… JWT_SECRET and OAuth env vars in CI
  • โœ… Separate jobs: unit tests (fast), E2E tests (Postgres + Redis), coverage

๐Ÿ” OAuth Support in CIโ€‹

  • โœ… Battle.net and Discord OAuth client IDs/secrets/callbacks configured for test environment
  • โœ… Enables E2E tests that require auth flows

๐Ÿงช E2E Test Improvementsโ€‹

  • โœ… Clean up e2e test configuration and data isolation
  • โœ… Fix membership test data isolation
  • โœ… Fix tests after findGuildByIdOrAlias implementation

โšก Frontend UXโ€‹

  • โœ… Suspense boundaries and loading states on guild routes
  • โœ… Fix guild alias input validation

February 1, 2026โ€‹

๐ŸŒ Internationalization (i18n) System (3 pts)โ€‹

Implemented comprehensive i18n system using i18next for multi-language support.

i18n Infrastructureโ€‹

  • โœ… Installed i18next, react-i18next, i18next-browser-languagedetector
  • โœ… Created namespaced translation structure (common, guild, character, roster)
  • โœ… TypeScript integration with type-safe translation keys
  • โœ… Auto language detection (localStorage โ†’ browser โ†’ fallback to English)

Translation Files (English)โ€‹

  • โœ… common.json - Shared translations (actions, errors, validation, status)
  • โœ… guild.json - Guild management with confirmations and messages
  • โœ… character.json - Character operations
  • โœ… roster.json - Roster management and sync

i18n Featuresโ€‹

  • โœ… Zero latency (translations bundled with app, no network requests)
  • โœ… Type-safe keys with IDE autocomplete
  • โœ… Interpolation for dynamic values ({{name}}, {{count}})
  • โœ… Built-in pluralization (member vs members)
  • โœ… Language persistence in localStorage
  • โœ… Git-based workflow (translations versioned with code)

i18n Documentationโ€‹

  • โœ… Complete guide in docs/guides/internationalization.md
  • โœ… Usage examples and best practices
  • โœ… Migration strategy from hard-coded strings
  • โœ… Guide for adding new languages
  • โœ… Example component with all patterns

i18n Benefitsโ€‹

  • Frontend-only (no backend dependency)
  • Reliable offline development
  • Industry-standard approach (same as GitHub, Linear, Vercel)
  • Ready to integrate with Lokalise/Crowdin when needed
  • Follows "boring solutions" philosophy

Next Step: Gradually extract hard-coded strings to translation keys

๐Ÿ”” Toast Notification System (3 pts)โ€‹

Implemented comprehensive UI toast notification system for user feedback.

Toast Infrastructureโ€‹

  • โœ… Installed @heroui/toast package (v2.0.20)
  • โœ… Added ToastProvider to app root with portal-based rendering
  • โœ… Created useToast() hook for simple toast API
  • โœ… Created useToastMutations() for RTK Query integration

Toast Featuresโ€‹

  • โœ… Success, error, info, warning, and loading toast variants
  • โœ… Promise-based toasts that auto-update on resolve/reject
  • โœ… Rich descriptions and custom timeouts
  • โœ… Perfect integration with HeroUI theme (light/dark mode)
  • โœ… Top-right placement with close buttons
  • โœ… Max 3 visible toasts, 4s auto-dismiss

Toast Documentationโ€‹

  • โœ… Complete guide in docs/guides/toast-notifications.md
  • โœ… Example component with usage patterns
  • โœ… API reference and best practices

Toast Benefitsโ€‹

  • Native HeroUI component (no external dependencies like Sonner)
  • Automatic theme integration
  • Ready for mutation feedback across the app
  • Accessible and mobile-friendly

Next Step: Integrate toasts into existing mutations (guild create/update/delete, etc.)

๐Ÿ—‘๏ธ Archive & Delete Feature (21 pts)โ€‹

Complete archive and delete functionality for guilds and characters with smart business rules.

Backend Changesโ€‹

  • โœ… Added active and archivedAt fields to Guild model
  • โœ… Archive/restore/delete endpoints for guilds (PATCH /archive, PATCH /restore, DELETE /:id)
  • โœ… Archive/restore/delete endpoints for characters
  • โœ… Business rules: only standalone guilds and manual characters can be hard-deleted
  • โœ… Proper cache invalidation on all operations

Frontend Changesโ€‹

  • โœ… New ConfirmDialog reusable component for destructive actions
  • โœ… Archive/restore/delete actions in character cards (dropdown menu)
  • โœ… Danger Zone in guild settings with archive/delete options
  • โœ… "Show Archived" toggle on guild list (persisted in localStorage)
  • โœ… Archived count always visible regardless of toggle state
  • โœ… Restore UI for archived guilds with prominent notice
  • โœ… Visual indicators for archived items (opacity + chip badges)

Permissions & Securityโ€‹

  • โœ… Fixed CASL permissions to grant Delete action to users with canManageGuild
  • โœ… Ownership verification for character operations
  • โœ… Only standalone guilds can be permanently deleted (not Blizzard-synced)
  • โœ… Only manual characters can be permanently deleted (not Blizzard-synced)

Cache Managementโ€‹

  • โœ… Frontend: Proper query key invalidation for all guild queries
  • โœ… Backend: Redis pattern-based cache clearing for guild lists
  • โœ… Immediate UI updates after operations

UX Improvementsโ€‹

  • โœ… Persistent toggle state in localStorage
  • โœ… Confirmation dialogs for all destructive actions (archive/restore/delete)
  • โœ… Clear messaging about reversibility and data preservation
  • โœ… Color-coded dialogs (warning for archive, danger for delete, primary for restore)

See Archive & Delete Documentation for full details.

๐Ÿ” OAuth & Authentication Fixesโ€‹

OpenID Connect Complianceโ€‹

  • โœ… Added openid scope to Battle.net OAuth configuration
  • โœ… Battle.net authentication now follows OIDC standards
  • โœ… Receives signed ID token with verified user claims
  • โœ… Enhanced security with cryptographic token verification
  • โœ… Updated documentation with OIDC implementation details

Benefits:

  • Standardized authentication flow
  • Cryptographically verified identity tokens
  • Interoperability with OIDC-compliant systems
  • Security best practices for identity verification

Fixed OAuth callback URLs and login flowโ€‹

  • โœ… Corrected backend OAuth callback URLs from port 3000 to 3001
    • DISCORD_CALLBACK_URL: http://localhost:3001/api/v1/auth/discord/callback
    • BATTLENET_CALLBACK_URL: http://localhost:3001/api/v1/auth/battlenet/callback
  • โœ… Fixed frontend login page to use correct API URL (VITE_API_URL)
  • โœ… Updated settings page OAuth links to use correct port
  • โœ… Modernized login page UI with HeroUI components

Action Requiredโ€‹

๐ŸŽญ Role System Updatesโ€‹

Added founder role protectionโ€‹

  • โœ… Added isFounder field to Role model
  • โœ… Founder role (initial guild master) cannot be deleted
  • โœ… Still allows editing name and permissions
  • โœ… Frontend shows warning message for founder roles
  • โœ… Backend enforces deletion protection

Guild creation improvementsโ€‹

  • โœ… Added Step 5 to guild creation: "Master Role"
  • โœ… Users can customize the guild master role name
  • โœ… Default: "Guild Master" (customizable to "Leader", "Admin", etc.)
  • โœ… Shows full permissions info card
  • โœ… Simplified standalone guild creation (one custom role instead of 5 WoW ranks)

Custom role refinementsโ€‹

  • โœ… Custom roles always have wowRank = null
  • โœ… WoW ranks (0-9) only used for Blizzard-synced guilds
  • โœ… Removed WoW rank field from custom role creation
  • โœ… Improved modal validation and error messages

๐Ÿ“‹ Role Management Modal Refactorโ€‹

Component modularizationโ€‹

  • โœ… Split large modal component into smaller files
    • index.tsx - Main container
    • types.ts - Shared interfaces
    • RoleFormFields.tsx - Form inputs
    • PermissionsSection.tsx - Permissions toggles
    • InfoCards.tsx - Reusable info/warning cards
    • useRoleMutations.ts - API mutations hook
  • โœ… Replaced emojis with Heroicons throughout
  • โœ… Improved type safety and maintainability

๐Ÿ“š Documentation (1 pt)โ€‹

Updated guidesโ€‹

Development Notesโ€‹

Port Configuration Summaryโ€‹

ServicePortURL
API3001http://localhost:3001
Web5173http://localhost:5173
Docs3002http://localhost:3002
PostgreSQL5432localhost:5432
Redis6379localhost:6379

OAuth Flowโ€‹

flowchart LR
A[User] --> B["Frontend (5173)"]
B --> C["API (3001)"]
C --> D[OAuth Provider]
D --> E["Callback (3001)"]
E --> F["Frontend (5173)"]

Role Typesโ€‹

TypewowRankisFounderDeletable?
WoW-synced0-9falseโŒ No (synced)
Founder rolenulltrueโŒ No (founder)
Custom rolenullfalseโœ… Yes (if no members)

Testingโ€‹

All changes include:

  • โœ… Unit tests (guild service: 6/6, role service: 17/17)
  • โœ… Zero linting errors
  • โœ… Migration tested on dev database
  • โœ… OAuth flow tested with actual providers

Future Improvementsโ€‹

Plannedโ€‹

  • Token refresh for longer sessions
  • Multi-factor authentication
  • Session management (view/revoke)

Role Systemโ€‹

  • Bulk role assignment
  • Role templates
  • Permission presets
  • Role hierarchy visualization

UI/UXโ€‹

  • Dark mode improvements
  • Mobile responsive design
  • Loading states
  • Error boundaries