# GNexus UI Kit — AI Agent Reference

When working on a Vue project that depends on `gnexus-ui-kit`, **always use the official Vue adapter components** instead of writing raw HTML/CSS markup. This document is the single source of truth for which components exist and when to use them.

## Golden Rule

> Prefer `gnexus-ui-kit/vue` components. Do not recreate modal, drawer, tab, toast, form, button, or table markup manually unless the adapter does not expose the needed component yet.

## Demo Synchronization

`demo/index.html` (vanilla) and `demo/vue.html` (Vue) must always be **pixel-identical**. Any change to one requires an immediate matching change to the other.

- Section order, navigation items, and `currentSection` observer targets must match exactly.
- If a Vue adapter component cannot render markup identical to the vanilla partial, use raw HTML from the vanilla partial in the Vue demo. Do not force the adapter to match at the cost of visual differences.
- Verify synchronization after every edit by running the Playwright height comparison (`node compare-heights.js`) — it must report 0 differences across all sections.

## 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";
```

## Import Pattern

Named imports (preferred):

```js
import { GnButton, GnInput, GnModal } from "gnexus-ui-kit/vue";
```

Global registration:

```js
import { GnexusUiVue } from "gnexus-ui-kit/vue";
app.use(GnexusUiVue);
```

## Icon Naming

All icons **must** carry the `ph` class. Every icon — everywhere, always. Phosphor Icons are the only supported icon set.

- In Vue `icon` props: always use the `ph-` prefix (`ph-house`, `ph-bold ph-spinner`).
- In raw HTML: always use `<i class="ph ph-house"></i>` (both `ph` and `ph-*`).

```js
// Correct
icon="ph-house"
icon="ph-plus"
icon="ph-bold ph-spinner"

// Wrong — will trigger a dev warning and may not render
icon="house"
icon="plus"
```

```html
<!-- Correct -->
<i class="ph ph-house"></i>

<!-- Wrong — missing base `ph` class -->
<i class="ph-house"></i>
```

## Component Catalog

| Need | Component | Props you will use |
|------|-----------|-------------------|
| Button | `GnButton` | `variant`, `size`, `icon`, `loading`, `disabled` |
| Icon-only button | `GnIconButton` | `icon`, `label`, `size` |
| Copy button | `GnCopyButton` | `text`, `icon`, `successIcon`, `duration`, `label`, `size` |
| Status label | `GnBadge` | `variant`, `outline` |
| Message block | `GnAlert` | `variant` |
| Card / panel | `GnCard` | `title` (slot), `footer` (slot) |
| Horizontal card | `GnHorizontalCard` | `image`, `title`, `titleHref`, `icon`, `footer` (slot) |
| Login card | `GnLoginCard` | `title`, `logoIcon`, `usernameLabel`, `passwordLabel`, `rememberMe`, `submitText`, `error`, `forgotHref`, `signupHref` |
| User card | `GnUserCard` | `name`, `email`, `role`, `avatar`, `href`, `compact`, `actions` |
| Page title bar | `GnPageHeader` | `kicker`, `title`, `subtitle`, `meta`, `actions` (slot) |
| Text field | `GnInput` | `v-model`, `label`, `icon`, `state`, `help` |
| Textarea | `GnTextarea` | `v-model`, `label`, `state`, `help` |
| Select dropdown | `GnSelect` | `v-model`, `label`, `options` |
| Checkbox | `GnCheckbox` | `v-model`, `label`, `disabled` |
| Toggle switch | `GnSwitch` | `v-model`, `label`, `disabled` |
| Radio | `GnRadio` / `GnRadioGroup` | `v-model`, `options` |
| Range slider | `GnRange` | `v-model`, `label`, `min`, `max` |
| File upload | `GnFileUpload` | `v-model`, `badge`, `multiple`, `accept` |
| Searchable select | `GnCombobox` | `v-model`, `label`, `options`, `placeholder` |
| Tag / multi-value input | `GnTagInput` | `v-model`, `label`, `placeholder`, `separator`, `unique`, `maxItems` |
| Tabs | `GnTabs` | `v-model`, `items` |
| Router tabs | `GnRouterTabs` | `items` (with `to`), `activeMatch` |
| Accordion | `GnAccordion` | `items`, `v-model`, `multiple` |
| Modal dialog | `GnModal` | `v-model:open`, `title`, `closeOnBackdrop` |
| Side drawer | `GnDrawer` | `v-model:open`, `title`, `position` |
| Toasts | `GnToastProvider` + `useToast` | Wrap app once; call `toast.success({ title, text })` |
| Confirm dialog | `GnConfirmDialog` | `v-model:open`, `title`, `message`, `confirmVariant` |
| Table | `GnTable` | `columns`, `rows`, `caption`, `emptyText` |
| Toolbar | `GnToolbar` | `title`, `meta`, `actions` (slot) |
| Input group | `GnInputGroup` | `addon` (slot), `action` (slot) |
| Search field | `GnSearchField` | `v-model` |
| Pagination | `GnPagination` | `page`, `total-pages` |
| Empty state | `GnEmptyState` | `title`, `text`, `icon`, `actions` (slot) |
| Skeleton loader | `GnSkeleton` | — |
| Key-value list | `GnDescriptionList` | `items` |
| Progress bar | `GnProgress` | `value`, `label`, `animated` |
| Usage meter card | `GnUsageMeter` | `value`, `label`, `max` |
| Staged progress | `GnProgressStages` | `items` |
| Wizard steps | `GnSteps` | `items`, `current` |
| Chip / tag | `GnChip` | `variant`, `selected`, `removable` |
| Chip group | `GnChipGroup` | — |
| Avatar | `GnAvatar` | `initials`, `icon`, `image`, `size`, `status` |
| Identity row | `GnIdentity` | `title`, `meta`, `avatar` props |
| Avatar stack | `GnAvatarStack` | `items` |
| Timeline | `GnTimeline` | `items` |
| Activity log | `GnActivityLog` | `items` |
| Basic list | `GnList` | `items` |
| Definition list | `GnDefinitionList` | `items` |
| Action list | `GnActionList` | `items` |
| Loader / spinner | `GnLoader` | — |
| Status card | `GnStatusCard` | `variant`, `title`, `meta` |
| Metric card | `GnMetricCard` | `title`, `value`, `trend` |
| Action card | `GnActionCard` | `title`, `text`, `actions` (slot) |
| Dropdown menu | `GnDropdown` | `items`, `label`, `variant` |
| Popover panel | `GnPopover` | `title`, `text`, `label` |
| Tooltip | `GnTooltip` | `text` |
| Navigation list | `GnNavList` | `items` |
| App shell | `GnNavigationShell` | `brand`, `items`, `current`, `footerLeft`, `footerRight` |

## Variants

Use only these variant names:

```
primary, secondary, accent, success, warning, danger, error, info
```

`danger` and `error` render the same color in most components.

## What NOT to do

- Do **not** copy raw modal markup from demo partials into Vue apps.
- Do **not** call `GNexusUIKit.Modals.create()` from Vue components.
- Do **not** call `GNexusUIKit.Overlays.init()` or `GNexusUIKit.NavigationShell.init()` in Vue projects.
- Do **not** run `Accordion.init()` or `Tabs.init()` inside Vue components.
- Do **not** use the legacy `advancedSelect()` helper in Vue; use `GnCombobox`.
- Do **not** use `InputPatterns.init()` for Vue file upload previews; use `GnFileUpload`.
- Do **not** invent new variant names.
- Do **not** duplicate GNexus CSS in scoped `<style>` blocks.
- Do **not** write `class="btn btn-primary"` when `GnButton` exists.

## Acceptable Raw Markup

Raw classes are only acceptable for layout wrappers and content that has **no Vue adapter yet**:

```html
<div class="form-grid">...</div>
<div class="demo-actions">...</div>
```

For any interactive component, add or extend the adapter first.

## Slot Conventions

- `GnTabs` uses tab ids as slot names: `<template #overview>...</template>`
- `GnTable` uses `cell-${column.key}`: `<template #cell-status="{ value }"><GnBadge>{{ value }}</GnBadge></template>`
- `GnModal` uses `default`, `title`, `footer`, and `actions` (actions receives `{ close }`)

## Behavior Contracts

- `GnModal` and `GnDrawer` handle Escape, focus return, and Tab trapping automatically.
- `GnToastProvider` shows one toast at a time: a new toast replaces the previous one.
- `GnCombobox` owns combobox/listbox ARIA and keyboard navigation.
- `GnFileUpload` owns preview object URLs and cleans them up on remove/unmount.

## Working on This Repo — Lessons

These rules exist because previous sessions produced regressions when they were ignored.

### One change, one verification
Never make three edits and then check the result. After every single change, run `npx gulp build`, open the browser, and confirm the affected component still renders correctly. Catching a bug in the first edit saves more time than catching three stacked bugs in the fourth.

### Always compare with vanilla HTML before implementing or fixing a Vue component
When adding or fixing a Vue adapter component (e.g., `GnLoginCard`), open `demo/partials/*.html` first. The vanilla HTML is the source of truth for markup structure, CSS classes, icon names, and default prop values. Do not trust the existing Vue component or the existing demo page to be correct.

### Screenshot every visual change
If a component's appearance changed, take a Playwright screenshot before and after. A diff that looks "slightly off" often hides a real bug (wrong padding, missing border, clipped text). Do not dismiss visual glitches as "probably fine."

### CSS transitions + Vue render cycle
When a component's visibility is toggled via `v-if` / conditional rendering, the initial render must include the "hidden" state class first, and the "visible" state class must be applied in a `nextTick` + `requestAnimationFrame`. If both states exist in the same render frame, the browser skips the transition entirely. This applies to `GnModal`, `GnDrawer`, and any future overlay component.

### Validate prop contracts against demo data
If a demo passes `{ label: "Foo" }` and the component expects `{ title: "Foo" }`, the UI will render empty items. Before wiring demo data into a component, read the component's `props` declaration. If they don't match, fix the component fallback (e.g., `item.title || item.label`) or fix the demo data — don't leave it broken.

### Components with intrinsic max-width do not belong in tight grids
`GnNavList`, `GnSteps`, `GnProgress`, `GnTimeline`, `GnHorizontalCard`, `GnLoginCard`, `GnUsageMeter`, and `GnUserCard` define their own `max-width` in CSS. Putting them inside `display: grid` with `minmax(320px, 1fr)` causes overflow, clipping, or forced squishing. Render them as block-level siblings instead.

### Do not stack unrelated UI into one demo section
A section titled "Chips & Toolbar" that also contains Pagination creates confusion about component boundaries. Each major component gets its own `demo-section` with its own `h3`.

### Check event handler syntax in Vue templates
`@click="toastRef.value?.success(...)"` does not work in Vue 3 SFC/inline templates the way it works in plain JS. The template compiler handles ref unwrapping differently from `setup()` return values. If a method call fails silently, extract a helper function in `setup()` and call that instead.

## Verification

Before claiming Vue adapter compatibility in this repo, run:

```bash
npm run build
npm run test:vue-adapter
```
