diff --git a/.claude/commands/debug-module.md b/.claude/commands/debug-module.md
new file mode 100644
index 00000000..b5693023
--- /dev/null
+++ b/.claude/commands/debug-module.md
@@ -0,0 +1,131 @@
+Diagnose why a module isn't being discovered or working correctly in SimpleModule.
+
+If no module name was supplied as an argument, ask: "Which module would you like to debug? (e.g. Products, Orders)"
+
+Work through all 7 checks below **in order**. For each check, mark it ✅ pass / ❌ fail / ⚠️ warning, then apply the described fix before moving to the next check. Do not skip checks even if earlier ones fail.
+
+**Status marker meanings:** ✅ = passes all requirements · ❌ = fails, fix required · ⚠️ = exists and will work, but has a non-critical configuration difference (e.g., SM00xx warnings but no errors, or a file exists but a setting differs from the expected default).
+
+Replace `{Name}` with the module name throughout.
+
+---
+
+## Check 1 — Module class
+
+Read `modules/{Name}/src/SimpleModule.{Name}/{Name}Module.cs`.
+
+Verify:
+- The file exists.
+- The class is decorated with `[Module(...)]`.
+- The class implements `IModule`.
+- The class is `public`.
+- The class is **not** `sealed`.
+
+Fix if failing: Create or correct the file so the class is `public`, non-sealed, carries `[Module(...)]`, and implements `IModule`.
+
+---
+
+## Check 2 — Contracts SDK
+
+Read `modules/{Name}/src/SimpleModule.{Name}.Contracts/SimpleModule.{Name}.Contracts.csproj`.
+
+Verify:
+- The file exists.
+- `Sdk="Microsoft.NET.Sdk"` (must NOT be `Microsoft.NET.Sdk.Razor` or `Microsoft.NET.Sdk.Web`).
+
+Fix if failing: Set the `Sdk` attribute to `Microsoft.NET.Sdk`.
+
+---
+
+## Check 3 — Implementation SDK + FrameworkReference
+
+Read `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj`.
+
+Verify:
+- The file exists.
+- `Sdk="Microsoft.NET.Sdk"` (must NOT be `Microsoft.NET.Sdk.Razor` or `Microsoft.NET.Sdk.Web`).
+- Contains ``.
+
+Fix if failing: Set `Sdk="Microsoft.NET.Sdk"` if it is wrong. If `` is missing, add it inside an ``.
+
+---
+
+## Check 4 — Host reference
+
+Read `template/SimpleModule.Host/SimpleModule.Host.csproj`.
+
+Verify:
+- A `` whose path contains `modules\{Name}\src\SimpleModule.{Name}\SimpleModule.{Name}.csproj` (use a substring match to tolerate slash style differences). The correct relative path from the host project is `..\..\modules\{Name}\src\SimpleModule.{Name}\SimpleModule.{Name}.csproj`.
+
+Fix if failing: Add the `` inside an `` in the host `.csproj`.
+
+---
+
+## Check 5 — Solution file
+
+Read `SimpleModule.slnx`.
+
+Verify:
+- The contracts `.csproj` path appears: `modules/{Name}/src/SimpleModule.{Name}.Contracts/SimpleModule.{Name}.Contracts.csproj`
+- The implementation `.csproj` path appears: `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj`
+
+Fix if failing: Locate the existing `` element in `SimpleModule.slnx` and add a new child `` block inside it with `` entries for both the contracts and implementation `.csproj` files.
+
+---
+
+## Check 6 — dotnet build diagnostics
+
+Run:
+```
+dotnet build --no-incremental 2>&1 | grep -E "SM0|error" | head -50
+```
+
+Surface any SM00xx source generator diagnostic codes. Their meanings:
+
+| Code | Meaning |
+|------|---------|
+| SM0001 | Module class must be public and non-sealed |
+| SM0010 | IEndpoint implementation must be internal sealed |
+| SM0020 | IViewEndpoint implementation must be internal sealed |
+| SM0030 | [Dto] types must live in a Contracts assembly |
+| SM0040 | No impl→impl project references allowed between modules |
+| SM0044 | Inertia.Render component name not found in Pages/index.ts |
+
+Fix each reported code using the guidance above before continuing.
+
+---
+
+## Check 7 — Page registry
+
+Run:
+```
+npm run validate-pages
+```
+
+If it exits with an error, identify each missing entry and show the exact line to add to `modules/{Name}/src/SimpleModule.{Name}/Pages/index.ts`:
+
+```typescript
+'{Name}/{ViewName}': () => import('../Views/{ViewName}'),
+```
+
+Derive `{ViewName}` from the component name reported missing by `npm run validate-pages` (e.g., if it reports `Products/Browse`, the ViewName is `Browse`).
+
+Add the missing entries to the `pages` record in `Pages/index.ts`, then re-run `npm run validate-pages` to confirm it passes.
+
+---
+
+## Final Summary
+
+Print a summary table of all 7 checks:
+
+| Check | Status | Notes |
+|-------|--------|-------|
+| 1. Module class | ✅/❌/⚠️ | |
+| 2. Contracts SDK | ✅/❌/⚠️ | |
+| 3. Implementation SDK + FrameworkReference | ✅/❌/⚠️ | |
+| 4. Host reference | ✅/❌/⚠️ | |
+| 5. Solution file | ✅/❌/⚠️ | |
+| 6. dotnet build diagnostics | ✅/❌/⚠️ | |
+| 7. Page registry | ✅/❌/⚠️ | |
+
+For every ❌ row, include specific fix instructions. If all checks pass, confirm the module should be discovered correctly on next build.
diff --git a/.claude/commands/module-status.md b/.claude/commands/module-status.md
new file mode 100644
index 00000000..1b4d158d
--- /dev/null
+++ b/.claude/commands/module-status.md
@@ -0,0 +1,131 @@
+# /module-status
+
+Print a health snapshot of a SimpleModule module. No builds required — reads and greps only.
+
+## Step 0 — Resolve Module Name
+
+If no argument was supplied, ask: "Which module would you like to inspect? (e.g., Products, Orders, Users)"
+
+Use the provided name as `{Name}` throughout. Construct the paths:
+- `CONTRACTS = modules/{Name}/src/SimpleModule.{Name}.Contracts/`
+- `IMPL = modules/{Name}/src/SimpleModule.{Name}/`
+- `TESTS = modules/{Name}/tests/SimpleModule.{Name}.Tests/`
+
+First, verify that IMPL exists by checking if `{Name}Module.cs` or any `.csproj` is present at that path. If not, note: "Note: The implementation directory may use a non-standard name. Check `modules/{Name}/src/` to find the correct directory name and adjust paths accordingly."
+
+---
+
+## Step 1 — Inventory
+
+**API Endpoints**
+Use Glob with pattern `modules/{Name}/src/SimpleModule.{Name}/Endpoints/**/*Endpoint.cs`. Count the results and collect the bare file names (strip path and `.cs`).
+
+**View Endpoints**
+Use Glob with pattern `modules/{Name}/src/SimpleModule.{Name}/Views/**/*Endpoint.cs`. Count and collect bare names. Note whether any view endpoints exist — this drives Sections 2 and 5.
+
+**Entities**
+Check whether `modules/{Name}/src/SimpleModule.{Name}/EntityConfigurations/` exists. If it does, grep for `HasKey(` in that directory (all `.cs` files). For each matching file, derive the entity name by stripping `Configuration` from the class name (e.g., `ProductConfiguration` → `Product`). If the directory does not exist, note "N/A — no EntityConfigurations directory".
+
+**Domain Events**
+Grep for `: IEvent` in `modules/{Name}/src/SimpleModule.{Name}.Contracts/` (all `.cs` files). Extract the `record` name from each match. List them; if none, show `0`.
+
+**Services**
+Grep for `class\s+\w+\s*:\s*I\w+Contracts` in `modules/{Name}/src/SimpleModule.{Name}/` (all `.cs` files). List each class name found. If no matches found, show "none detected".
+
+---
+
+## Step 2 — Page Registry Coverage
+
+Skip this section entirely (output "N/A — no view endpoints") if no `*Endpoint.cs` files exist in the Views/ directory.
+
+Otherwise:
+
+1. Grep for `Inertia.Render(` in `modules/{Name}/src/SimpleModule.{Name}/` (all `.cs` files). For each match, extract the first string argument — the component name — from the call (e.g., `Inertia.Render("Products/Browse", ...)` → `Products/Browse`). Collect these as **C# renders**.
+
+2. Read `modules/{Name}/src/SimpleModule.{Name}/Pages/index.ts`. Extract every key from the `pages` record (quoted strings before the `:` on each entry line). Collect these as **TS entries**.
+
+3. Compute:
+ - **Missing**: keys present in C# renders but absent from TS entries.
+ - **Orphaned**: keys present in TS entries but absent from C# renders.
+ - **Coverage**: `(count of C# renders with a matching TS entry) / (total C# renders)` shown as `N/N (X%)`.
+
+---
+
+## Step 3 — Test File Coverage
+
+1. Collect all endpoint class names from both Endpoints/ and Views/ (same lists from Step 1).
+2. Use Glob `modules/{Name}/tests/SimpleModule.{Name}.Tests/**/*.cs` to list all test files.
+3. For each endpoint class name (e.g., `GetAllEndpoint`), check whether that string appears in any test file name or — if needed — grep for it across the test directory.
+4. Report covered count / total. List any endpoint classes with no test file found.
+
+---
+
+## Step 4 — Cross-Module Dependencies
+
+**Consumes**
+Read `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj`. List every `` `Include` path that points to another module's `.Contracts` project (i.e., paths containing `Contracts` but not `SimpleModule.{Name}.Contracts` itself). Extract just the project name.
+
+**Consumed by**
+Grep for `SimpleModule.{Name}.Contracts` in all `.csproj` files under `modules/`, excluding the module's own projects (`modules/{Name}/`). For each match, extract the module name from the file path.
+
+---
+
+## Step 5 — Required Files
+
+For each entry, check whether the file or directory exists and assign a status symbol.
+
+| File | Required | Status |
+|------|----------|--------|
+| `*Constants.cs` anywhere under `modules/{Name}/src/` | Yes | ✅ if at least one found (list filenames), ❌ if none |
+| `I*Contracts.cs` in CONTRACTS | Yes | ✅ if at least one found (list filenames), ❌ if none |
+| `{Name}Module.cs` in IMPL | Yes | ✅ if exists, ❌ if not |
+| `Pages/index.ts` in IMPL | If view endpoints exist | ✅/❌ or N/A |
+| `vite.config.ts` in IMPL | If view endpoints exist | ✅/❌ or N/A |
+| `package.json` in IMPL | If view endpoints exist | ✅/❌ or N/A |
+| `tests/` directory (`TESTS`) | Recommended | ✅ if exists, ⚠️ if not |
+
+Mark Pages/index.ts, vite.config.ts, and package.json as `N/A` when no view endpoints exist.
+
+---
+
+## Output Format
+
+Print exactly this structure (fill in real values):
+
+```
+## Module Status: {Name}
+
+### Inventory
+- API Endpoints: N — [Name1, Name2, ...]
+- View Endpoints: N — [Name1, Name2, ...]
+- Entities: N — [Name1, Name2, ...] (or "N/A — no EntityConfigurations directory")
+- Domain Events: N — [Name1, Name2, ...] (or "0")
+- Services: N — [ClassName, ...]
+
+### Page Registry
+- Coverage: N/N (X%)
+- Missing (C# → no TS entry): [list] or "none"
+- Orphaned (TS entry → no C# render): [list] or "none"
+(or "N/A — no view endpoints")
+
+### Test Coverage
+- Covered: N/N endpoint files
+- Missing tests for: [EndpointClass1, ...] or "all covered"
+
+### Dependencies
+- Consumes: [Module1.Contracts, ...] or "none"
+- Consumed by: [Module2, ...] or "none"
+
+### Required Files
+| File | Required | Status |
+|------|----------|--------|
+| *Constants.cs (any, under src/) | Yes | ✅ [filename(s)] / ❌ |
+| I*Contracts.cs (any, in Contracts/) | Yes | ✅ [filename(s)] / ❌ |
+| {Name}Module.cs | Yes | ✅/❌ |
+| Pages/index.ts | If view endpoints | ✅/❌/N/A |
+| vite.config.ts | If view endpoints | ✅/❌/N/A |
+| package.json | If view endpoints | ✅/❌/N/A |
+| tests/ directory | Recommended | ✅/⚠️ |
+```
+
+Do not include any additional commentary outside these sections.
diff --git a/.claude/commands/review-module.md b/.claude/commands/review-module.md
new file mode 100644
index 00000000..16280f92
--- /dev/null
+++ b/.claude/commands/review-module.md
@@ -0,0 +1,131 @@
+Convention review for a SimpleModule module. Checks the module against the Constitution and project conventions.
+
+If no module name was supplied as an argument, ask: "Which module would you like to review? (e.g. Products, Orders)"
+
+Work through all 7 areas below in order. Do **not** auto-fix violations — record them and report at the end. For each violation found, record: **file path**, **approximate line or pattern**, **what's wrong**, **how to fix it**.
+
+Replace `{Name}` with the module name throughout.
+
+---
+
+## Area 1 — Architecture: No impl→impl dependencies
+
+Read `modules/{Name}/src/SimpleModule.{Name}/SimpleModule.{Name}.csproj`.
+
+For every `` path in that file, check whether the path points into another module's implementation assembly. A violation is a path that:
+- Contains `modules/` (or `modules\`)
+- Does NOT end with `.Contracts/SimpleModule.*.Contracts.csproj` (or the backslash equivalent)
+
+References to `.Contracts` projects are fine. References to the host or shared infrastructure are fine.
+
+Violation fix: Replace the implementation reference with a reference to the other module's `.Contracts` project. Inject `I{OtherModule}Contracts` via constructor rather than using the concrete class directly.
+
+---
+
+## Area 2 — Endpoints: CrudEndpoints helpers and TypedResults
+
+Grep for `MapGet|MapPost|MapPut|MapDelete` in `modules/{Name}/src/SimpleModule.{Name}/Endpoints/`.
+
+Check each match:
+
+1. **CrudEndpoints helpers** — Standard CRUD operations should use `CrudEndpoints.GetAll`, `CrudEndpoints.GetById`, `CrudEndpoints.Create`, `CrudEndpoints.Update`, or `CrudEndpoints.Delete` rather than raw inline `Results.*` returns for the same operation. Flag any endpoint that manually reimplements what a CrudEndpoints helper already provides.
+
+2. **TypedResults** — API endpoints must use `TypedResults.*` (e.g., `TypedResults.Ok(...)`, `TypedResults.NotFound()`) not the non-generic `Results.*` static methods. Flag any use of `Results.Ok`, `Results.NotFound`, etc.
+
+3. **RequirePermission** — Endpoints must use `.RequirePermission(...)` not bare `.RequireAuthorization()`. Flag any `.RequireAuthorization()` call without a permission argument.
+
+---
+
+## Area 3 — Naming conventions
+
+Grep for class definitions (`class `) in `modules/{Name}/src/SimpleModule.{Name}/Endpoints/` and `modules/{Name}/src/SimpleModule.{Name}/Views/`.
+
+For each source file found:
+
+1. **One class per file** — if a file contains more than one `class` definition, flag it.
+2. **Class name matches file name** — e.g., `CreateEndpoint.cs` must contain `class CreateEndpoint`. Flag mismatches.
+3. **public class** — endpoint and view classes must be declared `public class`. Flag any that are `internal`, `private`, or `sealed`.
+4. **File-scoped namespaces** — check for `namespace Foo {` (brace-style). Must be `namespace Foo;` (file-scoped). Flag any brace-style namespace declarations.
+5. **Private field naming** — grep for `private` field declarations. Fields must use `_camelCase` prefix. Flag any private field that does not start with `_`.
+
+---
+
+## Area 4 — Frontend: Page registry completeness
+
+Grep for `Inertia.Render(` in `modules/{Name}/src/SimpleModule.{Name}/`. Extract the first string argument from each call — this is the component key (e.g., `"Products/Browse"`).
+
+Read `modules/{Name}/src/SimpleModule.{Name}/Pages/index.ts`. Extract all keys defined in the `pages` record.
+
+Compare the two sets: every component key from an `Inertia.Render` call must appear as a key in `pages`. Flag any that are missing.
+
+Then run `npm run validate-pages` for authoritative output and report any additional mismatches it finds.
+
+Violation fix: Add a matching entry to `Pages/index.ts`:
+```typescript
+"{Name}/{ViewName}": () => import("../Views/{ViewName}"),
+```
+
+---
+
+## Area 5 — Events: Proper definition and registration
+
+Grep for `: IEvent` in `modules/{Name}/src/SimpleModule.{Name}.Contracts/`.
+
+For each event type found:
+
+1. **Record type** — events must be `record` types, not `class`. Flag any `class` that implements `IEvent`.
+
+Grep for `IEventHandler<` in `modules/{Name}/src/SimpleModule.{Name}/`.
+
+For each handler found, check that a corresponding `services.AddScoped, THandler>()` call exists in `ConfigureServices` (or wherever DI is configured in the module class). Flag any handler with no registration.
+
+Violation fix for missing registration: Add `services.AddScoped, {HandlerType}>();` in `ConfigureServices`.
+
+---
+
+## Area 6 — Tests: Coverage by endpoint file
+
+List all files matching `*Endpoint.cs` in:
+- `modules/{Name}/src/SimpleModule.{Name}/Endpoints/`
+- `modules/{Name}/src/SimpleModule.{Name}/Views/`
+
+List all test files in `modules/{Name}/tests/SimpleModule.{Name}.Tests/`.
+
+For each endpoint class, check whether a corresponding test file exists (match by class name, e.g., `CreateEndpoint.cs` → `CreateEndpointTests.cs` or `CreateTests.cs`). Flag endpoint classes with no matching test file as missing coverage.
+
+Note: integration tests using `SimpleModuleWebApplicationFactory` with `CreateAuthenticatedClient` are strongly preferred over unit tests that mock HTTP context.
+
+---
+
+## Area 7 — Permissions: Sealed class with const strings
+
+Grep for `IModulePermissions` in `modules/{Name}/src/` (covers both the implementation assembly and the `.Contracts` assembly).
+
+For the permissions class found:
+
+1. **Sealed** — the class must be `sealed`. Flag if not.
+2. **Naming pattern** — all `const string` values must follow the `"Module.Action"` format (e.g., `"Products.Create"`, `"Products.Delete"`). Flag any permission string that does not match this pattern.
+
+If a permissions class implementing `IModulePermissions` was found above, grep for `AddPermissions<` or `builder.AddPermissions` in `modules/{Name}/src/SimpleModule.{Name}/{Name}Module.cs`.
+
+3. **ConfigurePermissions registration** — This check is conditional:
+ - If a permissions class implementing `IModulePermissions` exists **and** `builder.AddPermissions<{Name}Permissions>()` is absent from `{Name}Module.cs`: flag it as a violation.
+ - If no permissions class exists and no `AddPermissions` call exists: mark Area 7 as N/A.
+ - If both exist and the type matches: OK.
+
+---
+
+## Final Report
+
+After completing all 7 areas, print a grouped report:
+
+- If no violations in an area: `✅ [Area Name] — OK`
+- If violations exist: `❌ [Area Name]` followed by a bulleted list of violations, each with:
+ - File path (absolute or relative from repo root)
+ - Approximate line or pattern where the problem appears
+ - What is wrong
+ - Specific fix
+
+End the report with one of:
+- `✅ {Name} module passes convention review.` (if zero violations found)
+- `Found N violations across N areas.` (if any violations found)
diff --git a/.claude/skills/simplemodule/SKILL.md b/.claude/skills/simplemodule/SKILL.md
index 5446b87d..5244e2ed 100644
--- a/.claude/skills/simplemodule/SKILL.md
+++ b/.claude/skills/simplemodule/SKILL.md
@@ -7,7 +7,9 @@ description: >
understanding the project architecture. Triggers on: "add module", "new module",
"add feature", "create endpoint", "module architecture", "how does SimpleModule work",
"event bus", "permissions", "menu", "settings", "database context", "contracts",
- "IModule", "IEndpoint", "IViewEndpoint", "Inertia", "CrudEndpoints".
+ "IModule", "IEndpoint", "IViewEndpoint", "Inertia", "CrudEndpoints",
+ "debug module", "review module", "module status", "SM00", "source generator diagnostic",
+ "module not found", "page 404", "event bus", "settings", "IEventBus", "SettingDefinition".
---
# SimpleModule Framework Guide
@@ -171,6 +173,107 @@ When adding a new module, ensure:
7. Run `dotnet build` to verify source generator picks up the module
8. Run `npm run validate-pages` to verify page registry matches endpoints
+## Common Pitfalls
+
+| Symptom | Cause | Fix |
+|---------|-------|-----|
+| Module not discovered at build | Missing `[Module]` attribute or wrong project not referenced in Host | Add `[Module]` to module class; add `` to Host `.csproj` |
+| Page navigates but shows blank | Missing `Pages/index.ts` entry | Add `'Module/Page': () => import('../Views/Page')` to Pages/index.ts |
+| SM0011 diagnostic | Direct impl→impl project reference | Reference only the `.Contracts` project; inject `I{Name}Contracts` interface |
+| SM0014 diagnostic | No public interfaces in Contracts assembly | Add `I{Name}Contracts` to the Contracts project |
+| SM0025 diagnostic | No implementation for contract interface | Create `{Name}ContractsService` implementing `I{Name}Contracts` and register in `ConfigureServices` |
+| SM0041/SM0042 diagnostic | View endpoint misconfigured | Ensure `Inertia.Render` name is prefixed with module name; add `ViewPrefix` to `[Module]` attribute |
+| `TreatWarningsAsErrors` build failure | Nullable, unused variable, or analyzer warning | Fix the warning; suppress in `.editorconfig` only if genuinely intentional |
+| Event handler never called | Handler not registered in DI | Add `services.AddScoped, MyHandler>()` in `ConfigureServices` |
+| Cross-module data wrong | Injecting impl class directly | Always inject `I{Name}Contracts` interface, never the concrete service class |
+
+## Events Pattern
+
+Define events in the **Contracts** assembly as `record` types:
+
+```csharp
+// In SimpleModule.{Name}.Contracts
+public record OrderPlaced(OrderId OrderId, decimal Total) : IEvent;
+```
+
+Publish from a service:
+
+```csharp
+// Awaits all handlers before returning
+await _eventBus.PublishAsync(new OrderPlaced(order.Id, order.Total), ct);
+
+// Fire-and-forget (background task, not awaited)
+_eventBus.PublishInBackground(new OrderPlaced(order.Id, order.Total));
+```
+
+Handle in any module:
+
+```csharp
+public sealed class SendConfirmationEmailHandler : IEventHandler
+{
+ public async Task HandleAsync(OrderPlaced evt, CancellationToken ct)
+ {
+ // use evt.OrderId, evt.Total
+ }
+}
+```
+
+Register in `ConfigureServices`:
+
+```csharp
+services.AddScoped, SendConfirmationEmailHandler>();
+```
+
+**Semantics:** Handlers run sequentially. A failing handler throws `AggregateException`. Write handlers to be idempotent.
+
+## Settings Pattern
+
+Register setting definitions in `ConfigureSettings` on the module class:
+
+```csharp
+public void ConfigureSettings(ISettingsBuilder builder)
+{
+ builder.AddDefinition(new SettingDefinition
+ {
+ Key = "Orders.MaxItemsPerOrder",
+ DisplayName = "Max Items Per Order",
+ Group = "Orders",
+ Scope = SettingScope.Application, // System | Application | User
+ Type = SettingType.Number, // Text | Number | Bool | Json
+ DefaultValue = "50",
+ });
+}
+```
+
+Read a setting in a service:
+
+```csharp
+var max = await _settings.GetSettingAsync("Orders.MaxItemsPerOrder", SettingScope.Application);
+```
+
+**Scopes:** `System` = application-wide (e.g., feature flags), `Application` = per tenant/deployment, `User` = per authenticated user.
+
+## Constitution Diagnostics (SM00xx)
+
+The source generator enforces these rules at build time. Run `/debug-module {Name}` to check all at once.
+
+| Code | Rule | Common cause |
+|------|------|-------------|
+| SM0001 | No duplicate DbSet property names across modules | Two modules declare a DbSet with the same property name |
+| SM0007 | No duplicate entity configurations | Registered the same `IEntityTypeConfiguration` twice |
+| SM0010 | No circular module dependencies | Contract projects reference each other in a cycle |
+| SM0011 | No direct module-to-module implementation references | Took a shortcut referencing another module's impl project |
+| SM0014 | Contracts assembly has no public interfaces | Forgot to add `I{Name}Contracts` to the Contracts project |
+| SM0015 | No duplicate view page names | Two `IViewEndpoint`s return the same Inertia component name |
+| SM0025 | No implementation found for contract interface | Forgot to create the `{Name}ContractsService` class |
+| SM0032 | Permission class must be `sealed` | Permissions class is not sealed |
+| SM0040 | No duplicate module names | Two modules share the same `[Module]` name string |
+| SM0041 | View page name must be prefixed with module name | Inertia component name doesn't start with the module name |
+| SM0042 | `ViewPrefix` required when module has view endpoints | Module has `IViewEndpoint`s but no `ViewPrefix` on `[Module]` |
+| SM0043 | Module must override at least one `IModule` method | Empty or placeholder module class |
+
+For the full list of diagnostics, see `docs/CONSTITUTION.md`.
+
## Key Constraints
- Source generator targets **netstandard2.0** with `IIncrementalGenerator`