diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md deleted file mode 100644 index 5377b0b..0000000 --- a/.claude/CLAUDE.md +++ /dev/null @@ -1,83 +0,0 @@ -# GitHub Copilot Instructions - -## Project Philosophy - -Cratis builds tools for event-sourced systems with a focus on **ease of use**, **productivity**, and **maintainability**. Every rule in these instructions serves one or more of these core values: - -- **Lovable APIs** — APIs should be pleasant to use. Provide sane defaults, make them flexible, extensible, and overridable. If an API feels awkward, it is wrong. -- **Easy to do things right, hard to do things wrong** — Convention over configuration. Artifact discovery by naming. Minimal boilerplate. The framework should guide developers into the pit of success. -- **Events are facts** — Immutable records of things that happened. Never nullable, never ambiguous, never multipurpose. If you find yourself adding a nullable property to an event, you need a second event. -- **High cohesion through vertical slices** — Everything for a behavior lives together: backend, frontend, specs. Navigate by feature, not by technical layer. A developer working on "author registration" should never need to jump between `Commands/`, `Handlers/`, and `Events/` folders. -- **Full-stack type safety** — Shared models flow from C# through proxy generation to TypeScript. End-to-end typing without manual synchronization. -- **Specialization over reuse** — Build focused, purpose-specific projections and read models rather than reusing one model across conflicting scenarios. Dedicated models are easier to maintain, perform better, and never break unrelated features. -- **Consistency is king** — When in doubt, follow the established pattern. Consistency across the codebase trumps local optimization. A slightly less elegant solution that matches the rest of the codebase is better than a clever one that stands out. - -When these instructions don't explicitly cover a situation, apply these values to make a judgment call. - -## General - -- **Always use American English spelling** in all code, comments, documentation, and XML docs — no exceptions. - - `-ize` not `-ise`: initialize, serialize, customize, normalize, organize, authorize, specialize, centralize, utilize - - `-or` not `-our`: behavior, color, favor, honor, humor, neighbor, flavor - - `-ization` not `-isation`: initialization, serialization, customization, normalization, organization, authorization - - `-er` not `-re`: center, fiber, meter - - `-og` not `-ogue`: dialog, catalog, analog - - `-ling` not `-lling`: modeling, signaling, labeling, canceling - - `-ense` not `-ence`: license, defense, offense - - `-ment` not `-ement`: judgment, acknowledgment - - Other: gray (not grey), program (not programme), fulfill (not fulfil), enroll (not enrol) - - When in doubt, use the US spelling — check a US dictionary. -- Write clear and concise comments for each function. -- Make only high confidence suggestions when reviewing code changes. -- 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. -- Always ensure that the code compiles without warnings. -- Always treat warnings as errors and fix them before considering the work complete. -- 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. -- For PR descriptions, use short release-note bullets that focus on **user-facing impact only** — new APIs, changed behavior, fixed bugs. Do not include internal implementation details (storage changes, converter updates, gRPC internals, spec additions). Append the **actual** issue number only when the PR is associated with a real GitHub issue (for example `(#351)`). If there is no associated issue, omit the reference entirely. Never use placeholder text like `(#issue)`, never leave the literal example `(#123)`, and never invent a random issue number. Never include Copilot "Original prompt" blocks. **Always verify the issue number using the `search_issues` or `list_issues` GitHub MCP tool — never guess or invent a number.** -- Always reuse the active terminal for commands. -- Do not create new terminals unless current one is busy or fails. -- When asked to commit, push, create a PR, ship, or land changes, always use the **ship-changes** skill. - -## 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. -- Before concluding any task, run the relevant specs/tests for every affected project and do not stop until they pass. -- At the end of every task, from repository root run `dotnet clean` and then `dotnet build -c Release`. The task is not complete until build output is zero warnings and zero errors. -- **After pushing changes to a PR**, use the GitHub MCP tools (`pull_request_read` with `get_check_runs`, `get_job_logs`) to monitor CI check results. If any checks fail, investigate the logs, fix the failures, and push again. The task is not complete until all CI checks pass or the remaining failures are confirmed to be pre-existing flaky tests unrelated to the PR changes. - -## Detailed Guides - -These guides contain the full rules, examples, and rationale for each topic. The sections above are the global defaults; the guides go deeper into each area: - - [Code Quality](./instructions/code-quality.instructions.md) - - [Code Quality — C#](./instructions/code-quality.csharp.instructions.md) - - [Code Quality — TypeScript](./instructions/code-quality.typescript.instructions.md) - - [C# Conventions](./instructions/csharp.instructions.md) - - [How to Write Specs](./instructions/specs.instructions.md) - - [How to Write C# Specs](./instructions/specs.csharp.instructions.md) - - [How to Write TypeScript Specs](./instructions/specs.typescript.instructions.md) - - [Entity Framework Core](./instructions/efcore.instructions.md) - - [Entity Framework Core Specs](./instructions/efcore.specs.instructions.md) - - [Concepts (ConceptAs)](./instructions/concepts.instructions.md) - - [Documentation](./instructions/documentation.instructions.md) - - [Pull Requests](./instructions/pull-requests.instructions.md) - - [Vertical Slices](./instructions/vertical-slices.instructions.md) - - [TypeScript Conventions](./instructions/typescript.instructions.md) - - [React Components](./instructions/components.instructions.md) - - [Dialogs](./instructions/dialogs.instructions.md) - - [Reactors](./instructions/reactors.instructions.md) - - [Orleans](./instructions/orleans.instructions.md) - -## Header - -All files should start with the following header: - -```csharp -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -``` diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 120000 index 0000000..c930a01 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +../.ai/rules/general.md \ No newline at end of file diff --git a/.claude/agents b/.claude/agents new file mode 120000 index 0000000..2f6c069 --- /dev/null +++ b/.claude/agents @@ -0,0 +1 @@ +../.ai/agents \ No newline at end of file diff --git a/.claude/hooks b/.claude/hooks new file mode 120000 index 0000000..5e5c5a2 --- /dev/null +++ b/.claude/hooks @@ -0,0 +1 @@ +../.ai/hooks \ No newline at end of file diff --git a/.claude/prompts b/.claude/prompts new file mode 120000 index 0000000..d24f01f --- /dev/null +++ b/.claude/prompts @@ -0,0 +1 @@ +../.ai/prompts \ No newline at end of file diff --git a/.claude/rules/code-quality.csharp.md b/.claude/rules/code-quality.csharp.md deleted file mode 100644 index a24b621..0000000 --- a/.claude/rules/code-quality.csharp.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -applyTo: "**/*.cs" ---- - -# Code Quality — C# - -C#-specific applications of the general [Code Quality](./code-quality.instructions.md) principles. - -## Composition over Inheritance - -Use constructor injection to compose behavior. Primary constructors make this natural in modern C# — the type's dependencies are visible at a glance and can be substituted in tests. - -```csharp -// ❌ Inheritance — child is tightly coupled to parent internals -public class ReportExporter : BaseExporter -{ - public override void Export(Report report) { ... } -} - -// ✅ Composition — behavior is injected and interchangeable -public class ReportExporter(IExportStrategy strategy) -{ - public void Export(Report report) => strategy.Execute(report); -} -``` - -**Rules:** -- Never extend a concrete class to add or change behavior — inject a collaborator instead. -- Use interfaces and `ConceptAs` record wrappers rather than inheritance chains. -- Inheritance is acceptable only for framework integration points with a well-defined extension mechanism (e.g. `Specification`, `Migration`, `AggregateRoot`). - -## Open/Closed Principle - -The framework's `IInstancesOf` mechanism makes the open/closed pattern effortless — adding a new implementation is all it takes to extend behavior. Use it instead of growing `switch` statements. - -```csharp -// ❌ Modified every time a new format is added -public class ReportFormatter -{ - public string Format(Report report, string formatType) - { - if (formatType == "csv") return FormatAsCsv(report); - if (formatType == "json") return FormatAsJson(report); - throw new UnknownFormat(formatType); - } -} - -// ✅ New formats added by implementing the interface — no existing code changes -public interface IReportFormatter -{ - string Format(Report report); -} - -public class CsvReportFormatter : IReportFormatter { ... } -public class JsonReportFormatter : IReportFormatter { ... } -``` - -**Rules:** -- Prefer strategy interfaces over `switch`/`if-else` chains that grow over time. -- Use `IInstancesOf` to discover all implementations by convention — no manual registration needed. -- Design public APIs as contracts (interfaces/records) rather than concrete implementations. - -## Separation of Concerns - -The Chronicle + Arc stack has clear layer boundaries. Violating them creates coupling that is hard to undo. - -**Rules:** -- Domain types must not reference EF Core, MongoDB, or HTTP concepts directly. -- Command handlers express intent in domain terms — they delegate persistence and I/O to injected collaborators. -- Projections build read models; they must not trigger commands or produce side effects. -- Reactors handle side effects; they must not directly read or write the event log. - -## Low Coupling - -**Rules:** -- Depend on abstractions (interfaces, records), not on concrete implementations. -- Use constructor injection — it makes dependencies explicit and testable. -- Avoid reaching through an object to call methods on its dependencies (`a.B.C.Do()` is a sign of tight coupling). -- Limit constructor dependencies to four or five — more is a signal the type is doing too much. -- Never reference types from unrelated features directly; go through a shared contract or event instead. - -## Cross-Cutting Concerns - -**Rules:** -- Never write logging statements directly inside command handlers, projections, or domain types. Use the `[LoggerMessage]` pattern in a co-located `*Logging.cs` partial class. -- Never perform authorization checks inside domain logic — express them as attributes or middleware applied at the boundary. -- Never duplicate error-handling or retry logic across handlers — centralize it in a pipeline or middleware. -- Use `ICommandPipeline`, middleware, and decorators to apply cross-cutting concerns at the infrastructure layer so that domain code remains unaware of them. -- When you notice the same infrastructural pattern appearing in two or more places (logging a specific event, catching a specific exception, checking a specific condition), extract it into a shared cross-cutting mechanism rather than duplicating it. diff --git a/.claude/rules/code-quality.csharp.md b/.claude/rules/code-quality.csharp.md new file mode 120000 index 0000000..1b8150c --- /dev/null +++ b/.claude/rules/code-quality.csharp.md @@ -0,0 +1 @@ +../../.ai/rules/code-quality.csharp.md \ No newline at end of file diff --git a/.claude/rules/code-quality.md b/.claude/rules/code-quality.md deleted file mode 100644 index c429fe3..0000000 --- a/.claude/rules/code-quality.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -applyTo: "**/*" ---- - -# Code Quality - -Good code is not just code that works — it is code that can be understood, changed, and extended safely. The principles below are the foundation for writing code that remains maintainable as the system grows. They are not abstract ideals; each one has a concrete, practical consequence for how you write and structure code in this project. - -When these principles don't explicitly cover a situation, apply these values to make a judgment call. See the language-specific guides for concrete rules and examples: -- [Code Quality — C#](./code-quality.csharp.instructions.md) -- [Code Quality — TypeScript](./code-quality.typescript.instructions.md) - -## Composition over Inheritance - -Prefer composing behavior from smaller, focused collaborators over building class hierarchies. Inheritance couples the child tightly to the parent's internal structure — a change to the parent can break every subclass. Composition keeps collaborators independent and replaceable. - -**Rules:** -- Never extend a concrete class to add or change behavior — inject a collaborator instead. -- Inheritance is acceptable only for framework integration points where a base class is part of a well-defined extension mechanism. - -## Single Responsibility Principle - -Every type and every method should have **one reason to change** — it should do one thing and do it well. A class that fetches data, transforms it, validates it, and sends an email has four reasons to change. When any of those concerns shifts, you have to touch — and risk breaking — all the others. - -**Rules:** -- A class or method that requires a comment explaining what each section does is a sign it should be split. -- Methods longer than ~20 lines are a signal they are doing too much — extract collaborators or helper methods. -- If a type needs collaborators from two unrelated domains, question whether it has two responsibilities. -- Follow the [File Size Guideline](#file-size--200-line-guideline) below. - -## Open/Closed Principle - -Types should be **open for extension, closed for modification**. Once a type is in use, changing its internals to support new behavior risks breaking existing callers. Instead, design extension points — interfaces, strategies, event hooks — that allow new behavior to be added without touching existing code. - -**Rules:** -- Prefer strategy interfaces over `switch`/`if-else` chains that grow over time. -- Design public APIs as contracts (interfaces/records) rather than concrete implementations. - -## Separation of Concerns - -Each layer and each module should own exactly one concern. Mixing concerns — for example, querying the database and formatting the HTTP response in the same method — creates entanglement that makes both concerns harder to change or test independently. - -**Rules:** -- Keep domain logic out of infrastructure — domain types must not reference infrastructure or transport concepts directly. -- Keep infrastructure out of domain logic — handlers and domain types express intent; they delegate to collaborators for persistence, messaging, and I/O. - -## Low Coupling - -Coupling is the degree to which one module depends on the internals of another. High coupling means a change in one place forces changes everywhere else. Low coupling means modules can evolve independently. - -**Rules:** -- Depend on abstractions, not on concrete implementations. -- Avoid reaching through an object to call methods on its dependencies — this is a sign of tight coupling. -- Limit the number of dependencies a single type takes — more than four or five is a signal it is doing too much. -- Never reference types from unrelated features directly; go through a shared contract or event instead. - -## High Cohesion - -Cohesion measures how closely related the responsibilities within a module are. A highly cohesive class has all its methods and properties working together toward a single goal. A low-cohesion class is a collection of unrelated utilities that happen to live in the same file. - -**Rules:** -- Group code by feature, not by technical role — everything for a behavior belongs together. -- If you find yourself writing methods in a type that use completely different sets of fields or dependencies, the type likely needs to be split. -- Utilities and helpers are acceptable only when the operations they provide are genuinely shared across features; otherwise, keep logic in the feature that owns it. - -## File Size — 200-Line Guideline - -A file exceeding **200 lines** is a strong signal that it contains too many responsibilities. This is not a hard limit — some files are legitimately longer — but whenever you find yourself adding to a file that already approaches this size, stop and ask: can this be split? - -**Rules:** -- When a file crosses 200 lines, look for natural split points: a sub-concept that could become its own type, a behavior that could move to a collaborator, or a section that belongs in a different layer. -- Aim for files that can be understood in a single reading without scrolling. -- Instruction and documentation files follow the same principle — a guide over 200 lines usually contains multiple distinct topics that deserve their own files. - -## Cross-Cutting Concerns - -Cross-cutting concerns — logging, validation, authorization, error handling, metrics, caching — affect many parts of the system but belong to none of them. Scattering them through business logic creates noise and duplication. Centralizing them in infrastructure keeps domain code clean. - -**Rules:** -- Never write logging or authorization checks inside domain logic — apply them at the infrastructure boundary. -- Never duplicate error-handling or retry logic — centralize it in a pipeline, middleware, or decorator. -- When you notice the same infrastructural pattern appearing in two or more places, extract it into a shared cross-cutting mechanism rather than duplicating it. diff --git a/.claude/rules/code-quality.md b/.claude/rules/code-quality.md new file mode 120000 index 0000000..c76eaba --- /dev/null +++ b/.claude/rules/code-quality.md @@ -0,0 +1 @@ +../../.ai/rules/code-quality.md \ No newline at end of file diff --git a/.claude/rules/code-quality.typescript.md b/.claude/rules/code-quality.typescript.md deleted file mode 100644 index 715cb99..0000000 --- a/.claude/rules/code-quality.typescript.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -applyTo: "**/*.ts,**/*.tsx" ---- - -# Code Quality — TypeScript - -TypeScript/React-specific applications of the general [Code Quality](./code-quality.instructions.md) principles. - -## Composition over Inheritance - -React is built on composition — components accept children, hooks compose other hooks, and higher-order utilities wrap behavior. Avoid class hierarchies entirely; the language and framework have moved on. - -```tsx -// ❌ Inheritance — fragile, couples component to base class internals -class SpecialButton extends BaseButton { - override render() { ... } -} - -// ✅ Composition — wrap or delegate, keep each piece independent -export const SpecialButton = ({ onClick, label }: SpecialButtonProps) => ( - -); -``` - -**Rules:** -- Never use class inheritance for React components — compose with props, children, and hooks instead. -- Extract repeated UI patterns into small, focused components rather than adding conditions to a shared parent. -- Extract repeated logic into custom hooks — a hook that does two unrelated things should be two hooks. - -## Open/Closed Principle - -TypeScript discriminated unions and generic constraints let you add new variants without touching existing code. Prefer them over ever-growing `if-else` / `switch` chains. - -```ts -// ❌ Grows every time a new shape is needed -function area(shape: string, a: number, b?: number): number { - if (shape === 'circle') return Math.PI * a * a; - if (shape === 'rectangle') return a * (b ?? 0); - throw new Error('Unknown shape'); -} - -// ✅ New shapes extend the union — existing handler functions are untouched -type Circle = { kind: 'circle'; radius: number }; -type Rectangle = { kind: 'rectangle'; width: number; height: number }; -type Shape = Circle | Rectangle; - -function area(shape: Shape): number { - switch (shape.kind) { - case 'circle': return Math.PI * shape.radius ** 2; - case 'rectangle': return shape.width * shape.height; - } -} -``` - -**Rules:** -- Model variation with discriminated unions rather than optional fields or string literals. -- Design utility functions to accept an interface or generic constraint so new types can be handled by adding a new implementation, not by editing existing code. - -## Separation of Concerns - -React components have one job: render UI and delegate events. Keep data-fetching, business logic, and side effects in dedicated hooks or services — not inline in the component body. - -**Rules:** -- Never write data-fetching or business logic directly in a component — extract it into a hook. -- Component files (`.tsx`) must not import from infrastructure layers such as HTTP clients or storage utilities directly — go through an abstraction or a generated proxy. -- Keep style concerns in co-located `.css` files; keep data concerns in hooks; keep rendering in the component. - -## Low Coupling - -Coupling in TypeScript is often hidden in deep import paths. Barrel files and path aliases make coupling explicit and keep refactoring safe. - -**Rules:** -- Import from barrel `index.ts` files, not from deep internal paths — this limits the blast radius of refactoring. -- Use the configured path aliases (e.g. `Strings`, `Components`) rather than relative `../../../` chains. -- Never import from an unrelated feature's internal files — go through that feature's public barrel export. -- Keep the number of imports in a single file reasonable — many imports from many different areas is a coupling smell. - -## Cross-Cutting Concerns - -**Rules:** -- Use React Error Boundaries to centralize error display — never scatter `try/catch` blocks inside component render paths. -- Use a single top-level provider or hook for global state (e.g. authentication, theming) — never drill context down through many component layers. -- Centralize API error handling in a shared hook or service layer — do not duplicate toast/notification logic per component. -- Apply logging, analytics, and monitoring at the infrastructure edge (e.g. router callbacks, global error handlers) so that feature components remain unaware of them. diff --git a/.claude/rules/code-quality.typescript.md b/.claude/rules/code-quality.typescript.md new file mode 120000 index 0000000..26b440a --- /dev/null +++ b/.claude/rules/code-quality.typescript.md @@ -0,0 +1 @@ +../../.ai/rules/code-quality.typescript.md \ No newline at end of file diff --git a/.claude/rules/components.md b/.claude/rules/components.md deleted file mode 100644 index d1ad855..0000000 --- a/.claude/rules/components.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -applyTo: "**/*.tsx" ---- - -# Building React Components - -## Composition over Monoliths - -A well-built component tree is like a well-organized kitchen — every tool has a place, and you can find what you need without opening every drawer. Large components that do everything are hard to understand, hard to test, and hard to change without breaking something unrelated. - -- Split components into small, focused pieces and compose them together. Each component should have a single, clear responsibility. -- Parent components own state and event handlers; children receive props. This makes data flow predictable and debuggable. -- If you find yourself writing a block comment like `// Author list section` inside a component, that section should be its own component. The comment is a code smell — the component name should provide that context instead. - -## Folder Structure - -- Single-file component → place directly in the parent feature folder. -- Multi-file component (sub-components, hooks, CSS) → create a folder named after the component: - -``` -PrototypeWindow/ - PrototypeWindow.tsx ← composition root - PrototypeWindow.css ← styles for the composition - TitleBar.tsx ← sub-component - CanvasArea.tsx ← sub-component - ResizeHandle.tsx ← sub-component - index.ts ← re-exports public API -``` - -Add an `index.ts` that re-exports the public surface so import paths stay stable. - -## Styling - -Consistent styling comes from discipline: static styles in CSS files, dynamic values inline, and colors always from PrimeReact's design tokens. This ensures theming works automatically and no component breaks the visual language. - -- Use **CSS classes in co-located `.css` files** for static styles. -- Each component must have its own CSS file — never add sub-component styles to the parent's CSS. This keeps styles co-located with the component they belong to. -- The composition root's CSS only contains layout/grid rules for positioning children — it should not style the children themselves. -- Use inline `style` props **only** for runtime-dynamic values (pixel positions, computed sizes). -- Use **PrimeReact CSS variables** for all colors, backgrounds, borders. This ensures the application respects theming and dark/light mode switches: - - `var(--surface-0)` through `var(--surface-900)`, `var(--surface-card)`, `var(--surface-border)`, `var(--surface-ground)` - - `var(--text-color)`, `var(--text-color-secondary)`, `var(--primary-color)`, `var(--primary-color-text)`, `var(--highlight-bg)` - - Never hard-code hex or `rgb()` for UI chrome — it will break when themes change. Only hard-code colors that are intentionally theme-independent (e.g. brand-specific accent dots, traffic-light indicators). -- Name CSS classes with a BEM-like prefix matching the component name. - -## Props - -Props are a component's public API. They should be clear, minimal, and well-documented. - -- Each sub-component declares its own `*Props` interface with JSDoc on every prop. -- Pass only needed props — avoid threading large prop bags through component trees. -- Event handlers follow `on*` naming: `onPointerDown`, `onSelect`. - -## Dialogs - -See [dialogs.instructions.md](./dialogs.instructions.md) for the full dialog guide. - -**Summary:** Never import `Dialog` from `primereact/dialog`. Use `CommandDialog` from `@cratis/components/CommandDialog` for command-executing dialogs and `Dialog` from `@cratis/components/Dialogs` for data-collection dialogs. Do not render manual `