This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Restore, build, test
dotnet restore LSPDFRManager.sln
dotnet build LSPDFRManager.sln
dotnet test LSPDFRManager.Tests/LSPDFRManager.Tests.csproj
# Run a single test class or test by name
dotnet test LSPDFRManager.Tests/LSPDFRManager.Tests.csproj --filter "FullyQualifiedName~ModDetectorTests"
# Self-contained release build (for local distribution)
dotnet publish LSPDFRManager.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
# CI release build uses --self-contained false (framework-dependent)
# Scaffold a new feature slice
.\tools\New-FeatureSlice.ps1 -Name MyFeature
.\tools\New-FeatureSlice.ps1 -Name MyFeature -WithArchitectureTest
# Frontend (React/Vite)
cd frontend
npm install
npm run dev # dev server with HMR
npm run build # produces output into ../LSPDFRManager.LocalApi/wwwroot
npm run typecheck # TypeScript type check without emitting
npm run lint # ESLintThis is a .NET 8 WPF desktop application for managing GTA V / LSPDFR mods. It has four projects:
| Project | Type | Role |
|---|---|---|
LSPDFRManager.csproj |
WPF net8.0-windows |
Shell: App.xaml, MainWindow, Views, ViewModels, Services, Core |
LSPDFRManager.Shared |
Class library net8.0 |
Domain models, AppLogger, CarInstall helpers — no WPF dependency |
LSPDFRManager.LocalApi |
ASP.NET Core net8.0 |
Minimal API hosted in-process; serves the React UI and exposes REST endpoints |
LSPDFRManager.Tests |
xUnit net8.0-windows |
~1004 tests; references both WPF project and Shared |
App.xaml.cs starts LocalApiHost.StartAsync() on a background thread, which picks a free port and spins up the ASP.NET Core minimal API in-process. The WPF MainWindow hosts a WebView2 that points to http://127.0.0.1:{port} and renders the React SPA.
The React frontend (frontend/) is a separate build step. vite build outputs to LSPDFRManager.LocalApi/wwwroot/, which the .csproj copies into the WPF output directory. At runtime the API serves these static files. LocalhostOnlyMiddleware enforces loopback-only access.
- MVVM —
ObservableObjectbase,RelayCommand/AsyncAppCommand.MainViewModelowns all tab view-models and routes navigation. - Features/ — Feature slices, each following the shape:
I<Name>Controllerinterface,<Name>WorkflowControllerimplementation,Commands/,Models/. Generated bytools/New-FeatureSlice.ps1. Install flow:IInstallController→InstallWorkflowController. The architecture guard tests enforce that ViewModels never callFileInstallerdirectly or touchInstallQueue.Enqueue— they must go through the controller interface. Controllers orchestrate services and return results; ViewModels keep bindable state and delegate to controllers. - Core/ —
InstallQueue(singleton background queue),UiDispatcher(marshals to WPF dispatcher),OivPipeline/(OIV build/install pipeline steps). - Services/ —
ModLibraryService(singleton mod registry backed bylibrary.json),LspdfrStatusService,BackupScheduler,ProfileManager, etc. - Persistent state lives in
%APPDATA%\LSPDFRManager\:library.json,config.json,configs.json,Backups/,app.log.
Minimal API endpoints in LSPDFRManager.LocalApi/Endpoints/ map to REST routes. Long-running operations use JobQueue (in-memory, prunes after 10 minutes) — endpoints return a jobId and the frontend polls /jobs/{id}. DTOs live in Dtos/.
LocalApiHost is the in-process host; Program.cs is used only when running the API project standalone (e.g. for integration tests against port 5284).
Stack: React 19, Vite 8, Tailwind CSS 4, TanStack Query v5, React Router v7, Radix UI primitives, Lucide icons. TypeScript throughout.
frontend/src/lib/api/— one file per domain area, thin wrappers around the local APIfrontend/src/types/— TypeScript types mirroring the C# DTOsfrontend/src/pages/— one page component per tab- API base URL is read from
window.__LSPDFRMANAGER_BASE_URL__(injected by LocalApiHost) or falls back tohttp://127.0.0.1:5284for standalone dev.
- ModInfo — in-flight mod before install (detected type, archive path, confidence score)
- InstalledMod — persisted library entry
- InstallPlan / InstallPlanEntry — reviewed plan that is built once and executed as-is (no double-build)
- ModType — enum for plugin types:
LspdfrPlugin,AsiMod,VehicleDlc,VehicleReplace,Script,Eup,Map,Sound,Unknown - PathSafety — all archive entries are validated through this before extraction (path traversal protection)
- AppConfig — singleton, serialized to
config.json - TransactionService — singleton that persists install transactions to
transactions.json; drives user-initiated rollback (removes added files only if unchanged, restores overwritten files from per-transaction backup folder) - InstalledModFileService.IsOrphaned() — static helper (in
LSPDFRManager.Shared) that returns true when a mod'sInstalledFileslist is non-empty but none of the files exist on disk (active or.disabled). Used byModLibraryService.SyncWithDirectory()and the/api/v1/mods/syncendpoint to prune ghost entries.
- ViewModels must not call
FileInstallerdirectly or callInstallQueue.Enqueue— useIInstallController. InstallQueue.Enqueueis only allowed inCore/,Features/Install/, andServices/.- Passive events (progress callbacks, status updates) must not install, enqueue, delete, or write durable state.
- Tests that touch
AppConfig.InstanceorAppDataPathssingletons must useAppDataPaths.OverrideRoot(tempDir)in setup andAppDataPaths.ClearOverride()in teardown (IDisposable).CommandCenterTestBaseprovides this boilerplate for command-center integration tests. If a test also mutatesAppConfig.Instance.GtaPath, save and restore it manually inDispose. - Test classes that share singleton state (
AppConfig,AppDataPaths,ModLibraryService) must be placed in the"AppData serial"or"CommandCenter"xUnit collection (both disable parallelization). Most other tests run in parallel by default. Any class that setsAppConfig.Instance.GtaPathmust be in"AppData serial"to avoid racing with other serialised classes. FakeArchive(in the test project) is the helper for constructing in-memory archives for installer tests.- When bumping the app version, update
<Version>in bothLSPDFRManager.csprojandLSPDFRManager.Shared/LSPDFRManager.Shared.csproj, then update the hardcoded assertions inVersionAndBrowseGuardTests(AssemblyVersion_Is_X_Y_Z_0) andSetupWizardTests(result.CurrentVersion).