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
12 changes: 12 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Summary

<!-- Provide a brief summary of the changes and the motivation behind them. -->

## Release Title Check

- [ ] If this PR changes published package behavior, the PR title is a Conventional Commit such as `fix: ...` or `feat: ...`.
- [ ] If this PR is docs, CI, tests, or chores only, the PR title uses an appropriate non-release type such as `docs: ...`, `ci: ...`, or `chore: ...`.

## Validation

<!-- Describe how you validated your changes (e.g., manual testing, unit tests, etc.) -->
42 changes: 42 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# AGENTS.md

## Scope

These instructions apply to the entire repository.

## Project Snapshot

`@tomny-dev/uzi` is a React component library built with Vite, TypeScript, CSS modules, and pnpm. Do not commit generated `dist/` output.

## Start Here

- Use `AGENTS.md` as the canonical agent instruction file.
- Read `DEVELOPMENT.md` for setup, local consumer-app testing, preview builds, and publishing.
- Read `README.md` and `docs/` for component APIs, theming, layout, and form patterns.
- For component work, prefer native HTML for simple controls, Radix for interaction-heavy primitives, and `uzi` composition for design-system templates.

## Commands

Use Node 24 LTS for local development and CI parity.

- Install: `pnpm install`
- Typecheck: `pnpm lint`
- Test: `pnpm test`
- Build: `pnpm build`

## Release Conventions

Stable versioning is managed by Release Please. Release Please only opens release PRs for Conventional Commit messages that land on `main`.

- PR titles and squash/merge commit titles must use Conventional Commit format.
- Use `fix: ...` for patch releases and `feat: ...` for minor releases.
- Use `fix(scope): ...` or `feat(scope): ...` when a scope helps, for example `fix(modal): constrain dialog height`.
- Use `!` or a `BREAKING CHANGE:` footer only for intentional major releases.
- Do not merge release-worthy changes with titles like `[codex] ...`, `fix modal sizing`, or `update stuff`.
- `docs:`, `chore:`, `ci:`, `test:`, and `refactor:` are valid for non-release work, but they do not normally publish a new stable package.

Before opening or updating a PR, make the PR title match the intended squash/merge title. If a PR changes runtime package behavior, prefer a release-triggering `fix:` or `feat:` title.

## Done Criteria

For code changes, run `pnpm lint`, `pnpm test`, and `pnpm build` when feasible. For docs-only changes, run a targeted formatting or content check and explain why package checks were not needed.
152 changes: 2 additions & 150 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,153 +1,5 @@
# CLAUDE.md

## Overview
This repository uses `AGENTS.md` as the canonical agent instruction file.

`@tomny-dev/uzi` is a personal React component library. CSS modules for styling, no Tailwind dependency.

## Commands

```bash
pnpm install # install deps
pnpm dev # watch mode
pnpm build # production build (Vite + tsc -> dist/)
pnpm lint # type check only
```

## Publishing

Every push to `main` automatically:
1. Bumps the patch version in `package.json`
2. Commits the version bump with `[skip ci]`
3. Publishes to npm as `@tomny-dev/uzi`

Pull requests also publish preview builds to npm under a PR-specific dist-tag (for example `pr-123`) without updating `latest`. Use these to test consumer apps before merge.

For minor/major bumps, edit `package.json` version manually before pushing.

## Adding Components

1. Create `src/components/<name>/<Name>.tsx` and `<name>.module.css`
2. Add `"use client";` at the top of any component that uses React hooks
3. Export from `src/index.ts`
4. Choose the right base before implementing:
- native HTML for simple controls
- Radix for accessibility-heavy interactive primitives
- `uzi` composition for layout shells and opinionated templates

## Architecture

- **No Tailwind** - uses CSS modules
- **`cx()` utility** - exported from `src/utils/cx.ts`, use for conditional class names
- **Vite + TypeScript** - Vite builds the ESM/CJS bundles and CSS, while `tsc` emits type declarations to `dist/`
- **`"use client"` boundary** - client entry points declare `"use client"` in source; the server entry remains server-safe
- **`src/server.ts`** - separate Vite library entry with no `"use client"` boundary; safe to import in Next.js server components. Currently exports `getThemeScript`.
- **`@tomny-dev/uzi/server`** - the `/server` subpath export; consumer tsconfigs need `"moduleResolution": "bundler"` to resolve it

### Component philosophy

- **Native first for simple controls** - prefer plain HTML inputs when the browser behavior is already correct and styling requirements are modest.
- **Radix for hard primitives** - use Radix for components with non-trivial keyboard interaction, focus management, portals, overlays, or selection state machines.
- **`uzi` for the design system layer** - `uzi` should provide the styled API, tokens, and app-level templates rather than reinventing primitive behavior from scratch.

Good `uzi` targets: `TopBar`, `SidebarNav`, `AppShell`, themed wrappers, and opinionated composites.

Good Radix-backed targets: `Select`, `DropdownMenu`, `Modal`, `Toast`, and future `Popover`, `Tooltip`, `Tabs`, and similar interaction-heavy components.

## Design Tokens (CSS custom properties)

Defined in `src/theme/theme.css`. Components use these - never hardcode colors.

| Token | Purpose |
|---|---|
| `--background` | Page background |
| `--foreground` | Primary text |
| `--card` | Elevated surface (cards, panels) |
| `--border` | Borders and dividers |
| `--muted` | Subtle background fills |
| `--muted-foreground` | Secondary/placeholder text |
| `--primary` | Brand accent color |
| `--ring` | Focus ring base color |
| `--focus-ring` | `2px solid color-mix(in srgb, var(--ring) 50%, transparent)` - use on `:focus-visible` |
| `--focus-ring-offset` | `2px` - pair with `--focus-ring` |
| `--surface-topbar` | TopBar background |
| `--destructive` | Error/danger color |

### Focus rings

All interactive elements must use:
```css
:focus-visible {
outline: var(--focus-ring);
outline-offset: var(--focus-ring-offset);
}
```

Never use `outline: none` without replacing it with `:focus-visible`. Never use `box-shadow` as a focus ring substitute.

### Using alpha variants

Use `color-mix` - never hardcode rgba:
```css
background: color-mix(in srgb, var(--primary) 10%, transparent);
```

## Theming

- Themes are applied via `data-uzi-theme="dark|light"` on `<html>`
- Accents via `data-uzi-accent="amber|cyan|..."` on `<html>`
- `ThemeProvider` applies these at runtime; for SSR flash prevention, set attributes directly on `<html>` in the server layout
- `getThemeScript()` (from `@tomny-dev/uzi/server`) returns a blocking inline script string that reads localStorage and sets theme attributes before first paint - use when theme is user-switchable
- For fixed themes (e.g. always dark), skip `getThemeScript` and hardcode `className="dark" data-uzi-theme="dark" style={{ colorScheme: "dark" }}` on `<html>`

## AppShell layout contract

`AppShell` renders a full-viewport grid (`min-height: 100dvh`). The `main` area has no default padding - **each page is responsible for its own padding and max-width**. Use `mx-auto w-full max-w-7xl px-4 py-6 sm:px-6 lg:px-8` as the standard page wrapper. Pages that need full-bleed layouts (e.g. split-panel UIs) can skip this wrapper and control their own layout directly.

## For Consumer Repos

When using `@tomny-dev/uzi` in a project, add this to the consumer's `CLAUDE.md`:

```md
## UI Components
This project uses `@tomny-dev/uzi` for UI primitives.
- No Tailwind - components use CSS modules internally
- `"use client"` is handled by the bundle - no need to wrap imports
- App-specific components (e.g. BrandLogo) should live in the app, not in uzi
- Server-safe exports (e.g. `getThemeScript`) are at `@tomny-dev/uzi/server`

Available components:
- `Button` - primary, secondary, outline, ghost variants
- `Card` - surface container with tone and padding props
- `Pill` - inline badge/tag
- `Alert` - feedback banner; tones: success, error, warning, info
- `Modal` - Radix-backed overlay dialog with size variants (sm, md, lg, xl)
- `ModalOverlay` - Radix-backed bare dialog layer; use when you need a custom modal layout
- `MultiSelect` - custom multi-option picker with checkbox-style menu
- `Select` - styled Radix-based single-select field
- `Dropdown` - deprecated compatibility alias for Select
- `AppShell` - responsive layout with collapsible sidebar and sticky topbar; no default padding on main area
- `SidebarNav` - sidebar navigation list
- `ThemeProvider` - applies theme/accent tokens; use `theme` prop for controlled (fixed) themes, `defaultTheme` for user-switchable
- `ThemeToggleButton` - light/dark toggle button for the topbar
- `ToastProvider` / `useToast` - Radix-backed toast notification system
- `cx` - utility for conditional class name merging
```

## Component List

| Component | Hooks used | Notes |
|---|---|---|
| `Button` | none | |
| `Card` | none | |
| `Pill` | none | |
| `Alert` | none | role="alert"; tones: success, error, warning, info |
| `Modal` | none | Built on `ModalOverlay` and Radix Dialog |
| `ModalOverlay` | none | Radix Dialog wrapper for custom modal layouts |
| `MultiSelect` | `useMemo`, `useCallback`, `forwardRef` | Custom multi-select with hidden input support |
| `Select` | `forwardRef` | Radix-backed single-select with styled popup content |
| `Dropdown` | `forwardRef` | Deprecated Select compatibility alias |
| `AppShell` | `useState`, `useEffect`, `useRef`, `useId` | |
| `SidebarNav` | none | Active item uses `color-mix(primary)` bg |
| `ThemeProvider` | `useState`, `useEffect` | Lazy initializers read localStorage synchronously |
| `ThemeToggleButton` | none | |
| `ToastProvider` / `useToast` | `createContext`, `useState`, `useEffect`, `useRef`, `useMemo`, `useCallback` | Radix Toast viewport and root primitives |
Claude and other agents should read and follow [AGENTS.md](./AGENTS.md).
4 changes: 3 additions & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ Each workflow run also publishes a new exact prerelease version for that PR and

Versioning and stable npm publishing are managed by Release Please:

1. Merge feature and fix PRs into `main` using Conventional Commit messages such as `fix: ...` or `feat: ...`.
1. Merge feature and fix PRs into `main` using Conventional Commit titles such as `fix: constrain modal height` or `feat: add auth card`.
2. The release workflow opens or updates a Release Please PR that bumps `package.json`, updates `CHANGELOG.md`, and advances `.release-please-manifest.json`.
3. Merge the Release Please PR to create the `vX.Y.Z` tag and GitHub release.
4. The same release workflow validates and publishes `@tomny-dev/uzi@X.Y.Z` to npm.

PR titles and squash/merge commit titles matter because the squash title usually becomes the commit subject on `main`. Use `fix: ...` for patch releases and `feat: ...` for minor releases. Do not merge release-worthy changes with titles like `[codex] ...`, `fix modal sizing`, or `update components`.

Stable releases publish with npm provenance. Configure npm Trusted Publishing for the `Release` workflow, or keep a scoped `NPM_TOKEN` repository secret as a fallback. Preview builds continue to use `NPM_TOKEN` because they publish PR-specific prerelease versions.
Loading