From 4f94fbad67ea4fccce7121b5c0320d514493c26d Mon Sep 17 00:00:00 2001 From: woksin Date: Sat, 30 May 2026 17:22:37 +0200 Subject: [PATCH 01/20] Add Components onboarding, recipes, and curated navigation Add Why Components, getting-started, and form/data/wizard/list recipes; replace the storybook-based toc with curated, Diataxis-bucketed navigation; fix Toolbar link depth. Co-Authored-By: Claude Opus 4.8 --- Documentation/Toolbar/basic-usage.md | 2 +- Documentation/Toolbar/fan-out.md | 2 +- Documentation/Toolbar/folder.md | 2 +- Documentation/building-a-form.md | 64 ++++++ Documentation/displaying-data.md | 56 +++++ Documentation/getting-started.md | 59 +++++ Documentation/list-screen-with-actions.md | 51 +++++ Documentation/multi-step-form.md | 45 ++++ Documentation/toc.yml | 262 ++++------------------ Documentation/why-components.md | 35 +++ 10 files changed, 358 insertions(+), 220 deletions(-) create mode 100644 Documentation/building-a-form.md create mode 100644 Documentation/displaying-data.md create mode 100644 Documentation/getting-started.md create mode 100644 Documentation/list-screen-with-actions.md create mode 100644 Documentation/multi-step-form.md create mode 100644 Documentation/why-components.md diff --git a/Documentation/Toolbar/basic-usage.md b/Documentation/Toolbar/basic-usage.md index 22f48ec..79fb176 100644 --- a/Documentation/Toolbar/basic-usage.md +++ b/Documentation/Toolbar/basic-usage.md @@ -52,4 +52,4 @@ import { FaPencil } from 'react-icons/fa6'; ``` -See [Icon](../../Common/icon.md) for the shared `Icon` type and `IconDisplay` component. +See [Icon](../Common/icon.md) for the shared `Icon` type and `IconDisplay` component. diff --git a/Documentation/Toolbar/fan-out.md b/Documentation/Toolbar/fan-out.md index e0b8b0c..1375c7f 100644 --- a/Documentation/Toolbar/fan-out.md +++ b/Documentation/Toolbar/fan-out.md @@ -46,4 +46,4 @@ import { FaShapes } from 'react-icons/fa6'; ``` -See [Icon](../../Common/icon.md) for the shared `Icon` type and `IconDisplay` component. +See [Icon](../Common/icon.md) for the shared `Icon` type and `IconDisplay` component. diff --git a/Documentation/Toolbar/folder.md b/Documentation/Toolbar/folder.md index cc70c8d..107dd47 100644 --- a/Documentation/Toolbar/folder.md +++ b/Documentation/Toolbar/folder.md @@ -54,7 +54,7 @@ import { FaFolder } from 'react-icons/fa6'; ``` -See [Icon](../../Common/icon.md) for the shared `Icon` type and `IconDisplay` component. +See [Icon](../Common/icon.md) for the shared `Icon` type and `IconDisplay` component. ## List Mode diff --git a/Documentation/building-a-form.md b/Documentation/building-a-form.md new file mode 100644 index 0000000..875d3d1 --- /dev/null +++ b/Documentation/building-a-form.md @@ -0,0 +1,64 @@ +--- +title: "Recipe: Building a form" +description: Execute a command from a typed form using CommandDialog and CommandForm fields, with validation handled for you. +sidebar: + order: 3 +--- + +**Goal:** collect input and run an Arc command — with the confirm button disabled while it executes, validation wired up, and no manual fetch. + +## Use CommandDialog + +`CommandDialog` takes your generated command constructor, instantiates it, renders the fields you supply plus the OK/Cancel footer, and executes on confirm: + +```tsx +import { CommandDialog } from '@cratis/components/CommandDialog'; +import { InputTextField, TextAreaField } from '@cratis/components/CommandForm'; +import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs'; +import { RegisterAuthor } from './RegisterAuthor'; // generated proxy + +export const AddAuthor = ({ closeDialog }: DialogProps) => ( + + command={RegisterAuthor} + title="Add author" + okLabel="Add" + validateOn="blur" + onConfirm={() => closeDialog(DialogResult.Ok)}> + value={i => i.name} title="Name" placeholder="Jane Austen" /> + value={i => i.bio} title="Bio" /> + +); +``` + +The `value={i => i.name}` accessor is **typed against the command** — rename `Name` in C#, rebuild, and this line stops compiling until you fix it. + +## Show the dialog + +Open it from a button and await the result with `useDialog`: + +```tsx +import { useDialog } from '@cratis/arc.react/dialogs'; +import { DialogResult } from '@cratis/arc.react/dialogs'; + +const [AddAuthorDialog, showAddAuthor] = useDialog(AddAuthor); + +// in your component +<> + + + +``` + +## Tips + +- **Injected (non-input) values** — set required values like a parent id via `initialValues`, not `onBeforeExecute`. Values set in `onBeforeExecute` run too late to affect validity, so the confirm button would stay disabled. Use `onBeforeExecute` only for generated values (like a new `Guid`) that don't gate validity. +- **Multi-step forms** — reach for `StepperCommandDialog` when one command needs to be gathered across several stages. +- Don't import `Dialog` from `primereact/dialog` directly — use the Cratis wrappers so execution, validation timing, and footers stay consistent. + +## Next + +- [Displaying data](/components/displaying-data/) — render the results. +- The full field set and dialog options are in the Components reference and Storybook. diff --git a/Documentation/displaying-data.md b/Documentation/displaying-data.md new file mode 100644 index 0000000..6ce74e7 --- /dev/null +++ b/Documentation/displaying-data.md @@ -0,0 +1,56 @@ +--- +title: "Recipe: Displaying data" +description: Render query results in a data table that updates live, and build list-and-detail screens with DataPage. +sidebar: + order: 4 +--- + +**Goal:** show the results of an Arc query in a table — and have it update on its own when the underlying read model changes. + +## A live table from an observable query + +If your query is observable, `DataTableForObservableQuery` subscribes for you and re-renders as new data arrives — no polling, no manual subscription: + +```tsx +import { DataTableForObservableQuery } from '@cratis/components'; +import { Column } from 'primereact/column'; +import { AllAuthors } from './Author'; // generated observable query proxy + +export const Authors = () => ( + + + + +); +``` + +For a one-shot (non-live) query, use `DataTableForQuery` the same way. + +## Prefer the hook when you need the data, not a table + +When you want the values themselves (to filter, summarize, or render custom markup), call `.use()` on the proxy: + +```tsx +const [authors] = AllAuthors.use(); +const count = authors.data.length; +``` + +## List-and-detail with DataPage + +For the common "table on the left, details on the right" screen, `DataPage` gives you the layout, selection, and a resizable detail panel out of the box: + +```tsx +import { DataPage } from '@cratis/components/DataPage'; +``` + +See the DataPage reference for menu items, the details panel, and selection wiring. + +## Tips + +- **Eventual consistency** — the read model behind your query updates shortly after a command appends its event. An observable table reflects that automatically the moment the projection catches up. See [Read Models](/chronicle/read-models/). +- Keep read models **specialized per screen** — a table query and a detail query can read different, purpose-built read models rather than one shared model. + +## Next + +- [Building a form](/components/building-a-form/) — the write side. +- [Build a full-stack feature](/build-a-full-app/) — the table and the form together against one Arc slice. diff --git a/Documentation/getting-started.md b/Documentation/getting-started.md new file mode 100644 index 0000000..c5f3db4 --- /dev/null +++ b/Documentation/getting-started.md @@ -0,0 +1,59 @@ +--- +title: Getting started +description: Install Components, wire the provider, and render your first command form and data table. +sidebar: + order: 2 +--- + +This gets `@cratis/components` installed and rendering. By the end you'll have the provider mounted and know where to go for forms and tables. + +## 1. Install + +Install the package and its PrimeReact peers: + +```bash +npm install @cratis/components primereact primeicons +``` + +The optional peers (`pixi.js` for `PivotViewer`, `framer-motion` for animated panels, `allotment` for `DataPage`) are only needed when you use those components. + +## 2. Import the styles + +Import the pre-compiled utility styles once, at your app entry point: + +```typescript +import '@cratis/components/styles'; +``` + +This works with any bundler and ships the [`--cratis-*` token layer](/components/styling/) the components read from. + +## 3. Mount the provider + +Wrap your app in `CratisComponentsProvider`. It configures PrimeReact's global settings (unstyled mode, pass-through, locale, ripple, overlay z-index): + +```tsx +import '@cratis/components/styles'; +import { CratisComponentsProvider } from '@cratis/components'; + +export const App = () => ( + + + +); +``` + +## 4. Choose how styling works + +The components render structurally as soon as the provider is mounted. Give them a visual identity by picking one setup — see [Styling](/components/styling/): + +- a PrimeReact theme, +- a custom palette on top of a theme, or +- fully unstyled with your own pass-through preset. + +## Next + +Now build something: + +- [Building a form](/components/building-a-form/) — execute a command with `CommandDialog` and form fields. +- [Displaying data](/components/displaying-data/) — show query results in a live data table. +- The full, interactive component catalog is in **Storybook** (see the Components reference pages). diff --git a/Documentation/list-screen-with-actions.md b/Documentation/list-screen-with-actions.md new file mode 100644 index 0000000..e995140 --- /dev/null +++ b/Documentation/list-screen-with-actions.md @@ -0,0 +1,51 @@ +--- +title: "Recipe: A list screen with actions" +description: Combine a live table with command dialogs to build the everyday "list things, add/edit/remove them" screen. +sidebar: + order: 6 +--- + +**Goal:** the most common screen in any app — a table of things with a toolbar to add, and per-row actions to edit or remove. This recipe wires [displaying data](/components/displaying-data/) and [running commands](/components/building-a-form/) together. + +## The shape + +- A **live table** reads an observable query, so it reflects changes the instant a command lands. +- A **toolbar button** opens an "add" `CommandDialog`. +- **Row actions** open edit/remove command dialogs for the selected item. + +```tsx +import { DataPage } from '@cratis/components/DataPage'; +import { useDialog, DialogResult } from '@cratis/arc.react/dialogs'; +import { AllAuthors } from './Author'; // observable query proxy +import { AddAuthor } from './AddAuthor'; // CommandDialog from the form recipe + +export const Authors = () => { + const [AddAuthorDialog, showAddAuthor] = useDialog(AddAuthor); + + return ( + <> + + + query={AllAuthors} + // selection + a resizable detail panel come built in + /> + + + ); +}; +``` + +## Why it stays simple + +You never write the glue between the write and the read: the "add" command appends an event, the projection updates the read model, and the observable table re-renders — automatically. There's no "refresh the list after saving" code because there's nothing to refresh. + +## Tips + +- **Edit and remove are just more commands.** Model them as commands (`RenameAuthor`, `RemoveAuthor`) and open them in dialogs the same way; the table updates itself. +- **Keep the table's read model specialized.** The list query and a detail query can read different, purpose-built read models — don't force one model to serve every screen. +- For a plain table without the detail panel, use [`DataTableForObservableQuery`](/components/displaying-data/) directly. + +## Next + +- [Building a form](/components/building-a-form/) and [Displaying data](/components/displaying-data/) — the two halves in detail. +- [Build a full-stack feature](/build-a-full-app/) — the backend slice this screen sits on. diff --git a/Documentation/multi-step-form.md b/Documentation/multi-step-form.md new file mode 100644 index 0000000..c438e59 --- /dev/null +++ b/Documentation/multi-step-form.md @@ -0,0 +1,45 @@ +--- +title: "Recipe: Multi-step form" +description: Gather a command's input across several named steps with StepperCommandDialog. +sidebar: + order: 5 +--- + +**Goal:** one command needs more input than fits comfortably on a single screen. Split it into a wizard — named steps the user moves through — that still executes a single command at the end. + +## Use StepperCommandDialog + +`StepperCommandDialog` is `CommandDialog` with stages. You group fields into `StepperPanel`s; it handles next/back navigation, per-step validation, and runs the command when the last step is confirmed: + +```tsx +import { StepperCommandDialog, StepperPanel } from '@cratis/components/StepperCommandDialog'; +import { InputTextField, DropdownField } from '@cratis/components/CommandForm'; +import { RegisterMember } from './RegisterMember'; // generated proxy + +export const RegisterMemberWizard = () => ( + command={RegisterMember} title="Register member"> + + value={i => i.name} title="Name" /> + value={i => i.email} title="Email" /> + + + value={i => i.tier} title="Tier" options={tierOptions} /> + + +); +``` + +## Notes + +- **One command, many steps.** Every field across every panel maps to the *same* command — the wizard is just how you collect it. The command still validates and executes once. +- **Per-step validation.** The user can't advance past a step whose required fields are invalid; use `validateOnInit` if you need a step validated as soon as it's shown. +- **Required values that aren't inputs** still go through `initialValues` (not `onBeforeExecute`), same as a single-step dialog — see [Building a form](/components/building-a-form/). + +## When to use a wizard vs. a plain dialog + +Reach for a wizard when the input is genuinely staged or long enough that one screen would overwhelm. For three or four fields, a single [CommandDialog](/components/building-a-form/) is friendlier — don't add steps for their own sake. + +## Next + +- [Building a form](/components/building-a-form/) — the single-step version and the field set. +- The full stepper options are in the Components reference and Storybook. diff --git a/Documentation/toc.yml b/Documentation/toc.yml index aca6a09..bc3cc43 100644 --- a/Documentation/toc.yml +++ b/Documentation/toc.yml @@ -1,222 +1,50 @@ - name: Overview href: index.md -- name: Storybook - href: storybook.md - items: - - name: CommandDialog - items: - - name: Default - href: storybook.md?story=commanddialog-commanddialog--default - href: storybook.md?story=commanddialog-commanddialog--default - - name: PrimeReact Fields - items: - - name: All Fields - href: storybook.md?story=commandform-primereact-fields--all-fields - - name: Input Text Field Example - href: storybook.md?story=commandform-primereact-fields--input-text-field-example - - name: Number Field Example - href: storybook.md?story=commandform-primereact-fields--number-field-example - - name: Text Area Field Example - href: storybook.md?story=commandform-primereact-fields--text-area-field-example - - name: Dropdown Field Example - href: storybook.md?story=commandform-primereact-fields--dropdown-field-example - - name: Slider Field Example - href: storybook.md?story=commandform-primereact-fields--slider-field-example - - name: Checkbox Field Example - href: storybook.md?story=commandform-primereact-fields--checkbox-field-example - href: storybook.md?story=commandform-primereact-fields--all-fields - - name: Common - items: - - name: ErrorBoundary - href: storybook.md?story=common-errorboundary--default - items: - - name: Default - href: storybook.md?story=common-errorboundary--default - - name: With Error - href: storybook.md?story=common-errorboundary--with-error - - name: FormElement - href: storybook.md?story=common-formelement--default - items: - - name: Default - href: storybook.md?story=common-formelement--default - - name: With Email Icon - href: storybook.md?story=common-formelement--with-email-icon - - name: With Search Icon - href: storybook.md?story=common-formelement--with-search-icon - - name: Page - href: storybook.md?story=common-page--default - items: - - name: Default - href: storybook.md?story=common-page--default - - name: With Panel - href: storybook.md?story=common-page--with-panel - href: storybook.md?story=common-errorboundary--default - - name: DataPage - items: - - name: Default - href: storybook.md?story=datapage-datapage--default - - name: Without Details - href: storybook.md?story=datapage-datapage--without-details - href: storybook.md?story=datapage-datapage--default - - name: DataTables - items: - - name: DataTableForObservableQuery - href: storybook.md?story=datatables-datatableforobservablequery--default - items: - - name: Default - href: storybook.md?story=datatables-datatableforobservablequery--default - - name: With Selection - href: storybook.md?story=datatables-datatableforobservablequery--with-selection - - name: DataTableForQuery - href: storybook.md?story=datatables-datatableforquery--default - items: - - name: Default - href: storybook.md?story=datatables-datatableforquery--default - - name: With Selection - href: storybook.md?story=datatables-datatableforquery--with-selection - href: storybook.md?story=datatables-datatableforobservablequery--default - - name: Dialogs - items: - - name: BusyIndicatorDialog - href: storybook.md?story=dialogs-busyindicatordialog--default - items: - - name: Default - href: storybook.md?story=dialogs-busyindicatordialog--default - - name: ConfirmationDialog - href: storybook.md?story=dialogs-confirmationdialog--default - items: - - name: Default - href: storybook.md?story=dialogs-confirmationdialog--default - href: storybook.md?story=dialogs-busyindicatordialog--default - - name: Components - items: - - name: ObjectContentEditor - href: storybook.md?story=components-objectcontenteditor--default - items: - - name: Default - href: storybook.md?story=components-objectcontenteditor--default - - name: With Timestamp - href: storybook.md?story=components-objectcontenteditor--with-timestamp - - name: Simple Object - href: storybook.md?story=components-objectcontenteditor--simple-object - - name: With Array - href: storybook.md?story=components-objectcontenteditor--with-array - - name: ObjectNavigationalBar - href: storybook.md?story=components-objectnavigationalbar--interactive - items: - - name: Interactive - href: storybook.md?story=components-objectnavigationalbar--interactive - - name: At Root - href: storybook.md?story=components-objectnavigationalbar--at-root - - name: Single Level - href: storybook.md?story=components-objectnavigationalbar--single-level - - name: Deep Path - href: storybook.md?story=components-objectnavigationalbar--deep-path - - name: SchemaEditor - href: storybook.md?story=components-schemaeditor--interactive - items: - - name: Interactive - href: storybook.md?story=components-schemaeditor--interactive - - name: View Mode - href: storybook.md?story=components-schemaeditor--view-mode - - name: Edit Mode - href: storybook.md?story=components-schemaeditor--edit-mode - - name: Read Only - href: storybook.md?story=components-schemaeditor--read-only - - name: Empty Schema - href: storybook.md?story=components-schemaeditor--empty-schema - - name: Simple Schema - href: storybook.md?story=components-schemaeditor--simple-schema - href: storybook.md?story=components-objectcontenteditor--default - - name: PivotViewer - items: - - name: Default - href: storybook.md?story=pivotviewer-pivotviewer--default - href: storybook.md?story=pivotviewer-pivotviewer--default - - name: TimeMachine - items: - - name: EventsView - href: storybook.md?story=timemachine-eventsview--default - items: - - name: Default - href: storybook.md?story=timemachine-eventsview--default - - name: Single Event - href: storybook.md?story=timemachine-eventsview--single-event - - name: Empty Events - href: storybook.md?story=timemachine-eventsview--empty-events - - name: Properties - href: storybook.md?story=timemachine-properties--default - items: - - name: Default - href: storybook.md?story=timemachine-properties--default - - name: Left Aligned - href: storybook.md?story=timemachine-properties--left-aligned - - name: Right Aligned - href: storybook.md?story=timemachine-properties--right-aligned - - name: With Null Values - href: storybook.md?story=timemachine-properties--with-null-values - - name: ReadModelView - href: storybook.md?story=timemachine-readmodelview--default - items: - - name: Default - href: storybook.md?story=timemachine-readmodelview--default - - name: Single Version - href: storybook.md?story=timemachine-readmodelview--single-version - - name: TimeMachine - href: storybook.md?story=timemachine-timemachine--default - items: - - name: Default - href: storybook.md?story=timemachine-timemachine--default - - name: Start At Latest Version - href: storybook.md?story=timemachine-timemachine--start-at-latest-version - - name: High Scroll Sensitivity - href: storybook.md?story=timemachine-timemachine--high-scroll-sensitivity - href: storybook.md?story=timemachine-eventsview--default +- name: Why Components + href: why-components.md +- name: Getting started + href: getting-started.md - name: Styling href: Styling/toc.yml -- name: Migration guide +- name: Building a form + href: building-a-form.md +- name: Displaying data + href: displaying-data.md +- name: Multi-step form + href: multi-step-form.md +- name: A list screen with actions + href: list-screen-with-actions.md +- name: CommandDialog + href: CommandDialog/toc.yml +- name: CommandForm + href: CommandForm/toc.yml +- name: CommandStepper + href: CommandStepper/toc.yml +- name: StepperCommandDialog + href: StepperCommandDialog/toc.yml +- name: DataPage + href: DataPage/toc.yml +- name: DataTables + href: DataTables/toc.yml +- name: Dialogs + href: Dialogs/toc.yml +- name: Dropdown + href: Dropdown/index.md +- name: Toolbar + href: Toolbar/toc.yml +- name: ObjectNavigationalBar + href: ObjectNavigationalBar/index.md +- name: ObjectContentEditor + href: ObjectContentEditor/index.md +- name: PivotViewer + href: PivotViewer/toc.yml +- name: SchemaEditor + href: SchemaEditor/toc.yml +- name: TimeMachine + href: TimeMachine/toc.yml +- name: Common + href: Common/toc.yml +- name: Types + href: Types/toc.yml +- name: Migration href: migration.md -- name: Components - items: - - name: Command Components - items: - - name: CommandDialog - href: CommandDialog/toc.yml - - name: CommandStepper - href: CommandStepper/toc.yml - - name: StepperCommandDialog - href: StepperCommandDialog/toc.yml - - name: CommandForm Field Types - href: CommandForm/toc.yml - - name: Data Components - items: - - name: DataPage - href: DataPage/toc.yml - - name: DataTables - href: DataTables/toc.yml - - name: Common Components - items: - - name: Common - href: Common/toc.yml - - name: Dialogs - href: Dialogs/toc.yml - - name: Dropdown - href: Dropdown/index.md - - name: Toolbar - href: Toolbar/toc.yml - - name: Specialized Components - items: - - name: PivotViewer - href: PivotViewer/toc.yml - - name: TimeMachine - href: TimeMachine/toc.yml - - name: SchemaEditor - href: SchemaEditor/toc.yml - - name: ObjectContentEditor - href: ObjectContentEditor/index.md - - name: ObjectNavigationalBar - href: ObjectNavigationalBar/index.md - - name: Types - items: - - name: Types - href: Types/toc.yml diff --git a/Documentation/why-components.md b/Documentation/why-components.md new file mode 100644 index 0000000..fb3aa7f --- /dev/null +++ b/Documentation/why-components.md @@ -0,0 +1,35 @@ +--- +title: Why Components +description: What the Cratis Components library is for, and why it exists on top of PrimeReact. +sidebar: + order: 1 +--- + +You *could* build your UI straight on PrimeReact (or any component kit) and wire each form and table to your [Arc](/arc/) backend by hand — instantiate the command, manage loading and error state, render the footer buttons, bind each field, call the query, handle the observable subscription. Components does that wiring for you. + +It's a set of React components built on **PrimeReact** that know how to talk to **Arc's generated proxies**. The result: a command form or a live data table is a few declarative lines instead of a few files of glue. + +## What it removes + +| Without Components | With Components | +|---|---| +| Instantiate a command, track `isExecuting`, disable the button, render OK/Cancel | `` does all of it | +| Bind each input to command state and validate by hand | ` i.name} />` — typed to the command | +| Fetch a query, subscribe to the observable, re-render on change | `` / `query.use()` | +| Hand-roll list pages with detail panels | `` | + +## What you get + +- **Command components** — `CommandDialog`, `StepperCommandDialog`, and the full `CommandForm` field set (text, number, dropdown, date, checkbox, slider, …), all typed against your generated command. +- **Data components** — `DataTableForQuery`, `DataTableForObservableQuery`, and `DataPage` for list-and-detail screens. +- **Building blocks** — dialogs, toolbars, navigation, and specialized editors (`PivotViewer`, `TimeMachine`, `SchemaEditor`). +- **Theming that stays out of the way** — use a PrimeReact theme, repaint with a custom palette, or go fully unstyled. See [Styling](/components/styling/). + +## When to use it + +Use Components when you're building a Cratis app and want the UI to track the backend with minimal ceremony. If you only need one-off presentational widgets unrelated to commands/queries, plain PrimeReact is fine — and the two coexist happily. + +## Next + +- [Getting started](/components/getting-started/) — install, wire the provider, render your first form and table. +- [Build a full-stack feature](/build-a-full-app/) — see Components consume a real Arc slice end to end. From cfd0ec8d6b145c09a4d62e7bb8f6f52500eaa97c Mon Sep 17 00:00:00 2001 From: woksin Date: Sat, 30 May 2026 23:55:23 +0200 Subject: [PATCH 02/20] Rebuild the Components overview as a landing page Replace the reference index with an aspire.dev-style Overview: a TopicHero plus Start here, Recipes, and Key components card grids. Co-Authored-By: Claude Opus 4.8 --- Documentation/index.md | 138 ---------------------------------------- Documentation/index.mdx | 62 ++++++++++++++++++ 2 files changed, 62 insertions(+), 138 deletions(-) delete mode 100644 Documentation/index.md create mode 100644 Documentation/index.mdx diff --git a/Documentation/index.md b/Documentation/index.md deleted file mode 100644 index 64bd709..0000000 --- a/Documentation/index.md +++ /dev/null @@ -1,138 +0,0 @@ -# Cratis Components - -The Cratis Components library provides a comprehensive set of reusable React components built on top of PrimeReact, designed specifically for building data-driven applications with the Cratis Arc framework. - -## Overview - -This library includes specialized components for: - -- **Command Handling**: Dialog and form components for executing commands -- **Data Display**: Advanced data tables and pages for queries and observable queries -- **Data Visualization**: Pivot viewers, time machines, and schema editors -- **Common UI Elements**: Pages, dialogs, and form elements -- **Navigation**: Object navigational bars and content editors - -## Key Features - -- Built with TypeScript for type safety -- Integrates seamlessly with Cratis Arc framework -- Uses PrimeReact components for consistent UI/UX -- Supports both queries and observable queries -- Three supported [styling options](Styling/index.md) — use a PrimeReact theme, apply your own palette, or run fully unstyled -- Full PrimeReact `pt` (pass-through) forwarding on every wrapper for per-slot styling -- Comprehensive Storybook documentation -- Accessibility-focused design - -## Getting Started - -### Install - -Install `@cratis/components` along with the required PrimeReact peer dependencies: - -```bash -npm install @cratis/components primereact primeicons -# or -yarn add @cratis/components primereact primeicons -``` - -The optional peers (`pixi.js` for `PivotViewer`, `framer-motion` for animated panels, `allotment` for `DataPage` resizable layout) are only required when you use the corresponding component. - -### Importing Styles - -The library ships pre-compiled utility styles that must be imported once in your application entry point (e.g. `main.tsx`): - -```typescript -import '@cratis/components/styles'; -``` - -This is required because the components use Tailwind utility classes that are compiled into the package at build time, and because it ships the [`--cratis-*` token layer](Styling/cratis-tokens.md) every Cratis-scoped surface reads from. The import works with any bundler (Vite, webpack, Rollup) regardless of whether your application uses Tailwind. - -If you bring your own Tailwind setup and want only the token layer, import `@cratis/components/tokens` instead. - -### Wire the Provider - -Mount [`CratisComponentsProvider`](Common/cratis-components-provider.md) at the root of your tree to configure PrimeReact's global settings — `unstyled`, `pt`, `ptOptions`, locale, ripple, overlay z-index, and the rest: - -```tsx -import '@cratis/components/styles'; -import { CratisComponentsProvider } from '@cratis/components'; - -export const App = () => ( - - - -); -``` - -### Choose how styling works - -The components render structurally as soon as the provider is mounted. To give them a visual identity, choose the setup that matches how much control you need: - -- [**Use a PrimeReact theme**](Styling/themed.md) — load a theme stylesheet and tweak with CSS or `className` as needed. -- [**Use a custom palette on top of a PrimeReact theme**](Styling/custom-palette.md) — keep the theme's structure and repaint with CSS variables on `:root`. -- [**Use fully unstyled mode**](Styling/unstyled.md) — disable PrimeReact's base styles and provide the visuals through a `pt` preset in CSS or Tailwind. - -See the [Styling overview](Styling/index.md) for the full mental model. - -### Importing Components - -All components are exported from the main package and can be imported as needed: - -```typescript -import { CommandDialog, DataPage, PivotViewer } from '@cratis/components'; -``` - -For tree-shaking-friendly subpath imports: - -```typescript -import { Dialog } from '@cratis/components/Dialogs'; -import { DataPage } from '@cratis/components/DataPage'; -import { InputTextField } from '@cratis/components/CommandForm/fields'; -``` - -## Component Categories - -### Command Components - -Components for handling command execution and user interactions — `CommandDialog`, `StepperCommandDialog`, `CommandStepper`, and the full `CommandForm/fields` set (text, number, dropdown, date, checkbox, radio, slider, multiselect, chips, color picker). - -### Data Components - -Components for displaying and interacting with data from queries — `DataPage`, `DataTableForQuery`, `DataTableForObservableQuery`. - -### Common Components - -Reusable UI elements for building consistent layouts — [`CratisComponentsProvider`](Common/cratis-components-provider.md), `Page`, `FormElement`, `Icon`, `Tooltip`, `ErrorBoundary`. - -### Specialized Components - -Advanced components for specific use cases — `PivotViewer`, `TimeMachine`, `SchemaEditor`, `ObjectContentEditor`, `ObjectNavigationalBar`. - -## Styling - -Styling is designed to stay out of the way: choose the setup that matches how much control you want, and the other layers stay invisible. See the dedicated [Styling section](Styling/index.md) for: - -- [Getting Started](Styling/getting-started.md) — the one-line setup every option shares -- [Use a PrimeReact theme](Styling/themed.md) -- [Use a custom palette on top of a PrimeReact theme](Styling/custom-palette.md) -- [Use fully unstyled mode](Styling/unstyled.md) -- [Cratis token reference](Styling/cratis-tokens.md) -- [Pass-through (pt) cheat sheet](Styling/pass-through.md) -- [Combining styling setups](Styling/mixing-paths.md) - -## Upgrading from an earlier release - -See the [Migration guide](migration.md) for the required install changes (PrimeReact and PrimeIcons are now peer dependencies), the visual differences to be aware of, and a tour of the new optional capabilities (Cratis Components Provider, `--cratis-*` tokens, full `pt` forwarding). - -## Development - -The components are built using: - -- Vite for development and bundling -- Vitest for testing -- Storybook for component documentation (`yarn dev` from `Source/`) -- yarn for package management - -## Documentation - -Each component includes comprehensive documentation and examples in Storybook. See the individual component pages for detailed usage instructions, props documentation, and live examples. diff --git a/Documentation/index.mdx b/Documentation/index.mdx new file mode 100644 index 0000000..5a47314 --- /dev/null +++ b/Documentation/index.mdx @@ -0,0 +1,62 @@ +--- +title: Components +description: The React component library for Cratis — command dialogs, forms, and data tables that consume Arc's generated proxies. +--- + +import { CardGrid } from '@astrojs/starlight/components'; +import SimpleCard from '@components/SimpleCard.astro'; +import TopicHero from '@components/TopicHero.astro'; + + +Command dialogs, forms, and data tables that consume [Arc's](/arc/) generated proxies — so a screen is a few lines, not a few files. Built on PrimeReact, fully typed, and styled the way you choose. [Get started →](/components/getting-started/) · [Why Components? →](/components/why-components/) + + +## Start here + + + + Install the package, mount the provider, and render your first proxy-driven screen. + + + What the library buys you over wiring PrimeReact to your proxies by hand. + + + Three paths — a PrimeReact theme, your own palette, or fully unstyled with a `pt` preset. + + + +## Recipes + + + + Collect input and execute a command with `CommandDialog` and the `CommandForm` fields. + + + Render a query or observable query with `DataPage` and the data-table wrappers. + + + Gather information across named stages with `StepperCommandDialog`. + + + A full screen: list rows, add and edit through dialogs, and react to selection. + + + +## Key components + + + + Instantiates, validates, and executes a generated command, with the footer handled for you. + + + A resizable page that lists query data with toolbar actions and detail panels. + + + Typed input fields bound to command properties — text, number, dropdown, date, and more. + + + Data-collection dialogs that return values without executing a command. + + + +Components renders [Arc's](/arc/) proxies over [Chronicle](/chronicle/) read models — see [Why Cratis](/why-cratis/) for the full stack. From a644746952236e6c299142643334dbfceb04818b Mon Sep 17 00:00:00 2001 From: woksin Date: Sun, 31 May 2026 09:57:52 +0200 Subject: [PATCH 03/20] Add a 'choosing a component' decision guide Help readers pick between the overlapping components: CommandDialog vs StepperCommandDialog vs CommandForm vs Dialog for input, and DataPage vs DataTables for display, with rules of thumb and links to the recipes. Co-Authored-By: Claude Opus 4.8 --- Documentation/choosing-a-component.md | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/choosing-a-component.md diff --git a/Documentation/choosing-a-component.md b/Documentation/choosing-a-component.md new file mode 100644 index 0000000..366cb7f --- /dev/null +++ b/Documentation/choosing-a-component.md @@ -0,0 +1,46 @@ +--- +title: Choosing a component +description: A decision guide for the overlapping Components — CommandDialog vs StepperCommandDialog, DataPage vs DataTables, and Dialog vs CommandDialog. +--- + +Several Components solve similar-looking problems, and it's not always obvious which one to reach for. +This page is the decision guide: pick by what you're building, then follow the link to the recipe or +reference. + +## Collecting input + +The question is whether confirming the form **runs a command**, and whether it's one step or several. + +| You want to… | Use | Why | +| --- | --- | --- | +| Collect a few fields and run one command | [`CommandDialog`](./CommandDialog/index.md) | Instantiates, validates, and executes the command; handles the footer and button states. The default. | +| Run a command, but gather input across **named steps** | [`StepperCommandDialog`](./StepperCommandDialog/index.md) | A wizard over a single command — validate per step, navigate back and forth, execute at the end. | +| Embed command fields **in a page**, not a dialog | [`CommandForm`](./CommandForm/index.md) | The same typed fields `CommandDialog` uses, without the dialog chrome. | +| Collect data and return it **without** running a command | [`Dialog`](./Dialogs/index.md) | A confirmation or data-entry dialog that hands values back to the caller. No command involved. | + +Rule of thumb: **if confirming the dialog executes a generated command, it's a `CommandDialog`** (or its +stepper variant). If it just gathers values and returns them, it's a `Dialog`. Never reach for +PrimeReact's raw `Dialog` — these wrappers handle validation timing, loading state, and footers +consistently. + +## Displaying data + +Both render a query or observable query; the question is whether you want a **whole page** or a **table +to drop into one**. + +| You want to… | Use | Why | +| --- | --- | --- | +| A full screen: a list, a toolbar of actions, detail panels | [`DataPage`](./DataPage/index.md) | A resizable page composition with toolbar and detail areas wired to the query. | +| Just a grid inside a layout you already have | [`DataTables`](./DataTables/index.md) | The table wrappers — paging, sorting, and selection over a query — without the page scaffolding. | + +If you're building a list-screen-with-actions from scratch, start with the +[list screen recipe](./list-screen-with-actions.md), which composes `DataPage` with `CommandDialog` +actions. + +## Putting it together + +A typical CRUD screen combines these: a `DataPage` lists the rows, a toolbar button opens a +`CommandDialog` to add one, and selecting a row opens another `CommandDialog` to edit it. That whole +screen is the [list screen with actions](./list-screen-with-actions.md) recipe. + +Still deciding how to style any of this? See [Styling](./Styling/index.md). From 27ef6f63d84e666a60d80efede6740cae8046139 Mon Sep 17 00:00:00 2001 From: woksin Date: Sun, 31 May 2026 13:21:29 +0200 Subject: [PATCH 04/20] Rewrite Components getting-started as a guided tour Reshape the flat install page into the project's guided-tour voice, matching the Chronicle get-started exemplar. Authored as .mdx with Steps and an Aside, threaded to the same RegisterAuthor/AllAuthors slice used across the Arc getting-started pages. Adds a "what the provider sets up" explanation, a Mermaid diagram showing where Components sits between the generated proxies and PrimeReact, and a CommandDialog snippet so the payoff is visible. Ends with a recap and forward links. Co-Authored-By: Claude Opus 4.8 --- Documentation/getting-started.md | 59 ----------------- Documentation/getting-started.mdx | 101 ++++++++++++++++++++++++++++++ Documentation/toc.yml | 2 +- 3 files changed, 102 insertions(+), 60 deletions(-) delete mode 100644 Documentation/getting-started.md create mode 100644 Documentation/getting-started.mdx diff --git a/Documentation/getting-started.md b/Documentation/getting-started.md deleted file mode 100644 index c5f3db4..0000000 --- a/Documentation/getting-started.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Getting started -description: Install Components, wire the provider, and render your first command form and data table. -sidebar: - order: 2 ---- - -This gets `@cratis/components` installed and rendering. By the end you'll have the provider mounted and know where to go for forms and tables. - -## 1. Install - -Install the package and its PrimeReact peers: - -```bash -npm install @cratis/components primereact primeicons -``` - -The optional peers (`pixi.js` for `PivotViewer`, `framer-motion` for animated panels, `allotment` for `DataPage`) are only needed when you use those components. - -## 2. Import the styles - -Import the pre-compiled utility styles once, at your app entry point: - -```typescript -import '@cratis/components/styles'; -``` - -This works with any bundler and ships the [`--cratis-*` token layer](/components/styling/) the components read from. - -## 3. Mount the provider - -Wrap your app in `CratisComponentsProvider`. It configures PrimeReact's global settings (unstyled mode, pass-through, locale, ripple, overlay z-index): - -```tsx -import '@cratis/components/styles'; -import { CratisComponentsProvider } from '@cratis/components'; - -export const App = () => ( - - - -); -``` - -## 4. Choose how styling works - -The components render structurally as soon as the provider is mounted. Give them a visual identity by picking one setup — see [Styling](/components/styling/): - -- a PrimeReact theme, -- a custom palette on top of a theme, or -- fully unstyled with your own pass-through preset. - -## Next - -Now build something: - -- [Building a form](/components/building-a-form/) — execute a command with `CommandDialog` and form fields. -- [Displaying data](/components/displaying-data/) — show query results in a live data table. -- The full, interactive component catalog is in **Storybook** (see the Components reference pages). diff --git a/Documentation/getting-started.mdx b/Documentation/getting-started.mdx new file mode 100644 index 0000000..8f95703 --- /dev/null +++ b/Documentation/getting-started.mdx @@ -0,0 +1,101 @@ +--- +title: Getting started +description: Install Components, mount the provider, and render your first type-safe command form — a few declarative lines instead of a few files of glue. +sidebar: + order: 2 +--- + +import { Steps, Aside } from '@astrojs/starlight/components'; + +You've built an Arc backend — a `RegisterAuthor` command, an `AllAuthors` query — and `dotnet build` has generated the typed proxies for both. Now you need a screen: a form to add an author, a table that lists them and refreshes itself when one's added. + +You *could* build that straight on PrimeReact — instantiate the command, track its executing state, disable the button while it runs, render OK/Cancel, bind every field, call the query, subscribe to its observable, re-render on change. That's a few files of glue for one screen. `@cratis/components` already knows how to do all of it against your generated proxies, so the same screen is a few declarative lines. Let's wire it up. + + + +## Install and wire it up + + + +1. **Install the package and its PrimeReact peers.** + + ```bash title="Install Components" + npm install @cratis/components primereact primeicons + ``` + + A few components pull extra peers only when you use them — `pixi.js` for `PivotViewer`, `framer-motion` for animated panels, `allotment` for `DataPage`. Skip them until you reach for those. + +2. **Import the styles once**, at your app's entry point: + + ```typescript title="main.tsx" + import '@cratis/components/styles'; + ``` + + This ships the pre-compiled utility styles and the `--cratis-*` [token layer](/components/styling/) the components read their colors and spacing from. It works with any bundler. + +3. **Mount the provider** around your app: + + ```tsx title="App.tsx" + import '@cratis/components/styles'; + import { CratisComponentsProvider } from '@cratis/components'; + + export const App = () => ( + + + + ); + ``` + + + +## What the provider sets up + +That one wrapper does more than it looks. Components renders PrimeReact in **unstyled mode** with a pass-through preset, configures locale, ripple, and overlay z-index, and routes every component's look through the `--cratis-*` tokens you just imported. The upshot: everything below it renders structurally right away, and it takes its visual identity from tokens you control — not from styles baked into the library. + +```mermaid +flowchart LR + Proxy["generated proxy
RegisterAuthor · AllAuthors"] --> C["Components
CommandDialog · DataTable"] + C --> PR["PrimeReact (unstyled)"] + Tokens["--cratis-* tokens"] -.->|theming| PR +``` + +The components sit between your typed proxies and PrimeReact: they speak commands and queries on one side, render PrimeReact on the other, and take their styling from tokens. + +## See the payoff + +Here's the add-author form — the whole thing. `CommandDialog` takes your generated command, renders the fields and the confirm/cancel buttons, and disables confirm while it executes: + +```tsx title="AddAuthor.tsx" +import { CommandDialog } from '@cratis/components/CommandDialog'; +import { InputTextField } from '@cratis/components/CommandForm'; +import { RegisterAuthor } from './Authors/RegisterAuthor'; // generated proxy + +export const AddAuthor = () => ( + command={RegisterAuthor} title="Add author" okLabel="Add"> + value={i => i.name} title="Name" /> +
+); +``` + +No `isExecuting` flag, no button wiring, no validation written by hand — and because `RegisterAuthor` is generated from your C#, `i => i.name` is type-checked against the real command. Rename the property in C#, rebuild, and this line stops compiling until you fix it. That's the few-lines-not-few-files promise, with the type safety to back it. + +## Choose how it looks + +The components render structurally the moment the provider is mounted; their *look* is a separate, deliberate choice. Pick one setup on the [Styling](/components/styling/) page: + +- a ready-made PrimeReact theme, +- a theme repainted with a custom `--cratis-*` palette, or +- fully unstyled, with your own pass-through preset. + +## Recap + +You installed `@cratis/components`, imported its styles, and wrapped your app in `CratisComponentsProvider` — and with that one provider in place, a typed command form is a handful of lines instead of a handful of files. Everything else in the library works the same way: declare it, point it at a proxy, done. + +## Where to go next + +- **[Building a form](/components/building-a-form/)** — `CommandDialog`, field types, validation, and multi-step wizards. +- **[Displaying data](/components/displaying-data/)** — live data tables that re-render when the read model changes. +- **[Choosing a component](/components/choosing-a-component/)** — a decision guide from "what's on the screen" to the right component. +- **[Build a full-stack feature](/build-a-full-app/)** — see Components consume a real Arc slice end to end. diff --git a/Documentation/toc.yml b/Documentation/toc.yml index bc3cc43..651d7ed 100644 --- a/Documentation/toc.yml +++ b/Documentation/toc.yml @@ -3,7 +3,7 @@ - name: Why Components href: why-components.md - name: Getting started - href: getting-started.md + href: getting-started.mdx - name: Styling href: Styling/toc.yml - name: Building a form From 8aa42a8107cf1a34fe5d7104858e324e79f1862d Mon Sep 17 00:00:00 2001 From: woksin Date: Sun, 31 May 2026 14:58:09 +0200 Subject: [PATCH 05/20] Add Components tutorial and a Coming from PrimeReact bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A threaded three-chapter tutorial builds one library screen on the Arc proxies — a live data table, command-driven add/edit/remove, and a DataPage list-and-detail view. A migration bridge maps the PrimeReact forms and tables you'd hand-wire onto their Components equivalents, side by side. Also surfaces the existing Choosing a component guide in the sidebar. Co-Authored-By: Claude Opus 4.8 --- Documentation/coming-from-primereact.md | 127 +++++++++++++++++++++ Documentation/toc.yml | 6 + Documentation/tutorial/act-on-it.mdx | 90 +++++++++++++++ Documentation/tutorial/index.md | 48 ++++++++ Documentation/tutorial/list-and-detail.mdx | 88 ++++++++++++++ Documentation/tutorial/list-it.mdx | 51 +++++++++ Documentation/tutorial/toc.yml | 8 ++ 7 files changed, 418 insertions(+) create mode 100644 Documentation/coming-from-primereact.md create mode 100644 Documentation/tutorial/act-on-it.mdx create mode 100644 Documentation/tutorial/index.md create mode 100644 Documentation/tutorial/list-and-detail.mdx create mode 100644 Documentation/tutorial/list-it.mdx create mode 100644 Documentation/tutorial/toc.yml diff --git a/Documentation/coming-from-primereact.md b/Documentation/coming-from-primereact.md new file mode 100644 index 0000000..7e138dc --- /dev/null +++ b/Documentation/coming-from-primereact.md @@ -0,0 +1,127 @@ +--- +title: Coming from PrimeReact +description: You already know PrimeReact. Here's how the forms and tables you'd hand-wire map onto Cratis Components — concept by concept, side by side — and what changes. +--- + +Cratis Components isn't a different component kit — it's built **on** PrimeReact. Your theme, your `Column`, your `Button`, your icons all still apply. What changes is the *wiring*: instead of binding a form to component state by hand and fetching a query in a `useEffect`, you point a component at an Arc-generated proxy and the binding is done for you. This page maps the PrimeReact code you'd write today onto its Components equivalent, so the shift is short. + +## The one-paragraph version + +In a PrimeReact app you build a screen and connect it to your backend yourself: instantiate a request, track loading, render the dialog footer, bind each input, call your API, subscribe to updates. Components knows how to do all of that against your generated command and query proxies — so a form or a table is a few declarative lines, and it's type-checked against the C# it came from. You keep PrimeReact; you drop the glue. + +## A command form + +You have a dialog with a field and a save button. By hand, that's local state, a loading flag, a fetch, and footer buttons: + +```tsx +// PrimeReact, by hand +const [name, setName] = useState(''); +const [saving, setSaving] = useState(false); + +const save = async () => { + setSaving(true); + await fetch('/api/authors/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name }), + }); + setSaving(false); +}; + + + +``` + +With Components, the command *is* the form — instantiation, the footer, the executing state, and validation are handled: + +```tsx +// Components + command={RegisterAuthor} title="Add author" okLabel="Add"> + value={i => i.name} title="Name" /> + +``` + +`i => i.name` is typed against the generated `RegisterAuthor` — rename the property in C#, rebuild, and this line stops compiling. + +## A data table + +By hand, a table means fetching on mount, holding rows in state, and — if you want live data — wiring up a subscription: + +```tsx +// PrimeReact, by hand +const [authors, setAuthors] = useState([]); + +useEffect(() => { + fetch('/api/authors').then(r => r.json()).then(setAuthors); + // ...and a subscription if you want it to stay current +}, []); + + + + +``` + +With Components, you hand the table the query proxy; it subscribes and re-renders as the read model changes: + +```tsx +// Components + + + +``` + +The `Column` is the same PrimeReact component you already use. Only the data binding changed — and it stays live with no subscription code. + +## A list-and-detail page + +The "table left, details right, toolbar on top" layout — split panes, selection state, showing the panel only when a row is picked — is a lot of plumbing by hand. `DataPage` is that layout as one component: + +```tsx +// Components + a.name}> + + showAddAuthor()} /> + + + + + + +``` + +Selection, the resizable split, and disabling menu items until a row is selected all come built in. + +## How the pieces map + +| You know (PrimeReact, by hand) | In Components | +|---|---| +| `Dialog` + footer `Button`s + a fetch | `CommandDialog command={...}` | +| `InputText` + `useState` + manual validation | `InputTextField value={i => i.field}` — typed to the command | +| `DataTable value={...}` + `useEffect` fetch | `DataTableForObservableQuery query={...}` (live) or `DataTableForQuery` | +| Split panes + selection + detail wiring | `DataPage` with `DetailsComponent` | +| A multi-step wizard you build yourself | `StepperCommandDialog` | +| A PrimeReact theme | the same theme, plus `--cratis-*` tokens for repainting | + +## What stays the same + +- It's still PrimeReact underneath. `Column`, `Button`, icons, and your chosen theme all work as you know them. +- You keep using plain PrimeReact for purely presentational widgets that aren't tied to a command or query — the two coexist happily on the same screen. +- Your styling knowledge carries over; Components renders PrimeReact in unstyled mode and reads colors and spacing from tokens you control. See [Styling](/components/styling/). + +## What changes (and why it's less code) + +- **Forms bind to commands, not to local state.** No `useState` per field, no loading flag, no hand-written validation — the command carries its own rules, and the proxy runs them on both sides. +- **Tables bind to queries, not to fetched arrays.** An observable query keeps the table current with no subscription code and no "refresh after save." +- **The binding is type-checked.** Field accessors and column fields line up with the generated types, so a backend change that breaks the screen is a compile error, not a runtime surprise. + +## Where to go next + +- [Getting started](/components/getting-started/) — install Components and mount the provider. +- [Build the library screen](/components/tutorial/) — the same ideas built up one screen at a time. +- [Why Components](/components/why-components/) — the case for the library, and when plain PrimeReact is the better choice. diff --git a/Documentation/toc.yml b/Documentation/toc.yml index 651d7ed..54430e3 100644 --- a/Documentation/toc.yml +++ b/Documentation/toc.yml @@ -2,8 +2,14 @@ href: index.md - name: Why Components href: why-components.md +- name: Coming from PrimeReact + href: coming-from-primereact.md - name: Getting started href: getting-started.mdx +- name: Tutorial + href: tutorial/toc.yml +- name: Choosing a component + href: choosing-a-component.md - name: Styling href: Styling/toc.yml - name: Building a form diff --git a/Documentation/tutorial/act-on-it.mdx b/Documentation/tutorial/act-on-it.mdx new file mode 100644 index 0000000..01f8af4 --- /dev/null +++ b/Documentation/tutorial/act-on-it.mdx @@ -0,0 +1,90 @@ +--- +title: "2. Act on the list" +description: Add, rename, and remove authors with typed command dialogs — and watch the live table update itself the moment a command lands. +--- + +import { Steps, Aside } from '@astrojs/starlight/components'; + +The table lists authors; now a librarian needs to *change* the list. Every change is an Arc command — `RegisterAuthor`, `RenameAuthor`, `RemoveAuthor` — and Components turns each one into a form-with-a-button without you binding a single field. Let's start with adding. + +## Add an author + + + +1. **Build the dialog from the command.** `CommandDialog` takes the generated command, renders the fields you list plus the OK/Cancel footer, validates, and executes on confirm. The field accessor `i => i.name` is typed against `RegisterAuthor`: + + ```tsx title="AddAuthor.tsx" + import { CommandDialog } from '@cratis/components/CommandDialog'; + import { InputTextField } from '@cratis/components/CommandForm'; + import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs'; + import { RegisterAuthor } from './Authors/RegisterAuthor'; // generated proxy + + export const AddAuthor = ({ closeDialog }: DialogProps) => ( + + command={RegisterAuthor} + title="Add author" + okLabel="Add" + onConfirm={() => closeDialog(DialogResult.Ok)}> + value={i => i.name} title="Name" placeholder="Jane Austen" /> + + ); + ``` + +2. **Open it from the screen.** `useDialog` gives you a wrapper element to render and a function to show it. Drop a toolbar button next to the table from chapter 1: + + ```tsx title="Authors.tsx" + import { Button } from 'primereact/button'; + import { useDialog } from '@cratis/arc.react/dialogs'; + import { AddAuthor } from './AddAuthor'; + + export const Authors = () => { + const [AddAuthorDialog, showAddAuthor] = useDialog(AddAuthor); + return ( + + - + + emptyMessage="No authors yet"> + + showAddAuthor()} /> + + + + + ); From 6531562ec3527dd5b7ccefb643ff53c674c50aed Mon Sep 17 00:00:00 2001 From: woksin Date: Mon, 1 Jun 2026 15:45:48 +0200 Subject: [PATCH 08/20] Add Cratis docs AI rules and skills (shared from Documentation) The documentation-authoring rules (tour voice, structure & formatting, the cross-repo edit->sync->verify workflow, rendering/QA, correct examples) and skills (edit/add/qa docs) live canonically in the Documentation repo's .ai/ and are shared to every repo. Copied here manually ahead of the propagation-on-merge so docs work has the guidance now. Co-Authored-By: Claude Opus 4.8 --- .ai/rules/astro-starlight-site.md | 47 ++++++++++ .ai/rules/documentation-rendering-and-qa.md | 49 +++++++++++ .../documentation-structure-and-formatting.md | 86 +++++++++++++++++++ .ai/rules/editing-cratis-docs.md | 54 ++++++++++++ .ai/rules/writing-correct-examples.md | 35 ++++++++ .ai/rules/writing-cratis-docs.md | 75 ++++++++++++++++ .ai/skills/add-cratis-docs-page/SKILL.md | 41 +++++++++ .ai/skills/edit-cratis-docs/SKILL.md | 48 +++++++++++ .ai/skills/qa-cratis-docs/SKILL.md | 49 +++++++++++ .claude/rules/astro-starlight-site.md | 1 + .../rules/documentation-rendering-and-qa.md | 1 + .../documentation-structure-and-formatting.md | 1 + .claude/rules/editing-cratis-docs.md | 1 + .claude/rules/writing-correct-examples.md | 1 + .claude/rules/writing-cratis-docs.md | 1 + .../astro-starlight-site.instructions.md | 1 + ...mentation-rendering-and-qa.instructions.md | 1 + ...n-structure-and-formatting.instructions.md | 1 + .../editing-cratis-docs.instructions.md | 1 + .../writing-correct-examples.instructions.md | 1 + .../writing-cratis-docs.instructions.md | 1 + .github/skills | 1 + 22 files changed, 497 insertions(+) create mode 100644 .ai/rules/astro-starlight-site.md create mode 100644 .ai/rules/documentation-rendering-and-qa.md create mode 100644 .ai/rules/documentation-structure-and-formatting.md create mode 100644 .ai/rules/editing-cratis-docs.md create mode 100644 .ai/rules/writing-correct-examples.md create mode 100644 .ai/rules/writing-cratis-docs.md create mode 100644 .ai/skills/add-cratis-docs-page/SKILL.md create mode 100644 .ai/skills/edit-cratis-docs/SKILL.md create mode 100644 .ai/skills/qa-cratis-docs/SKILL.md create mode 120000 .claude/rules/astro-starlight-site.md create mode 120000 .claude/rules/documentation-rendering-and-qa.md create mode 120000 .claude/rules/documentation-structure-and-formatting.md create mode 120000 .claude/rules/editing-cratis-docs.md create mode 120000 .claude/rules/writing-correct-examples.md create mode 120000 .claude/rules/writing-cratis-docs.md create mode 120000 .github/instructions/astro-starlight-site.instructions.md create mode 120000 .github/instructions/documentation-rendering-and-qa.instructions.md create mode 120000 .github/instructions/documentation-structure-and-formatting.instructions.md create mode 120000 .github/instructions/editing-cratis-docs.instructions.md create mode 120000 .github/instructions/writing-correct-examples.instructions.md create mode 120000 .github/instructions/writing-cratis-docs.instructions.md create mode 120000 .github/skills diff --git a/.ai/rules/astro-starlight-site.md b/.ai/rules/astro-starlight-site.md new file mode 100644 index 0000000..d6cfd26 --- /dev/null +++ b/.ai/rules/astro-starlight-site.md @@ -0,0 +1,47 @@ +--- +applyTo: "web/**" +paths: + - "web/**" +--- + +# The Astro Starlight Docs Site + +The published docs site lives in `Documentation/web/` — an **Astro Starlight** app that aggregates every product's docs into one site. Read this before changing anything in `web/`. + +## Content is converted, not owned + +**Source of truth = each product repo's `Documentation/` folder** (cloned as a sibling: `/{Chronicle,Arc,Components,Fundamentals,cli,.github}`). At build time `web/scripts/sync-content.mjs` converts that DocFX-style Markdown into Starlight content under `web/src/content/docs//`. + +- **Never edit `web/src/content/docs/{chronicle,arc,components,cli,fundamentals,contributing}/`** — it's generated and git-ignored. Edit the product-repo source and re-sync (`npm run sync`). +- **Site-level pages** (owned by no product) ARE authored directly in `web/src/content/docs/` as `.mdx`: `index.mdx` (splash front door), `why-cratis.mdx`, `cratis-stack.mdx`, `adopting-cratis.mdx`, `ai-native-development.mdx`, `studio.mdx`, `comparisons/*`, etc. + +## Navigation + +Per-product `toc.yml` → Diátaxis **buckets** (defined in `sync-content.mjs`'s `PRODUCTS[].buckets`) → emitted to **`src/generated/topics.json`**, which `astro.config.mjs` imports. **`topics.json` is the real sidebar source — NOT the stale `src/generated/sidebar.json`** (don't be fooled inspecting it). To add a product page: create it in the product repo, add a `toc.yml` entry, and add that entry's `name` to the right bucket's `sections` array in `sync-content.mjs`. Site-level pages are wired in `astro.config.mjs` (`overviewTopic.items` + the `overview` glob). + +## `.mdx` and links + +The converter **keeps `.mdx`** (sync-content.mjs ~line 304). Author rich pages as `.mdx`: frontmatter `title`+`description`, no body H1, import `{ Steps, Tabs, TabItem, Aside }` from `@astrojs/starlight/components` and shared components from `@components` (`FullStackTabs` = synced C#/TS tabs, `TopicHero`, `SimpleCard`, `StackDiagram`). When renaming `.md`→`.mdx`, update the `toc.yml` href too. + +- **Intra-doc links to a `.mdx` page must be EXTENSION-LESS** (`./validation`, not `./validation.mdx`) — the converter's slugify strips the dot and breaks the link. Links to `.md` keep the extension fine (the converter strips `.md`). +- Site-level `.mdx` pages do NOT go through the converter — use clean root-relative URLs (`/arc/...`), never `.md`. +- Astro slug rules (in `slugify`): lowercase, **strip dots**, keep `-`/`_`. Cross-product links are root-relative `/chronicle/...`. + +## The QA gate + +`npm run check` = `build` + `lint-docs` (fails on non-descriptive link text + leftover DocFX-isms; advisory Google/MS style warnings) + `check-links` (HARD gate: every internal link resolves) + `lint-prose` (Vale, graceful) + `lint-markdown` (markdownlint, graceful) + `check-external` (lychee, graceful). The three optional linters **skip when the tool isn't installed** (install in CI / locally to activate). Must end **0 error(s)** and **0 broken**. Run it after every change. + +## Related rules + +- **Where content lives + the edit→sync→verify loop across repos:** [Editing Cratis Documentation](./editing-cratis-docs.md). +- **Mermaid build-time pre-rendering, fonts, GFM tables, and headless visual QA:** [Documentation Rendering & QA](./documentation-rendering-and-qa.md). + +## Gotchas (hard-won) + +- **A running `npm run dev` degrades** (every page 500s / GFM tables stop rendering / `RenderError: slug "…" does not exist`) after a long session, or when **`npm run check`/`build` runs against the same tree while dev is live** (the build re-syncs content dev is watching). Fix: **restart `npm run dev`**. Re-verify a fresh dev server before trusting any "X doesn't render". +- **Stale Astro content cache** (`.astro/`, `node_modules/.astro`) silently serves a PARTIAL prior render. If a change won't take, `rm -rf .astro node_modules/.astro` and rebuild. +- Sections whose landing is `overview.md` (no `index.md`) **404 on the bare URL** (e.g. `/arc/backend/tenancy/`) — link to a specific page (`/arc/backend/tenancy/overview/`). +- **`SimpleCard`/`TopicHero` link props are JSX attributes — `check-links` does NOT validate them.** Verify card `link="…"` targets by hand. +- Mermaid diagrams are **pre-rendered to SVG at build time** (see the rendering rule); the front door splash diagram renders fine. `git reset --hard` is blocked by the harness — use `git revert` to undo a commit. +- Commit completed, green, logical units locally; **don't push or open PRs without explicit approval**. +- Verify a file with a fresh `Read` immediately before an `Edit`; a failed Edit means your read was stale — re-Read, don't force it. diff --git a/.ai/rules/documentation-rendering-and-qa.md b/.ai/rules/documentation-rendering-and-qa.md new file mode 100644 index 0000000..eb54c2c --- /dev/null +++ b/.ai/rules/documentation-rendering-and-qa.md @@ -0,0 +1,49 @@ +--- +applyTo: "**/Documentation/web/**" +paths: + - "**/Documentation/web/**" +--- + +# Documentation Rendering & Visual QA + +How the docs site turns Markdown into pixels, and how to verify it looks right. These mechanisms exist because the obvious defaults each caused a real, user-reported bug. Don't undo them without understanding why. + +## Mermaid diagrams are rendered at BUILD time + +`web/scripts/mermaid-prerender.mjs` (a remark plugin wired into `astro.config.mjs` `markdown.remarkPlugins`, before astro-mermaid) renders every ```mermaid block to SVG **at build time** using the **system Chrome over CDP — no npm dependency** — and inlines it. This is why diagrams ship in the HTML: no client-side draw delay, no layout shift, correct scroll restoration. + +**Rules / gotchas (each one cost a debugging round):** +- **`autoTheme` MUST stay `false`** on the `astro-mermaid` integration. Its theme-change observer strips `data-processed` from every `pre.mermaid` and re-renders client-side — which **clobbers the pre-rendered SVGs**. Light/dark still works because `cratis.css` themes the SVG via CSS variables that flip with the theme; the JS re-render isn't needed. +- The inlined SVG gets an inline **`aspect-ratio` (from its viewBox) + `width:100%`** (`makeResponsive()`), so the browser reserves its height at first paint — otherwise it settles/reflows, or squishes to a thin strip with naive `height:auto`. +- **Clear `.astro`/`node_modules/.astro` between iterations** — stale Astro content cache serves a PARTIAL prior render (looks like only some diagrams rendered). +- **Graceful fallback:** no Chrome / a render error → the block falls through to astro-mermaid's client-side rendering, so the build can't break. In CI set `CHROME_PATH` if Chrome isn't at a standard path (GitHub `ubuntu-latest` has it). +- **Invalid Mermaid falls back silently.** Watch for syntax the parser rejects — e.g. `()` in an *unquoted* edge label (`-->|.use() hook|`) breaks it; quote the label (`-->|".use() hook"|`). +- Diagram colors/frame are themed entirely in `cratis.css` (the "Mermaid diagrams" section), using the brand accent + Starlight gray CSS vars. Mermaid's own SVG rules use `#id` selectors but **no `!important`**, so class selectors with `!important` win. Never bump label `font-weight` via CSS post-render — it widens text past the measured box and clips. + +## Fonts: `font-display: optional`, not `swap` + +The brand fonts are declared in `web/src/components/Head.astro` (a Starlight `` override) with **`font-display: optional` + ``**, and the `@fontsource/*/index.css` imports are removed from `astro.config` `customCss`. This is deliberate: @fontsource ships `font-display: swap`, which **re-wraps text when the web font arrives after first paint** — the "page grows then pops" twitch on load/refresh. `optional` means the browser uses the (preloaded) font if it's ready in a short window, else keeps the metric-matched fallback for that load — **never a mid-page swap**. Metric-matched fallback `@font-face`s in `cratis.css` cover the brief first-paint case. If you change font loading, re-verify with `document.fonts.check(...)` that Inter/JetBrains Mono still load AND that there's no reflow. + +## GFM tables need `remark-gfm` explicitly + +`markdown.remarkPlugins` includes `remarkGfm`. Without it, **tables render as raw `|` pipe text on every `.mdx` page** (the front door's comparison table, why-cratis, …) while plain `.md` is fine — because astro-mermaid injects plugins via the deprecated `markdown.remarkPlugins` path, which leaves `@astrojs/mdx`'s own `gfm` flag falsy. Diagnose with `grep -c '/index.html` after a build. + +## Visual + layout-shift QA (headless, no extra deps) + +`shot-scraper`/Playwright aren't installed, but Chrome is. Use the committed **`web/scripts/screenshot.mjs`** to capture any page in **light or dark**, full-page: + +```bash +cd Documentation/web && npm run dev # serve at http://localhost:4321 +node scripts/screenshot.mjs http://localhost:4321/chronicle/concepts/event-source/ /tmp/es.png dark +node scripts/screenshot.mjs http://localhost:4321/chronicle/concepts/event-source/ /tmp/es-light.png light +``` + +Then **read the PNG** to evaluate it, and crop/zoom with the `sharp` already in `node_modules`: + +```bash +node -e "require('sharp')('/tmp/es.png').extract({left:300,top:600,width:900,height:500}).resize({width:1400}).toFile('/tmp/crop.png')" +``` + +- The **bar is aspire.dev** (`~/src/repos/aspire.dev/src/frontend`) — study its `site.css`/`mermaid.css` for depth (gradient glows, framed diagrams, lifted cards). Much of `cratis.css` is adapted from it. +- For **layout-shift / "twitch"** bugs, inject a buffered `layout-shift` PerformanceObserver via CDP `Page.addScriptToEvaluateOnNewDocument` and sample `documentElement` height over time; use a **fresh user-data-dir** for a cold (uncached) load, reuse it for warm. Run CDP scripts **serially** (they share the debug port). +- Run the gate (`npm run check`) and restart `npm run dev` afterward — the gate's re-sync degrades a running dev server (see [Editing Cratis Documentation](./editing-cratis-docs.md)). diff --git a/.ai/rules/documentation-structure-and-formatting.md b/.ai/rules/documentation-structure-and-formatting.md new file mode 100644 index 0000000..e585ea9 --- /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 — see the 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..c531c9f --- /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 internals: [The Astro Starlight Docs Site](./astro-starlight-site.md). Rendering pipeline (Mermaid, fonts, tables) + visual QA: [Documentation Rendering & QA](./documentation-rendering-and-qa.md). diff --git a/.ai/rules/writing-correct-examples.md b/.ai/rules/writing-correct-examples.md new file mode 100644 index 0000000..4d4dedf --- /dev/null +++ b/.ai/rules/writing-correct-examples.md @@ -0,0 +1,35 @@ +--- +applyTo: "**/*.{md,mdx}" +paths: + - "**/*.md" + - "**/*.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..0ae642d --- /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`. +- **`