19 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Documentation-First Approach
IMPORTANT: This project has comprehensive documentation in the docs/
directory (100+ files). When handling requests:
- Reference docs instead of repeating information — prevents filling context windows with redundant content.
- Point to docs when answering questions (e.g., "See
docs/04-frontend/architecture.md"). - Read docs when needed using the Read tool:
- Architecture:
docs/02-architecture/ - Backend:
docs/03-backend/ - Frontend:
docs/04-frontend/ - API Reference:
docs/06-api-reference/ - Design System:
docs/07-design-system/ - Development:
docs/08-development/ - Deployment:
docs/09-deployment/ - Features:
docs/10-features/ - Diagrams:
docs/11-diagrams/ - Reference:
docs/12-reference/
- Architecture:
- Update docs after changes — always ask the user if relevant documentation should be updated.
Quick Documentation Reference
| Question | Documentation File |
|---|---|
| How do I set up the project? | docs/08-development/setup-guide.md |
| What commands are available? | docs/08-development/commands.md |
| How does authentication work? | docs/10-features/01-authentication-authorization.md |
| What are all the API endpoints? | docs/06-api-reference/README.md |
| What's the database schema? | docs/12-reference/database-schema-reference.md |
| How do components work? | docs/04-frontend/components/component-overview.md |
| How do I deploy? | docs/09-deployment/README.md |
| What's the architecture? | docs/02-architecture/system-architecture.md |
| Design system/theming? | docs/07-design-system/theme-system.md |
| Common errors/pitfalls? | docs/08-development/common-pitfalls.md |
| TypeScript types? | docs/12-reference/type-definitions.md |
| Environment variables? | docs/09-deployment/environment-variables.md |
| Game content serving? | docs/05-game-service/architecture.md |
| Security measures? | docs/12-reference/security-measures.md |
Project Overview
Flashpoint Web is a self-hosted web application for browsing and playing games from the Flashpoint Archive. Monorepo with two services:
- backend (Express/TypeScript, port 3100): REST API + integrated game
content serving via
/game-proxy/*and/game-zip/*routes - frontend (Vite/React/TypeScript, port 5173): SPA with TanStack Query, Zustand, Tailwind CSS, Ruffle emulator
Two SQLite databases: flashpoint.sqlite (read-only, game metadata) and
user.db (app data — users, roles, playlists, play sessions). See
docs/02-architecture/system-architecture.md for full architecture details.
Development Commands
# Root level
npm run install:all # Install all dependencies
npm run dev # Start all services (concurrent)
npm run dev:backend # Backend only
npm run dev:frontend # Frontend only
npm run build # Build all
npm run typecheck # Type check all
npm run typecheck && npm run build # Verify before committing
# Backend (cd backend)
npm run dev # Dev server with hot reload (tsx)
npm run build # Build to dist/
npm test # Run tests (vitest)
npm run typecheck # Type check only
# Frontend (cd frontend)
npm run dev # Dev server with HMR (Vite)
npm run build # Build to dist/
npm run typecheck # Type check only
npm run copy-ruffle # Copy Ruffle emulator files (auto on postinstall)
# Docker
docker-compose up -d # Production
docker-compose -f docker-compose.dev.yml up -d # Development
Frontend API Guidelines
CRITICAL: All backend API calls MUST go through frontend/src/lib/api.ts.
Never use raw fetch() — it bypasses authentication (JWT headers, token
refresh, maintenance mode bypass for admins).
// CORRECT — in frontend/src/lib/api.ts
export const exampleApi = {
getData: async () => {
const { data } = await api.get<{ success: boolean; data: Result }>(
'/example'
);
return data.data;
},
};
// WRONG — bypasses auth
const response = await fetch('/api/example'); // Never do this
When adding new endpoints:
- Create backend route in
backend/src/routes/ - Add typed API method to
frontend/src/lib/api.ts - Use the API method in components
- Test with maintenance mode enabled
Common Pitfalls
- Route ordering: Static routes (
/random) MUST come before parameterized routes (/:id) in Express routers ||vs??: Use??for config defaults —||treats"",0,falseas falsyArray.sort()mutates: Use[...arr].sort()when original must be preservedparseInt()validation: Always checkisNaN()— NaN silently passes to SQL- Limit caps: Cap
limitquery params withMath.min()to prevent data dumps asyncHandler: Required on ALL async Express handlersres.headersSent: Check before error responses in SSE endpoints- Raw
fetch(): Never use for backend calls — see Frontend API Guidelines - flashpoint.sqlite is read-only: Never write to it
- Stream cleanup: Handle
res.on('close')to destroy streams on client disconnect - Atomic swap for hot-reload: Open new resource, swap reference, close old
- Security allowlists > denylists: For CGI env vars, download domains, etc.
- Path security: Always validate resolved paths stay within base
directories using
path.resolve()+ startsWith check - Ruffle files missing: Run
npm run copy-rufflein frontend
See docs/08-development/common-pitfalls.md for the full list with details.
Key Architecture Notes
- Game play flow: Frontend calls
GET /api/games/:id/launch→ backend mounts ZIP → if downloading, frontend polls every 2s → when ready, renders player - Game content routes (
/game-proxy/*,/game-zip/*) are registered BEFORE auth middleware for cross-origin access - Edition auto-detection: Read from
{FLASHPOINT_PATH}/version.txt, stored inconfigobject (not DB). Frontend gets it via/api/settings/public - JWT auth with RBAC: Permissions cached 5min via
PermissionCache. Maintenance mode blocks non-admins (503) - Token storage: Refresh tokens in HTTP-only cookies (
fp_refresh, path/api/auth, 30-day expiry, secure flag in prod, SameSite=lax). Access tokens in memory only. Session recovery on page reload via/api/auth/refresh(cookie sent automatically) - CSRF protection: Axios client uses
withCredentials: trueto enable cookie transmission with requests - Database hot-reload:
DatabaseServicewatches flashpoint.sqlite and reloads on changes from Flashpoint Launcher - Resource limits: Server has
keepAliveTimeout,headersTimeout,maxConnections; external requests use dedicated axios withmaxSockets: 10; concurrent downloads capped at 3
Environment Configuration
# Required
FLASHPOINT_PATH=D:/Flashpoint
JWT_SECRET=your-secure-random-string
# Optional
DOMAIN=http://localhost:5173
LOG_LEVEL=info
All paths derived from FLASHPOINT_PATH. See backend/.env.example for all
options and docs/09-deployment/environment-variables.md for details.
Source Code References
- Flashpoint Launcher source: D:\Repositories\Community\launcher
- Flashpoint Game Server: D:\Repositories\Community\FlashpointGameServer
- Flashpoint Infinity Edition: "D:\Flashpoint Infinity 14.0.3"
- Flashpoint Ultimate Edition: "E:\Flashpoint Ultimate"
Recent Changes & Lessons Learned
2026-02-07: Navigation Revamp - Breadcrumb Bar with Back Button
Change: Redesigned the Breadcrumbs component as a navigation bar with an integrated back button. Added breadcrumbs to GamePlayerView (previously had none). Removed standalone "Back" text links from GameDetailView, GamePlayerView, and SharedPlaylistView.
What changed:
Breadcrumbs.tsx: Restyled as nav bar (bg-muted/50 rounded-lg) with ArrowLeft back button, vertical divider, and pill-style breadcrumb items. NewshowBackButtonprop (default: true). AddedBreadcrumbContext(moved from GameCard.tsx) andPlayerBreadcrumbContexttypes.GameDetailView.tsx: Removed standalone back button. Play button and auto-play now passPlayerBreadcrumbContextvia React Router navigation state.GamePlayerView.tsx: Added breadcrumbs showingHome > [Context] > Game Title > Play. Reads context from navigation state, falls back toBrowse > Game > Playfor direct URL access. Hidden in fullscreen.SharedPlaylistView.tsx: Removed standalone "Back to my playlists" link.- 5 library components: Updated
BreadcrumbContextimport source fromGameCard.tsxtoBreadcrumbs.tsx.
Files modified (9):
frontend/src/components/common/Breadcrumbs.tsxfrontend/src/views/GameDetailView.tsxfrontend/src/views/GamePlayerView.tsxfrontend/src/views/SharedPlaylistView.tsxfrontend/src/components/library/GameCard.tsxfrontend/src/components/library/GameBrowseLayout.tsxfrontend/src/components/library/GameGrid.tsxfrontend/src/components/library/GameList.tsxfrontend/src/components/library/GameListItem.tsx
Key Lesson: Breadcrumb context must flow through navigation state
(location.state) since React Router doesn't preserve component state across
page transitions. Use a typed context interface (PlayerBreadcrumbContext) and
always provide sensible fallbacks for direct URL access.
2026-02-07: Low-Priority Code Quality Fixes (~48 issues)
Change: Final round of code quality improvements from the comprehensive code review. Focused on type safety, dead code removal, DRY violations, import consistency, and theme token standardization.
Backend fixes (14 files):
- Replaced
anytypes with proper types across 6 services (MetadataSyncService, PlaylistService, UserPlaylistService, JobExecutionService, CommunityPlaylistService, RuffleService) - Removed
.jsextension from import, simplified redundantparseInt(String(...))calls - Added
.catch()on fire-and-forget promises,.unref()on cleanup intervals - Demoted noisy
logger.infotologger.debug, removed duplicate default export
Game service fixes (9 files):
- Added
headersSentguard tosendError()in proxy-request-handler to prevent write-after-headers errors - Made
findActiveDownloadprivate andPRIVATE_IP_RANGESstatic readonly - Removed unused imports (
AxiosError), aliases (realpath), and convenience re-exports - Removed
exportfrom internal-onlyCUSTOM_MIME_TYPESconstant
Frontend DRY + types fixes (12 files):
- Deleted duplicate
errorUtils.ts- consolidated totypes/api-error.ts - Replaced duplicate
formatDuration/formatPlaytimefunctions with shared import fromcron-utils.ts - Fixed
anytypes inlogger.ts(any[]→unknown[]) andcreateQueryHook.ts - Removed dead
Window.operatype declaration, redundant variable aliases, duplicate conditional rendering - Fixed
LazyBackgroundImagecallback refs to prevent effect dependency issues
Frontend imports + theming fixes (15 files):
- Standardized 10 files from relative
../imports to@/alias imports - Replaced hardcoded Tailwind colors with theme tokens across 4 components
(e.g.,
text-gray-400→text-muted-foreground,border-primary-500→border-primary) - Fixed index-based list keys → content-based keys in GameListItem
Key Lessons:
- Duplicate utility functions (
errorUtils.tsvsapi-error.ts) cause confusion - consolidate early - Callback props in
useEffectdependencies cause re-render loops - useuseRefpattern - Fire-and-forget promises need
.catch()to prevent unhandled rejections setIntervalwithout.unref()prevents Node.js graceful shutdown- Internal-only constants should not be exported - reduces public API surface
- Theme tokens (
text-muted-foreground) adapt to dark/light mode; hardcoded colors don't
2026-02-07: ~78 Medium-Priority Code Quality Fixes
Change: Applied ~78 medium-priority fixes across backend routes, backend services, game service, and frontend from comprehensive code review.
Backend Routes (27 fixes):
asyncHandleradded to:game-zip.ts,github.ts,game-proxy.ts,authenticate/optionalAuthmiddleware- Zod validation:
community-playlists.ts(downloadUrl),shared-playlists.ts(newTitle),cache.ts(cacheType),activities.ts(timeRange) parseIntNaN fallback:favorites.ts,jobs.tsoffset;config.ts6 env vars- Bug fix:
cache.tsoverlappingif→switch(was clearing wrong caches) .catch()on fire-and-forget cache write inproxy.ts.unref()on 3 cleanup intervals inserver.ts
Backend Services (12 fixes):
- Removed
anytypes:shared-playlists.ts,activities.ts,CommunityPlaylistService.ts - Consolidated
ALLOWED_DOWNLOAD_DOMAINSas single exported constant - Fixed timeout leak in
UpdateService.ts - Simplified duplicate validation in
DomainService.ts - Removed redundant
ensureLogDirinerrors.ts
Game Service (19 fixes):
.catch()ondownloadAndMountInBackground;.unref()on cleanup interval- Shared
MAX_BUFFERED_FILE_SIZEconstant replaces 50MB magic number in 3 files - Hostname port stripping:
host.split(':')[0] - JSON parse vs ENOENT differentiation in config loading
- 5 defensive checks in
GameDataDownloader.ts - Removed unused
hasActiveDownloads(); addedLegacyServer.clearCache() - Fixed hostname variation skip for already-prefixed hostnames
- Per-request logging downgraded info→debug
Frontend (20 fixes):
id!→id ?? ''inPlaylistDetailView.tsx; NaN guard inUserPlaylistDetailView.tsx|| 24→?? 24inHomeView.tsxLazyImage.tsx: cancellation flag + ref-stored callbacksNetworkStatusIndicator.tsx: timeout moved to useRef- Tag keys index→
tag.trim()in 2 views console.error/console.debug→loggerin 3 files- Removed
window.operadead code; addedGameCard.displayName - Removed
isInViewfromuseLazyLoaddeps
Key Lessons:
- Use
switchfor mutually exclusive branching, not overlappingifblocks - Add
.catch()to all fire-and-forget promises - Call
.unref()on non-critical intervals to allow graceful shutdown - Strip port from
req.headers.hostbefore hostname validation - Store setTimeout/setInterval IDs in
useRef, notletvariables - Return cleanup functions from
useEffectfornew Image()preloads - Export shared constants to eliminate magic number duplication
2026-02-07: 22 High-Priority Security & Data Integrity Fixes
Change: Applied 22 high-priority fixes across backend, game service, and frontend from targeted code review.
Backend Auth & Data Integrity (7 fixes):
middleware/auth.ts: Guest users (id=0) excluded from shared access bypassAuthService.ts: Registration uniqueness checks moved inside transaction (TOCTOU fix)UserService.ts:createUser()wrapped in transaction (TOCTOU fix)SystemSettingsService.ts:updateCategory()wrapped in transactionuser-playlists.ts: Zod schema limits aligned (500→100)UserPlaylistService.ts:cloneSharedPlaylist()wrapped in transactiondownloads.ts: SSE write-after-close prevention withclosedflag
DownloadManager (1 fix):
DownloadManager.ts: AbortSignal listener cleanup in finally blocks (memory leak fix)
Game Service (8 fixes):
zip-manager.ts: ZIP handle closed on mount failure +shuttingDownflag for graceful shutdownproxy-request-handler.ts:collectRequestBodydouble-settlement prevented withsettledflaglegacy-server.ts: Only cache ENOENT (not validation errors) + URL constructor for path safetyGameDataDownloader.ts: DNS rebinding SSRF prevention viadns.lookup()pre-checkcgi-executor.ts: Symlink bypass prevention viafs.realpath()before path validationgamezipserver.ts: Downloaded file path re-validated after download
Frontend (6 fixes):
store/auth.ts: Guest permissions restricted to read-only (removedgames.play)store/theme.ts: Server data validated before applying (mode + primaryColor)AuthContext.tsx: Maintenance mode check uses authenticatedapiClient; permissions refreshed from server after token refreshuseSharedPlaylistAccess.ts:encodeURIComponent(shareToken)for URL safetyusePlayTracking.ts:isMountedRefguard prevents StrictMode double session start
Key Lessons:
- Wrap all uniqueness-check-then-insert patterns in
db.transaction()to prevent TOCTOU races - Track
closedstate for SSE connections and check before everyres.write() - Close ZIP handles in catch blocks when mount operations fail after open
- Use
shuttingDownflag to prevent new resource acquisition during shutdown - Only cache definitive ENOENT errors, never transient failures (negative cache poisoning)
- Use
URLconstructor for safe URL resolution, not string concatenation - Pre-check DNS resolution to block private addresses (DNS rebinding SSRF)
- Resolve symlinks with
fs.realpath()before path security validation - Validate server data (theme settings) before applying to prevent crashes
- Use
isMountedRef+ timeout to survive React StrictMode unmount/remount cycles
Files Modified (19 files):
- Backend:
auth.ts(middleware),AuthService.ts,UserService.ts,SystemSettingsService.ts,user-playlists.ts,UserPlaylistService.ts,downloads.ts,DownloadManager.ts - Game service:
zip-manager.ts,proxy-request-handler.ts,legacy-server.ts,GameDataDownloader.ts,cgi-executor.ts,gamezipserver.ts - Frontend:
store/auth.ts,store/theme.ts,AuthContext.tsx,useSharedPlaylistAccess.ts,usePlayTracking.ts
Documentation Maintenance
After ANY code change, always:
- Run Prettier on all new and modified files:
npx prettier --write <file1> <file2> ...— also instruct sub-agents to do this. - Verify no build errors:
npm run typecheckandnpm run build - Ask the user if relevant documentation should be updated
- Update docs as needed:
- Architecture →
docs/02-architecture/ - API endpoints →
docs/06-api-reference/ - DB schema →
docs/12-reference/database-schema-reference.md - Features →
docs/10-features/ - Components →
docs/04-frontend/components/ - Config →
docs/03-backend/configuration.mdordocs/09-deployment/environment-variables.md
- Architecture →