diff --git a/.claude/AGENTS.md b/.claude/AGENTS.md index 3f073f6d4..77b965531 100644 --- a/.claude/AGENTS.md +++ b/.claude/AGENTS.md @@ -149,7 +149,7 @@ The legacy monolithic skill `skills/component-create.md` has been **removed**. N | Focused skills (one per phase) | [`.claude/skills/`](.claude/skills/) — `spec-create`, `spec-validate`, `figma-discover`, `token-map`, `reuse-audit`, `structure-decide`, `component-scaffold`, `storybook-write`, `code-connect-write`, `validate-component`, `echo-report` | | Isolated sub-agent prompts | [`.claude/agents/`](.claude/agents/) — one per skill, no chat history, no cross-talk | | Immutable rules (injected verbatim) | [`.claude/rules/`](.claude/rules/) — `no-invention`, `migration`, `dependencies` | -| Centralized design docs (sources of truth) | [`.claude/docs/`](.claude/docs/) — `DESIGN.md` (tokens + animations + forbidden list), `COMPONENT_REQUIREMENTS.md` (component pattern + Storybook discipline), `PRIMEVUE_ABSTRACTION.md` | +| Centralized design docs (sources of truth) | [`.claude/docs/`](.claude/docs/) — `DESIGN.md` (tokens + animations + forbidden list), `COMPONENT_REQUIREMENTS.md` (component pattern + Storybook discipline) | | Run logs (audit trail) | [`.claude/logs/.jsonl`](.claude/logs/) | **How `/component-create` runs:** @@ -191,7 +191,7 @@ The trigger logic now lives in the orchestrator command [`.claude/commands/compo - New components use ` +``` + +`defineOptions` sets `name` in **PascalCase** (drives the six naming surfaces — [`naming.md`](./naming.md)) and `inheritAttrs: false` so attributes flow explicitly to the root (see [`root-element.md`](./root-element.md)). + +## Hard prohibitions + +- Do not put more than one component in a `.vue` file. +- Do not place a component outside `src/components///`. +- Do not leak the category into the public import path or the export key (see [`imports.md`](./imports.md)). +- Do not hoist a component-specific composable / `injection-key.ts` to `src/` before a second consumer exists. +- Do not reorder the ` +``` + +- The `@deprecated` tag **names the replacement** and the **removal version** — a deprecation with no exit is just a warning that never ends. +- The deprecated part **keeps working** for its cycle; do not change its behaviour, only annotate it. +- **`catalog.json`** carries the deprecation (surfaced by the MCP) so AI-written consumer code is steered off the deprecated part before removal. +- Removal is a **`major`** commit, marked `BREAKING CHANGE:`, updating the spec, the story, and the exports map in the same change (see [`migration.md`](./migration.md) and [`event-payloads.md`](./event-payloads.md) for the same "reshape = breaking" discipline). + +## Hard prohibitions + +- Do not remove or rename a public part without a deprecation cycle first (except while a component is still on the legacy whitelist and unreleased). +- Do not write `@deprecated` without naming the replacement and the removal version. +- Do not change a deprecated part's behaviour during its cycle — annotate, don't mutate. +- Do not ship a removal as anything other than a `major` (`BREAKING CHANGE:`). + +## Enforcement + +- **`webkit/no-deprecated-component`** (ESLint, `error` in `recommended`) flags a consumer importing a component marked deprecated in the catalog. +- **`.releaserc`** (`releaseRules`) maps the bump; a `BREAKING CHANGE:` footer / `!` produces the `major` (see [`release-types.md`](./release-types.md)). +- **`catalog.json`** records `deprecated` per part; `build-catalog.mjs` stamps it and the MCP surfaces it. + +## Why this rule exists + +Breaking changes were happening (the branch renamed `variant`→`kind`, `closeable`→`closable`, removed `empty-results-block`), each correctly marked breaking — but there was no written lifecycle, so whether a part got a deprecation window or was removed outright depended on the PR. A fixed cycle (mark → one major → remove) plus a machine-readable `@deprecated` in the catalog gives every consumer the same, predictable migration path, and lets `no-deprecated-component` warn them inside the window. diff --git a/.claude/rules/emits.md b/.claude/rules/emits.md new file mode 100644 index 000000000..b764092a5 --- /dev/null +++ b/.claude/rules/emits.md @@ -0,0 +1,56 @@ +# Rule: emits — typed `defineEmits`, kebab names, model vs. activation + +Events are declared with the **type-only `defineEmits`** form, named in **kebab-case**, and split into two kinds with two different payload shapes. This rule fixes the declaration and the naming; the **payload of activation events** (event-first `(event, item)`) is fixed by [`event-payloads.md`](./event-payloads.md), and it is the authority whenever the two overlap. + +## The rule + +> `const emit = defineEmits<{ ... }>()` with the **tuple type-literal** syntax. Event names are **kebab-case**. **Model events** (`update:`) carry the new value; **activation events** carry `(event, item?)`. No runtime array form, no camelCase names, no `on`-prefixed props. + +```vue + +``` + +### Two kinds of event + +| Kind | Shape | Example | +|---|---|---| +| **Model / value** | the new value only, no DOM event | `update:modelValue`, `value-change`, `sort` | +| **Activation** (click, select, remove, row-click…) | `(event: Event, item?)` — DOM event **first**, subject **second** | `item-click`, `row-click`, `remove` | + +- **Prefer `defineModel`** over declaring `update:` by hand — a two-way value is a model, not a manual emit (see [`v-model.md`](./v-model.md)). Declare `update:*` in `defineEmits` only for a one-way "changed" signal that is not a `v-model`. +- **No redundant echo.** When `update:` (or `v-model`) exists, do **not** also emit `-change` — it is blocked (see [`prop-vocabulary.md`](./prop-vocabulary.md)). A genuinely distinct commit event (commit-on-blur) is a bare `change`, never `-change`. +- **Activation payloads follow [`event-payloads.md`](./event-payloads.md) exactly** — event first, subject second, never `(item, event)`, never the event buried in an object. + +### Naming + +- **kebab-case** (`item-click`, `row-click`, `update:modelValue`), never camelCase (`itemClick`, `onValueChange`). +- No `on`-prefixed **props** to fake events — declare the event, let the consumer bind `@item-click`. +- The event name says what happened in the component's own vocabulary (see [`event-payloads.md`](./event-payloads.md) for what counts as the `item`). + +## Hard prohibitions + +- Do not use the runtime array form (`defineEmits(['click'])`) — use the typed tuple-literal form. +- Do not name an event in camelCase or with an `on` prefix. +- Do not hand-declare `update:modelValue` when `defineModel` expresses the two-way value. +- Do not emit a redundant `-change` alongside `update:` / `v-model`. +- Do not emit an activation event without the DOM `event` first (see [`event-payloads.md`](./event-payloads.md)). +- Do not add an event the spec's Events table does not list (see [`no-invention.md`](./no-invention.md)). + +## Enforcement + +- **[`validate-spec-compliance.mjs`](../hooks/validate-spec-compliance.mjs)** blocks events not in the spec, camelCase event names, and redundant `-change` echoes. +- [`event-payloads.md`](./event-payloads.md) governs the payload order for activation events; its enforcement covers the `(event, item)` shape. +- The scaffolder emits the typed `defineEmits` form from the spec's Events table. + +## Why this rule exists + +Events had drifted on two axes at once: **declaration** (some used the runtime array form, losing the payload types) and **payload** (`item-click` emitting `(item)`, `step-click` emitting `(index, event)`, `select` burying the event in `{ value, event }`). Fixing the declaration here and the payload order in [`event-payloads.md`](./event-payloads.md) makes every handler in a consumer read the same — `@item-click="(event, item) => …"` — and every payload fully typed. diff --git a/.claude/rules/migration.md b/.claude/rules/migration.md index f9e62e1c3..c3cb5e73f 100644 --- a/.claude/rules/migration.md +++ b/.claude/rules/migration.md @@ -1,6 +1,6 @@ # Rule: migration policy — never inherit, always rewrite -When importing a component, pattern, or any artifact from **outside** this codebase (another design system, a Figma file, a Base UI / Reka UI / Radix / shadcn / PrimeVue example, a third-party library, a previous repo, a Stack Overflow answer, a CONTRACT.md file written before this pipeline existed), **you do not bring it as-is**. You rewrite it to our conventions before the spec is written, and you write the spec from scratch following [`.specs/_template.md`](../../.specs/_template.md). +When importing a component, pattern, or any artifact from **outside** this codebase (another design system, a Figma file, a Base UI / Reka UI / Radix / shadcn example, a third-party library, a previous repo, a Stack Overflow answer, a CONTRACT.md file written before this pipeline existed), **you do not bring it as-is**. You rewrite it to our conventions before the spec is written, and you write the spec from scratch following [`.specs/_template.md`](../../.specs/_template.md). ## The rule @@ -18,9 +18,9 @@ When importing a component, pattern, or any artifact from **outside** this codeb | Source | events may be camelCase (`onValueChange`) | Always kebab-case (`update:value`, `value-change`). | | Source | boolean props may have `is`/`has` prefixes | No prefix (`disabled`, `loading`, `open`). | | Source | `class` declared in `defineProps` | Never. Use `useAttrs()` + `rootClasses` merging `attrs.class`. | -| Source | Inline `@keyframes` or animation library | Only the semantic utilities catalogued in [`tokens.md`](./tokens.md#animations--semanticanimationsjs). No animation lib (see [`dependencies.md`](./dependencies.md)). | +| Source | Inline `@keyframes` or animation library | Only the semantic utilities catalogued in [`DESIGN.md`](../docs/DESIGN.md#animations--semanticanimationsjs). No animation lib (see [`dependencies.md`](./dependencies.md)). | | Source | `@floating-ui/vue` for positioning | CSS only (anchor positioning, Popover API, `` + `position: fixed`). | -| Source | Their tokens (`#fff`, `--brand-50`, `text-blue-600`) | Our tokens (`var(--bg-canvas)`, `var(--primary)`, `text-button-lg`). Catalog: [`tokens.md`](./tokens.md). | +| Source | Their tokens (`#fff`, `--brand-50`, `text-blue-600`) | Our tokens (`var(--bg-canvas)`, `var(--primary)`, `text-button-lg`). Catalog: [`DESIGN.md`](../docs/DESIGN.md). | ### When migrating from Figma diff --git a/.claude/rules/no-invention.md b/.claude/rules/no-invention.md index 2aaf60f92..d9ae77d18 100644 --- a/.claude/rules/no-invention.md +++ b/.claude/rules/no-invention.md @@ -11,9 +11,9 @@ You are a transcriber, not an author. The spec at `.specs/.md` is the cont 5. **Do not invent imports.** Every `@aziontech/webkit/*` path must exist in `packages/webkit/package.json#exports`. Every relative import must resolve. Every npm package must be installed. 6. **Do not create composables, utilities, or sibling components** unless the task explicitly tells you to. 7. **Do not edit files outside the paths your task names.** -8. **Do not bring artifacts from outside this codebase as-is.** Migrating from another design system, Figma, Base UI/Reka UI/Radix/shadcn/PrimeVue examples, a previous repo, or any inherited `CONTRACT.md`/`README.md` requires **rewriting** to our conventions. See [`migration.md`](./migration.md). +8. **Do not bring artifacts from outside this codebase as-is.** Migrating from another design system, Figma, Base UI/Reka UI/Radix/shadcn examples, a previous repo, or any inherited `CONTRACT.md`/`README.md` requires **rewriting** to our conventions. See [`migration.md`](./migration.md). 9. **Do not introduce external libs for positioning or animation.** No `floating-ui`, `popper.js`, `tippy`, `gsap`, `framer-motion`, `motion`, `@vueuse/motion`, `@formkit/auto-animate`, drag-drop runtimes, scroll virtualization. Use CSS + tokens + Vue primitives. Catalog: [`dependencies.md`](./dependencies.md). -10. **Do not improvise animations.** Every `animate-*` / `transition-*` class comes from the catalog. Every motion-bearing class pairs with `motion-reduce:*`. No component-local `@keyframes`. See [`tokens.md`](./tokens.md#animations--semanticanimationsjs). +10. **Do not improvise animations.** Every `animate-*` / `transition-*` class comes from the catalog. Every motion-bearing class pairs with `motion-reduce:*`. No component-local `@keyframes`. See [`DESIGN.md`](../docs/DESIGN.md#animations--semanticanimationsjs). 11. **Do not add Figma references to Storybook stories.** No `parameters.design`, no `parameters.figma`, no Figma URLs in `docs.description.component`, no addon-designs links. The story documents the **component API and behavior**; the Figma file documents the design. They stay separate. See [`.claude/docs/COMPONENT_REQUIREMENTS.md`](../docs/COMPONENT_REQUIREMENTS.md) § 13.x. 12. **Do not create CSS class presets in JavaScript** (`const kindClasses = {...}`, `const sharedClasses = [...]`, `const sizeClasses = {...}`, `const rootClasses = computed(...)` that only composes classes). Variants live on `data-*` attributes; styles live as Tailwind utilities on the root element's `class` attribute. No ` +``` diff --git a/packages/webkit/cli-templates/claude/rules/webkit-testid.md b/packages/webkit/cli-templates/claude/rules/webkit-testid.md new file mode 100644 index 000000000..50725c7ac --- /dev/null +++ b/packages/webkit/cli-templates/claude/rules/webkit-testid.md @@ -0,0 +1,80 @@ +# Rule: `data-testid` — derived name on the root, overridable + +Every webkit component root carries a **`data-testid`** derived from the component's own +name — **`-`**, or **`input-`** for input components (`input-chip`, +`content-badge`, `feedback-toast`, `data-table`). Your tests target that attribute instead +of inventing selectors, and you override it per instance by passing `data-testid` on the +tag. Components you author in this project follow the same pattern. This is a convention +enforced by review — no lint rule checks it, so apply it deliberately. + +## Do + +- Target components in tests via their derived `data-testid`, never via DOM structure or + internal classes. +- Override the testid per instance when a page renders more than one of the same + component: `