diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..df3235f
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,25 @@
+# AGENTS.md
+
+Instructions for AI coding agents working in this repository **or** in projects that depend on `gnexus-ui-kit`.
+
+## If you are building UI with GNexus UI Kit
+
+1. **Check the catalog before writing any markup**: [`docs/catalog.json`](docs/catalog.json) maps every need (button, modal, status label, ...) to its component, props and when to use it. If a component exists, use it — do not invent your own markup for what the kit already covers.
+2. Read the full agent guide: [`docs/ai-guide.md`](docs/ai-guide.md) — component selection, variant/icon rules, and the recipe for building a custom component that matches the GNexus style (tokens, `hard_panel`, uppercase titles, `focus_ring`, `hover_touch`).
+3. In Vue projects, use the adapter: `import { GnButton, GnModal } from "gnexus-ui-kit/vue"`. Full Vue contract: [`docs/vue/ai-usage-guide.md`](docs/vue/ai-usage-guide.md).
+4. Icons are Phosphor only, always with the base class: `icon="ph-house"` in Vue, `` in HTML.
+5. Variants are a closed set: `primary, secondary, accent, success, warning, danger, error, info`.
+
+Quick copy-paste block for the consuming project's own CLAUDE.md/AGENTS.md is at the bottom of [`docs/ai-guide.md`](docs/ai-guide.md).
+
+## If you are working on this repository
+
+Follow [`CLAUDE.md`](CLAUDE.md) — it is the maintainer reference: demo sync rules (vanilla vs Vue demo must stay pixel-identical), build/verification commands (`npm run release:check`), screenshot policy, and the Vue adapter lessons. The component catalog table in CLAUDE.md is generated from `docs/catalog.json` — edit the JSON, then run `npm run gen:catalog`.
+
+## Single sources of truth
+
+| Source | Generated from it |
+|--------|-------------------|
+| `docs/catalog.json` | catalog table in `CLAUDE.md`, selection table in `docs/ai-guide.md` |
+| `src/scss/_palette-colors.scss`, `_design-tokens.scss`, `_spacing.scss` | all component styling |
+| `demo/partials/*.html` | both demo pages (vanilla + Vue) |
\ No newline at end of file
diff --git a/README.md b/README.md
index a920f8d..4889014 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,16 @@
Человеческая документация по подключению, стилю, JS API и компонентам находится в [`docs/`](docs/index.md).
Официальный Vue 3 adapter описан в [`docs/vue.md`](docs/vue.md).
+## Использование с ИИ-агентами
+
+Если над проектом работает AI-агент (Claude, Cursor, Copilot, Codex, ...), укажите ему на:
+
+- [`AGENTS.md`](AGENTS.md) — точка входа для агентов (файл едет в npm-пакет).
+- [`docs/ai-guide.md`](docs/ai-guide.md) — полный гайд: как выбирать компоненты из каталога, какие варианты/иконки допустимы, и рецепт «как сделать кастомный компонент в стиле GNexus» (токены, `hard_panel`, uppercase-заголовки, `focus_ring`, `hover_touch`).
+- [`docs/catalog.json`](docs/catalog.json) — машиночитаемый каталог: need → component → props → useWhen.
+
+Быстрый способ — вставить готовый блок правил в `CLAUDE.md`/`AGENTS.md` проекта-потребителя (сниппет в конце [`docs/ai-guide.md`](docs/ai-guide.md)).
+
## Быстрый старт
```bash
diff --git a/docs/ai-guide.md b/docs/ai-guide.md
new file mode 100644
index 0000000..bdb8a06
--- /dev/null
+++ b/docs/ai-guide.md
@@ -0,0 +1,181 @@
+# AI Agent Guide
+
+This guide is for AI agents (Claude, Cursor, Copilot, Codex, ...) building interfaces with GNexus UI Kit — in Vue projects, plain HTML pages, or inside this repository.
+
+It answers the two most common failure modes:
+
+1. **The agent invents its own component** instead of using one from the kit → see [Component selection](#component-selection).
+2. **The agent builds a custom component that drifts from the styleguide** → see [Building a custom component in the GNexus style](#building-a-custom-component-in-the-gnexus-style).
+
+## The one rule
+
+> **Before writing ANY markup, check the [component selection table](#component-selection) (or `docs/catalog.json`). If a component exists for the need, use it. Do not hand-roll buttons, modals, tabs, toasts, forms, badges or tables.**
+
+Raw markup is acceptable only for layout wrappers and content with no adapter component yet.
+
+## Required setup (once per app)
+
+```js
+import "gnexus-ui-kit/dist/css/kit.css";
+import "gnexus-ui-kit/dist/assets/fonts/phosphor-icons/src/css/icons.css";
+```
+
+Vue: use the adapter (`gnexus-ui-kit/vue`) — see [`docs/vue/ai-usage-guide.md`](vue/ai-usage-guide.md) for the full Vue contract. Vanilla JS: the bundle publishes `GNexusUIKit.*` on `window` — see `docs/javascript.md`.
+
+## Component selection
+
+Every mapping below is generated from `docs/catalog.json` — the single source of truth. If your need is not in this table, search `docs/catalog.json` before inventing markup.
+
+
+
+| Need | Use | When |
+|------|-----|------|
+| Button | `GnButton` | Any clickable command or submit action |
+| Icon-only button | `GnIconButton` | Action that shows only an icon (always pass `label` for accessibility) |
+| Copy button | `GnCopyButton` | Copy-to-clipboard action with built-in success feedback |
+| Status label | `GnBadge` | Small colored status text (active, pending, failed) — never a hand-styled span |
+| Message block | `GnAlert` | Inline info/warning/error message inside page content |
+| Card / panel | `GnCard` | Framed content block with optional title and footer |
+| Horizontal card | `GnHorizontalCard` | Media-left preview card (image + linked title) |
+| Login card | `GnLoginCard` | Username/password sign-in form |
+| User card | `GnUserCard` | Person or account profile block with avatar, email and role |
+| Page title bar | `GnPageHeader` | Screen-level title with kicker, subtitle, meta and action buttons |
+| Text field | `GnInput` | Single-line text input with label, icon and validation state |
+| Textarea | `GnTextarea` | Multiline text input |
+| Select dropdown | `GnSelect` | Choose one option from a short list via native dropdown |
+| Checkbox | `GnCheckbox` | Boolean opt-in/opt-out choice |
+| Toggle switch | `GnSwitch` | Instant on/off setting toggle |
+| Radio | `GnRadio` / `GnRadioGroup` | Single choice from a small, fully visible option set |
+| Range slider | `GnRange` | Numeric value picked on a min–max scale |
+| File upload | `GnFileUpload` | File picker with previews; it owns preview object URLs |
+| Searchable select | `GnCombobox` | Choose one option from a large or filterable list |
+| Tag / multi-value input | `GnTagInput` | Enter multiple free-form values as chips |
+| Tabs | `GnTabs` | Switch between views within one page |
+| Router tabs | `GnRouterTabs` | Tabs mapped to vue-router routes |
+| Accordion | `GnAccordion` | Collapsible disclosure groups |
+| Modal dialog | `GnModal` | Blocking dialog; Escape, focus trap and focus return are handled for you |
+| Side drawer | `GnDrawer` | Slide-in panel for details, quick edit or secondary flows |
+| Toasts | `GnToastProvider` + `useToast` | Transient global notification; one toast at a time by design |
+| Confirm dialog | `GnConfirmDialog` | Yes/no confirmation before a destructive or important action |
+| Table | `GnTable` | Structured data rows with per-cell customization slots |
+| Toolbar | `GnToolbar` | Title/meta + action buttons row above content |
+| Input group | `GnInputGroup` | Input with prefix/suffix addon or embedded action button |
+| Search field | `GnSearchField` | Search input with clear button |
+| Pagination | `GnPagination` | Page switcher for tables and lists |
+| Empty state | `GnEmptyState` | No-data placeholder with icon, text and actions |
+| Skeleton loader | `GnSkeleton` | Loading placeholder blocks while content loads |
+| Key-value list | `GnDescriptionList` | Read-only key-value metadata pairs |
+| Progress bar | `GnProgress` | Determinate progress from 0 to max |
+| Usage meter card | `GnUsageMeter` | Limit/quota usage card with progress bar |
+| Staged progress | `GnProgressStages` | Segmented progress across named stages |
+| Wizard steps | `GnSteps` | Step-by-step wizard flow indicator (horizontal or vertical) |
+| Chip / tag | `GnChip` | Small label that can be selectable or removable |
+| Chip group | `GnChipGroup` | Row/wrap container for a group of chips |
+| Avatar | `GnAvatar` | User/entity avatar from image, initials or icon, with optional status |
+| Identity row | `GnIdentity` | Compact avatar + title + meta row |
+| Avatar stack | `GnAvatarStack` | Overlapping group of avatars |
+| Timeline | `GnTimeline` | Vertical event history with state markers |
+| Activity log | `GnActivityLog` | Compact audit or job log rows |
+| Basic list | `GnList` | Simple item list |
+| Definition list | `GnDefinitionList` | Term/description pairs |
+| Action list | `GnActionList` | List of clickable actions |
+| Loader / spinner | `GnLoader` | Indeterminate loading spinner |
+| Status card | `GnStatusCard` | Card communicating a state (success/warning/error/info) |
+| Metric card | `GnMetricCard` | KPI number with delta and meta |
+| Action card | `GnActionCard` | Card with text and action buttons |
+| Dropdown menu | `GnDropdown` | Menu of actions revealed from a trigger button |
+| Popover panel | `GnPopover` | Small anchored panel with title and text |
+| Tooltip | `GnTooltip` | Short hover hint |
+| Navigation list | `GnNavList` | Vertical navigation links list (router-aware) |
+| App shell | `GnNavigationShell` | Whole-app topbar/drawer/footer navigation frame |
+
+
+
+## Rules that prevent drift
+
+- **Variants**: only `primary, secondary, accent, success, warning, danger, error, info`. Never invent a variant name (`danger` and `error` render the same in most components).
+- **Icons**: Phosphor only, always with the base `ph` class. Vue props: `icon="ph-house"` (prefix required). Raw HTML: ``.
+- **Colors**: use design tokens, never raw hex values. In SCSS: `$surface-panel-muted`, `$color-text-light`, `$color-primary`, `$color-secondary`, `$color-success/warning/error/info`. In plain CSS (no build step): use the kit's own component classes and utility classes (`p-*`, `m-*`, `g-*`, `row`, `d-flex`, `text-*`) instead of restyling.
+- **Spacing**: `$space-1` … `$space-12` scale, never arbitrary pixel values.
+- **Focus**: every interactive element shows a visible focus ring (`@include focus_ring` in SCSS).
+- **Hover**: wrap hover styling in `@include hover_touch` so touch devices fall back to `:active`.
+- **Motion**: `$motion-fast` (0.15s) / `$motion-base` (0.2s) / `$motion-slow` (0.28s) with `ease`. No decorative animation.
+
+## Building a custom component in the GNexus style
+
+If — and only if — the catalog has no component for the need, build one, but it must look native. Checklist:
+
+1. **Surface**: `@include hard_panel` — `$surface-panel-muted` background, 2px solid border (`$border-width-base`), zero border-radius. Sharp corners are non-negotiable.
+2. **Accent**: important panels get a thick left accent — `@include hard_panel($border-color-muted, $border-width-accent)` (6px left border).
+3. **Title**: small, bold, uppercase (`$font-size-sm`, `$font-weight-bold`, `text-transform: uppercase`). No hero-size headings inside panels.
+4. **Text**: `$color-text-light` for primary text, `$color-text-medium` for secondary/meta. Meta text uses `$font-size-xs`/`$font-size-sm`.
+5. **Typeface**: IBM Plex Mono is already the base font — do not override `font-family`.
+6. **Interactive states**: `@include focus_ring` on `:focus-visible`, `@include hover_touch` for hover affordances, transitions on `$motion-fast`/`$motion-base`.
+7. **Icons**: ``, sizes from `$icon-size-xs` … `$icon-size-xl`.
+8. **State colors**: `$color-success`, `$color-warning`, `$color-error`, `$color-info` — the same four the kit's badges/alerts/status cards use.
+
+Do (matches the kit's own panels):
+
+```scss
+.my-widget {
+ @include hard_panel($border-color-muted, $border-width-accent);
+ padding: $space-4;
+ transition-duration: $motion-base;
+ transition-property: border-color;
+ transition-timing-function: $motion-ease;
+
+ @include hover_touch {
+ border-color: $color-secondary;
+ }
+
+ &:focus-visible {
+ @include focus_ring;
+ }
+}
+
+.my-widget-title {
+ font-size: $font-size-sm;
+ font-weight: $font-weight-bold;
+ text-transform: uppercase;
+}
+```
+
+Don't (the classic drift):
+
+```scss
+// WRONG: invented palette, rounded corners, random spacing, no focus ring
+.my-widget {
+ background: #1a1b26;
+ border: 1px solid #333;
+ border-radius: 8px;
+ padding: 13px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
+}
+```
+
+Full token reference: [`docs/style-guide.md`](style-guide.md).
+
+## Paste this into the consuming project
+
+Add this block to the **consuming project's** `CLAUDE.md` / `AGENTS.md` (whichever your agent reads), so the agent knows the kit exists:
+
+```markdown
+## GNexus UI Kit
+
+This project uses GNexus UI Kit (npm: `gnexus-ui-kit`). Rules:
+
+- Before writing ANY UI markup, check the component catalog in
+ `node_modules/gnexus-ui-kit/docs/catalog.json` (columns: need, component,
+ props, useWhen). If a component exists for the need, use it.
+- In Vue: import components from `gnexus-ui-kit/vue` (GnButton, GnModal, ...).
+ In vanilla HTML: use the kit's documented classes, not custom lookalikes.
+- Icons: Phosphor only — `icon="ph-house"` in Vue, `` in HTML.
+- Variants: only `primary, secondary, accent, success, warning, danger, error, info`.
+- Custom components must follow the GNexus recipe:
+ `node_modules/gnexus-ui-kit/docs/ai-guide.md` ("Building a custom component").
+- Full agent guide: `node_modules/gnexus-ui-kit/docs/ai-guide.md`
+```
+
+## For maintainers of this repository
+
+When working **inside this repo**, follow [`CLAUDE.md`](../CLAUDE.md) — it carries the repo workflow rules (demo sync, build verification, screenshot policy) on top of everything above.
\ No newline at end of file
diff --git a/docs/catalog.json b/docs/catalog.json
index 1116dbb..135c99e 100644
--- a/docs/catalog.json
+++ b/docs/catalog.json
@@ -1,60 +1,60 @@
[
- { "need": "Button", "component": "GnButton", "props": ["variant", "size", "icon", "loading", "disabled"] },
- { "need": "Icon-only button", "component": "GnIconButton", "props": ["icon", "label", "size"] },
- { "need": "Copy button", "component": "GnCopyButton", "props": ["text", "icon", "successIcon", "duration", "label", "size"] },
- { "need": "Status label", "component": "GnBadge", "props": ["variant", "outline"] },
- { "need": "Message block", "component": "GnAlert", "props": ["variant"] },
- { "need": "Card / panel", "component": "GnCard", "props": ["title (slot)", "footer (slot)"] },
- { "need": "Horizontal card", "component": "GnHorizontalCard", "props": ["image", "title", "titleHref", "icon", "footer (slot)"] },
- { "need": "Login card", "component": "GnLoginCard", "props": ["title", "logoIcon", "usernameLabel", "passwordLabel", "rememberMe", "submitText", "error", "forgotHref", "signupHref"] },
- { "need": "User card", "component": "GnUserCard", "props": ["name", "email", "role", "avatar", "href", "compact", "actions"] },
- { "need": "Page title bar", "component": "GnPageHeader", "props": ["kicker", "title", "subtitle", "meta", "actions (slot)"] },
- { "need": "Text field", "component": "GnInput", "props": ["v-model", "label", "icon", "state", "help"] },
- { "need": "Textarea", "component": "GnTextarea", "props": ["v-model", "label", "state", "help"] },
- { "need": "Select dropdown", "component": "GnSelect", "props": ["v-model", "label", "options"] },
- { "need": "Checkbox", "component": "GnCheckbox", "props": ["v-model", "label", "disabled"] },
- { "need": "Toggle switch", "component": "GnSwitch", "props": ["v-model", "label", "disabled"] },
- { "need": "Radio", "component": "GnRadio / GnRadioGroup", "props": ["v-model", "options"] },
- { "need": "Range slider", "component": "GnRange", "props": ["v-model", "label", "min", "max"] },
- { "need": "File upload", "component": "GnFileUpload", "props": ["v-model", "badge", "multiple", "accept"] },
- { "need": "Searchable select", "component": "GnCombobox", "props": ["v-model", "label", "options", "placeholder"] },
- { "need": "Tag / multi-value input", "component": "GnTagInput", "props": ["v-model", "label", "placeholder", "separator", "unique", "maxItems"] },
- { "need": "Tabs", "component": "GnTabs", "props": ["v-model", "items"] },
- { "need": "Router tabs", "component": "GnRouterTabs", "props": ["`items` (with `to`)", "activeMatch"] },
- { "need": "Accordion", "component": "GnAccordion", "props": ["items", "v-model", "multiple"] },
- { "need": "Modal dialog", "component": "GnModal", "props": ["v-model:open", "title", "closeOnBackdrop"] },
- { "need": "Side drawer", "component": "GnDrawer", "props": ["v-model:open", "title", "position"] },
- { "need": "Toasts", "component": "GnToastProvider + useToast", "propsNote": "Wrap app once; call `toast.success({ title, text })`" },
- { "need": "Confirm dialog", "component": "GnConfirmDialog", "props": ["v-model:open", "title", "message", "confirmVariant"] },
- { "need": "Table", "component": "GnTable", "props": ["columns", "rows", "caption", "emptyText"] },
- { "need": "Toolbar", "component": "GnToolbar", "props": ["title", "meta", "actions (slot)"] },
- { "need": "Input group", "component": "GnInputGroup", "props": ["addon (slot)", "action (slot)"] },
- { "need": "Search field", "component": "GnSearchField", "props": ["v-model"] },
- { "need": "Pagination", "component": "GnPagination", "props": ["page", "total-pages"] },
- { "need": "Empty state", "component": "GnEmptyState", "props": ["title", "text", "icon", "actions (slot)"] },
- { "need": "Skeleton loader", "component": "GnSkeleton", "props": [] },
- { "need": "Key-value list", "component": "GnDescriptionList", "props": ["items"] },
- { "need": "Progress bar", "component": "GnProgress", "props": ["value", "label", "animated"] },
- { "need": "Usage meter card", "component": "GnUsageMeter", "props": ["title", "value", "max", "meta"] },
- { "need": "Staged progress", "component": "GnProgressStages", "props": ["items"] },
- { "need": "Wizard steps", "component": "GnSteps", "props": ["items", "vertical"] },
- { "need": "Chip / tag", "component": "GnChip", "props": ["variant", "selected", "removable"] },
- { "need": "Chip group", "component": "GnChipGroup", "props": [] },
- { "need": "Avatar", "component": "GnAvatar", "props": ["src", "alt", "initials", "icon", "size", "status"] },
- { "need": "Identity row", "component": "GnIdentity", "propsNote": "`title`, `meta`, `avatar` props" },
- { "need": "Avatar stack", "component": "GnAvatarStack", "props": ["items"] },
- { "need": "Timeline", "component": "GnTimeline", "props": ["items"] },
- { "need": "Activity log", "component": "GnActivityLog", "props": ["items"] },
- { "need": "Basic list", "component": "GnList", "props": ["items"] },
- { "need": "Definition list", "component": "GnDefinitionList", "props": ["items"] },
- { "need": "Action list", "component": "GnActionList", "props": ["items"] },
- { "need": "Loader / spinner", "component": "GnLoader", "props": [] },
- { "need": "Status card", "component": "GnStatusCard", "props": ["title", "text", "icon", "variant"] },
- { "need": "Metric card", "component": "GnMetricCard", "props": ["label", "value", "icon", "delta", "negative", "meta"] },
- { "need": "Action card", "component": "GnActionCard", "props": ["title", "text", "actions (slot)"] },
- { "need": "Dropdown menu", "component": "GnDropdown", "props": ["items", "label", "variant"] },
- { "need": "Popover panel", "component": "GnPopover", "props": ["title", "text", "label"] },
- { "need": "Tooltip", "component": "GnTooltip", "props": ["text"] },
- { "need": "Navigation list", "component": "GnNavList", "props": ["items"] },
- { "need": "App shell", "component": "GnNavigationShell", "props": ["brand", "items", "current", "footerLeft", "footerRight"] }
+ { "need": "Button", "component": "GnButton", "props": ["variant", "size", "icon", "loading", "disabled"], "useWhen": "Any clickable command or submit action" },
+ { "need": "Icon-only button", "component": "GnIconButton", "props": ["icon", "label", "size"], "useWhen": "Action that shows only an icon (always pass `label` for accessibility)" },
+ { "need": "Copy button", "component": "GnCopyButton", "props": ["text", "icon", "successIcon", "duration", "label", "size"], "useWhen": "Copy-to-clipboard action with built-in success feedback" },
+ { "need": "Status label", "component": "GnBadge", "props": ["variant", "outline"], "useWhen": "Small colored status text (active, pending, failed) — never a hand-styled span" },
+ { "need": "Message block", "component": "GnAlert", "props": ["variant"], "useWhen": "Inline info/warning/error message inside page content" },
+ { "need": "Card / panel", "component": "GnCard", "props": ["title (slot)", "footer (slot)"], "useWhen": "Framed content block with optional title and footer" },
+ { "need": "Horizontal card", "component": "GnHorizontalCard", "props": ["image", "title", "titleHref", "icon", "footer (slot)"], "useWhen": "Media-left preview card (image + linked title)" },
+ { "need": "Login card", "component": "GnLoginCard", "props": ["title", "logoIcon", "usernameLabel", "passwordLabel", "rememberMe", "submitText", "error", "forgotHref", "signupHref"], "useWhen": "Username/password sign-in form" },
+ { "need": "User card", "component": "GnUserCard", "props": ["name", "email", "role", "avatar", "href", "compact", "actions"], "useWhen": "Person or account profile block with avatar, email and role" },
+ { "need": "Page title bar", "component": "GnPageHeader", "props": ["kicker", "title", "subtitle", "meta", "actions (slot)"], "useWhen": "Screen-level title with kicker, subtitle, meta and action buttons" },
+ { "need": "Text field", "component": "GnInput", "props": ["v-model", "label", "icon", "state", "help"], "useWhen": "Single-line text input with label, icon and validation state" },
+ { "need": "Textarea", "component": "GnTextarea", "props": ["v-model", "label", "state", "help"], "useWhen": "Multiline text input" },
+ { "need": "Select dropdown", "component": "GnSelect", "props": ["v-model", "label", "options"], "useWhen": "Choose one option from a short list via native dropdown" },
+ { "need": "Checkbox", "component": "GnCheckbox", "props": ["v-model", "label", "disabled"], "useWhen": "Boolean opt-in/opt-out choice" },
+ { "need": "Toggle switch", "component": "GnSwitch", "props": ["v-model", "label", "disabled"], "useWhen": "Instant on/off setting toggle" },
+ { "need": "Radio", "component": "GnRadio / GnRadioGroup", "props": ["v-model", "options"], "useWhen": "Single choice from a small, fully visible option set" },
+ { "need": "Range slider", "component": "GnRange", "props": ["v-model", "label", "min", "max"], "useWhen": "Numeric value picked on a min–max scale" },
+ { "need": "File upload", "component": "GnFileUpload", "props": ["v-model", "badge", "multiple", "accept"], "useWhen": "File picker with previews; it owns preview object URLs" },
+ { "need": "Searchable select", "component": "GnCombobox", "props": ["v-model", "label", "options", "placeholder"], "useWhen": "Choose one option from a large or filterable list" },
+ { "need": "Tag / multi-value input", "component": "GnTagInput", "props": ["v-model", "label", "placeholder", "separator", "unique", "maxItems"], "useWhen": "Enter multiple free-form values as chips" },
+ { "need": "Tabs", "component": "GnTabs", "props": ["v-model", "items"], "useWhen": "Switch between views within one page" },
+ { "need": "Router tabs", "component": "GnRouterTabs", "props": ["`items` (with `to`)", "activeMatch"], "useWhen": "Tabs mapped to vue-router routes" },
+ { "need": "Accordion", "component": "GnAccordion", "props": ["items", "v-model", "multiple"], "useWhen": "Collapsible disclosure groups" },
+ { "need": "Modal dialog", "component": "GnModal", "props": ["v-model:open", "title", "closeOnBackdrop"], "useWhen": "Blocking dialog; Escape, focus trap and focus return are handled for you" },
+ { "need": "Side drawer", "component": "GnDrawer", "props": ["v-model:open", "title", "position"], "useWhen": "Slide-in panel for details, quick edit or secondary flows" },
+ { "need": "Toasts", "component": "GnToastProvider + useToast", "propsNote": "Wrap app once; call `toast.success({ title, text })`", "useWhen": "Transient global notification; one toast at a time by design" },
+ { "need": "Confirm dialog", "component": "GnConfirmDialog", "props": ["v-model:open", "title", "message", "confirmVariant"], "useWhen": "Yes/no confirmation before a destructive or important action" },
+ { "need": "Table", "component": "GnTable", "props": ["columns", "rows", "caption", "emptyText"], "useWhen": "Structured data rows with per-cell customization slots" },
+ { "need": "Toolbar", "component": "GnToolbar", "props": ["title", "meta", "actions (slot)"], "useWhen": "Title/meta + action buttons row above content" },
+ { "need": "Input group", "component": "GnInputGroup", "props": ["addon (slot)", "action (slot)"], "useWhen": "Input with prefix/suffix addon or embedded action button" },
+ { "need": "Search field", "component": "GnSearchField", "props": ["v-model"], "useWhen": "Search input with clear button" },
+ { "need": "Pagination", "component": "GnPagination", "props": ["page", "total-pages"], "useWhen": "Page switcher for tables and lists" },
+ { "need": "Empty state", "component": "GnEmptyState", "props": ["title", "text", "icon", "actions (slot)"], "useWhen": "No-data placeholder with icon, text and actions" },
+ { "need": "Skeleton loader", "component": "GnSkeleton", "props": [], "useWhen": "Loading placeholder blocks while content loads" },
+ { "need": "Key-value list", "component": "GnDescriptionList", "props": ["items"], "useWhen": "Read-only key-value metadata pairs" },
+ { "need": "Progress bar", "component": "GnProgress", "props": ["value", "label", "animated"], "useWhen": "Determinate progress from 0 to max" },
+ { "need": "Usage meter card", "component": "GnUsageMeter", "props": ["title", "value", "max", "meta"], "useWhen": "Limit/quota usage card with progress bar" },
+ { "need": "Staged progress", "component": "GnProgressStages", "props": ["items"], "useWhen": "Segmented progress across named stages" },
+ { "need": "Wizard steps", "component": "GnSteps", "props": ["items", "vertical"], "useWhen": "Step-by-step wizard flow indicator (horizontal or vertical)" },
+ { "need": "Chip / tag", "component": "GnChip", "props": ["variant", "selected", "removable"], "useWhen": "Small label that can be selectable or removable" },
+ { "need": "Chip group", "component": "GnChipGroup", "props": [], "useWhen": "Row/wrap container for a group of chips" },
+ { "need": "Avatar", "component": "GnAvatar", "props": ["src", "alt", "initials", "icon", "size", "status"], "useWhen": "User/entity avatar from image, initials or icon, with optional status" },
+ { "need": "Identity row", "component": "GnIdentity", "propsNote": "`title`, `meta`, `avatar` props", "useWhen": "Compact avatar + title + meta row" },
+ { "need": "Avatar stack", "component": "GnAvatarStack", "props": ["items"], "useWhen": "Overlapping group of avatars" },
+ { "need": "Timeline", "component": "GnTimeline", "props": ["items"], "useWhen": "Vertical event history with state markers" },
+ { "need": "Activity log", "component": "GnActivityLog", "props": ["items"], "useWhen": "Compact audit or job log rows" },
+ { "need": "Basic list", "component": "GnList", "props": ["items"], "useWhen": "Simple item list" },
+ { "need": "Definition list", "component": "GnDefinitionList", "props": ["items"], "useWhen": "Term/description pairs" },
+ { "need": "Action list", "component": "GnActionList", "props": ["items"], "useWhen": "List of clickable actions" },
+ { "need": "Loader / spinner", "component": "GnLoader", "props": [], "useWhen": "Indeterminate loading spinner" },
+ { "need": "Status card", "component": "GnStatusCard", "props": ["title", "text", "icon", "variant"], "useWhen": "Card communicating a state (success/warning/error/info)" },
+ { "need": "Metric card", "component": "GnMetricCard", "props": ["label", "value", "icon", "delta", "negative", "meta"], "useWhen": "KPI number with delta and meta" },
+ { "need": "Action card", "component": "GnActionCard", "props": ["title", "text", "actions (slot)"], "useWhen": "Card with text and action buttons" },
+ { "need": "Dropdown menu", "component": "GnDropdown", "props": ["items", "label", "variant"], "useWhen": "Menu of actions revealed from a trigger button" },
+ { "need": "Popover panel", "component": "GnPopover", "props": ["title", "text", "label"], "useWhen": "Small anchored panel with title and text" },
+ { "need": "Tooltip", "component": "GnTooltip", "props": ["text"], "useWhen": "Short hover hint" },
+ { "need": "Navigation list", "component": "GnNavList", "props": ["items"], "useWhen": "Vertical navigation links list (router-aware)" },
+ { "need": "App shell", "component": "GnNavigationShell", "props": ["brand", "items", "current", "footerLeft", "footerRight"], "useWhen": "Whole-app topbar/drawer/footer navigation frame" }
]
\ No newline at end of file
diff --git a/package.json b/package.json
index c3bef6f..f39df3c 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,8 @@
"src/vue/",
"docs/",
"README.md",
- "CLAUDE.md"
+ "CLAUDE.md",
+ "AGENTS.md"
],
"exports": {
".": {
diff --git a/scripts/generate-catalog.mjs b/scripts/generate-catalog.mjs
index e7099fd..122c1d4 100644
--- a/scripts/generate-catalog.mjs
+++ b/scripts/generate-catalog.mjs
@@ -5,7 +5,7 @@
// src/vue/components/*.js.
//
// node scripts/generate-catalog.mjs regenerate + validate
-// node scripts/generate-catalog.mjs --check fail if CLAUDE.md is stale
+// node scripts/generate-catalog.mjs --check fail if CLAUDE.md or ai-guide is stale
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -14,8 +14,11 @@
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const catalogPath = join(root, "docs/catalog.json");
const claudePath = join(root, "CLAUDE.md");
+const aiGuidePath = join(root, "docs/ai-guide.md");
const START = "";
const END = "";
+const AI_START = "";
+const AI_END = "";
const rows = JSON.parse(readFileSync(catalogPath, "utf8"));
@@ -63,6 +66,10 @@
};
for(const row of rows) {
+ if(!row.useWhen) {
+ warn(`row "${row.need}" (${row.component}) is missing "useWhen"`);
+ }
+
if(row.propsNote) {
continue; // free-form note, nothing to validate
}
@@ -121,6 +128,13 @@
...rows.map(row => `| ${row.need} | ${renderComponent(row.component)} | ${renderProps(row)} |`)
].join("\n");
+const selectionTable = [
+ "| Need | Use | When |",
+ "|------|-----|------|",
+ ...rows.map(row => `| ${row.need} | ${renderComponent(row.component)} | ${row.useWhen ?? "—"} |`)
+].join("\n");
+
+// --- write CLAUDE.md --------------------------------------------------------
const claude = readFileSync(claudePath, "utf8");
const pattern = new RegExp(`${escapeRegExp(START)}[\\s\\S]*${escapeRegExp(END)}`);
if(!pattern.test(claude)) {
@@ -128,17 +142,29 @@
process.exit(1);
}
-const updated = claude.replace(pattern, `${START}\n\n${table}\n\n${END}`);
+const updatedClaude = claude.replace(pattern, `${START}\n\n${table}\n\n${END}`);
+
+// --- write docs/ai-guide.md -------------------------------------------------
+const aiGuide = readFileSync(aiGuidePath, "utf8");
+const aiPattern = new RegExp(`${escapeRegExp(AI_START)}[\\s\\S]*${escapeRegExp(AI_END)}`);
+if(!aiPattern.test(aiGuide)) {
+ console.error(`[catalog] markers not found in docs/ai-guide.md; wrap the selection table with:\n${AI_START}\n...\n${AI_END}`);
+ process.exit(1);
+}
+
+const updatedAiGuide = aiGuide.replace(aiPattern, `${AI_START}\n\n${selectionTable}\n\n${AI_END}`);
if(process.argv.includes("--check")) {
- if(updated !== claude) {
- console.error("[catalog] CLAUDE.md is stale — run `npm run gen:catalog`");
+ if(updatedClaude !== claude || updatedAiGuide !== aiGuide) {
+ const stale = [updatedClaude !== claude && "CLAUDE.md", updatedAiGuide !== aiGuide && "docs/ai-guide.md"].filter(Boolean).join(" and ");
+ console.error(`[catalog] ${stale} stale — run \`npm run gen:catalog\``);
process.exit(1);
}
- console.log("[catalog] CLAUDE.md is up to date");
+ console.log("[catalog] CLAUDE.md and docs/ai-guide.md are up to date");
} else {
- writeFileSync(claudePath, updated);
- console.log(`[catalog] CLAUDE.md updated (${rows.length} rows)`);
+ writeFileSync(claudePath, updatedClaude);
+ writeFileSync(aiGuidePath, updatedAiGuide);
+ console.log(`[catalog] CLAUDE.md and docs/ai-guide.md updated (${rows.length} rows)`);
}
if(warnings > 0) {