# AGENTS.md — @42-components

Guidance for AI agents and contributors working in this repository.

> **For component usage docs (markup, options, events)** → see [`LLM.md`](LLM.md)

## What this project is

`@42-components` is a **headless UI component library** written in vanilla
TypeScript. Components ship behaviour, state, accessibility and keyboard support
but **no opinionated styling**. An optional theme is published as a separate package.

- npm scope: **`@42`** (public).
- Package manager: **pnpm workspaces** (`shamefully-hoist=true`).
- Node `>=24`.

## Monorepo layout

```
packages/
  core/            @42/core  — headless controllers (framework-agnostic)
    <component>/
      <component>.ts         controller class (state, ARIA, keyboard, events)
      <component>.types.ts   options + event names/types
      <component>.css        functional (unstyled) CSS
      <component>.test.ts    Vitest + jsdom
      index.ts               public barrel
    shared/        dom.ts (uid, toArray), focus.ts (getFocusable, FocusTrap),
                   controller.ts (HeadlessController contract),
                   textarea-caret.ts (getCaretCoordinates — selection geometry)
  styles/          @42/styles — optional theme (CSS custom properties)
stories/           Storybook stories (@storybook/html-vite)
.storybook/        Storybook config
docs/llm/          Deep dives for complex components + per-category reference
                   (reference/<category>.md are generated; reference/_fragments/
                   *.md are the authored source with frontmatter)
LLM.md             Router/index for AI agents (generated "Choosing a component" block)
```

## Headless contract (must follow for every component)

A controller attaches to existing DOM the consumer provides (progressive
enhancement). It MUST:

1. Find its parts via `data-*` selectors (e.g. `[data-c42-accordion-trigger]`).
2. Manage state and set ARIA attributes (`aria-expanded`, `aria-controls`,
   `role`, `aria-modal`, etc.).
3. Handle keyboard interaction (arrows, Home/End, Esc, Tab/focus trap).
4. Reflect state on `data-state="open|closed"` (and `data-disabled` where
   relevant) so CSS can react — never apply visual styles in JS.
5. Dispatch typed `CustomEvent`s on the root (`<name>:open`, `<name>:change`…),
   bubbling, and expose an `on(event, handler)` convenience returning an
   unsubscribe fn.
6. Expose `destroy()` that removes every listener/side effect.
7. Throw a clear `[42/<name>]` error when required markup is missing.

Constructor signature convention: `new Component(root: HTMLElement, options?)`.

## Packages independence (no coupling)

- `@42/core` has **no framework dependencies**. Component-specific libs are real
  deps only where used (e.g. `@floating-ui/dom` for `tooltip`/`dropdown`) and are
  externalized in the build.
- Subpath exports give per-component tree-shaking: import `@42/core/accordion`,
  not the barrel, in size-sensitive code.

## Build & tooling

- **Build**: Vite library mode, multi-entry per component, `vite-plugin-dts` for
  `.d.ts`. Core copies functional CSS to `dist` via `copy-assets.mjs` (run after
  `vite build`). When adding a component, update:
  `packages/core/vite.config.ts` (entry), `packages/core/package.json` (exports
  + `style.css`), `copy-assets.mjs`, and `packages/core/index.ts`.
- **Tests**: Vitest + jsdom + `@testing-library/dom`. Config in
  `vitest.config.ts` (root). Keep tests layout-agnostic (no offsetParent /
  visibility assumptions). Do NOT assert floating-ui pixel positions.
- **Lint/format**: ESLint flat config (`typescript-eslint`) + Prettier
  (single quotes, semicolons, width 100).
- **Storybook**: `@storybook/html-vite` with `@storybook/addon-a11y`. Stories are
  plain HTML that instantiate the controller; import the theme CSS in the story.
  Toolbar toggle for dark mode via `data-theme` attribute.

### Commands

```bash
pnpm install
pnpm test               # all unit tests
pnpm build              # build @42/core, @42/styles (topological)
pnpm lint
pnpm build-storybook    # verifies stories + CSS compile (artifact is gitignored)
pnpm docs:generate      # regenerate LLM.md index + docs/llm/reference/<category>.md
pnpm docs:check         # fail if generated docs are stale (CI/pre-commit)
```

Always run `pnpm test` and `pnpm lint` before committing. For style/story
changes, run `pnpm build-storybook` as the integration check.

## Theming system

Two-layer CSS custom properties:

1. **Primitives** (`--c42-primary-500`, `--c42-gray-900`, etc.) — the palette users override.
2. **Semantic tokens** (`--c42-color-accent`, `--c42-color-fg`, etc.) — what components consume.

Dark mode is in `packages/styles/dark.css` — activates via `[data-theme="dark"]` or `prefers-color-scheme`.

A global minimal scrollbar ships in `packages/styles/scrollbar.css` (imported by
`index.css`); it is theme-aware (derives from `--c42-color-fg`) and tunable via
`--c42-scrollbar-size` / `--c42-scrollbar-thumb` / `--c42-scrollbar-thumb-hover`.

See `LLM.md` → Theming section for details.

## TypeScript conventions

- Strict mode, `verbatimModuleSyntax: true` → use `import type` / inline `type`.
- Explicit return types on public methods.
- Re-export floating-ui's `Placement` only from the component's own subpath, NOT
  from the root barrel (avoids duplicate-name clashes across `export *`).

## Adding a new component (checklist)

1. `packages/core/<name>/` with `<name>.ts`, `<name>.types.ts`, `<name>.css`,
   `<name>.test.ts`, `index.ts` (follow the headless contract above).
2. Functional CSS in core; themed CSS in `packages/styles/<name>.css` + add to
   `index.css` and the `@42/styles` exports map.
3. Register build entry/exports/copy-assets/barrel (see Build section).
4. Add a story in `stories/<Name>.stories.ts` with a category-grouped title
   `Core/<Category>/<Name>` (see `docs/component-taxonomy.md` for the categories).
5. Add `docs/llm/reference/_fragments/<name>.md` with frontmatter
   (`name`, `category`, `summary`, `subpath`, optional `deepDoc`) and the usage
   body; add `docs/llm/<name>.md` if complex. Then run `pnpm docs:generate`
   (the `LLM.md` index and `docs/llm/reference/<category>.md` are generated — do
   not edit them by hand).
6. `pnpm test && pnpm lint && pnpm build && pnpm docs:check`.

## Commit convention

### Format

```
type(scope): short description
```

- One space after the colon, no spaces before the parenthesis.
- `scope` is optional but recommended.
- Description: lowercase, imperative mood, concise.
- Subject line under 72 characters, no trailing period.

### Types

| Type | When to use |
| --- | --- |
| `feat` | New component, feature, or functionality |
| `fix` | Bug fix |
| `refactor` | Code change that neither fixes a bug nor adds a feature |
| `chore` | Tooling, dependencies, config, scripts |
| `docs` | Documentation only (`LLM.md`, `AGENTS.md`, `docs/llm/`) |
| `style` | Formatting, whitespace, missing semicolons (no logic change) |
| `test` | Adding or updating tests |
| `perf` | Performance improvement |
| `revert` | Reverts a previous commit |

### Scope guidance

Use the scope to identify the affected area. Keep it short and consistent:

| Scope | Meaning |
| --- | --- |
| `accordion`, `dialog`, `tooltip`… | A specific component |
| `core` | Shared utilities in `packages/core/shared/` |
| `styles` | The `@42/styles` package |
| `stories` | Storybook stories or config |
| `build` | Vite config, `copy-assets.mjs`, exports maps |
| `deps` | Dependency updates |

### Examples

```
feat(tabs): add vertical orientation support
fix(dialog): prevent scroll when modal is open
refactor(core): extract focus-trap to shared utility
chore(deps): upgrade vitest to v3
docs(accordion): update LLM.md entry with new options
test(form): add unit tests for custom validators
perf(tooltip): debounce position recalculations
style(core): fix prettier violations in shared/dom.ts
```