# NoMercy Media Server The flagship media server for encoding, organizing, and streaming personal media. > **Prime directive: leave the code more maintainable than you found it.** > Every change is judged on whether the next person — or model — can read it, > test it, and safely extend it. Working-but-unmaintainable is a failed change. This file is the short, always-read contract: keep it lean and accurate. For depth (build/run commands, architecture, SQLite restrictions, security, dev container, provider integrations) see **`.claude/CLAUDE.md`**. ## The loop — every commit, no exceptions 1. **One slice at a time.** Make the smallest coherent change. 2. **Build clean: 0 errors AND 0 warnings.** A warning is a failure here. 3. **`dotnet test` is green.** The suite is fast — use it as your safety net, not an afterthought. 4. **`dotnet csharpier format`** every changed `.cs` file. 5. **Commit only when 2–4 all pass.** A commit that isn't build- and test-green never gets made. If a change is too big to stay green in one slice, split it. ## Non-negotiable rules (the ones that bite most) - **Explicit types — never `var`.** Name the type. The *only* exception is an unnameable anonymous-type projection (`.Select(x => new { ... })`), where C# requires `var`. Nowhere else. - **License header** (the SPDX block) at the top of every new file — copy it from any existing `.cs`. - **One public type per file**, filename matches the type. - **Depend on abstractions, not statics.** Collaborators are injected via the constructor (interface + DI) — not reached through static singletons or `new`d inline. This is the direction the whole codebase is moving; don't grow the static pile. - **No business logic in EF models.** Models hold data; behavior lives in services. - **No EF Core types in interface contracts.** Repositories take/return domain types and `Task>` — never `IQueryable`, `IIncludableQueryable`, or `MediaContext`. A repository owns its context via `IDbContextFactory`. - **`.AsNoTracking()` on every read-only EF query.** - **Filesystem I/O goes through the storage facade.** Inject `IStorage` (path-validated; library/encoder/media paths) or `IStorageBackend` (raw FS, for dashboard/picker/drive code that browses outside library roots). Never call `System.IO.File.*` or `System.IO.Directory.*` directly in new code. (`DriveInfo.GetDrives()` and `Environment.GetFolderPath()` are fine.) - **Packages** go in `Directory.Packages.props`, never individual `.csproj`. - **Schema changes** require EF Core migrations — never hand-edit the schema. - **New API endpoints** need test coverage under `tests/`. ## Naming - Projects/Classes/Methods/Properties: PascalCase. Locals/params: camelCase. - Private fields `_camelCase`; private constants PascalCase. - **The `I` prefix is for interfaces only** (`IMovieRepository`, `IUserCache`) — never on concrete classes or value objects. Value objects live in `NoMercy.Database/ValueObjects/` (e.g. `VideoProfile`, `ColorPalette`). ## Modern C# (required) - Target-typed `new()` when the type is on the left: `List libraries = new();` - Collection expressions for empties: `List movies = [];` - File-scoped namespaces, always. - Primary constructors for DI. - Pattern matching: `is not null`, `is { Prop: not null }`. - Async all the way: return `Task`/`Task`, suffix `Async`, `ToListAsync()` for EF. - `[JsonProperty("...")]` on serialized properties — match the casing already used in the file. ## Structure (authoritative list: `ls src/`) - **Hosts / entry:** `NoMercy.Service` (web host), `NoMercy.Cli`, `NoMercy.App`, `NoMercy.Launcher` - **Web:** `NoMercy.Api` (controllers, DTOs, hubs, middleware), `NoMercy.Networking` - **Data:** `NoMercy.Database` (EF models, contexts, migrations, `ValueObjects/`), `NoMercy.Data` (repositories + services), `NoMercy.Storage` (filesystem facade) - **Domain:** `NoMercy.MediaProcessing`, `NoMercy.Encoder`, `NoMercy.OpticalMedia`, `NoMercy.Providers` (TMDB, etc.) - **Cross-cutting:** `NoMercy.NmSystem` (system ops), `NoMercy.Authorization` (user cache + policy), `NoMercy.Monitoring`, `NoMercy.Events`, `NoMercy.Setup`, `NoMercy.Resources` - **Queue:** `NoMercyQueue`, `NoMercyQueue.Core`, `NoMercyQueue.Sqlite`, `NoMercy.Queue.MediaServer` - **Plugins:** `NoMercy.Plugins`, `NoMercy.Plugins.Abstractions` - **Tests:** `tests/NoMercy.Tests.*` (xUnit + FluentAssertions + Moq) Tech stack: C# / .NET 10, ASP.NET Core, EF Core (SQLite), SignalR; solution `NoMercy.Server.sln`; central package management in `Directory.Packages.props`. ## Release assets (don't rename casually) GitHub release assets use fixed names (`nomercy-windows-x64.exe`, `nomercy-linux-x64`, `nomercy_VERSION_amd64.deb`, …). Renaming them breaks `infra/nomercy-packages` and `saas/nomercy-tv` download URLs — update those too.