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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ on:

permissions:
contents: write
pull-requests: read

jobs:
release:
Expand Down Expand Up @@ -164,7 +165,29 @@ jobs:
needs: [release]

steps:
- name: Report that nothing was published
- name: Verify an intentional no-release merge
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "::error::Nothing was published and no release was cut (reason: ${{ needs.release.outputs.reason }}). For 'no-label', add exactly one of major, minor or patch to the merged pull request and re-run this workflow - see verify-semver-label, which is meant to catch this before the merge."
reason='${{ needs.release.outputs.reason }}'
if [ "$reason" = "error" ]; then
echo "::error::The release action failed; no-release cannot suppress a release error."
exit 1
fi

pulls=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls")
merged_count=$(printf '%s' "$pulls" | jq --arg sha "$GITHUB_SHA" '[.[] | select(.merge_commit_sha == $sha)] | length')
if [ "$merged_count" -ne 1 ]; then
echo "::error::Expected exactly one pull request whose merge commit is $GITHUB_SHA; found $merged_count."
exit 1
fi
no_release=$(printf '%s' "$pulls" | jq -r --arg sha "$GITHUB_SHA" \
'[.[] | select(.merge_commit_sha == $sha) | .labels[].name] | any(. == "no-release")')

if [ "$no_release" = "true" ]; then
echo "The merged pull request explicitly selected no-release; publishing nothing is the intended result."
exit 0
fi

echo "::error::Nothing was published and no release was cut (reason: ${{ needs.release.outputs.reason }}). Add exactly one release-intent label before merge; use no-release when publishing nothing is intentional."
exit 1
22 changes: 9 additions & 13 deletions .github/workflows/verify-semver-label.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
name: Verify Semver Label

# A merged pull request with no major/minor/patch label produces a Publish run that reports success while
# skipping every publish step, because the release action resolves should-publish to false. That reads as a
# release having happened when nothing was published. Requiring the label here turns a silent non-release into
# a visible failure before the merge, where it costs nothing to fix.
#
# It also closes a race the publish workflow cannot: a label added moments after the merge may land too late
# for the release to pick it up. Demanding the label before the merge means there is nothing to race.
# Every pull request declares one release intent. major/minor/patch authorizes a package release when the
# publish workflow's path filters also match; no-release explicitly records that the change should publish
# nothing. Requiring exactly one intent prevents both accidental releases and ambiguous silent skips.
#
# Triggered on labeled/unlabeled as well as the usual events, so adding the label re-runs the check rather than
# leaving a red cross behind that only a push would clear.
Expand All @@ -16,7 +12,7 @@ concurrency:

on:
pull_request:
types: [opened, reopened, synchronize, labeled, unlabeled]
types: [opened, reopened, synchronize, labeled, unlabeled, edited]
# Scoped to the same branch Publish releases from. A pull request stacked onto another one's branch cannot
# cut a release, so demanding a version label of it would be asking which version a merge that publishes
# nothing should carry.
Expand All @@ -31,21 +27,21 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Require exactly one semantic version label
- name: Require exactly one release-intent label
env:
LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
run: |
count=$(printf '%s' "$LABELS" | jq '[.[] | select(. == "major" or . == "minor" or . == "patch")] | length')
count=$(printf '%s' "$LABELS" | jq '[.[] | select(. == "major" or . == "minor" or . == "patch" or . == "no-release")] | length')

if [ "$count" -eq 1 ]; then
echo "Found one semantic version label."
echo "Found one release-intent label."
exit 0
fi

if [ "$count" -eq 0 ]; then
echo "::error::This pull request has no semantic version label. Add exactly one of major, minor or patch. Without one, merging produces a Publish run that succeeds while skipping every publish step, so no release is cut and nothing is published."
echo "::error::This pull request has no release-intent label. Add exactly one of major, minor, patch or no-release."
else
echo "::error::This pull request carries $count semantic version labels. Exactly one of major, minor or patch is required, since the release version cannot be derived from more than one."
echo "::error::This pull request carries $count release-intent labels. Add exactly one of major, minor, patch or no-release."
fi

exit 1
10 changes: 5 additions & 5 deletions Codemods/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# @cratis/components-codemods

Published migration codemods for moving from Components 3 root namespaces to Components 4 explicit subpath imports. Components 4 keeps only package-wide provider setup at the root; every component is imported from its explicit subpath (`@cratis/components/Canvas`, for example). See the published
[`no-root-barrel-import` ESLint rule](https://www.npmjs.com/package/@cratis/eslint-plugin-components#no-root-barrel-import)
for the lint-time guard that enforces this once a consumer has migrated.
Migration codemods in the Components 4 candidate move Components 3 root namespaces to Components 4 explicit subpath imports. Components 4 keeps only package-wide provider setup at the root; every component is imported from its explicit subpath (`@cratis/components/Canvas`, for example). The companion `@cratis/eslint-plugin-components` package's `no-root-barrel-import` rule enforces this once a consumer has migrated.

## `remove-root-namespace-imports`

Expand Down Expand Up @@ -80,8 +78,10 @@ consumers are left exactly as they are.

### Use

The published CLI requires Node.js 20 or newer and brings its own TypeScript parser; it does
not depend on the consumer application's TypeScript version.
The packaged CLI requires Node.js 20 or newer and brings its own TypeScript parser; it does
not depend on the consumer application's TypeScript version. Use the `npx` commands only with
an exact published package version that contains this tool; contributors can run the repository
source directly.

```sh
# Preview what would change, without writing anything:
Expand Down
10 changes: 5 additions & 5 deletions Documentation/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ sidebar:

import { Steps, Aside } from '@astrojs/starlight/components';

You've built an Arc backend—a `RegisterAuthor` command and an `AllAuthors` query—and `dotnet build` generated typed proxies for both. Components turns those proxies into accessible forms, dialogs, and data views without duplicating command state, validation, or query lifecycle code.
You've built an Arc backend—a `RegisterAuthor` command and an `AllAuthors` query—and `dotnet build` generated typed proxies for both. Components connects those proxies to typed forms, dialogs, and data views with documented command, validation, and query-lifecycle behavior.

## Prerequisites

Expand All @@ -24,9 +24,9 @@ You've built an Arc backend—a `RegisterAuthor` command and an `AllAuthors` que
npm install @cratis/components
```

React Aria and the internationalized date implementation are internal dependencies. You do not install a UI kit, theme runtime, icon package, or commercial license for Components.
React Aria and the internationalized date implementation are internal dependencies. The current package manifest does not declare a separate UI kit, theme runtime, or icon package as a dependency or peer.

The remaining peers—React, Arc, Fundamentals, `reflect-metadata`, and `tsyringe`—already come with an Arc frontend. Arc 20, 21, and 22 are supported.
The package declares React, Arc, Fundamentals, `reflect-metadata`, and `tsyringe` peer ranges. Its Arc range is `>=20.3.1 <23`; verify the exact manifest and application profile before installation.

`pixi.js@^8.20.0` is an additional **optional** peer, needed only if you use `Canvas` or `PivotViewer`. Every other component needs nothing beyond the peers above; install Pixi later, when you reach a spatial workspace or card-grid screen. See [Choosing a component](/components/choosing-a-component/#spatial-workspaces).

Expand All @@ -50,7 +50,7 @@ You've built an Arc backend—a `RegisterAuthor` command and an `AllAuthors` que
3. **Mount the provider** around your application:

```tsx title="App.tsx"
import { CratisComponentsProvider } from '@cratis/components/Common';
import { CratisComponentsProvider } from '@cratis/components';

export const App = () => (
<CratisComponentsProvider value={{ locale: 'en-US' }} toaster>
Expand Down Expand Up @@ -140,7 +140,7 @@ Do not target React Aria class names or internal DOM structure. See [Styling](/c

## Recap

You installed one UI package, imported Cratis-owned styles, and mounted a locale/toast provider. Components owns the public UI contract while Arc supplies command and query behavior and React Aria supplies low-level accessible interactions.
You installed one UI package, imported Components-owned styles, and mounted a locale/toast provider. Components owns the public React contract, Arc supplies command and query behavior, and React Aria supplies selected low-level interaction primitives internally.

## Where to go next

Expand Down
164 changes: 94 additions & 70 deletions Documentation/index.mdx
Original file line number Diff line number Diff line change
@@ -1,93 +1,117 @@
---
title: Components
description: The React component library for Cratis — command dialogs, forms, and data tables that consume Arc's generated proxies.
description: React components aligned with Arc application patterns.
---

import { CardGrid } from '@astrojs/starlight/components';
import { Card, CardGrid, Steps } from '@astrojs/starlight/components';
import SimpleCard from '@components/SimpleCard.astro';
import TopicHero from '@components/TopicHero.astro';

<TopicHero
icon='laptop'
eyebrow='Components'
title='React components wired to your proxies'
icon="laptop"
eyebrow="Components"
title="React components aligned with Arc application patterns"
>
Command dialogs, forms, and data tables that consume [Arc's](/arc/) generated proxies.
Cratis-owned, fully typed, accessible, and styled the way you choose. [Get started
→](/components/getting-started/) · [Why Components? →](/components/why-components/)
Components is a React component library aligned with Arc application patterns.
The current package owns its public React markup, TypeScript contracts,
semantic tokens, stable parts, and component behavior.
</TopicHero>

## Start here

<CardGrid>
<SimpleCard title='Getting started' icon='rocket' link='/components/getting-started/'>
Install the package, mount the provider, and render your first proxy-driven
screen.
</SimpleCard>
<SimpleCard
title='Why Components'
icon='approve-check'
link='/components/why-components/'
>
What the library adds around generated Arc proxies and accessible interaction
behavior.
</SimpleCard>
<SimpleCard title='Styling' icon='seti:folder' link='/components/styling/'>
Use the baseline theme or map a product design system directly onto Cratis tokens
and parts.
</SimpleCard>
<SimpleCard title="Install Components" icon="rocket" link="#minimal-setup">
Install the package, import its structural styles, and mount the provider.
</SimpleCard>
<SimpleCard title="Choose a component area" icon="list-format" link="#component-areas">
Start from the screen job rather than a raw component inventory.
</SimpleCard>
<SimpleCard title="Keep Arc separate" icon="puzzle" link="#relationship-to-arc">
Distinguish Arc-generated contracts from the React compositions that consume them.
</SimpleCard>
</CardGrid>

## Recipes
## Minimal setup

<Steps>
1. Install the package.

```bash
npm install @cratis/components
```

2. Import semantic tokens and component structure. The baseline theme is
optional.

```tsx
import '@cratis/components/tokens';
import '@cratis/components/styles';
import '@cratis/components/theme'; // optional baseline appearance
```

3. Mount the provider.

```tsx
import { CratisComponentsProvider } from '@cratis/components';

export const App = () => (
<CratisComponentsProvider value={{ locale: 'en-US' }} toaster>
<YourApp />
</CratisComponentsProvider>
);
```
</Steps>

The current package manifest defines the exact React, Arc, Fundamentals, and
optional Pixi peer ranges. Verify those ranges for the package version selected.

## Relationship to Arc

Components consumes generated command and query contracts and React contexts
from Arc packages. Applications may use Arc without Components.

Components does not by itself establish design-system completeness,
accessibility conformance, browser coverage, or compatibility with every
Arc/React/package-version combination.

## Component areas

<CardGrid>
<SimpleCard title='Build a form' icon='open-book' link='/components/building-a-form/'>
Collect input and execute a command with `CommandDialog` and the `CommandForm`
fields.
</SimpleCard>
<SimpleCard title='Display data' icon='seti:db' link='/components/displaying-data/'>
Render a query or observable query with `DataPage` and the data-table wrappers.
</SimpleCard>
<SimpleCard
title='Multi-step form'
icon='list-format'
link='/components/multi-step-form/'
>
Gather information across named stages with `StepperCommandDialog`.
</SimpleCard>
<SimpleCard
title='A list screen with actions'
icon='puzzle'
link='/components/list-screen-with-actions/'
>
A full screen: list rows, add and edit through dialogs, and react to selection.
</SimpleCard>
<Card title="Command input" icon="approve-check">
Typed fields, forms, dialogs, and multi-step command flows.
</Card>
<Card title="Data display" icon="seti:db">
Query-backed and local-array tables, list pages, filters, and detail surfaces.
</Card>
<Card title="Application surfaces" icon="laptop">
Dialogs, notifications, dropdowns, display primitives, page chrome, and toolbars.
</Card>
<Card title="Structured editors" icon="pencil">
JSON content, JSON Schema, navigation, canvas, pivot, and time-oriented views.
</Card>
<Card title="Styling boundary" icon="seti:config">
Semantic tokens, component styles, an optional baseline theme, and stable parts.
</Card>
</CardGrid>

## Key components
:::caution[Evaluate the exact application profile]
Package existence, examples, Storybook output, and passing checks do not
establish maturity, accessibility conformance, browser coverage, support,
security, or production suitability. Direct third-party UI dependencies retained
by an application keep their own package, provider, styling, and license
boundaries.
:::

## Continue

<CardGrid>
<SimpleCard title='CommandDialog' icon='seti:react' link='/components/commanddialog/'>
Instantiates, validates, and executes a generated command, with the footer handled
for you.
</SimpleCard>
<SimpleCard title='DataPage' icon='laptop' link='/components/datapage/'>
A resizable page that lists query data with toolbar actions and detail panels.
</SimpleCard>
<SimpleCard
title='CommandForm fields'
icon='approve-check'
link='/components/commandform/'
>
Typed input fields bound to command properties — text, number, dropdown, date, and
more.
</SimpleCard>
<SimpleCard title='Dialogs' icon='open-book' link='/components/dialogs/'>
Data-collection dialogs that return values without executing a command.
</SimpleCard>
<SimpleCard title='Canvas' icon='seti:image' link='/components/canvas/'>
A pan/zoom workspace with measured items, controls, minimap, notes, regions, and chat shapes.
</SimpleCard>
<SimpleCard title="Components source and README" icon="seti:folder" link="https://github.com/Cratis/Components">
Package setup, migration guidance, contribution paths, and current limits.
</SimpleCard>
<SimpleCard title="Components on npm" icon="open-book" link="https://www.npmjs.com/package/@cratis/components">
Published package metadata and versions.
</SimpleCard>
<SimpleCard title="Arc" icon="puzzle" link="/arc/">
The CQRS framework whose application patterns Components aligns with.
</SimpleCard>
</CardGrid>

Components renders [Arc](/arc/) command and query proxies. Those queries can come from Chronicle read models in an event-sourced application or from any Arc query source — Chronicle is optional. See [Why developers choose Cratis](/why-cratis/) for the full stack.
Loading
Loading