Coding Standards
Comments
Every comment must be worth reading. A comment earns its place when it tells the reader something the code cannot: what a type or member is for, a non-obvious invariant, why a surprising line must stay, the bug it prevents, or an external quirk it works around.
Comments are never narrative. Do not write:
- History — what the code used to do, the path that led to a fix, or alternatives that were rejected. Git keeps that. Keep only the constraint that survives: "X must run before Y or Z breaks".
- Reasoning — how the author got there ("which is how this reached production"). A later reader needs the conclusion, not the journey.
- Restatement — a comment that repeats the line beneath it (
// loop over the rows). - Phantom references — a property, flag, or branch that is not in the code.
- Cross-reference bouncing — "see the note on X" when the point fits in the comment itself.
When a comment warns, phrase it as an instruction — "Do not map X here; it throws under a real provider" — not as an observation ("X is deliberately not mapped"). If deleting a comment loses nothing a reader can't see from the code, delete it.
Match the comment density of the file you are editing.
XML documentation (.NET)
Classes, records, interfaces, enums, methods, and properties carry /// XML documentation. It is how a
reader discovers what a type is for without opening it — the summary appears in IntelliSense wherever the
member is used — and it is where the context belongs that a name alone cannot carry.
Write the summary for someone who can see the name and signature but not the body. From Project:
/// <summary>
/// The project's fractional sort key within its portfolio's ranking (Jira-style: an opaque
/// between-able key, not the displayed position; the #1/#2/#3 shown to users is derived at read
/// time). Every project is ranked from creation, so this is always set. A human prioritization,
/// independent of the computed score. Mutated only via the owning <see cref="ProjectPortfolio"/>
/// aggregate.
/// </summary>
public double Rank { get; private set; }
not
/// <summary>Gets or sets the rank.</summary>
public double Rank { get; private set; }
- Say what the member is for and anything a caller must know: units, ranges, what
nullmeans, when it throws or fails, what it does not do. - Use
<param>,<returns>, and<remarks>when they add something; skip them when the summary covers it. - The same no-narrative rule applies: no history, no "this was added to…".
API models publish their documentation. Wayd.Web.Api and every *.Application project generate their XML
documentation files, and the OpenAPI document reads them: the /// summary on a request or response model —
including the DTOs and commands an Application project defines — and on each of its properties becomes the
description in the OpenAPI spec, the JSDoc on the generated TypeScript client and the schema agents read
through MCP. Write those for an API consumer, in plain lines the document can carry as written: name another
member by its JSON name with <c>name</c>, not <see cref>, which is published as its C# signature
(string? ImportProductRequest.ImportId); and leave out <value>, <para> and HTML tags: <value> is
appended to the summary, a <c> inside a <para> loses its code marks, and HTML is published as raw markup.
Say what the value means, not its name ("Gets the X" tells an agent nothing), and put notes meant only for
maintainers — mapping, how a handler computes it — in <remarks>, which is not published. The root
Directory.Build.props turns this on for every project named *.Application and suppresses CS1591 and CS1573
there, so a missing summary is a review finding rather than a warning. An endpoint's published summary and
description come from its [EndpointSummary] and [EndpointDescription] attributes instead, so the /// on a
controller action can speak to maintainers.
Test markers
Every test marks its phases with // Arrange, // Act, and // Assert. See
Testing.
.NET Backend
Naming Conventions
- Classes/Methods - PascalCase
- Parameters/Local Variables - camelCase
- Private Fields - _camelCase with underscore prefix
- Constants - PascalCase
- Async Methods - Do NOT use
Asyncsuffix for new methods
Time Handling
Always use NodaTime types instead of DateTime:
Instantfor timestampsLocalDatefor dates without timeLocalDateTimefor date+time without timezone
Never use DateTime.UtcNow. Always inject IDateTimeProvider for the current time.
Result Pattern
Return Result<T> or Result from command handlers; CqrsPatternTests fails one that returns anything else. Query handlers return their response type: usually a nullable DTO, and a Result<TDto> when the query can fail for a reason other than finding nothing. Never throw exceptions for business logic failures.
Primary constructors
With a primary constructor, assign each parameter to a private readonly _field and use the field in
method bodies, as handlers and controllers do:
public sealed class DeleteScoringModelCommandHandler(IWaydDbContext waydDbContext, /* … */)
: ICommandHandler<DeleteScoringModelCommand>
{
private readonly IWaydDbContext _waydDbContext = waydDbContext;
Package Management
Uses Central Package Management via Directory.Packages.props. All package versions are defined centrally - do not specify versions in individual project files.
Entity Configuration
Use fluent API in separate configuration classes implementing IEntityTypeConfiguration<T>. Never use data annotations on entities.
Frontend (React/Next.js)
Theming
Always use Ant Design theme tokens. Never hardcode colors.
Prefer CSS variables in CSS modules:
.container {
border-color: var(--ant-color-border);
color: var(--ant-color-primary);
}
Only use theme.useToken() when you need token values in JS logic.
API Calls
Always use the NSwag-generated typed client (e.g., getProjectsClient()). Never use authenticatedFetch() directly.
Component Patterns
- Use functional components with hooks
- Prefer CSS Modules for styling
- Use
next/dynamicwithssr: falsefor heavy client-only components - Use direct imports instead of barrel imports where possible