Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 0 additions & 171 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,192 +19,21 @@ When these instructions don't explicitly cover a situation, apply these values t
- Always use American English spelling in all code, comments, and documentation (e.g. "color" not "colour", "behavior" not "behaviour").
- Write clear and concise comments for each function.
- Make only high confidence suggestions when reviewing code changes.
- Always use the latest version C#, currently C# 13 features.
- Never change global.json unless explicitly asked to.
- Never change package.json or package-lock.json files unless explicitly asked to.
- Never change NuGet.config files unless explicitly asked to.
- Never leave unused using statements in the code.
- Always ensure that the code compiles without warnings.
- Always ensure that the code passes all tests.
- Always ensure that the code adheres to the project's coding standards.
- Always ensure that the code is maintainable.
- Review Directory.Build.props and .editorconfig for all warnings configured as errors.
- Never generate code that would violate these warning settings.
- Always respect the project's nullable reference type settings.
- Always reuse the active terminal for commands.
- Do not create new terminals unless current one is busy or fails.

## Formatting

- Honor the existing code style and conventions in the project.
- Apply code-formatting style defined in .editorconfig.
- Prefer file-scoped namespace declarations and single-line using directives.
- Insert a new line before the opening curly brace of any code block (e.g., after `if`, `for`, `while`, `foreach`, `using`, `try`, etc.).
- Ensure that the final return statement of a method is on its own line.
- Use pattern matching and switch expressions wherever possible.
- Use `nameof` instead of string literals when referring to member names.
- Place private class declarations at the bottom of the file.

## C# Instructions

- Prefer `var` over explicit types when declaring variables.
- Do not add unnecessary comments or documentation.
- Use `using` directives for namespaces at the top of the file.
- Sort the `using` directives alphabetically.
- Use namespaces that match the folder structure.
- Remove unused `using` directives.
- Use file-scoped namespace declarations.
- Use single-line using directives.
- For types that do not have an implementation, don't add a body (e.g., `public interface IMyInterface;`).
- Prefer using `record` types for immutable data structures (events, commands, read models, concepts).
- Use expression-bodied members for simple methods and properties.
- Use `async` and `await` for asynchronous programming.
- Use `Task` and `Task<T>` for asynchronous methods.
- Use `IEnumerable<T>` for collections that are not modified.
- Never return mutable collections from public APIs.
- Don't use regions in the code.
- Never add postfixes like Async, Impl, etc. to class or method names.
- Favor collection initializers and object initializers.
- Use string interpolation instead of string.Format or concatenation.
- Favor primary constructors for all types.

## Chronicle & Arc Key Conventions

These conventions exist because the frameworks rely on convention-based discovery. Breaking them doesn't just violate style — it breaks runtime behavior.

- `[EventType]` takes **no arguments** — the type name is used automatically. Adding a GUID or string argument is a common mistake from other frameworks; Chronicle does not use it.
- `[Command]` records define `Handle()` directly on the record — never create separate handler classes. The framework discovers `Handle()` by convention; a separate class breaks that discovery.
- `[ReadModel]` records define static query methods directly on the record. The proxy generator creates TypeScript hooks from these methods automatically.
- Prefer `ConceptAs<T>` over raw primitives in all domain models, commands, events, and queries. This prevents accidental mix-ups (passing a `UserId` where an `AuthorId` was expected) and makes APIs self-documenting.
- Use model-bound projection attributes (`[FromEvent<T>]`, `[SetFrom<T>]`, etc.) when possible; fall back to `IProjectionFor<T>` for complex cases.
- For fluent projections, AutoMap is on by default — just call `.From<>()` directly.
- Projections join **events**, never read models. This is fundamental to event sourcing: projections rebuild state from the event stream, not from other projections.
- `IReactor` is a marker interface — method dispatch is by first-parameter event type, method name is descriptive.
- All backend artifacts for a vertical slice go in a **single `.cs` file**. This keeps cohesion high and makes the scope of a slice immediately visible.

## Proxy Generation — Build Dependency

Commands and Queries generate TypeScript proxies at build time via `dotnet build`. This creates `.ts` files that the frontend imports (hooks, execute methods, change tracking). Until the backend compiles, **no proxy files exist** and frontend code cannot reference them.

**This is a hard sequencing constraint:**
1. Backend C# code must be written and compile successfully first.
2. `dotnet build` must complete — this generates the TypeScript proxy files.
3. Only then can frontend React components import and use the generated proxies.

**When running parallel agents or sub-agents:** backend and frontend work for the same slice **cannot** run in parallel. The backend agent must finish and `dotnet build` must succeed before the frontend agent starts. Independent slices (no shared events) can have their backends worked on in parallel, but each slice's frontend still depends on its own backend build completing first.

## TypeScript / Frontend Instructions

- Prefer `const` over `let` over `var` when declaring variables.
- Never use shortened or abbreviated names for variables, parameters, or properties.
- Use full descriptive names: `deltaX` not `dx`, `index` not `idx`, `event` not `e`, `previous` not `prev`, `direction` not `dir`, `position` not `pos`, `contextMenu` not `ctx`/`ctxMenu`.
- The only acceptable short names are well-established domain terms (e.g. `id`, `url`, `min`, `max`).
- Never leave unused import statements in the code.
- Always ensure that the code compiles without warnings.
- Use `yarn compile` to verify (if successful it doesn't output anything).
- Do not prefix a file, component, type, or symbol with the name of its containing folder or the concept it belongs to. Instead, use folder structure to provide that context.
- Favor functional folder structure over technical folder structure.
- Group files by the feature or concept they belong to, not by their technical role.
- Avoid folders like `components/`, `hooks/`, `utils/`, `types/` at the feature level.

## Development Workflow

- After creating each new file, run `dotnet build` (C#) or `yarn compile` (TypeScript) immediately before proceeding to the next file. Fix all errors as they appear — never accumulate technical debt.
- Before adding parameters to interfaces or function signatures, review all usages to ensure the new parameter is needed at every call site.
- When modifying imports, audit all occurrences — verify additions are used and removals don't break other files.
- Never use placeholder or temporary types — use proper types from the start.
- Review each file for lint compliance before moving on.
- The user may keep Storybook running — do not try to stop it, suggest stopping it, or start your own instance.

## XML Documentation

- All **public** types, methods, properties, and operators **must** have XML doc comments.
- Always use **multiline** `<summary>` tags — opening and closing tags on their own lines. Never single-line.
- Every method or operator with parameters must include `<param name="...">` for each parameter.
- Every non-void method or operator must include `<returns>`.
- Every method that throws must document the exception with `<exception cref="...">` tags.
- Use `<see cref="..."/>` and `<paramref name="..."/>` to cross-reference types and parameters.

## Exceptions

- Use exceptions for exceptional situations only.
- Don't use exceptions for control flow.
- Always provide a meaningful message when throwing an exception.
- Always create a custom exception type that derives from Exception.
- Never use any built-in exception types like InvalidOperationException, ArgumentException, etc.
- Add XML documentation for exceptions being thrown.
- XML documentation for exception should start with "The exception that is thrown when ...".
- Never suffix exception class names with "Exception".

## Nullable Reference Types

- Always use is null or is not null instead of == null or != null.
- Trust the C# null annotations and don't add null checks when the type system says a value cannot be null.
- Add `!` operator where nullability warnings occur.
- Use `is not null` checks before dereferencing potentially null values.

## Naming Conventions

- Follow PascalCase for component names, method names, and public members.
- Use camelCase for private fields and local variables.
- Prefix private fields with an underscore (e.g., `_privateField`).
- Prefix interface names with "I" (e.g., IUserService).

## Logging

- Use structured logging with named parameters.
- Use appropriate log levels (Information, Warning, Error, Debug).
- Always use a generic ILogger<T> where T is the class name.
- Keep logging in separate partial methods for better readability. Call the file `<SystemName>Logging.cs`. Make this class partial and static and internal and all methods should be internal.
- Use the `[LoggerMessage]` attribute to define log messages.
- Don't include `eventId` in the `[LoggerMessage]` attribute.

## Dependency Injection

- Systems that have a convention of IFoo to Foo does not need to be registered explicitly.
- Prefer constructor injection over method injection.
- Avoid service locator pattern (i.e., avoid using IServiceProvider directly).
- For implementations that should be singletons, use the `[Singleton]` attribute on the class.

## TypeScript Type Safety

- Never use `any` type - always use proper type annotations:
- Use `unknown` for values of unknown type that need runtime checking.
- Use `Record<string, unknown>` for objects with unknown properties.
- Use proper generic constraints like `<TCommand extends object = object>` instead of `= any`.
- Use `React.ComponentType<Props>` for React component types.
- When type assertions are necessary, use `unknown` as an intermediate type:
- Prefer `value as unknown as TargetType` over `value as any`.
- For objects with dynamic properties: `(obj as unknown as { prop: Type }).prop`.
- For generic React components:
- Use `unknown` as default generic parameter instead of `any`.
- Example: `<TCommand = unknown>` not `<TCommand = any>`.
- For Storybook files:
- Use `React.ComponentType<Record<string, never>>` for components with no props.
- Always use `as unknown as` when converting component imports to avoid type mismatch errors.
- Properly type story args instead of using `any`.
- For event handlers:
- Be careful with React.MouseEvent vs DOM MouseEvent - they are different types.
- React synthetic events: `React.MouseEvent<Element, MouseEvent>`.
- DOM native events: `MouseEvent`.
- Convert between them using: `nativeEvent as unknown as React.MouseEvent`.
- Use `e.preventDefault?.()` instead of `(e as any).preventDefault?.()`.
- For library objects (PIXI, etc.):
- Use proper library types when available.
- Use specific property types: `{ canvas?: HTMLCanvasElement }` instead of `any`.
- When working with external libraries that have strict generic constraints:
- Import necessary types (e.g., `Command` from `@cratis/arc/commands`).
- Use type assertions through `unknown` to satisfy constraints: `props.command as unknown as Constructor<Command<...>>`.
- Extract tuple results explicitly rather than destructuring when type assertions are needed.
- For function parameter types that may be unknown:
- Add type guards: `if (typeof accessor !== 'function') return ''`.
- Type parameters with fallbacks: `function<T = unknown>(accessor: ((obj: T) => unknown) | unknown)`.
- For arrays and collections accessed from `unknown` types:
- Cast to proper array type: `((obj as Record<string, unknown>).items || []) as string[]`.
- Type array elements when iterating: `array.forEach((item: string) => ...)`.
- For generic type parameters:
- Ensure proper type conversions: `String(value)` when string operations are needed.
- Use explicit Date parameter types: `new Date(value as string | number | Date)`.

## Detailed Guides

Expand Down
36 changes: 36 additions & 0 deletions .github/hooks/agent-stop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
on: agentStop
---

# Agent Stop — Release Build

When the agent finishes a session, automatically build every affected project in **Release** configuration to verify the delivered code compiles cleanly under production settings (stricter nullable enforcement, optimizer-driven warnings, etc.).

## Steps

1. **Identify affected projects** by running `git diff --name-only HEAD` (or `git status --short` for unstaged changes). Collect the set of unique project roots:
- For `.cs` files: walk up the directory tree to find the nearest `.csproj` file.
- For `.ts` / `.tsx` files: walk up the directory tree to find the nearest `package.json` that contains a `"build"` script.

2. **Build each affected .NET project** in Release mode:
```
dotnet build <project-path> -c Release
```
If no specific project can be identified, run `dotnet build -c Release` from the repository root.

3. **Build each affected TypeScript/React project**:
```
yarn build
```
Run from the package root that owns the changed files.

4. **If any build fails**:
- Report the full compiler output.
- Fix all errors and warnings before considering the session complete.
- Re-run the Release build to confirm it is clean.

## Rules

- Always run the Release build — never skip it even if the Debug build already passed earlier in the session.
- A session is not complete until `dotnet build -c Release` (and `yarn build` if applicable) exits with code `0` and **zero** warnings.
- Treat Release-only warnings (nullable annotations, unused variables stripped by the analyzer, etc.) as errors — fix them.
50 changes: 50 additions & 0 deletions .github/hooks/pre-commit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
on:
preToolUse:
tool: runInTerminal
---

# Pre-commit — Run Specs

Before executing any `git commit` terminal command, automatically run the specs for every affected project to ensure nothing is broken before changes are recorded in version control.

## When this hook applies

This hook fires before **every** `runInTerminal` call. Check whether the command being run is a `git commit` (including `git commit -m`, `git commit --amend`, etc.). If it is not a commit command, do nothing and let the tool proceed.

## Steps

1. **Detect a git commit command** — inspect the terminal command string. If it does not start with `git commit`, skip all steps below and proceed normally.

2. **Identify affected projects** from the staged changes:
```
git diff --name-only --cached
```
Collect unique project roots using the same rules as the `agentStop` hook:
- `.cs` files → walk up to the nearest `.csproj`.
- `.ts` / `.tsx` files → walk up to the nearest `package.json` with a `"test"` script.

3. **Run specs for each affected .NET project**:
```
dotnet test <specs-project-path> --no-build
```
If the specs project cannot be identified, run `dotnet test` from the repository root.

4. **Run specs for each affected TypeScript project**:
```
yarn test
```
Run from the package root that owns the changed files.

5. **If any spec fails**:
- Report the full test output including which specs failed and why.
- **Do not proceed with the `git commit`** — block the tool call and fix the failures first.
- Re-run the relevant specs to confirm they pass before retrying the commit.

6. **If all specs pass** — proceed with the `git commit` as originally requested.

## Rules

- Never skip the spec run before a commit, even for "minor" or "documentation-only" changes.
- A commit must not be made while any spec is failing.
- If a spec was already failing before the current changes (pre-existing failure), report it but do not block the commit — note the pre-existing failure clearly in the session output.
6 changes: 6 additions & 0 deletions .github/instructions/csharp.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,9 @@ These are the building blocks. Each type has a specific role in the vertical sli
| `EventContext` | Event metadata: `Occurred`, `SequenceNumber`, `CorrelationId`, `EventSourceId`, etc. |
| `ISubject<T>` | Observable query return type — enables real-time WebSocket push |
| `IMongoCollection<T>` | MongoDB collection — use `.Observe()` for reactive queries |

**Key conventions:**
- Prefer `ConceptAs<T>` over raw primitives in all domain models, commands, events, and queries — prevents accidental mix-ups and makes APIs self-documenting. See [concepts.instructions.md](./concepts.instructions.md) for details.
- Projections join **events**, never read models — projections rebuild state from the event stream, not from other projections.
- For fluent projections, AutoMap is on by default — just call `.From<EventType>()` without manually mapping every property.
- Use model-bound projection attributes (`[FromEvent<T>]`, `[SetFrom<T>]`, etc.) when possible; fall back to `IProjectionFor<T>` for complex cases.
5 changes: 4 additions & 1 deletion .github/instructions/dialogs.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ The Cratis dialog wrappers handle command execution, validation timing, loading
- Pass the command constructor to `command={}`. `CommandDialog` handles instantiation, execution, and confirm/cancel buttons.
- Use command form fields (`InputTextField`, `TextAreaField`, etc. from `@cratis/components/CommandForm`) for user-input values.
- `CommandDialog` automatically disables confirm while the command executes.
- Use `onBeforeExecute` only for values that cannot come from form fields (for example generated IDs).
- Any value that must be present for the form to be considered valid (i.e. passes `validateRequiredProperties`) must be supplied via `initialValues`, **not** via `onBeforeExecute`.
- `onBeforeExecute` fires only at execution time — the command is already validated before it runs, so values set there never influence `isValid` and the OK/Submit button will remain permanently disabled.
- Use `initialValues` for injected context values (e.g. a parent entity id passed as a prop).
- Use `onBeforeExecute` only for transformations that should not affect form validity (e.g. generated IDs).

```tsx
import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs';
Expand Down
Loading
Loading