From bf817a0b82e9ad370f50b966abf9ce7d9dd27cd2 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Thu, 27 Nov 2025 17:39:28 +0100 Subject: [PATCH 1/9] Update AI rules for backend and frontend --- .cursor/rules/backend/api-endpoints.mdc | 21 +- .cursor/rules/backend/api-tests.mdc | 10 +- .cursor/rules/backend/backend.mdc | 88 ++++++- .cursor/rules/backend/commands.mdc | 22 +- .cursor/rules/backend/database-migrations.mdc | 5 +- .cursor/rules/backend/domain-modeling.mdc | 15 +- .cursor/rules/backend/queries.mdc | 15 +- .cursor/rules/backend/repositories.mdc | 24 +- .cursor/rules/backend/strongly-typed-ids.mdc | 10 +- .cursor/rules/backend/telemetry-events.mdc | 4 +- .cursor/rules/developer-cli/developer-cli.mdc | 13 +- .../{e2e-tests.mdc => end-to-end-tests.mdc} | 49 ++-- .../rules/frontend/form-with-validation.mdc | 9 +- .cursor/rules/frontend/frontend.mdc | 66 ++++- .../frontend/known-accepted-warnings.mdc | 39 +++ .cursor/rules/frontend/modal-dialog.mdc | 94 ++++--- .../rules/frontend/react-aria-components.mdc | 44 ---- .../tanstack-query-api-integration.mdc | 9 +- .cursor/rules/frontend/translations.mdc | 20 +- .cursor/rules/main.mdc | 35 ++- .cursor/rules/tools.mdc | 140 ---------- .../rules/workflows/prepare-pull-request.mdc | 77 +++--- .cursor/rules/workflows/update-ai-rules.mdc | 246 +++++++++++------- .windsurf/rules/backend/api-endpoints.md | 21 +- .windsurf/rules/backend/api-tests.md | 10 +- .windsurf/rules/backend/backend.md | 88 ++++++- .windsurf/rules/backend/commands.md | 22 +- .../rules/backend/database-migrations.md | 5 +- .windsurf/rules/backend/domain-modeling.md | 15 +- .windsurf/rules/backend/queries.md | 15 +- .windsurf/rules/backend/repositories.md | 24 +- .windsurf/rules/backend/strongly-typed-ids.md | 10 +- .windsurf/rules/backend/telemetry-events.md | 4 +- .../rules/developer-cli/developer-cli.md | 13 +- .../{e2e-tests.md => end-to-end-tests.md} | 49 ++-- .../rules/frontend/form-with-validation.md | 9 +- .windsurf/rules/frontend/frontend.md | 66 ++++- .../rules/frontend/known-accepted-warnings.md | 40 +++ .windsurf/rules/frontend/modal-dialog.md | 94 ++++--- .../rules/frontend/react-aria-components.md | 45 ---- .../tanstack-query-api-integration.md | 9 +- .windsurf/rules/frontend/translations.md | 20 +- .windsurf/rules/main.md | 35 ++- .windsurf/rules/tools.md | 140 ---------- .windsurf/workflows/prepare-pull-request.md | 77 +++--- .windsurf/workflows/update-ai-rules.md | 246 +++++++++++------- 46 files changed, 1183 insertions(+), 929 deletions(-) rename .cursor/rules/end-to-end-tests/{e2e-tests.mdc => end-to-end-tests.mdc} (89%) create mode 100644 .cursor/rules/frontend/known-accepted-warnings.mdc delete mode 100644 .cursor/rules/frontend/react-aria-components.mdc delete mode 100644 .cursor/rules/tools.mdc rename .windsurf/rules/end-to-end-tests/{e2e-tests.md => end-to-end-tests.md} (89%) create mode 100644 .windsurf/rules/frontend/known-accepted-warnings.md delete mode 100644 .windsurf/rules/frontend/react-aria-components.md delete mode 100644 .windsurf/rules/tools.md diff --git a/.cursor/rules/backend/api-endpoints.mdc b/.cursor/rules/backend/api-endpoints.mdc index df8f64967f..7478b8fd0c 100644 --- a/.cursor/rules/backend/api-endpoints.mdc +++ b/.cursor/rules/backend/api-endpoints.mdc @@ -15,12 +15,12 @@ Carefully follow these instructions when implementing minimal API endpoints in t ```csharp private const string RoutesPrefix = "/api/account-management/users"; ``` -4. Set up the route group with a tag name of the feature and `.RequireAuthorization()` and `.ProducesValidationProblem()`. E.g.: +4. Set up the route group with a tag name of the feature and `.RequireAuthorization()` and `.ProducesValidationProblem()`. ```csharp var group = routes.MapGroup(RoutesPrefix).WithTags("Users").RequireAuthorization().ProducesValidationProblem(); ``` 5. Structure each endpoint in exactly 3 lines (no logic in the body): - - Line 1: Signature with route and parameters (don't break the line even if longer than 120 characters). + - Line 1: Signature with route and parameters (do not break the line even if longer than 120 characters). - Line 2: Expression calling `=> mediator.Send()`. - Line 3: Optional configuration (`.Produces()`, `.AllowAnonymous()`, etc.). 6. Follow these requirements: @@ -45,6 +45,7 @@ public sealed class UserEndpoints : IEndpoints { var group = routes.MapGroup(RoutesPrefix).WithTags("Users").RequireAuthorization().ProducesValidationProblem(); + // ✅ DO: Use [AsParameters] for complex queries with many querystring parameters group.MapGet("/", async Task> ([AsParameters] GetUsersQuery query, IMediator mediator) => await mediator.Send(query) ).Produces(); @@ -57,9 +58,10 @@ public sealed class UserEndpoints : IEndpoints => await mediator.Send(command) ); + // ✅ DO: Use [AsParameters] even when the query has no parameters group.MapGet("/me", async Task> ([AsParameters] GetUserQuery query, IMediator mediator) => await mediator.Send(query) - ).Produces(); + ).Produces(); // ✅ DO: Add produces when API returns a strongly typed response } } @@ -72,7 +74,7 @@ public sealed class BadUserEndpoints : IEndpoints { var group = routes.MapGroup(RoutesPrefix).WithTags("Users"); // ❌ DON'T: Skip .RequireAuthorization() even if all endpoints AllowAnonymous - group.MapGet("/", async (IMediator mediator, HttpContext context) => + group.MapGet("/", async (IMediator mediator, HttpContext context) => { // ❌ DON'T: Add business logic inside endpoint methods var tenantId = context.User.GetTenantId(); @@ -81,7 +83,7 @@ public sealed class BadUserEndpoints : IEndpoints return Results.Ok(result); }); - // ❌ DON'T: Use Put for commands that don't update an existing resource + // ❌ DON'T: Use Put for commands that do not update an existing resource group.MapPut("/{id}/change-user-role", async Task ( UserId id, ChangeUserRoleCommand command, @@ -93,11 +95,14 @@ public sealed class BadUserEndpoints : IEndpoints // ❌ DON'T: Use MVC [FromBody] attribute group.MapPost("/bulk-delete", async Task ([FromBody] BulkDeleteUsersCommand command, IMediator mediator) => await mediator.Send(command) - ); + ).Produces(StatusCodes.Status201Created) // ❌ DON'T: Do not add produces status code + .ProducesProblem(StatusCodes.Status403Forbidden) // ❌ DON'T: Do not add produces status code + .ProducesProblem(StatusCodes.Status409Conflict); // ❌ DON'T: Forget leading slashes - group.MapGet("me", async Task> ([AsParameters] GetUserQuery query, IMediator mediator) - => await mediator.Send(query) + // ❌ DON'T: new up command and queries even if they have no parameters... use "[AsParameters] GetUserQuery query" instead + group.MapGet("me", async Task> (IMediator mediator) + => await mediator.Send(new GetUserQuery()) ).Produces(); } } diff --git a/.cursor/rules/backend/api-tests.mdc b/.cursor/rules/backend/api-tests.mdc index ce26662659..e69433897e 100644 --- a/.cursor/rules/backend/api-tests.mdc +++ b/.cursor/rules/backend/api-tests.mdc @@ -13,7 +13,7 @@ Carefully follow these instructions when writing tests for the backend. By defau - Test files should be named `[Feature]/[Command|Query]Tests.cs`. - Test classes should be named `[Command|Query]Tests` and be `sealed`. - Test methods should follow this pattern: `[Method]_[Condition]_[ExpectedResult]`. -2. Organize tests by feature area in directories matching the feature structure. Do _not_ create a `/features/` top-level folder. +2. Organize tests by feature area in directories matching the feature structure. Do not create a `/features/` top-level folder. 3. For endpoint tests, inherit from `EndpointBaseTest` for access to HTTP clients and test infrastructure. 4. Prefer creating API Tests to test behavior over implementation: - Use `AuthenticatedOwnerHttpClient` or `AuthenticatedMemberHttpClient` for authenticated requests. @@ -23,8 +23,8 @@ Carefully follow these instructions when writing tests for the backend. By defau 7. Use Bogus (Faker) to generate random test data instead of hardcoded values for strings, names, etc. 8. Use NSubstitute for mocking external dependencies but never mock repositories. 9. Follow the Arrange-Act-Assert pattern with clear comments for each section: - - Only use these three comment sections: `// Arrange`, `// Act`, and `// Assert` - - Only include `// Arrange` comments in tests when there's actually an arrange section with setup code. + - Only use these three comment sections: `// Arrange`, `// Act`, and `// Assert`. + - Only include `// Arrange` comments in tests when there is actually an arrange section with setup code. - Do not add additional comments for subsections (e.g., no `// Setup database` or `// Verify telemetry events`). 10. Test both happy path and error cases. 11. Avoid sharing fields between tests as they can change for different reasons; prefer local constants or variables within each test method. @@ -36,7 +36,7 @@ Carefully follow these instructions when writing tests for the backend. By defau - `Delete` to delete test data from the database. - `ExecuteScalar` to verify data was correctly inserted. - `RowExists` to check if specific records exist. -15. Never use Dapper for database operations in tests. +15. Never use Dapper for database operations in tests: - Using Dapper is the main reason for making tests that cannot be accepted. 16. The `EndpointBaseTest` class provides: - Authenticated and anonymous HTTP clients for endpoint testing. @@ -101,7 +101,7 @@ public class BadTestSetup connection.Open(); // Insert user // ❌ DON'T: Add comment - connection.Execute("INSERT INTO Users (Email, Id, TenantId) VALUES (@Email, @Id, @TenantId)", new { Email = "test@example.com", Id = Guid.NewGuid(), TenantId = 1 }); + connection.Execute("INSERT INTO Users (Email, Id, TenantId) VALUES (@Email, @Id, @TenantId)", new { Email = "test@example.com", Id = Guid.NewGuid(), TenantId = 1 }); } } ``` diff --git a/.cursor/rules/backend/backend.mdc b/.cursor/rules/backend/backend.mdc index a555934acd..27b369fb38 100644 --- a/.cursor/rules/backend/backend.mdc +++ b/.cursor/rules/backend/backend.mdc @@ -9,6 +9,7 @@ Carefully follow these instructions for C# backend development, including code s ## Code Style +- Be consistent. If you do something a certain way, do all similar things in the same way. - Always use these C# features: - Top-level namespaces. - Primary constructors. @@ -19,23 +20,89 @@ Carefully follow these instructions for C# backend development, including code s - Use `var` when possible. - Use simple collection types like `UserId[]` instead of `List` whenever possible. - JetBrains tooling is used for automatically formatting code, but automatic line breaking has been disabled for more readable code: - - Wrap lines if "new language" constructs are started after 120 characters. This allows lines longer than 120 characters but ensures that no "important code" is hidden after the 120 character mark. -- Use clear names instead of making comments. -- Never use acronyms. E.g., use `SharedAccessSignature` instead of `Sas`. + - Wrap lines if new language constructs are started after 120 characters. This allows lines longer than 120 characters but ensures that no important code is hidden after the 120 character mark. + - Important code means parameters, arguments, or constructs that are relevant for understanding the code. `CancellationToken cancellationToken` is NOT considered important and should never trigger a line break. + - Always prefer long lines over splitting to maximize code visible on screen. Only wrap when truly necessary. + - Examples: + ```csharp + // ✅ DO: Keep on one line even if longer than 120 chars (the 'c' in 'command' is before 120 chars) + public async Task> Handle(CompleteEmailConfirmationCommand command, CancellationToken cancellationToken) + + // ✅ DO: Keep constructor parameters on one line when they fit (the 'e' in 'executionContext' is before 120 chars) + public sealed class GetPaymentHistoryHandler(ISubscriptionRepository subscriptionRepository, IExecutionContext executionContext) + : IRequestHandler> + + // ✅ DO: Wrap to 2 lines when needed, but never 3, 4, or 5 lines + var updatedLocale = Connection.ExecuteScalar( + "SELECT Locale FROM Users WHERE Id = @id", new { id = DatabaseSeeder.Tenant1Owner.Id.ToString() } + ); + + // ❌ DON'T: Split method parameters across multiple lines when they fit before 120 chars + public async Task Handle( + UpgradeSubscriptionCommand command, + CancellationToken cancellationToken + ) + + // ❌ DON'T: Split constructor parameters across multiple lines when they fit before 120 chars + public sealed class GetPaymentHistoryHandler( + ISubscriptionRepository subscriptionRepository, + IExecutionContext executionContext + ) : IRequestHandler> + ``` - Avoid using exceptions for control flow: - When exceptions are thrown, always use meaningful exceptions following .NET conventions. - Use `UnreachableException` to signal unreachable code that cannot be reached by tests. - Exception messages should include a period. -- Log only meaningful events at appropriate severity levels. +- Log only meaningful events at appropriate severity levels: - Logging messages should not include a period. - Use structured logging. - Never introduce new NuGet dependencies. - Don't do defensive coding (e.g., do not add exception handling to handle situations we don't know will happen). +- Use `user?.IsActive == true` over `user != null && user.IsActive == true`. - Avoid try-catch unless we cannot fix the reason. We have global exception handling to handle unknown exceptions. - Use `SharedInfrastructureConfiguration.IsRunningInAzure` to determine if we are running in Azure. -- Don't add comments unless the code is truly not expressing the intent. -- Never add XML comments. - Use `TimeProvider.System.GetUtcNow()` and not `DateTime.UtcNow()`. +- Names rules: + - Never use acronyms or abbreviations. For example, use `SharedAccessSignature` instead of `Sas`, and `Context` over `Ctx`. + - Prefer long variable names for better readability. For example, `gravatarHttpClient` over `httpClient`, and `enterKeyListenerCancellationTokenSource` over `enterKeyListenerCancellation`. + - Choose descriptive and unambiguous names. + - Make meaningful distinction. + - Use pronounceable names. + - Use searchable names. + - Replace magic numbers with named constants. + - Avoid encodings. Do not append prefixes or type information. +- Comments rules: + - Don't explain what you change (that belongs to commit messages). Code should reflect the current state AND never refer how it used to work, or what have been changed. + - Always try to explain yourself in code. + - Don't be redundant. + - Don't add obvious noise. + - Don't use closing brace comments. + - Don't comment out code. Just remove. + - Use as explanation of intent. + - Use as clarification of code. + - Use as warning of consequences. +- Source code structure: + - Separate concepts vertically. + - Related code should appear vertically dense. + - Declare variables close to their usage. + - Dependent functions should be close. + - Similar functions should be close. + - Place functions in the downward direction. + - Public functions should be above internal functions, and internal functions should be above private functions. + - Don't use horizontal alignment. + - Use white space to associate related things and disassociate weakly related. + - Avoid nesting of code. Prefer early return, or break/continue statements. Keep the happy path return at the end when possible. +- Functions rules : + - Small. + - Do one thing. + - Use descriptive names. + - Prefer fewer arguments. + - Have no side effects. + - Don't use flag arguments. Split method into several independent methods that can be called from the client without the flag. +- For enum comparisons: + - When comparing enums to enums, use direct comparison: `tenant.State == tenantState.Trial` + - When comparing string properties to enums (e.g., JWT claims, database string columns), use `nameof` on the enum: `executionContext.UserInfo.Role == nameof(UserRole.Owner)`. + - Avoid unnecessary `Enum.TryParse` when the comparison context is clear and the string is expected to match the enum. ## Implementation @@ -43,10 +110,11 @@ IMPORTANT: Always follow these steps very carefully when implementing changes: 1. Always start new changes by writing new test cases (or change existing tests). Remember to consult [API Tests](mdc:.cursor/rules/backend/api-tests.mdc) for details. 2. Build and test your changes: - - Always run `[CLI_ALIAS] build --backend` to build the backend. See [Tools](mdc:.cursor/rules/tools.mdc) for details. - - Run `[CLI_ALIAS] test` to run all tests. + - Use the **execute MCP tool** with `command: "build"` for backend. + - Use the **execute MCP tool** with `command: "test"` to run all tests. + - If you change API contracts (endpoints, DTOs), use the **execute MCP tool** with `command: "build"` for frontend to ensure it still compiles. 3. Format your code: - - When all tests are passing and you think you are feature complete, run `[CLI_ALIAS] format --backend`. - - The format command will automatically fix code style issues according to our conventions. + - When all tests are passing and you think you are feature complete, use the **execute MCP tool** with `command: "format"` for backend. + - The format tool will automatically fix code style issues according to our conventions. When you see paths like `/[scs-name]/Core/Features/[Feature]/Domain` in rules, replace `[scs-name]` with the specific self-contained system name (e.g., `account-management`, `back-office`) you're working with. Replace `[Feature]` with the specific feature name you're working with (e.g., `Users`, `Tenants`, `Authentication`). A feature is often 1:1 with a domain aggregate (e.g., `User`, `Tenant`, `Login`). diff --git a/.cursor/rules/backend/commands.mdc b/.cursor/rules/backend/commands.mdc index 0b0215e8c8..51405ac712 100644 --- a/.cursor/rules/backend/commands.mdc +++ b/.cursor/rules/backend/commands.mdc @@ -20,23 +20,26 @@ Commands should be created in the `/[scs-name]/Core/Features/[Feature]/Commands` - Name with `Command` suffix. - Define properties in the primary constructor. - Use property initializers for simple input sanitization, such as trimming and casing. - - For route parameters, use `[JsonIgnore] // Removes from API contract` on properties (including the comment). + - For route parameters, use `[JsonIgnore] // Removes from API contract` on properties (including the comment). Do this on real properties and NOT on the primary constructor parameters! 3. Command validator: - Only validate if the command has user input. - Ideally each property should only have one shared validation message for all cases (required, max length, etc.). - Don't inject dependencies like repositories to validators; use guards in the handler instead. + - Only validate user input, not route parameters, enum values, strongly typed IDs, etc., that are validated by the ASP.NET model binder. 4. Handler: - Create a public sealed class with `Handler`. - Implement `IRequestHandler` or `IRequestHandler>`. - - Commands can optionally return e.g., a newly created ID. + - Commands can optionally return e.g., a newly created ID. But ONLY do this if you truly need the Id, most often you don't need it. - Use guard statements with early returns like `Result.BadRequest()`, `Result.NotFound()`. - Enclose dynamic values in single quotes and end messages with a period. - Never throw exceptions, but always return `Result.Xxx()`. - Always create [Telemetry Events](mdc:.cursor/rules/backend/telemetry-events.mdc) for successful command results. - - Optionally log telemetry for failed commands. + - Optionally log telemetry for failed commands when it adds business value (e.g. for a failed login). + - Prefer tracking one event per command. For bulk operations, track a single bulk event unless single operation equivalents exist (e.g., if both AssignTag and AssignTags exist, AssignTags should emit individual TagAssigned events for consistency). - Save changes: - Call `AddAsync()`, `Remove()`, `Update()` repositories to persist changes. - Never call Entity Framework `SaveChangesAsync()` directly. + - Never do N+1 operations. Find a way to load all entities and then process them in memory. 5. Command Composition: - Inject MediatR to chain commands: e.g., `await mediator.Send(new CreateUserCommand(...))`. - Extract shared logic to separate classes and store them in `/[scs-name]/Core/Features/[Feature]/Shared` (e.g., `await avatarUpdater.UpdateAvatar(user, ...)`). @@ -47,7 +50,7 @@ Note: Commands run through MediatR pipeline behaviors in this order: Validation ```csharp // CreateUser.cs -public sealed record CreateUserCommand(TenantId TenantId, string Email, string Name) +public sealed record CreateUserCommand(string Email, string Name) : ICommand, IRequest { [JsonIgnore] // Removes from API contract // ✅ DO: Add JsonIgnore for route parameters @@ -89,7 +92,7 @@ public sealed class CreateUserHandler(IUserRepository userRepository, ITelemetry ``` ```csharp -public sealed record CreateUserCommand(string Email, string Name) +public sealed record CreateUserCommand([JsonIgnore] TenantId TenantId; string Email) // ❌ DON'T: Add attributes on positional parameters (“primary constructor parameters”) : ICommand, IRequest; public sealed class CreateUserValidator : AbstractValidator @@ -117,7 +120,14 @@ public sealed class CreateUserHandler( // ❌ DON'T: Forgetting to enclose values in single quotes and forgetting trailing period throw new ArgumentException($"Email {command.Email} must be valid"); // ❌ DON'T: Throw exceptions } - + + if (someCondition) + { + return Result.BadRequest( // ❌ DON'T: Split Result returns across multiple lines if it fits on one line + $"User with email {command.Email} already exists" // ❌ DON'T: Missing single quotes around dynamic value and missing ending period. + ); + } + // ❌ DON'T: Call handlers directly instead of using MediatR or raise domain events await sendEmailHandler.Handle(new SendEmailCommand(command.Email, "Welcome!"), cancellationToken); diff --git a/.cursor/rules/backend/database-migrations.mdc b/.cursor/rules/backend/database-migrations.mdc index 24d9c64bb9..610a52175d 100644 --- a/.cursor/rules/backend/database-migrations.mdc +++ b/.cursor/rules/backend/database-migrations.mdc @@ -13,6 +13,7 @@ Carefully follow these instructions when creating database migrations. - Place migrations in the `/[scs-name]/Core/Database/Migrations` directory. - Name migration files with a 14-digit timestamp prefix in the format `YYYYMMDDHHmmss_MigrationName.cs`. - Only implement the `Up` method; do not implement the `Down` method. + - I repeat... DO NOT CREATE `Down` migration. 2. Follow this strict column ordering in all table creation statements: - `TenantId` (if applicable) @@ -25,7 +26,7 @@ Carefully follow these instructions when creating database migrations. - For strongly typed IDs default to `varchar(32)` (a ULID is 26 characters, plus underscore and max 5 char prefix). - Intelligent deduct use of varchar or nvarchar based on the property type, and command validators, enum values, etc. - Use `datetimeoffset` (default), `datetime2` (timezone agnostic) or `date` (date only) and never use `datetime`. - + - Default to 'varchar(10)' or 'varchar(20)' for enum values. 4. Create appropriate constraints and indexes: - Define primary keys using the `PK_TableName` naming convention. - Define foreign keys using the `FK_ChildTable_ParentTable_ColumnName` naming convention. @@ -33,7 +34,6 @@ Carefully follow these instructions when creating database migrations. 5. Migrate existing data: - Use `migrationBuilder.Sql("UPDATE [table] SET [column] = [value] WHERE [condition]")` to update data... but use with care. - 6. Use standard SQL Server naming conventions: - Table names should be plural (e.g., `Users`, not `User`). - Constraint and index names should follow the patterns mentioned above. @@ -72,6 +72,7 @@ public sealed class AddUserPreferences : Migration } } +// ❌ DON'T: Forget to add the attribute [DbContext(typeof(XxxDbContext))] for the self-contained system [Migration("20250507_AddUserPrefs")] // ❌ DON'T: Missing proper 14-digit timestamp public class AddUserPrefsMigration : Migration // ❌ DON'T: Not sealed and incorrect naming and suffix with Migration { diff --git a/.cursor/rules/backend/domain-modeling.mdc b/.cursor/rules/backend/domain-modeling.mdc index 5291917457..db88937596 100644 --- a/.cursor/rules/backend/domain-modeling.mdc +++ b/.cursor/rules/backend/domain-modeling.mdc @@ -9,7 +9,7 @@ Carefully follow these instructions when implementing DDD models for aggregates, ## Implementation -1. Create all DDD models in the `/[scs-name]/Core/Features/[Feature]/Domain` directory, including aggregates, entities, value objects, strongly typed IDs, repositories, and EF Core mapping. +1. Create all DDD models in the `/[scs-name]/Core/Features/[Feature]/Domain` directory, including aggregates, entities, value objects, strongly typed IDs, repositories, and Entity Framework Core mapping. 2. Understand the core DDD concepts: - Aggregates are the root of the DDD model and map 1:1 to database tables. - Entities belong to aggregates but have their own identity. @@ -23,6 +23,8 @@ Carefully follow these instructions when implementing DDD models for aggregates, - Use factory methods when creating aggregates. - Make properties private, and use methods when changing state and enforcing business rules. - Make properties immutable. + - For many-to-many aggregates, make them Tenant scoped (ITenantScopedEntity) to include TenantId column, even if foreign key constraints already ensure tenant isolation. + - For many-to-many aggregates, carefully consider if cascade delete is needed. If so, use `OnDelete(DeleteBehavior.Cascade)` in the EF Core IEntityTypeConfiguration. 5. For Entities: - Use public sealed classes that inherit from `Entity`. - Create a strongly typed ID for entities. @@ -33,7 +35,11 @@ Carefully follow these instructions when implementing DDD models for aggregates, 6. For Value Objects: - Use records to ensure immutability. - Value objects do not have an ID. - +7. Do NOT add Entity Framework configuration for primitive properties: + - We do not use Entity Framework tooling for creating migrations, so there is no need for primitive property configuration, like length of fields or nullable properties. + - Only configure Entity Framework properties for complex types, like collections, and value objects, that Entity Framework uses for generating SQL statements. +8. When implementing a new aggregate, start with the minimum required methods for the current feature. Add additional methods as new features require them, ensuring each method maintains aggregate invariants. + ## Examples ```csharp @@ -102,7 +108,7 @@ public sealed class InvoiceLine : Entity } [PublicAPI] -[IdPrefix("oline")] +[IdPrefix("invln")] [JsonConverter(typeof(StronglyTypedIdJsonConverter))] public sealed record InvoiceLineId(string Value) : StronglyTypedUlid(Value); @@ -154,6 +160,9 @@ public sealed class BadInvoiceConfiguration : IEntityTypeConfiguration builder) { builder.Property(t => t.Name).HasMaxLength(100).IsRequired(); // ❌ Do not configure primitive properties + builder.Property(t => t.Description).HasMaxLength(255); // ❌ Do not configure primitive properties + builder.HasIndex(u => new { u.Email }).IsUnique(); // ❌ Is Unique is a database constraint, not need by EF Core at runtime + builder.Property(u => u.Role).HasColumnType("varchar(10)").HasConversion(); // ❌ EntityFramework is configured to convert all enums to string } } ``` diff --git a/.cursor/rules/backend/queries.mdc b/.cursor/rules/backend/queries.mdc index e983b8faa1..98dba44e00 100644 --- a/.cursor/rules/backend/queries.mdc +++ b/.cursor/rules/backend/queries.mdc @@ -35,13 +35,14 @@ Carefully follow these instructions when implementing CQRS queries, including st - Create a public sealed class with `Handler` suffix: e.g., `GetUsersHandler`. - Implement `IRequestHandler>`. - Use guard statements with early returns that return [Result](mdc:application/shared-kernel/SharedKernel/Cqrs/Result.cs) instead of throwing exceptions. - - If result messages contain values always enclose them in single quotes: `$"User with ID '{userId}' not found."` + - If result messages contain values always enclose them in single quotes: `$"User with ID '{userId}' not found."`. - Use repositories to retrieve data from the database, and never use Entity Framework directly. - Prefer using Mapster to map domain aggregates and entities to response DTOs. For complex mapping, map manually. + - Never do N+1 operations. Find a way to load all entities and then process them in memory. - Queries should rarely track TelemetryEvents. -7. After changing the API, make sure to run `[CLI_ALIAS] build --backend` to generate the OpenAPI JSON contract. Then run `[CLI_ALIAS] build --frontend` to trigger `openapi-typescript` to generate the API contract used by the frontend. See [CLI Commands](.cursor/rules/cli-commands.mdc) for details. +7. After changing the API, use the **execute MCP tool** with `command: "build"` for backend to generate the OpenAPI JSON contract. Then use the **execute MCP tool** with `command: "build"` for frontend to trigger `openapi-typescript` to generate the API contract used by the frontend. -Note: Queries run through MediatR pipeline behaviors in this order: Validation → Query → PublishTelemetryEvents +Note: Queries run through MediatR pipeline behaviors in this order: Validation → Query → PublishTelemetryEvents. ## Examples @@ -108,6 +109,14 @@ public sealed class BadUsersHandler(IUserRepository userRepository) throw new NotFoundException($"User with ID {query.UserId} not found"); // ❌ Throws exception, wrong message format } + + if (someCondition) + { + return Result.NotFound( // ❌ DON'T: Split Result returns across multiple lines if it fits on one line + $"User with ID {query.UserId} not found" // ❌ DON'T: Missing single quotes around dynamic value and missing ending period. + ); + } + return new BadUsersDto(user.Id, user.Email, user.Role); // ❌ Manual mapping when Mapster can be used } } diff --git a/.cursor/rules/backend/repositories.mdc b/.cursor/rules/backend/repositories.mdc index 39bed37b51..22ab953df0 100644 --- a/.cursor/rules/backend/repositories.mdc +++ b/.cursor/rules/backend/repositories.mdc @@ -21,12 +21,17 @@ Carefully follow these instructions when implementing DDD repositories in the ba 8. Repositories are automatically registered in the DI container. 9. By default, Aggregates with the `ITenantScopedEntity` interface are automatically filtered by tenant using Entity Framework Core query filters: - In rare cases, you may need to disable this by using the `IgnoreQueryFilters` method, e.g., when looking up an anonymous user by email. - - If you use `IgnoreQueryFilters`, the repository method **must** have an `UnfilteredAsync` suffix and an XML comment warning that this is dangerous and disables tenant and soft-delete filters. + - If you use `IgnoreQueryFilters`, the repository method must have an `UnfilteredAsync` suffix and an XML comment warning that this is dangerous and disables tenant and soft-delete filters. 10. Use `IEntityTypeConfiguration` for Entity Framework Core model configuration. 11. Map strongly typed IDs in Entity Framework Core configurations using the appropriate extension method: - `MapStronglyTypedUuid` for ULIDs. - `MapStronglyTypedLongId` for long IDs. - `MapStronglyTypedGuid` for GUIDs. +12. Updating entities does not belong to the repository. Use commands to fetch the aggregate and update it, and then save it using the repository. +13. Never add `.AsTracking()` to queries. Instead, fetch aggregates through repositories and update them using repository.Update(), which handles tracking internally. +14. Never do N+1 queries. +15. Do not register repositories in the DI container. They are registered automatically by SharedKernel. +16. Do not add DbSets to the DbContext. This is automatically handled by RepositoryBase. ## Examples @@ -41,9 +46,9 @@ public sealed class LoginRepository(AccountManagementDbContext accountManagement : RepositoryBase(accountManagementDbContext), ILoginRepository; // ❌ DON'T: Use ICrudRepository if not all CRUD ops needed, or return DTOs -public interface IBadLoginRepository : ICrudRepository // ❌ DON'T: Use ICrudRepository if not all CRUD ops needed +internal interface IBadLoginRepository : ICrudRepository // ❌ DON'T: Do not make repositories internal { - Task GetDto(LoginId id); // ❌ DON'T: Return DTOs from repositories + Task GetDto(LoginId id); // ❌ DON'T: Return DTOs from repositories, mape entities in the query } // ✅ DO: Example with a custom query method @@ -58,14 +63,21 @@ public sealed class EmailConfirmationRepository(AccountManagementDbContext accou public EmailConfirmation[] GetByEmail(string email) => DbSet.Where(ec => !ec.Completed && ec.Email == email.ToLowerInvariant()).ToArray(); // ✅ DO: Implement custom query } + +public sealed class AccountManagementDbContext(DbContextOptions options, IExecutionContext executionContext) + : SharedKernelDbContext(options, executionContext) +{ + public DbSet EmailConfirmations => Set(); // ❌ DON'T: Add DbSet to DbContext... this is automatiiclay handled in RepositoryBase +} + ``` -### Use of .IgnoreQueryFilters() +### Use of IgnoreQueryFilters -- If you use `.IgnoreQueryFilters()`, the repository method **must** have an `UnfilteredAsync` suffix and an XML comment warning that this is dangerous and disables tenant and soft-delete filters. +If you use `.IgnoreQueryFilters()`, the repository method must have an `UnfilteredAsync` suffix and an XML comment warning that this is dangerous and disables tenant and soft-delete filters. ```csharp -/// // ✅ DO: Add XML comment explaining whay ignoring quiery filters are ok +/// // ✅ DO: Add XML comment explaining why ignoring query filters is acceptable /// Retrieves a user by email without applying tenant query filters. /// This method should only be used during the login processes where tenant context is not yet established. /// diff --git a/.cursor/rules/backend/strongly-typed-ids.mdc b/.cursor/rules/backend/strongly-typed-ids.mdc index a20232663b..e983baa138 100644 --- a/.cursor/rules/backend/strongly-typed-ids.mdc +++ b/.cursor/rules/backend/strongly-typed-ids.mdc @@ -17,12 +17,12 @@ Carefully follow these instructions when implementing strongly typed IDs in the 6. Always override `ToString()` in the concrete class, as record types will not inherit this method from the base class. 7. Place the ID class in the same file as its corresponding aggregate or entity. 8. Use strongly typed IDs everywhere: API endpoints, request/response DTOs, commands, queries, and even in the frontend webapp. -9. In rare cases, other ID types can be used for performance reasons (e.g., `TenantId` uses a strongly typed `long` because it's faster and used in almost every table). -10. `UserId` and `TenantId` are special cases as they need to be shared between self-contained systems, so they're defined in the shared kernel. +9. In rare cases, other ID types can be used for performance reasons (e.g., `TenantId` uses a strongly typed `long` because it is faster and used in almost every table). +10. `UserId` and `TenantId` are special cases as they need to be shared between self-contained systems, so they are defined in the shared kernel. 11. Map strongly typed IDs in Entity Framework Core configurations using the appropriate extension method: - - `MapStronglyTypedUuid` for ULIDs - - `MapStronglyTypedLongId` for long IDs - - `MapStronglyTypedGuid` for GUIDs + - `MapStronglyTypedUuid` for ULIDs. + - `MapStronglyTypedLongId` for long IDs. + - `MapStronglyTypedGuid` for GUIDs. ## Examples diff --git a/.cursor/rules/backend/telemetry-events.mdc b/.cursor/rules/backend/telemetry-events.mdc index ba1c696884..065356e3f9 100644 --- a/.cursor/rules/backend/telemetry-events.mdc +++ b/.cursor/rules/backend/telemetry-events.mdc @@ -20,6 +20,8 @@ Carefully follow these instructions when implementing telemetry events in the ba 7. Use snake_case for property names in event data to align with OpenTelemetry conventions. 8. Collect events using the `events.CollectEvent()` method in command handlers just before returning. 9. By default, events are only collected for successful commands. To collect events for failed commands, set `commitChanges: true` in the Result object. +10. Do not track id of many to many aggregates, but track the id of the two main aggregates. +11. Do not track IDs of single-use operations that are merely unique identifiers, as they provide no analytical value and may contain PII. Note: Telemetry events are automatically annotated with extra data from the request, including current tenant ID, authenticated user ID, user role, application version, user's location, device type, browser, etc. @@ -39,7 +41,7 @@ public sealed class UserRoleChanged(UserId userId, UserRole fromRole, UserRole t : TelemetryEvent(("user_id", userId), ("from_role", fromRole), ("to_role", toRole)); // ❌ DON'T: Use present tense or collect personal information -public sealed class CompleteLogin(UserId userId, string email, string ipAddress) // Wrong: present tense and collects personal info +public sealed class CompleteLogin(LoginID loginId, UserId userId, string email, string ipAddress) // ❌ LoginId is is not meaning full in events. Email and IP Address are PII data. : TelemetryEvent(("user_id", userId), ("email", email), ("ip_address", ipAddress)); ``` diff --git a/.cursor/rules/developer-cli/developer-cli.mdc b/.cursor/rules/developer-cli/developer-cli.mdc index 8c441da00e..9ea7c061ef 100644 --- a/.cursor/rules/developer-cli/developer-cli.mdc +++ b/.cursor/rules/developer-cli/developer-cli.mdc @@ -3,15 +3,15 @@ description: Rules for implementing Developer CLI commands globs: developer-cli/Commands/*.cs alwaysApply: false --- -# Developer CLI Rules +# Developer Command Line Interface Rules -Carefully follow these instructions when implementing and extending the custom Developer CLI commands. +Carefully follow these instructions when implementing and extending the custom Developer Command Line Interface (CLI) commands. ## Implementation 1. Command Structure: - Create one file per command in the `developer-cli/Commands` directory. - - Name the file with the `Command` iherit from `System.CommandLine.Command` base class. + - Name the file with the `Command` suffix and inherit from `System.CommandLine.Command` base class. - Provide a concise description in the constructor that explains the command's purpose. - Define all command options using `AddOption()` in the constructor. - Implement the command's logic in a private `Execute` method. @@ -135,12 +135,11 @@ public class BadBuildCommand : Command ## Troubleshooting -The CLI is self compiling, so to build you just have to run [CLI_ALIAS]. Somethimes you will get errors like: +The CLI is self-compiling, so to build use `execute_command(command: "build", cli: true)`. Sometimes you will get errors like: ```bash -Failed to publish new CLI. Please run 'dotnet run' to fix. Could not load file or assembly 'System.IO.Pipelines, -Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the +Failed to publish new CLI. Please run 'dotnet run' to fix. Could not load file or assembly 'System.IO.Pipelines, +Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the ``` Just retry the command and it should work. - diff --git a/.cursor/rules/end-to-end-tests/e2e-tests.mdc b/.cursor/rules/end-to-end-tests/end-to-end-tests.mdc similarity index 89% rename from .cursor/rules/end-to-end-tests/e2e-tests.mdc rename to .cursor/rules/end-to-end-tests/end-to-end-tests.mdc index 0b0ebea9a1..38a43bd051 100644 --- a/.cursor/rules/end-to-end-tests/e2e-tests.mdc +++ b/.cursor/rules/end-to-end-tests/end-to-end-tests.mdc @@ -9,19 +9,19 @@ These rules outline the structure, patterns, and best practices for writing end- ## Implementation -1. Use `[CLI_ALIAS] e2e` with these option categories to optimize test execution: - - Test filtering: `--smoke`, `--include-slow`, search terms (e.g., `"@smoke"`, `"smoke"`, `"user"`, `"localization"`), `--browser` - - Change scoping: `--last-failed`, `--only-changed` - - Flaky test detection: `--repeat-each`, `--retries`, `--stop-on-first-failure` - - Performance: `--debug-timings` shows step execution times with color coding +1. Use the **e2e MCP tool** to run end-to-end tests with these options: + - Test filtering: smoke tests only, specific browser, search terms. + - Change scoping: last failed tests, only changed tests. + - Flaky test detection: repeat tests, retry on failure, stop on first failure. + - Performance: debug timing to see step execution times. + - **Note**: The **e2e MCP tool** always runs with quiet mode automatically. 2. Test Search and Filtering: - - Search by test tags: `[CLI_ALIAS] e2e "@smoke"` or `[CLI_ALIAS] e2e "smoke"` (both work the same) - - Search by test content: `[CLI_ALIAS] e2e "user"` (finds tests with "user" in title or content) - - Search by filename: `[CLI_ALIAS] e2e "localization"` (finds localization-flows.spec.ts) - - Search by specific file: `[CLI_ALIAS] e2e "user-management-flows.spec.ts"` - - Multiple search terms: `[CLI_ALIAS] e2e "user" "management"` - - The CLI automatically detects which self-contained systems contain matching tests and only runs those + - Search by test tags: smoke, comprehensive. + - Search by test content: find tests containing specific text. + - Search by filename: find specific test files. + - Multiple search terms: `e2e(searchTerms=["user", "management"])`. + - The tool automatically detects which self-contained systems contain matching tests and only runs those. 3. Test-Driven Debugging Process: - Focus on one failing test at a time and make it pass before moving to the next. @@ -31,6 +31,7 @@ These rules outline the structure, patterns, and best practices for writing end- 4. Organize tests in a consistent file structure: - All e2e test files must be located in `[self-contained-system]/WebApp/tests/e2e/` folder (e.g., `application/account-management/WebApp/tests/e2e/`). - All test files use the `*-flows.spec.ts` naming convention (e.g., `login-flows.spec.ts`, `signup-flows.spec.ts`, `user-management-flows.spec.ts`). + - One test file per feature with max 2 tests: one @smoke and one @comprehensive. Prefer extending existing tests even for new features to optimize test speed. - Top-level describe blocks must use only these 3 approved tags: `test.describe("@smoke", () => {})`, `test.describe("@comprehensive", () => {})`, `test.describe("@slow", () => {})`. - `@smoke` tests: - Critical tests run on deployment of any self-contained system. @@ -39,14 +40,12 @@ These rules outline the structure, patterns, and best practices for writing end- - Focus on must-work functionality with extensive validation steps. - Include boundary cases and error handling within the same test scenario. - Avoid testing the same functionality multiple times across different tests. - - `@comprehensive` tests: - Thorough tests run when a specific self-contained system is deployed. - Focus on edge cases, error conditions, and less common scenarios. - Test specific features in depth with various input combinations. - Include tests for concurrency, validation rules, accessibility, etc. - Group related edge cases together to reduce test count while maintaining coverage. - - `@slow` tests: - Optional and run only ad-hoc using `--include-slow` flag. - Any tests that require waiting like `waitForTimeout` (e.g., for OTP timeouts) must be marked as `@slow`. @@ -61,7 +60,7 @@ These rules outline the structure, patterns, and best practices for writing end- 6. Structure each test with step decorators and proper monitoring: - All tests must start with `const context = createTestContext(page);` for proper error monitoring. - - Use step decorators: `await step("Complete signup & verify account creation")(async () => { /* test logic */ })();` + - Use step decorators: `await step("Complete signup & verify account creation")(async () => { /* test logic */ })();`. - Step naming conventions: - Always follow "[Business action + details] & [expected outcome]" pattern. - Use business action verbs like "Sign up", "Login", "Invite", "Rename", "Update", "Delete", "Create", "Submit". @@ -79,7 +78,7 @@ These rules outline the structure, patterns, and best practices for writing end- 7. Timeout Configuration: - Always use Playwright's built-in auto-waiting assertions: `toHaveURL()`, `toBeVisible()`, `toBeEnabled()`, `toHaveValue()`, `toContainText()`. - Never add timeouts to `.click()`, `.waitForSelector()`, etc. - - Global timeout configuration is handled in the shared Playwright. Don't change this. + - Global timeout configuration is handled in the shared Playwright. Do not change this. 8. Write deterministic tests - This is critical for reliable testing: - Each test should have a clear, linear flow of actions and assertions. @@ -92,20 +91,20 @@ These rules outline the structure, patterns, and best practices for writing end- 10. Test Fixtures and Page Management: - Use appropriate fixtures: `{ page }` for basic tests, `{ anonymousPage }` for tests with existing tenant/owner but not logged in, `{ ownerPage }`, `{ adminPage }`, `{ memberPage }` for authenticated tests. - - Destructure anonymous page data: `const { page, tenant } = anonymousPage; const existingUser = tenant.owner;` + - Destructure anonymous page data: `const { page, tenant } = anonymousPage; const existingUser = tenant.owner;`. - Pre-logged in users (`ownerPage`, `adminPage`, `memberPage`) are isolated between workers and will not conflict between tests. - When using pre-logged in users, do not put the tenant or user into an invalid state that could affect other tests. 11. Test Data and Constants: - - Use underscore separators: `const timeout = 30_000; // 30 seconds` - - Generate unique data: `const email = uniqueEmail();` - - Use faker.js to generate realistic test data: `const firstName = faker.person.firstName(); const email = faker.internet.email();` - - Long string testing: `const longEmail = \`${"a".repeat(90)}@example.com\`; // 101 characters total` + - Use underscore separators: `const timeout = 30_000; // 30 seconds`. + - Generate unique data: `const email = uniqueEmail();`. + - Use faker.js to generate realistic test data: `const firstName = faker.person.firstName(); const email = faker.internet.email();`. + - Long string testing: `const longEmail = \`${"a".repeat(90)}@example.com\`; // 101 characters total`. -12. Memory Management in E2E Tests: - - Playwright automatically handles browser context cleanup after tests - - Manual cleanup steps are unnecessary - focus on test clarity over micro-optimizations - - E2E test suites have minimal memory leak concerns due to their limited scope and duration +12. Memory Management in End-to-End Tests: + - Playwright automatically handles browser context cleanup after tests. + - Manual cleanup steps are unnecessary - focus on test clarity over micro-optimizations. + - End-to-End test suites have minimal memory leak concerns due to their limited scope and duration. ## Examples @@ -201,7 +200,7 @@ test.describe("@slow", () => { const codeValidationTimeout = 60_000; // 5 minutes const sessionTimeout = codeValidationTimeout + 60_000; // 6 minutes - test("should handle user logout after to many login attempts", async ({ page }) => { // ✅ DO: use new page, when testing e.g. account lockout + test("should handle user logout after too many login attempts", async ({ page }) => { // ✅ DO: use new page, when testing e.g. account lockout test.setTimeout(sessionTimeout); // ✅ DO: Set timeout based on actual wait times const context = createTestContext(page); diff --git a/.cursor/rules/frontend/form-with-validation.mdc b/.cursor/rules/frontend/form-with-validation.mdc index 644cd17834..c2ae0fe5bf 100644 --- a/.cursor/rules/frontend/form-with-validation.mdc +++ b/.cursor/rules/frontend/form-with-validation.mdc @@ -3,7 +3,7 @@ description: Rules for forms with validation using React Aria Components globs: *.tsx alwaysApply: false --- -# Form with Validation +# Form With Validation Carefully follow these instructions when implementing forms with validation in the frontend, covering UI components, mutation handling, and validation error display. @@ -21,7 +21,7 @@ Note: All .NET API endpoints are available as strongly typed API contracts in th ## Examples -### Example 1 - Basic Form with Validation +### Example 1 - Basic Form With Validation ```typescript // ✅ DO: Use mutationSubmitter and proper error handling @@ -113,7 +113,7 @@ function BadUserProfileForm({ user }) { } ``` -### Example 2 - Complex Form with Multiple API Calls +### Example 2 - Complex Form With Multiple Application Programming Interface Calls ```typescript // ✅ DO: Use custom mutation for complex scenarios @@ -146,7 +146,7 @@ export function UserProfileWithAvatarForm({ user, onSuccess, onClose }) { return await updateUserMutation.mutateAsync(data); }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["currentUser"] }); + queryClient.invalidateQueries(); onSuccess?.(); onClose?.(); } @@ -168,3 +168,4 @@ export function UserProfileWithAvatarForm({ user, onSuccess, onClose }) { ); } ``` + diff --git a/.cursor/rules/frontend/frontend.mdc b/.cursor/rules/frontend/frontend.mdc index 17eecbdde6..59d3d97a2a 100644 --- a/.cursor/rules/frontend/frontend.mdc +++ b/.cursor/rules/frontend/frontend.mdc @@ -1,11 +1,33 @@ --- description: Core rules for frontend TypeScript and React development -globs: *.tsx +globs: *.tsx,*.ts alwaysApply: false --- # Frontend -Carefully follow these instructions for frontend TypeScript and React development, including component structure, code style, and build/format steps. +Carefully follow these instructions for frontend TypeScript and React development, including component structure, code style, architecture patterns, and build/format steps. + +## Architecture Overview + +1. **SPA Served by .NET Backend**: + - SPA are served via `SinglePageAppFallbackExtensions.cs` from the backend. + - UserInfo is injected into HTML meta tags and available via `import.meta.user_info_env`. + - Authentication is server-side with HTTP-only cookies. + - YARP reverse proxy handles routing between SPA and APIs. + +2. **Module Federation for Micro-Frontends**: + - Each self-contained system has its own WebApp. + - Common UI exposed via federation in `federated-modules/`. + - Shared components in `application/shared-webapp/`. + - Never import directly between self-contained systems. + - Use `window.location.href` for navigation between self-contained systems (not TanStack Router). + +3. **API Integration**: + - API client auto-generated from OpenAPI spec. + - Located in `shared/lib/api/client.ts`. + - Never make direct fetch calls. + - Server state lives in TanStack Query only. + - Use `queryClient.invalidateQueries()` to refresh data after mutations. ## Implementation @@ -18,30 +40,46 @@ Carefully follow these instructions for frontend TypeScript and React developmen - UI elements with different functionality should be in separate components. - Avoid mixing unrelated functionality in one component. - Use clear, descriptive names instead of making comments. - - Never use acronyms in code (e.g., use `errorMessage` not `errMsg`, `button` not `btn`, `authentication` not `auth`, `navigation` not `nav`, `parameters` not `params`). + - Never use acronyms in code (e.g., use `errorMessage` not `errMsg`, `button` not `btn`, `authentication` not `auth`, `navigation` not `nav`). - Prioritize code readability and maintainability. - Never introduce new npm dependencies. - - Always use React Aria Components, and do not use native HTML elements like: ``, `