Files
MilkyShots/AGENTS.md
REDCODE 9dced21554 docs: reconcile AGENTS.md with current codebase
- Remove stale Npgsql legacy timestamp switch note (removed from code)
- Fix EF Core version mismatch description (Tools 8.0.15, not CLI)
- Expand analyzer rules to cover MS001–MS004
2026-07-14 12:09:33 +02:00

8.7 KiB
Raw Blame History

MilkyShots Agent Guide

Gitea repo owner: MilkyShots

Prerequisites

  • .NET 10 SDK (pinned in global.json; allowPrerelease: true)
  • PostgreSQL with pgvector extension
  • SCSS compiled by AspNetCore.SassCompiler MSBuild task (no Node.js needed; compiles during dotnet build)
  • Run dotnet tool restore before first migration — installs dotnet-ef

SCSS

SCSS source is in MilkStream.Client/Styles/. Compiled via AspNetCore.SassCompiler MSBuild target:

dotnet build            # compiles SCSS automatically
  • Config: MilkStream.Client/Styles/sasscompiler.json (expanded in Debug, compressed in Release)
  • Source maps disabled
  • Bootstrap 5.3.7 SCSS vendored in Styles/bootstrap/
  • Theme variables adjustable in Styles/_variables.scss ($accent for primary color)
  • Aggressive gradient mixins in Styles/_gradients.scss
  • Works in Docker — MSBuild task runs during dotnet build/dotnet publish, no hosted service

Projects (5 in solution)

Project Role Entrypoint
Butter Shared class library (DTOs, enums, MIME types)
Lactose.Analyzers Roslyn analyzer — enforces DateTime.UtcNow (not DateTime.Now) as error MS001
Lactose ASP.NET Core Web API — controllers, EF Core, repos, background jobs Lactose/Program.cs
MilkStream Blazor WASM host — serves WASM files + dynamic /appsettings.json MilkStream/Program.cs
MilkStream.Client Blazor WASM client — Razor components, SCSS, frontend services, shared UI components (ModalFrame, EmptyState) MilkStream.Client/Program.cs

Build & run

dotnet build MilkyShots.sln           # .NET 10, C# 12
dotnet run --project Lactose          # API on :5162 (host) / :8080 (container)
dotnet run --project MilkStream       # WASM host on :5269 (host) / :8080 (container)

Database: Default credentials in appsettings.json. Docker compose spins up all services.

Port gotcha: appsettings.json defaults DatabaseAddress:Port to 3306 (legacy MySQL port). PostgreSQL runs on 5432. The raw connection string must be overridden, or use docker-compose.yml which maps host 3306 → container 5432 automatically.

Infrastructure gotchas

  • MilkStream /appsettings.json: Dynamically generated from server config — maps before UseStaticFiles, so it shadows the static file in MilkStream.Client/wwwroot/. This endpoint provides LactoseBaseUrl to the WASM client.
  • Auth requires claims transformation: RefreshTokenTransformation (registered as IClaimsTransformation) must succeed on every authenticated request, or all endpoints return 403.
  • Microsoft.EntityFrameworkCore.Tools v8.0.15 vs EF Core Design v10.0.9: The Tools package is outdated but the dotnet-ef CLI (from dotnet-tools.json) and Design package are both v10.0.9 — CLI should work fine.
  • WASM client DI: LoginService is Singleton; all other services are Scoped. Two HttpClient registrations: one unauthenticated (default) for login, and one named "MilkstreamClient" which has JwtTokenRefresher as a DelegatingHandler. The handler transparently refreshes expired tokens pre-flight and retries once on 401 — authenticated services must never handle token refreshes manually. Do not add SendWithRefreshAsync or similar wrappers; rely solely on the named client.
  • HTTPS redirection is commented out in Lactose — API does not enforce HTTPS.
  • SharedWith.Any() EF Core translation: Cannot be translated to SQL through many-to-many join tables (AlbumAsset, AssetUser). When visibility filtering includes SharedWith.Any(u => u.Id == userId), you must materialize records first with .Include().ThenInclude() chains, then filter client-side in C#. The two-pass pagination approach preserves page-limited loading — see PersonRepository.SearchQuery and AlbumRepository.SearchQuery for the pattern.
  • DateTime analyzer: Lactose.Analyzers treats DateTime.Now as error MS001, DateTime.Today as error MS002, DateTime.Date as warning MS003, and non-UTC SpecifyKind as error MS004. Always use DateTime.UtcNow. All timestamp columns use timestamp with time zone.

XML docs enforced

Directory.Build.props at solution root enables GenerateDocumentationFile and treats CS1591 (missing XML comment) as error. All public APIs must have XML docs. Repo implementations use <inheritdoc /> — keep interface docs in sync.

Conventions

  • HttpPut = create, HttpPost("{id}") = update — not REST-idiomatic; do not "fix" without team buy-in
  • Soft delete only — set DeletedAt, never hard-delete (models: Asset, User)
  • Repository Save() must be called explicitly after insert/update/delete
  • Enum prefix E (e.g. EAccessLevel, EAssetType, EJobStatus)
  • JWT access token: 10 min, refresh token: 60 min (constants in LactoseAuthService.cs)
  • Pagination is zero-based everywhere — Page = 0 is the first page. All repositories use Skip(page * pageSize). All controllers validate Page < 0 (reject negative) and PageSize < 1 || PageSize > 250 (clamp 1250).
  • Conventional Commits enforced via cliff.toml; changelog generated with git-cliff
  • Granular commits — commit each logical change separately (e.g., DTO change, controller logic, UI component) with a descriptive commit message
  • Reuse API endpoints — prefer adding optional query parameters to existing endpoints over creating new routes. New endpoints are a last resort when the existing ones fundamentally cannot serve the need.

Modularity

Reusable UI components live in MilkStream.Client/Components/Shared/. When a markup pattern appears in 2+ places, extract it into a shared component. See issue #52 for the tracking list of candidate components.

CORS gotchas

  • CorsAllowedOrigins env var replaces the JSON array entirely (semicolon-separated)
  • MilkStream browser origin must be listed in Lactose's CORS
  • LactoseBaseUrl on MilkStream = URL the browser uses to reach Lactose

EF Core / Migrations

Migrations in Lactose/Migrations/ — generated, do not hand-edit.

dotnet ef migrations add <Name> --project Lactose
dotnet ef database update --project Lactose

Database indexes for performance should be defined using the fluent API in LactoseDbContext.OnModelCreating via HasIndex().HasFilter(). Do not use the [Index] data annotation — the Filter parameter is not available in the current package configuration (EF Core Tools v8.0.15 vs Design v10.0.9 mismatch). Example:

modelBuilder.Entity<Asset>().HasIndex(e => new { e.IsPubliclyShared, e.OwnerId })
    .HasDatabaseName("IX_Assets_VisibleForSearch")
    .HasFilter("\"DeletedAt\" IS NULL");
  • Trigram GIN indexes on People.Name and Albums.Title use HasMethod("gin") with HasOperators("gin_trgm_ops") for case-insensitive ILike search.
  • pgvector and pg_trgm extensions are enabled in OnModelCreating via HasPostgresExtension.

Tests

No unit/integration test project exists. API endpoint testing is done via REST Client .http files:

  • Lactose/WepApiTest.http — 67 numbered tests covering all API endpoints across 10 sections (Auth → User CRUD → Person → Album → Tag → Asset → Stats → Settings → Cleanup). Tests verify authorization at every level (anonymous, user, curator, admin). Self-contained — all test data is created at runtime and cleaned up.
  • Lactose/http-client.env.json — provides pre-filled variable values (userName, curatorName, etc.) for the REST Client extension.
  • Open in VS Code/Rider with the REST Client extension and run individual tests or the full suite.
  • Server must be running locally (default http://localhost:5162).
  • Paginated list tests (people, albums, users) use ?page=0&pageSize=5 — change these carefully.

PR workflow

  • Before starting work, ask the user whether they want the fix in a PR or directly on develop (for quick/trivial fixes).
  • Larger work always goes in a PR.
  • Always mark the PR as draft until the user confirms they are satisfied and have tested it works.
  • When making the PR ready (removing draft status), mark it ready for review and request:
    • REDCODE when the PR contains frontend changes
    • Fastwind when the PR contains backend changes
    • Both when both areas are touched

Gitea labels

Labels live at two levels:

  • Org level (MilkyShots org): generic issue types (bug, enhancement, help wanted, question, duplicate, invalid, wontfix)
  • Repo level (MilkyShots/MilkyShots): project-specific labels (area:frontend, area:backend, page:*, type:refactor, priority:*, etc.)
  • When labelling issues, check both levels with the label_read tool (list_org_labels / list_repo_labels).