Files
MilkyShots/AGENTS.md
T
REDCODE 0480b00766 fix: desloppify review fixes - PathFromGuid extraction, RegexHighlighter dedup, port default fix
- Extract PathFromGuid to shared PathUtils.cs (PreviewJob + ThumbnailJob)
- Consolidate RegexHighlighter duplicate methods via delegation
- Fix default port 3306 -> 5432 in appsettings.json + docker-compose.yml
- Remove port gotcha section from AGENTS.md
- Fix ITagRepository XML docs after parameter rename (id -> tag)
2026-07-29 20:12:58 +02:00

10 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 (tool manifest at dotnet-tools.json in repo root)

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.

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.
  • dotnet-ef CLI vs Microsoft.EntityFrameworkCore.Tools: The global dotnet-ef (from dotnet-tools.json, v10.0.9) is the correct CLI to use. The Tools package reference in Lactose.csproj (v10.0.9) provides the dotnet ef target for dotnet build-time validation.
  • 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.
  • 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.

Visibility Rules (R1R7)

Defined in issue #129. Each entity (Asset, Album, Person) carries independent EVisibility — no inheritance from container to child.

R1 — Independent Visibility

No entity's visibility is overridden by its container. An asset in a Public album does NOT become Public; the asset is only visible if the viewer has permission for the asset's own visibility.

R2 — Asset Visibility (Per-Asset Gate)

Actor Public Protected Private
Anonymous
User
Maintainer (not owning / not linked)
Maintainer (uploaded by self)
Maintainer (album → person they maintain)
Curator (non-deleted)
Curator (deleted, own upload)
Admin (all, including deleted)

R3 — Album Access Gating

Album's own visibility determines who can access it. Not visible → 404.

Actor Public Protected Private
Anonymous / User
Maintainer (unassigned)
Maintainer (maintains PersonOwner)
Curator / Admin

R4 — Person Access Gating

Same as R3. Not visible → 404.

R5 — Media Serving

Uses per-asset rules (R2). Album provenance not checked — too complex, marginal benefit.

R6 — Edit Mode (Frontend)

API returns Private assets to privileged users (Admin/Curator/Maintainer-on-owned-content). Frontend hides them in browse mode, shows them in edit/curator mode. No backend flag.

R7 — Deleted Assets

  • Admin: all deleted assets visible
  • Curator: own deleted uploads visible
  • Everyone else: deleted assets hidden (DeletedAt IS NULL)

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 v10.0.9 vs Design v10.0.9 mismatch). Example:

modelBuilder.Entity<Asset>().HasIndex(e => new { e.Visibility, e.UploadedBy })
    .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 — 100+ numbered tests covering all API endpoints across 12 sections (Auth → User CRUD → Person → Album → Tag → Asset → Stats → Settings → Data Visibility → Visibility Gates → Cleanup). Tests verify authorization at every level (anonymous, user, curator, admin) and include comprehensive visibility gate tests (R2R4). 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

Data sizes

Approximate record counts in the database:

Entity Count
Assets 1,000,000+
Albums 10,000+
People 1,000+
Users 100+
Maintainers 10+
Curators single digit
Admins single digit

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