Skip to content

Latest commit

 

History

History
97 lines (69 loc) · 6.95 KB

File metadata and controls

97 lines (69 loc) · 6.95 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

# 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         # ESLint

Architecture

This is a .NET 8 WPF desktop application for managing GTA V / LSPDFR mods. It has four projects:

Project layout

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

How the parts connect

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.

WPF layer (main project)

  • MVVMObservableObject base, RelayCommand/AsyncAppCommand. MainViewModel owns all tab view-models and routes navigation.
  • Features/ — Feature slices, each following the shape: I<Name>Controller interface, <Name>WorkflowController implementation, Commands/, Models/. Generated by tools/New-FeatureSlice.ps1. Install flow: IInstallControllerInstallWorkflowController. The architecture guard tests enforce that ViewModels never call FileInstaller directly or touch InstallQueue.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 by library.json), LspdfrStatusService, BackupScheduler, ProfileManager, etc.
  • Persistent state lives in %APPDATA%\LSPDFRManager\: library.json, config.json, configs.json, Backups/, app.log.

Local API layer

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).

React frontend

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 API
  • frontend/src/types/ — TypeScript types mirroring the C# DTOs
  • frontend/src/pages/ — one page component per tab
  • API base URL is read from window.__LSPDFRMANAGER_BASE_URL__ (injected by LocalApiHost) or falls back to http://127.0.0.1:5284 for standalone dev.

Key domain concepts

  • 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's InstalledFiles list is non-empty but none of the files exist on disk (active or .disabled). Used by ModLibraryService.SyncWithDirectory() and the /api/v1/mods/sync endpoint to prune ghost entries.

Architecture constraints (enforced by tests)

  • ViewModels must not call FileInstaller directly or call InstallQueue.Enqueue — use IInstallController.
  • InstallQueue.Enqueue is only allowed in Core/, Features/Install/, and Services/.
  • Passive events (progress callbacks, status updates) must not install, enqueue, delete, or write durable state.

Test patterns

  • Tests that touch AppConfig.Instance or AppDataPaths singletons must use AppDataPaths.OverrideRoot(tempDir) in setup and AppDataPaths.ClearOverride() in teardown (IDisposable). CommandCenterTestBase provides this boilerplate for command-center integration tests. If a test also mutates AppConfig.Instance.GtaPath, save and restore it manually in Dispose.
  • 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 sets AppConfig.Instance.GtaPath must 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 both LSPDFRManager.csproj and LSPDFRManager.Shared/LSPDFRManager.Shared.csproj, then update the hardcoded assertions in VersionAndBrowseGuardTests (AssemblyVersion_Is_X_Y_Z_0) and SetupWizardTests (result.CurrentVersion).