Clicking "Browse as Guest" put the app into a redirect loop back to
/login. Guest mode is intentionally unauthenticated (isGuest, no token),
but the home page fetches an auth-only endpoint on load
(GET /favorites/game-ids), which returns 401. The response interceptor
treated that 401 as an expired session: since a guest is not
authenticated, it fell into the `!isAuthenticated` branch, cleared auth
state, and did `window.location.href = "/login"`. So guest mode never
survived the first page load.
Short-circuit the 401 handler for guests: a 401 from an auth-only
endpoint is expected for a guest (there is no token to refresh), so
reject and let the caller handle it instead of tearing down the session.
Authenticated-user and shared-access flows are unchanged.
Adds a regression test covering both the guest (no redirect) and
authenticated (redirect on failed refresh) paths.
Switch games search from GET to POST with JSON body to avoid URL
length limits when combining multiple filters. Filter options now
return context-aware results based on active selections.
Key changes:
- Backend: POST /api/games with array-based filter params
- Backend: Filter options cached per page combination + 30s TTL
for dynamic filter states
- Backend: Fix semicolon-delimited field matching (developers,
publishers, playModes, languages) using INSTR
- Frontend: Add URL compression (lz-string) for shareable filter URLs
- Frontend: Multi-select support in filter dropdowns and chips
- Frontend: Context-aware filter options hook
- Add YearRangeFilter component with from/to inputs and popover UI
- Check both game_data and game table for Flash SWF detection
- Add context-aware sort labels (Oldest/Newest vs A-Z/Z-A)
- Validate page param with isNaN() and Math.max(1, ...)
- Memoize filter handlers, updateFilter, and visibleConfigs
- Add error state with retry button for filter options
- Add LIMIT 100 to play modes query for consistency
- Stabilize useFilterDropdowns with useRef pattern
Add detailed documentation for multiple API endpoints and features:
- User playlists API with sharing and cloning endpoints
- Shared playlists API with access token generation
- Community playlists API for browsing/downloading
- Downloads API with SSE progress tracking
- Jobs API for scheduled task management
- Maintenance mode feature documentation
Update existing docs:
- Clarify Flashpoint vs User playlists distinction
- Add setup-status, most-played, and theme endpoints
- Expand settings cache and permission cache docs
- Reorganize API reference navigation structure
When a protected page includes query parameters (e.g., filters,
pagination), they were lost after login because only pathname was
extracted from location state. Now both pathname and search params
are preserved when redirecting back after authentication.
- Add isValidReturnUrl() helper in LoginForm.tsx
- Add sanitizeRedirectPath() helper in AuthContext.tsx
- Apply sanitization to both regular login and guest login flows
- Add COEP headers for iframe embedding when parent has COEP enabled
- Remove URL encoding in game proxy path (handler expects raw URLs)
- Preserve returnUrl as query param across cross-browser navigation
- Pass redirect path to loginAsGuest for proper return after login
- Filter play history to only return completed sessions
- Add documentation for CORS header responsibilities
- Replace hardcoded edition check with runtime schema introspection to
detect available game columns dynamically
- Move GameMetadataSource type to PreferencesService for reuse across
sync and update services
- Add hasMetadataSource() check instead of edition-based feature gating
- Handle orphaned parent references by nullifying parentGameId when
parent doesn't exist in batch or database
- Disable foreign key checks during bulk insert/delete operations to
match Flashpoint Launcher behavior
- Build combined Ruffle changelog showing all changes since current
version instead of just latest release
- Add skeleton loading state to MetadataUpdateCard during initial fetch
- Reduce per-game cycle warning spam to single summary log
- Fix platform filter to properly trim strings in array format
- Remove redundant breadcrumb context from random game navigation
- Add comprehensive tests for useRandomGame hook
- Reorder mock handlers to place static routes before parameterized
- Add explicit type annotation to breadcrumb items array
- Add glassmorphism effect to dialog, sheet, popover, dropdown, and
alert-dialog components with backdrop blur and subtle borders
- Improve sidebar highlighting by prioritizing breadcrumb context over
URL prefix matching for accurate section detection
- Add parent breadcrumb support for nested navigation (e.g., Flashpoint
Playlists > Playlist Name > Game)
- Replace "Back to" links with consistent Breadcrumbs component in
playlist detail views
- Add platform filter to random game endpoint (limit Lucky button to
Flash/HTML5)
- Remove unused role priority field from schema, types, and UI
- Sort roles table with system roles pinned at top, show "System Role"
badge instead of disabled actions
Introduce URL-based navigation structure where games retain their
section context (/flash/:id, /html5/:id, /animations/:id, /browse/:id).
Key changes:
- Shorten route paths: /flash-games -> /flash, /html5-games -> /html5
- Add sectionRoutes.ts with centralized section configuration
- Update sidebar active state detection to track section routes
- Enhance Breadcrumbs component with icon support and sticky styling
- Derive breadcrumb context from URL path, falling back to state
- Remove unused imports across backend files
- Add progress bar indicator to toast notifications showing auto-dismiss
countdown, pauses on hover for improved UX
- Add close button to toasts with proper styling per toast type
- Enhance "Feeling Lucky" button with bounce animation, ping effect,
and smooth hover/active transitions
- Update favicon to official Flashpoint branding
- Convert Tailwind config to use ESM imports instead of require()
- Replace generic Error with AppError for proper HTTP status codes
in SystemSettingsService validation (400 for client errors, 404
for missing settings, 500 for schema errors)
- Add useMediaQuery hook for reactive viewport tracking
- Replace window.innerWidth checks with useMediaQuery in Sidebar
to properly react to viewport changes
- Make currentPassword optional in ChangePasswordData type to
support admin password resets
- Integrate DownloadRegistry into concurrent download limit
and clean up timers on removal/clear
- Wrap MetadataSyncService batch deletes in single transaction
- Always re-throw checksum errors in RuffleService to prevent
verification bypass
- Fix system-settings early return skipping second job update
when first cron expression is invalid
- Increase DatabaseService hot-reload grace period to 30s
- Add permission guard to metadata sync status endpoint
- Use encodeURIComponent for game launch content URLs
- Clamp limit params to minimum of 1 in jobs routes
- Use non-null assertion for authenticated user permissions
- Fix ConfirmDialog not calling onCancel on overlay dismiss
- Guard FormattedDate against invalid date strings
- Add breadcrumbContext to GameListItem memo comparison
- Fix MetadataUpdateCard stale closure in interval callback
- Move Dockerfile healthcheck to docker-compose for flexibility
- Fix build:analyze script to set VITE_ANALYZE env var
feat: add shared download registry, type safety, and test infra
Add DownloadRegistry to prevent duplicate downloads between
DownloadManager and GameZipServer. Consolidate PreferencesService
(delete game/services copy, add compatibility aliases). Replace
`any` defaults with `unknown` in DatabaseService/UserDatabaseService
and add explicit type parameters to ~40 query call sites. Add
vitest config, test setup, test helpers, and unit tests for auth
middleware, RBAC, pagination, and time range utils. Enable
strictPropertyInitialization in backend tsconfig. Add SHA-256
checksum verification to RuffleService updates. Harden Docker
Compose with log rotation, resource limits, and bridge network.
Fix NODE_ENV default in dev compose, exclude guest userId from
activity logs, add activity logging to password change, guard
nullable permission descriptions, and align frontend Game/Permission
types with actual API shape.
- Move health check from Dockerfile to docker-compose
for easier override
- Remove unused VITE_API_URL build arg from frontend
Dockerfile
- Simplify build:analyze and check-bundle scripts
- Document PUID/PGID container permissions in
.env.example
mock data with actual API contracts
- Normalize all `cn` imports to `@/lib/utils` across
30+ UI and component files
- Replace relative imports with `@/` alias in auth,
roles, users, stats, and layout components
- Remove redundant `as` type assertions in playlist
routes (Zod parse already returns typed data)
- Replace `any` with proper types in FallbackCache,
mock handlers, and validateIcons
- Use `type` import for type-only imports
- Add type-safe `.filter(Boolean)` with type guard
in Sidebar nav item arrays
- Update MSW mock data to match actual API shape:
numeric IDs, `role` field, HTTP-only cookie auth,
paginated response format, and settings schema
- Add JSDoc to AuthTokens clarifying cookie-based
token storage
- Replace console.* calls with logger in icon
validation
- Ignore SQLite WAL files in .gitignore
- Demote per-request game service logs to debug level
- Add reentrant guard to DatabaseService.syncAndReload
- Enable WAL mode + PRAGMA tuning for user.db
- Align CachedSystemSettingsService cleanup to 90s interval
- Extract CACHE_TTL constant in GameSearchCache
- Add PerformanceMetrics hourly cleanup with .unref()
- Fix migration timing to measure exec only, type result
- Add parseInt NaN fallback for OTEL metrics interval
- Make config object readonly with `as const`
- Add error reporting to all error boundary components
- Replace hardcoded colors with theme tokens (destructive,
primary, amber) for dark mode compatibility
- Use `??` instead of `||` for config/setting defaults
- Replace `any` with `unknown` in asyncHandler types
- Add `Identifiable` constraint to createQueryHook generics
- Fix timeout leaks with useRef and componentWillUnmount
- Add `.catch()` on fire-and-forget promises
- Deduplicate theme sync logic into `maybeSyncToServer`
- Extract `resolveActualMode` helper in theme store
- Add Zustand `useShallow` selector to CardSizeControl
- Add axios module augmentation for custom request config
- Set 30s default timeout on API client
- Deduplicate toast IDs to prevent session-expired spam
- Fix TOAST_REMOVE_DELAY from 1000000ms to 1000ms
- Fix use-toast useEffect dependency array
- Remove redundant try/catch in asyncHandler-wrapped routes
- Lock body scroll when mobile sidebar is open
- Fix route ordering: static before wildcard in App.tsx
- Quote background-image URLs in LazyBackgroundImage
- Remove duplicate onCancel call in ConfirmDialog
- Add deferred TODO.md for architectural improvements
- Clear isMaintenanceMode on logout, stop persisting it
route handlers
- DRY timeRange validation into shared parseTimeRange()
- Add jobId format validation, cron schedule validation,
and date format validation on route inputs
- Add auth middleware to update check/metadata endpoints
- Harden sync start with atomic tryStartSync() to prevent
race conditions
- Add symlink escape prevention and SVG CSP headers to
proxy routes
- Cache public settings in CachedSystemSettingsService
- Fix MetadataSyncService: validate allowed hosts (SSRF),
batch deletes to avoid SQL variable limits, use ?? over
|| for nullable fields
- Remove unnecessary async from synchronous DB methods in
GameDatabaseUpdater
- Add path traversal check in makeRelativePath()
- Prevent setting creation via set() — only allow updates
- Remove unused getJobStatus/getAllJobs, lastRunTimes map
- Strip internal paths from getSystemInfo() response
- Sanitize update error messages to avoid leaking stderr
- Add job log cleanup interval with unref()
- Replace hardcoded colors with theme tokens, fix React
Fragment keys, use getErrorMessage() utility
- Convert MetadataUpdateCard ref to state for reactivity
- Use ?? instead of || for config defaults across settings
Backend:
- Add Zod UUID validation for game/playlist route params
- Fix parseInt NaN fallback in play-tracking query params
- Add password max-length (128) and self-delete prevention
- Whitelist sort direction to prevent SQL injection in
FavoritesService
- Use ?? over || for yearFrom/yearTo and presentOnDisk
- Fix TOCTOU in updateUserStats with atomic UPSERT subqueries
- Encode game content URL, add 500MB download size limit
- Fix DownloadManager promise double-settlement with guard
- Add optionalAuth to public playlist routes
- Coerce isActive to boolean in UserService responses
- Delete user_stats row on user deletion
- Use single AuthService instance in UserService
- Fix SQL quoting (double → single quotes)
Frontend:
- Extract shared chart-utils (CHART_COLORS, formatPlaytimeCompact,
truncateTitle, getCSSVar) from 3 chart components
- Replace hardcoded colors with theme-aware CSS variables in
PlaytimeChart and TopGamesChart
- Wrap GameListItem in React.memo with custom comparator
- Move Statistics type to API layer, re-export from hook
- Type statisticsApi return value
- Add maxLength to playlist title/description inputs
- Fix ChangePasswordDialog: skip current password for admin
resets, remove min-length on current password field
- Add max-length (128) to password fields across forms
- Validate roleId as positive integer in UserForm
- Replace hardcoded Tailwind colors with theme tokens
(text-destructive, bg-destructive/10, border-destructive)
- Fix tag separator from semicolon to comma in browse layout
- Guard division-by-zero in GamesDistributionChart
- Simplify UserTable delete handler, remove duplicate toast
- Fix unused import (toast) in UserTable
- Add permission escalation check to role create/update endpoints
so users cannot assign permissions they do not possess
- Replace permissive `settings.view` with `settings.read` across
cache, health, and metrics routes
- Require authentication and `playlists.create` for community
playlist downloads (was `optionalAuth`)
- Add `X-Content-Type-Options: nosniff` and `X-Frame-Options`
headers to game content responses
- Enable Content-Security-Policy via helmet with restrictive
default-src and frame-ancestors directives
- Set `server.maxConnections = 500` as defense-in-depth limit
- Delete role atomically with its permissions in a transaction
and invalidate permission cache afterward
- Unify registration error messages to prevent username/email
enumeration
- Disable permission management for system roles in RoleTable
- Encode share tokens and game IDs in frontend API URLs
- Add `optionalAuth` to statistics route
- Remove unused `rateLimitAuth` export
- Pin DNS resolution to prevent SSRF rebinding between
check and connect (GameDataDownloader)
- Fail-closed on realpath failure in GameZipServer
- Zip Slip validation before extraction (RuffleService)
- Escape LIKE wildcards in ActivityService queries
- Sanitize sort order to prevent SQL injection
- Whitelist sort order instead of passing raw user input
- Add headersSent guard to global error handler
- Remove cookie from CGI allowed headers, sanitize
content-type against null/CR/LF injection
- Revoke refresh tokens on password change and user
deactivation
- Wrap role/user uniqueness checks in transactions
(TOCTOU fix)
- Fix concurrent ZIP mount race with deferred promise
and double-close prevention via _manuallyClosed flag
- Handle client disconnect in collectRequestBody
- Tighten nginx CSP, repeat headers in location blocks,
hide server version, pin nginx:1.27-alpine
- Fix health check endpoint path in backend Dockerfile
- Use wget instead of curl for frontend health checks
- Replace any types with proper types across backend
and frontend
- Replace || with ?? for config/form defaults
- Replace hardcoded Tailwind colors with theme tokens
- Replace console.log/error with logger in ErrorReporter
and ChartErrorBoundary
- Conditionally render AddToPlaylistModal to avoid
hidden instances
- Add NaN guards for parseInt in GameBrowseLayout
- Standardize query key for publicSettings
- Simplify README to Docker-first quickstart with wiki
link
- Fix license to GPL-3.0 in backend package.json
- Slow down particle animation speed
- Add Zod max-length limits on playlist/error schemas
- Ensure positive limit in parsePaginationQuery
- Validate settings category param against allowlist
- Fix static/parameterized route ordering in favorites
- Add asyncHandler to game-zip wildcard route
- Close readline/stream on error in getRecentErrors
- Add .catch() on fire-and-forget syncAndReload
- Fix LazyBackgroundImage cleanup on unmount
- Store NetworkStatusIndicator timeout in ref
- Add Flash/SWF condition to random game query
- Clear force-kill timeout on process exit to prevent leaks
- Support LF-only line endings in CGI output parsing
- Add proper timeout handling for request body collection
- Save/restore env vars in tests to prevent pollution