diff --git a/.ai/rules/documentation-structure-and-formatting.md b/.ai/rules/documentation-structure-and-formatting.md new file mode 100644 index 0000000..87753fe --- /dev/null +++ b/.ai/rules/documentation-structure-and-formatting.md @@ -0,0 +1,86 @@ +--- +applyTo: "**/Documentation/**/*.{md,mdx}" +paths: + - "**/Documentation/**/*.md" + - "**/Documentation/**/*.mdx" +--- + +# Documentation Structure & Formatting (so a page fits the site) + +The mechanical conventions a page must follow to slot into the Astro Starlight site correctly. (For *what* to write — the tour voice — see [Writing Cratis Documentation](./writing-cratis-docs.md). For *where* a page lives — see [Editing Cratis Documentation](./editing-cratis-docs.md).) Product `.md` is run through `web/scripts/sync-content.mjs` before it reaches Starlight; this is what that converter expects and does. + +## Frontmatter + +```yaml +--- +title: Append an event # required-ish; becomes the page H1 +description: One sentence … # strongly recommended; SEO/meta + AI export +sidebar: # optional: order / label / badge + order: 2 + badge: { text: New, variant: tip } +--- +``` + +- The converter **keeps** `title`, `description`, `sidebar`; it **drops** DocFX keys (`uid`, `applyTo`, `storybook`, …). For product `.md` with no frontmatter, it derives a `title` from the first H1. +- **Starlight renders `title` as the page H1 — so do NOT put an H1 in the body.** Start the body at `##` (H2). A leading body H1 is stripped, but don't rely on that; structure from H2. + +## Headings + +- **No body H1** (the title is the H1). +- **The "On this page" ToC shows H2 only** (`tableOfContents: { min:2, max:2 }`). Structure each page as a flat list of `##` sections — sub-points go under them as `###`/prose, not as ToC entries. +- Use sentence case in headings; no trailing punctuation. + +## File & folder layout + +- Every folder has a **`toc.yml`** (navigation) and an **`index.md`** (landing). A folder whose landing is `overview.md` (no `index.md`) **404s on the bare URL** — give it an `index.md` or link to a specific page. +- A `toc.yml` entry pointing to a **missing page is dropped** from the sidebar (the sync prints "N broken toc entries dropped" — keep it 0). +- `toc.yml` sections are regrouped into Diátaxis **buckets** (Get started / Guides / Understand / Reference) via `PRODUCTS[].buckets` in `sync-content.mjs`. Sections named **"Getting started…"** auto-get a *Quickstart* badge; **"Tutorial"** gets a *Tutorial* badge. + +## Callouts / asides + +Write DocFX alerts in product `.md` (converted automatically) **or** Starlight directives directly: + +| DocFX | Becomes | +|---|---| +| `> [!NOTE]` / `> [!IMPORTANT]` | `:::note` | +| `> [!TIP]` | `:::tip` | +| `> [!WARNING]` | `:::caution` | +| `> [!CAUTION]` | `:::danger` | + +``` +:::note +Body of the callout. +::: +``` + +In `.mdx` you may also use `` (import from `@astrojs/starlight/components`). + +## Code blocks + +- **Always tag the language** for highlighting: ` ```csharp `, ` ```tsx `, ` ```bash `, ` ```yaml `, etc. DocFX-era custom fences (`env`, `pdl`, `ebnf`, `pql`, `gitignore`, `flow`) are aliased to plain text so they don't warn — but use a real language where one exists. +- **No spurious common leading indentation** — if a snippet is lifted from inside a method, dedent it to column 0 (the whole block shifted right reads as "indented"). Keep only the code's own internal indentation. +- Where a feature spans the stack, show **both sides** with `` (synced C# ↔ generated TS) rather than backend-only. +- `[!INCLUDE [x](./x.md)]` is inlined; `[Snippet source](…)` links stay as-is. + +## Tables, images, links, diagrams + +- **Tables** are GitHub-Flavored Markdown (`| … |` with a `| --- |` separator row, blank line before). They render as real tables (the site tints the header). A table that shows as raw `|` pipes means GFM isn't applying — check the Documentation repo's site-rendering rule. +- **Images** use relative paths next to the page; they're click-to-zoom automatically. Give meaningful alt text. +- **Links:** product `.md` may use `./foo.md`; links to a `.mdx` page must be **extension-less** (`./foo`); site-level `.mdx` uses clean root-relative URLs (`/arc/…`). Cross-product links are root-relative. **Link text must describe the destination** — never `[here]` / `[see documentation]` (the gate fails on these). +- **Diagrams:** ` ```mermaid ` fences for any non-trivial concept (pre-rendered to SVG at build). + +## `.mdx` specifics + +- Frontmatter `title` + `description`, **no body H1**, then imports: + ```mdx + import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components'; + import { FullStackTabs, TopicHero, SimpleCard, StackDiagram } from '@components'; + ``` +- Valid `` icons include `apple`, `linux`, `seti:c-sharp`, `seti:react`. +- The splash front door uses `template: splash` in frontmatter. + +## File hygiene + +- **End every file with a single trailing newline.** +- Don't use shell commands to modify a doc after writing it. +- Verify before done: `cd Documentation/web && npm run check` must end **0 error(s)** and **0 broken** links. diff --git a/.ai/rules/editing-cratis-docs.md b/.ai/rules/editing-cratis-docs.md new file mode 100644 index 0000000..8935f8c --- /dev/null +++ b/.ai/rules/editing-cratis-docs.md @@ -0,0 +1,54 @@ +--- +applyTo: "**/Documentation/**/*.{md,mdx}" +paths: + - "**/Documentation/**/*.md" + - "**/Documentation/**/*.mdx" +--- + +# Editing Cratis Documentation (the content is split across repos) + +Cratis documentation is **not** one folder. The content lives in **each product's own repo** under that repo's `Documentation/` folder, and a single published site (in the **`Documentation` repo**, at `Documentation/web/`) aggregates them all at build time. Know where a page actually lives before you edit it, or you'll edit a generated copy that gets overwritten. + +## Where each page actually lives + +| Page | Source of truth | +|---|---| +| `/chronicle/**` | `Chronicle/Documentation/**` | +| `/arc/**` | `Arc/Documentation/**` (the `ApplicationModel` repo, cloned as `Arc`) | +| `/components/**` | `Components/Documentation/**` | +| `/cli/**`, `/fundamentals/**`, `/contributing/**` | the matching repo's `Documentation/` | +| Site-level pages (`/`, `/why-cratis`, `/cratis-stack`, `/glossary`, `/comparisons/**`, …) | `Documentation/web/src/content/docs/*.{md,mdx}` (authored directly in the site) | + +**Map a URL to a file:** `/chronicle/concepts/event-source/` → `Chronicle/Documentation/concepts/event-source.md`. The site reads each product repo as a **sibling clone** (`/{Documentation,Chronicle,Arc,Components,Fundamentals,cli,.github}`), all on the same branch. + +## The one rule that prevents wasted work + +**NEVER edit `Documentation/web/src/content/docs/{chronicle,arc,components,cli,fundamentals,contributing}/`.** Those are **generated and git-ignored** — `web/scripts/sync-content.mjs` regenerates them from the product repos on every build. Edit the **product-repo source** and re-sync. (Site-level pages directly under `web/src/content/docs/` *are* hand-authored — only the per-product subfolders are generated.) + +## The loop + +1. **Edit** the source `.md`/`.mdx` in the owning product repo (or the site-level page in `web/src/content/docs/`). +2. **Sync + preview:** from `Documentation/web`, `npm run dev` (runs the sync, serves http://localhost:4321). The sync also runs automatically in `predev`/`prebuild`. +3. **Verify:** `npm run check` — the full gate. It MUST end **0 error(s)** and **0 broken** links (≈187 advisory style warnings are expected and fine). Run it after every change. +4. Commit the change in the **product repo** (content) — the site repo only changes if you touched site-level pages, nav buckets, or the build. + +## Adding or moving a page + +- Create the file in the product repo's `Documentation/`, add a `toc.yml` entry, and add that entry's `name` to the right Diátaxis **bucket** in `web/scripts/sync-content.mjs` (`PRODUCTS[].buckets`). Site-level pages are wired in `astro.config.mjs` instead. +- Buckets are the Diátaxis order: **Get started** (tutorials) → **Guides** (how-to) → **Understand** (explanation) → **Reference**. Keep every product on this shape. + +## Links (the rules differ by file type) + +- **Product `.md`** goes through the converter — `[x](./foo.md)` is fine (it strips `.md`). +- **Intra-doc links to a `.mdx` page must be EXTENSION-LESS** (`./validation`, not `./validation.mdx`) — slugify strips the dot and breaks `.mdx`. +- **Site-level `.mdx`** does NOT go through the converter — use clean root-relative URLs (`/arc/...`), never `.md`. +- Cross-product links are root-relative: `/chronicle/...`. Sections whose landing is `overview.md` (no `index.md`) 404 on the bare URL — link to a specific page. + +## Hard gotchas (these will bite you) + +- **A long-running `npm run dev` degrades** (every page 500s / tables stop rendering) — and **running `npm run check`/`build` while dev is live** corrupts it (the build re-syncs content the dev server is watching). Fix: **restart `npm run dev`** (kill `lsof -ti tcp:4321`). Re-verify a fresh dev server before trusting any "X doesn't render". +- **The Astro content cache (`.astro/`, `node_modules/.astro`) silently serves a PARTIAL prior render** between iterations. If a change "didn't take" or a build looks half-done, `rm -rf .astro node_modules/.astro` and rebuild. +- **Code examples are copied verbatim by readers** — verify every framework API against real source before writing it. See [Writing Correct Code Examples](./writing-correct-examples.md). +- Match the **tour voice** (teach, don't dump) and the right **Diátaxis** type — see [Writing Cratis Documentation](./writing-cratis-docs.md). + +→ Site build and rendering internals live in the Documentation repo. diff --git a/.ai/rules/writing-correct-examples.md b/.ai/rules/writing-correct-examples.md new file mode 100644 index 0000000..cbd0690 --- /dev/null +++ b/.ai/rules/writing-correct-examples.md @@ -0,0 +1,35 @@ +--- +applyTo: "**/Documentation/**/*.{md,mdx}" +paths: + - "**/Documentation/**/*.md" + - "**/Documentation/**/*.mdx" +--- + +# Writing Correct Code Examples (Technical Docs) + +Documentation code examples are **copied verbatim** by evaluators. A snippet that uses an API that doesn't exist is worse than no snippet — it breaks on first paste and loses trust. An audit of these docs found **~12 fabricated-API bugs** that had passed review and shipped. The discipline below is how you avoid adding the thirteenth. + +## The rule: verify every framework API against real source — before you write it + +For each framework type, attribute, method, prop, hook, or import in an example, confirm it exists and has that exact shape in **source**, not in another doc page (the docs themselves had the bugs): + +- **C# / backend** — grep real usage in **Studio** (`/Volumes/sourcecode/repos/cratis/Studio/Source`, `*.cs`) and the product `Source/` trees (`Arc/Source`, `Chronicle/Source`). For extension methods, find the `public static … (this …)` signature and note **which type it extends**. +- **React / Components** — the authoritative prop names are in the compiled type defs: `Components/Source/dist/esm/**/*.d.ts`. Real usage: Studio `*.tsx`. +- **Invented *domain* names are fine** (event/concept/command names like `AuthorRegistered`, `BookId`). Only **framework APIs** must be real. Never invent a framework interface, attribute, prop, method, or import path. + +## Complete and correct + +- No pseudo-code, no `// ...` elisions that leave the reader guessing, no props/members that don't exist. +- A snippet a reader pastes should compile (modulo the invented domain types they'd supply). + +## Verified gotchas (the real APIs — these are the ones docs kept getting wrong) + +- Commands/queries are **model-bound**: a `[Command]` record with `Handle()` **on the record**, and `[ReadModel]` records with **static** query methods. The marker/handler interfaces `ICommand`, `ICommandHandler`, `IQuery`, `IQueryHandler` **do not exist** — never use them. +- Bootstrap: `ArcApplication.CreateBuilder(args)` (not `ArcApplicationBuilder.CreateBuilder`). `builder.AddCratisArc()` on the builder (`WebApplicationBuilder`/`IHostBuilder`); `app.UseCratisArc()` on the built app and it takes **no args** (the listen URL comes from `ArcOptions.Hosting.ApplicationUrl`). +- Read the current user inside `Handle()` by injecting **`IHttpContextAccessor`** and reading `HttpContext?.User` (`ClaimsPrincipal`). There is no `CommandContext.User` and no `IUserAccessor` Arc type. In-`Handle` guards return `Result` + `ValidationResult.Error(...)` — there is no `CommandResult.Forbidden`/`Unauthorized` to return. +- Components: `DataPage` uses the **compound** `DataPage.Columns` / `DataPage.MenuItems`; the detail prop is **`detailsComponent`** (lowercase) — `detailsTitle`/`initialSizes` are **not** props. Import `DataTableForObservableQuery` from `@cratis/components/DataTables` (the root barrel only re-exports namespaces); `DataPage`/`MenuItem` from `@cratis/components/DataPage`. Required props like `emptyMessage`/`title` must be present. +- Chronicle model-bound projections use property attributes **`[SetFrom]`** / **`[SetValue]`** (and `[FromEvent]` AutoMap) — **not** `static On(event)` methods (that shape doesn't exist). Retrieve a read model with `eventStore.ReadModels.GetInstanceById(id)`. Integration assertion is `ShouldHaveAppendedEvent(seq, eventSourceId, validator)` (**3 args**). + +## Auditing at scale + +Re-run a snippet-correctness audit periodically — it keeps finding bugs (the list above came from three rounds). Delegate the cross-checking to subagents that compare each snippet to source and report only confirmed discrepancies; **verify each finding against source yourself before fixing**. Consider adopting an automated example tester (**Doc Detective**, **Squidler** — from awesome-docs) that actually runs the snippets, so correctness is enforced by CI rather than by hand. The principle, from jvns's "write good examples by starting with real code": derive examples from working source, don't compose them from memory. diff --git a/.ai/rules/writing-cratis-docs.md b/.ai/rules/writing-cratis-docs.md new file mode 100644 index 0000000..8e63b4d --- /dev/null +++ b/.ai/rules/writing-cratis-docs.md @@ -0,0 +1,75 @@ +--- +applyTo: "**/Documentation/**/*.{md,mdx}" +paths: + - "**/Documentation/**/*.md" + - "**/Documentation/**/*.mdx" +--- + +# Writing Cratis Documentation — tour voice + Starlight authoring + +The Cratis docs must **take the reader on a tour, like a teacher** — the way [Marten](https://martendb.io), [Wolverine](https://wolverinefx.net), and [aspire.dev](https://aspire.dev) docs do — **not** state facts like a reference dump. This is the project's strongest qualitative bar and it's been flagged repeatedly. The differentiator of those docs is **pedagogical structure**, not looks. Match it. + +## The bar + +- **Pain → relief.** Open by naming the friction the reader feels, then reveal the feature as the relief. (Aspire's "without vs. with".) +- **Why before how.** A reader who understands the reasoning handles edge cases the docs don't cover. +- **Active voice, present tense, second person.** "You append the event," not "the event is appended." +- **Be honest about limits.** A "when this is the wrong fit" section builds more trust than omitting it. + +## One page = one Diátaxis type (it also picks the nav bucket) + +| Type | Reader is… | Reads like | Nav bucket | +|---|---|---|---| +| **Tutorial** | learning by doing | a guided lesson — each step a visible result | Get started | +| **How-to** | solving a specific problem | a recipe — assume competence, no teaching | Guides | +| **Explanation** | trying to understand | a discussion — concepts, trade-offs, *why*, a diagram | Understand | +| **Reference** | looking something up | a dictionary — exhaustive, terse, tables/signatures | Reference | + +Never mix types. A tutorial padded with reference detail overwhelms; a how-to interrupted by concept digressions stops being a recipe. + +## The tour-voice checklist (apply to tutorials, getting-started, and explanations) + +1. **Open with a concrete scenario** ("a book shows up — let's record that"), not a definition of the tool. +2. **Name the friction first**, then the feature as its relief. +3. **"Let's…" with chronological verbs** (define → append → project → query). +4. **After every code block, explain the invisible** — what happens under the hood and why it matters. +5. **Recap before pivoting** ("this works well for X… however…"). +6. **Anticipate the reader's doubt** with an inline aside ("in a real app you'd…"). +7. **Show the result** — the output, the resulting read model/JSON — so success is visible. +8. **Organize by workflow**, not alphabetically (especially CLI/command docs). +9. **End every section with a forward link** to the natural next step. + +`Chronicle/Documentation/tutorial/*` is the reference voice — read it before writing. + +## Achieve the tour voice with Starlight's authoring tools + +Author rich pages as **`.mdx`** (the converter keeps `.mdx`): frontmatter `title` + `description`, **no body H1**, then import what you need: + +```mdx +import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components'; +import { FullStackTabs, TopicHero, SimpleCard, StackDiagram, YouWillLearn, Recap } from '@components'; +``` + +- **``** — procedures where each step produces a visible result (checklist point 1, 3). +- **`` / ``** — alternatives (OS, console-vs-web, C#-vs-fluent). Valid icons include `apple`, `linux`, `seti:c-sharp`, `seti:react`. +- **`