cloudflare/kumo
Publicmirrored fromhttps://github.com/cloudflare/kumoAvailable
AGENTS.md
1014lines · modecode
| 1 | # AGENTS.md |
| 2 | |
| 3 | Comprehensive guide for AI agents and developers working with Kumo. |
| 4 | |
| 5 | ## Quick Start |
| 6 | |
| 7 | ```bash |
| 8 | pnpm add @cloudflare/kumo |
| 9 | ``` |
| 10 | |
| 11 | ```tsx |
| 12 | import { Button } from "@cloudflare/kumo"; |
| 13 | ``` |
| 14 | |
| 15 | **CRITICAL:** Only use semantic tokens (`bg-kumo-base`, `text-kumo-default`). Never raw Tailwind colors (`bg-blue-500`). |
| 16 | |
| 17 | ```bash |
| 18 | pnpm --filter @cloudflare/kumo codegen:registry # Generate component-registry.{json,md} |
| 19 | ``` |
| 20 | |
| 21 | ## Repository Overview |
| 22 | |
| 23 | `Kumo` is Cloudflare's component library for building modern web applications. It is a `pnpm` monorepo containing a React component library and its documentation site. The library provides accessible, design-system-compliant UI components built on [Base UI](https://base-ui.com/). |
| 24 | |
| 25 | ## Dynamic Code Analysis Features |
| 26 | |
| 27 | Kumo provides extensive automated tooling (`packages/kumo/scripts/`): |
| 28 | |
| 29 | **1. Component Registry + CLI** (`scripts/component-registry/`, `src/command-line/`) - AI-readable metadata & CLI |
| 30 | |
| 31 | - Auto-generates `ai/component-registry.{json,md}` from TypeScript types + demo files |
| 32 | - **Demo examples** are extracted from `kumo-docs-astro/src/components/demos/` (not kumo) |
| 33 | - Includes: props, variants, examples, semantic tokens, sub-components |
| 34 | - **Exported CLI:** `npx @cloudflare/kumo {ls|doc|docs}` - Quick component reference |
| 35 | |
| 36 | **Build Pipeline:** |
| 37 | |
| 38 | ``` |
| 39 | kumo-docs-astro/src/components/demos/*.tsx |
| 40 | ↓ (pnpm codegen:demos) |
| 41 | kumo-docs-astro/dist/demo-metadata.json |
| 42 | ↓ (pnpm codegen:registry) |
| 43 | kumo/ai/component-registry.{json,md} |
| 44 | ``` |
| 45 | |
| 46 | **2. Figma Plugin** (`packages/kumo-figma/`) - React → Figma components |
| 47 | |
| 48 | - Separate package: `@cloudflare/kumo-figma` |
| 49 | - 30+ generators (Button, Dialog, Tabs, Toast, etc.) |
| 50 | - Parses Tailwind → Figma auto-layout, binds semantic tokens to variables |
| 51 | - Icon library generation, loader variants, opacity modifiers |
| 52 | - **IMPORTANT:** After modifying any generator in `packages/kumo-figma/src/generators/`, you must rebuild the plugin with `pnpm --filter @cloudflare/kumo-figma build` before testing in Figma |
| 53 | |
| 54 | **3. Figma Token Sync** (`packages/kumo-figma/scripts/`) - CSS → Figma Variables API |
| 55 | |
| 56 | - Syncs semantic tokens from kumo theme CSS to Figma |
| 57 | - Parses `light-dark()`, converts colors (oklch/hex → Figma RGB) |
| 58 | - Run with `pnpm --filter @cloudflare/kumo-figma figma:sync` |
| 59 | |
| 60 | **4. Custom Lint Rules** (`lint/`) - Design system enforcement |
| 61 | |
| 62 | - `no-primitive-colors`: Blocks `bg-blue-500`, enforces semantic tokens |
| 63 | - `no-tailwind-dark-variant`: Prevents `dark:` (auto via tokens) |
| 64 | |
| 65 | **5. Color Analysis** (`scripts/color/`) - Token extraction & usage stats |
| 66 | |
| 67 | - Analyzes semantic token usage, generates color docs |
| 68 | |
| 69 | **6. Primitives Generator** (`scripts/generate-primitives.ts`) - Base UI exports |
| 70 | |
| 71 | - Auto-generates tree-shakeable primitive exports, updates package.json |
| 72 | |
| 73 | **Key Commands:** |
| 74 | |
| 75 | ```bash |
| 76 | pnpm --filter @cloudflare/kumo codegen:registry # Component registry |
| 77 | pnpm --filter @cloudflare/kumo codegen # All codegen (primitives + registry) |
| 78 | npx @cloudflare/kumo doc # CLI docs (works in any project) |
| 79 | pnpm --filter @cloudflare/kumo-figma build # Build Figma plugin |
| 80 | pnpm --filter @cloudflare/kumo-figma figma:sync # Sync tokens to Figma |
| 81 | pnpm lint # Custom rules + oxlint |
| 82 | ``` |
| 83 | |
| 84 | ## Project Structure |
| 85 | |
| 86 | ``` |
| 87 | kumo/ |
| 88 | ├── packages/ |
| 89 | │ ├── kumo/ # Component library (@cloudflare/kumo) |
| 90 | │ │ ├── src/ |
| 91 | │ │ │ ├── components/ # UI components (button, dialog, input, etc.) |
| 92 | │ │ │ ├── blocks/ # Composite components (breadcrumbs, page-header) |
| 93 | │ │ │ ├── pages/ # Full page components |
| 94 | │ │ │ ├── styles/ # CSS including kumo-binding.css |
| 95 | │ │ │ ├── utils/ # Utilities (cn, link-provider) |
| 96 | │ │ │ └── index.ts # Main exports |
| 97 | │ │ ├── ai/ # Component registry and JSON UI catalog |
| 98 | │ │ ├── scripts/ # Build and linting scripts |
| 99 | │ │ └── package.json |
| 100 | │ ├── kumo-docs-astro/ # Documentation site (Astro) |
| 101 | │ │ ├── src/ |
| 102 | │ │ │ ├── components/demos/ # Interactive component demos |
| 103 | │ │ │ ├── pages/ # File-based routing |
| 104 | │ │ │ └── layouts/ # Page layouts |
| 105 | │ │ └── package.json |
| 106 | │ └── kumo-figma/ # Figma plugin (@cloudflare/kumo-figma) |
| 107 | │ ├── src/ |
| 108 | │ │ ├── generators/ # 30+ component generators |
| 109 | │ │ ├── parsers/ # Tailwind → Figma conversion |
| 110 | │ │ └── code.ts # Plugin entry point |
| 111 | │ ├── scripts/ # Token sync to Figma |
| 112 | │ └── package.json |
| 113 | ├── ci/ # CI/CD scripts and versioning |
| 114 | │ ├── reporters/ # MR report generation |
| 115 | │ ├── scripts/ # Deployment scripts |
| 116 | │ └── versioning/ # Beta/production release scripts |
| 117 | ├── lint/ # Custom ESLint rules |
| 118 | ├── .changeset/ # Changeset files for versioning |
| 119 | └── package.json # Workspace root |
| 120 | ``` |
| 121 | |
| 122 | ## Architecture |
| 123 | |
| 124 | ### Component Library (`packages/kumo`) |
| 125 | |
| 126 | - **Built with**: React, TypeScript, Tailwind CSS v4, Base UI |
| 127 | - **Icons**: `@phosphor-icons/react` |
| 128 | - **Styling**: `cn()` utility combining `clsx` + `tailwind-merge` |
| 129 | - **Build**: Vite in library mode with tree-shakeable exports |
| 130 | |
| 131 | ### Documentation Site (`packages/kumo-docs-astro`) |
| 132 | |
| 133 | - **Framework**: Astro + React |
| 134 | - **Deployment**: Cloudflare Workers |
| 135 | - **Dev server**: `http://localhost:4321` |
| 136 | - **Routes**: `/cli`, `/colors`, `/registry` for internal tools |
| 137 | |
| 138 | ## Component Registry (Source of Truth) |
| 139 | |
| 140 | **Location:** |
| 141 | |
| 142 | - `packages/kumo/ai/component-registry.json` - Machine-readable (28 components) |
| 143 | - `packages/kumo/ai/component-registry.md` - Human-readable (1575 lines) |
| 144 | |
| 145 | ### CLI Commands |
| 146 | |
| 147 | The Kumo CLI provides quick access to component documentation, especially useful when `node_modules` is gitignored: |
| 148 | |
| 149 | ```bash |
| 150 | # List all components grouped by category |
| 151 | npx @cloudflare/kumo ls |
| 152 | |
| 153 | # Get documentation for a specific component |
| 154 | npx @cloudflare/kumo doc Button |
| 155 | npx @cloudflare/kumo doc Dialog |
| 156 | |
| 157 | # Get documentation for ALL components |
| 158 | npx @cloudflare/kumo docs |
| 159 | |
| 160 | # Show help |
| 161 | npx @cloudflare/kumo help |
| 162 | ``` |
| 163 | |
| 164 | **Note:** `kumo doc` (without a component name) is equivalent to `kumo docs`. |
| 165 | |
| 166 | ### Query examples (using jq): |
| 167 | |
| 168 | ```bash |
| 169 | # Get Button props |
| 170 | jq '.components.Button.props' component-registry.json |
| 171 | |
| 172 | # List Action category components |
| 173 | jq '.search.byCategory.Action' component-registry.json |
| 174 | |
| 175 | # Get all component names |
| 176 | jq '.search.byName' component-registry.json |
| 177 | |
| 178 | # Find components using a specific token |
| 179 | grep "bg-kumo-base" component-registry.md |
| 180 | ``` |
| 181 | |
| 182 | **Registry contains:** |
| 183 | |
| 184 | - Props (type, required/optional, default values, descriptions) |
| 185 | - Variants (enum values with descriptions) |
| 186 | - Examples (real code from stories) |
| 187 | - Semantic colors used (kumo tokens only) |
| 188 | - Sub-components (for compound patterns like Dialog.Root, Dialog.Trigger) |
| 189 | |
| 190 | ## Critical Rules |
| 191 | |
| 192 | ### ❌ NEVER |
| 193 | |
| 194 | - **Raw Tailwind colors:** `bg-blue-500`, `text-gray-900` → Breaks theming, fails lint |
| 195 | - **Dark mode variants:** `dark:bg-black` → Dark mode is automatic via semantic tokens |
| 196 | - **Missing displayName:** forwardRef components must set `displayName` for debugging |
| 197 | - **Skipping registry:** Always check `component-registry.json` for component API before use |
| 198 | |
| 199 | ### ✅ ALWAYS |
| 200 | |
| 201 | - **Semantic tokens:** `bg-kumo-base`, `text-kumo-default`, `border-kumo-line`, `ring-kumo-ring` |
| 202 | - **Query registry first:** Props, variants, and examples are always current |
| 203 | - **Use `cn()` utility:** For className composition (`cn("base", conditional && "extra", className)`) |
| 204 | - **Forward refs:** Components wrapping DOM elements must use `forwardRef` |
| 205 | |
| 206 | ## Styling System |
| 207 | |
| 208 | ### Kumo Semantic Color Tokens |
| 209 | |
| 210 | **CRITICAL**: Always use Kumo semantic color classes, never raw Tailwind colors. |
| 211 | |
| 212 | The color system is defined in `packages/kumo/src/styles/kumo-binding.css`. Colors automatically adapt to light/dark mode via CSS `light-dark()` function. |
| 213 | |
| 214 | **Full token reference:** See `packages/kumo/ai/component-registry.md` - the "Kumo Color System" section contains all available tokens with usage counts, organized by category (surfaces, text, state, interactive, borders). |
| 215 | |
| 216 | ### Key Patterns |
| 217 | |
| 218 | ```tsx |
| 219 | // ✅ CORRECT - Using Kumo semantic tokens |
| 220 | <div className="bg-kumo-base border border-kumo-line rounded-lg"> // Card |
| 221 | <button className="bg-kumo-brand text-white">Primary</button> // Primary button |
| 222 | <button className="bg-kumo-control text-kumo-default ring ring-kumo-line"> // Secondary button |
| 223 | <input className="bg-kumo-control text-kumo-default ring ring-kumo-line" /> // Form input |
| 224 | <div className="bg-kumo-danger/20 border-kumo-danger text-kumo-danger">Error</div> // Error state |
| 225 | |
| 226 | // ❌ WRONG - Raw Tailwind colors break theming |
| 227 | <button className="bg-blue-500 text-white">Submit</button> |
| 228 | <div className="bg-white dark:bg-gray-900">Content</div> |
| 229 | ``` |
| 230 | |
| 231 | ### Dark Mode |
| 232 | |
| 233 | **NEVER use Tailwind's `dark:` variant**. Semantic tokens handle dark mode automatically via `light-dark()`. |
| 234 | |
| 235 | ```tsx |
| 236 | // ❌ WRONG |
| 237 | <div className="bg-white dark:bg-black" /> |
| 238 | |
| 239 | // ✅ CORRECT |
| 240 | <div className="bg-kumo-base text-kumo-default" /> |
| 241 | ``` |
| 242 | |
| 243 | ### Dark Mode |
| 244 | |
| 245 | **NEVER use Tailwind's `dark:` variant**. The Kumo color system handles dark mode automatically through CSS custom properties and `light-dark()`. |
| 246 | |
| 247 | All semantic tokens use `light-dark()` internally. **Never use `dark:` variant.** |
| 248 | |
| 249 | ```tsx |
| 250 | // ❌ WRONG - Manual dark mode handling |
| 251 | <div className="bg-white dark:bg-black text-black dark:text-white" /> |
| 252 | |
| 253 | // ✅ CORRECT - Automatic dark mode via semantic tokens |
| 254 | <div className="bg-kumo-base text-kumo-default" /> |
| 255 | ``` |
| 256 | |
| 257 | ### Surface Hierarchy |
| 258 | |
| 259 | Use layered surfaces for visual depth: |
| 260 | |
| 261 | ``` |
| 262 | bg-kumo-base → bg-kumo-elevated → bg-kumo-recessed |
| 263 | ``` |
| 264 | |
| 265 | ### Mode & Theme System |
| 266 | |
| 267 | Kumo uses two data attributes for styling control: |
| 268 | |
| 269 | - **`data-mode`**: Controls light/dark mode (`"light"` | `"dark"`) |
| 270 | - **`data-theme`**: Controls theme variants (e.g., `"fedramp"`) |
| 271 | |
| 272 | #### Dark Mode (`data-mode`) |
| 273 | |
| 274 | Set `data-mode` on a parent element (typically `<html>` or `<body>`) to control color scheme: |
| 275 | |
| 276 | ```tsx |
| 277 | // Light mode |
| 278 | <html data-mode="light"> |
| 279 | |
| 280 | // Dark mode |
| 281 | <html data-mode="dark"> |
| 282 | ``` |
| 283 | |
| 284 | The CSS uses `color-scheme` and `light-dark()` to automatically adapt all semantic tokens: |
| 285 | |
| 286 | ```css |
| 287 | :root { |
| 288 | color-scheme: light; |
| 289 | } |
| 290 | |
| 291 | [data-mode="dark"] { |
| 292 | color-scheme: dark; |
| 293 | } |
| 294 | ``` |
| 295 | |
| 296 | #### Themes (`data-theme`) |
| 297 | |
| 298 | Themes override semantic color tokens defined in `packages/kumo/src/styles/kumo-binding.css`. |
| 299 | |
| 300 | **Existing Themes:** |
| 301 | |
| 302 | - **Default**: No `data-theme` attribute required |
| 303 | - **FedRAMP**: `data-theme="fedramp"` - Government compliance styling |
| 304 | |
| 305 | #### Adding a New Theme |
| 306 | |
| 307 | 1. Add theme overrides in `kumo-binding.css` within `@layer base`: |
| 308 | |
| 309 | ```css |
| 310 | @layer base { |
| 311 | [data-theme="my-theme"] { |
| 312 | --color-surface: light-dark(#custom-light, #custom-dark); |
| 313 | --color-active: light-dark(#custom-light, #custom-dark); |
| 314 | --text-color-surface: light-dark(#custom-light, #custom-dark); |
| 315 | /* Override any semantic tokens as needed */ |
| 316 | } |
| 317 | } |
| 318 | ``` |
| 319 | |
| 320 | 2. Apply the theme by setting the `data-theme` attribute on a parent element: |
| 321 | |
| 322 | ```tsx |
| 323 | <div data-theme="my-theme"> |
| 324 | {/* All Kumo components inside will use theme overrides */} |
| 325 | </div> |
| 326 | ``` |
| 327 | |
| 328 | #### Theme Guidelines |
| 329 | |
| 330 | - **Use `light-dark()`**: Ensures themes work with both light and dark modes |
| 331 | - **Override sparingly**: Only override tokens that need to change |
| 332 | - **Semantic tokens only**: Themes should override `--color-*` and `--text-color-*` variables, not component-specific styles |
| 333 | - **Test both modes**: Verify theme looks correct in light and dark mode |
| 334 | |
| 335 | ## Component Patterns |
| 336 | |
| 337 | ### Standard Component Structure |
| 338 | |
| 339 | Each component follows this file structure: |
| 340 | |
| 341 | ``` |
| 342 | components/ |
| 343 | └── button/ |
| 344 | ├── button.tsx # Component implementation |
| 345 | └── index.ts # Re-exports |
| 346 | ``` |
| 347 | |
| 348 | Demo files for documentation live in `packages/kumo-docs-astro/src/components/demos/`. |
| 349 | |
| 350 | ### Component Implementation Pattern |
| 351 | |
| 352 | ```tsx |
| 353 | import { cn } from "../../utils/cn"; |
| 354 | import { forwardRef } from "react"; |
| 355 | |
| 356 | export type ButtonProps = { |
| 357 | variant?: "primary" | "secondary" | "ghost" | "destructive"; |
| 358 | size?: "xs" | "sm" | "base" | "lg"; |
| 359 | // ... other props |
| 360 | }; |
| 361 | |
| 362 | export const Button = forwardRef<HTMLButtonElement, ButtonProps>( |
| 363 | ({ variant = "secondary", size = "base", className, ...props }, ref) => { |
| 364 | return ( |
| 365 | <button |
| 366 | ref={ref} |
| 367 | className={cn( |
| 368 | // Base styles |
| 369 | "flex items-center font-medium", |
| 370 | // Variant styles using Kumo tokens |
| 371 | variant === "primary" && "bg-kumo-brand text-white", |
| 372 | variant === "secondary" && |
| 373 | "text-secondary bg-kumo-control ring ring-kumo-line", |
| 374 | // Size styles |
| 375 | size === "base" && "h-9 px-3 text-base", |
| 376 | className, |
| 377 | )} |
| 378 | {...props} |
| 379 | /> |
| 380 | ); |
| 381 | }, |
| 382 | ); |
| 383 | |
| 384 | Button.displayName = "Button"; |
| 385 | ``` |
| 386 | |
| 387 | ### Using Base UI |
| 388 | |
| 389 | Components are built on Base UI primitives: |
| 390 | |
| 391 | ```tsx |
| 392 | import { Dialog as DialogBase } from "@base-ui/react/dialog"; |
| 393 | |
| 394 | function DialogContent({ children }) { |
| 395 | return ( |
| 396 | <DialogBase.Portal> |
| 397 | <DialogBase.Backdrop className="bg-kumo-overlay opacity-80" /> |
| 398 | <DialogBase.Popup className="rounded-xl bg-kumo-base"> |
| 399 | {children} |
| 400 | </DialogBase.Popup> |
| 401 | </DialogBase.Portal> |
| 402 | ); |
| 403 | } |
| 404 | ``` |
| 405 | |
| 406 | ### Variants System |
| 407 | |
| 408 | Components export `KUMO_<NAME>_VARIANTS` constants defining available variants. |
| 409 | |
| 410 | **Check registry for:** |
| 411 | |
| 412 | - `props[].values` - Available variant values (e.g., `["primary", "secondary"]`) |
| 413 | - `props[].default` - Default variant (e.g., `"secondary"`) |
| 414 | - `props[].descriptions` - Variant descriptions (when to use each) |
| 415 | |
| 416 | **Example from registry:** |
| 417 | |
| 418 | ```json |
| 419 | { |
| 420 | "Button": { |
| 421 | "props": { |
| 422 | "variant": { |
| 423 | "type": "enum", |
| 424 | "values": ["primary", "secondary", "ghost", "destructive"], |
| 425 | "default": "secondary", |
| 426 | "descriptions": { |
| 427 | "primary": "Primary action button", |
| 428 | "secondary": "Secondary action button with border" |
| 429 | } |
| 430 | } |
| 431 | } |
| 432 | } |
| 433 | } |
| 434 | ``` |
| 435 | |
| 436 | ### Compound Components |
| 437 | |
| 438 | Check registry `subComponents` field for compound component patterns. |
| 439 | |
| 440 | **Example:** Dialog has sub-components: `Dialog.Root`, `Dialog.Trigger`, `Dialog.Title`, `Dialog.Description`, `Dialog.Close` |
| 441 | |
| 442 | ```json |
| 443 | { |
| 444 | "Dialog": { |
| 445 | "subComponents": { |
| 446 | "Root": { |
| 447 | "description": "Controls the open state", |
| 448 | "props": { "open": { "type": "boolean" } } |
| 449 | }, |
| 450 | "Trigger": { |
| 451 | "description": "Button that opens the dialog", |
| 452 | "renderElement": "<button>" |
| 453 | } |
| 454 | } |
| 455 | } |
| 456 | } |
| 457 | ``` |
| 458 | |
| 459 | ### Component Requirements |
| 460 | |
| 461 | All components must: |
| 462 | |
| 463 | 1. Export `KUMO_<NAME>_VARIANTS` (variants config) |
| 464 | 2. Export `KUMO_<NAME>_DEFAULT_VARIANTS` (default values) |
| 465 | 3. Use `forwardRef` when wrapping DOM elements |
| 466 | 4. Set `displayName` for React DevTools |
| 467 | |
| 468 | ## Adding Components |
| 469 | |
| 470 | ### Workflow |
| 471 | |
| 472 | 1. **Run the scaffolding tool:** |
| 473 | |
| 474 | ```bash |
| 475 | pnpm --filter @cloudflare/kumo new-component |
| 476 | ``` |
| 477 | |
| 478 | 2. **Implement the component:** |
| 479 | - Use semantic tokens only (never raw Tailwind colors) |
| 480 | - Add KUMO*\*\_VARIANTS and KUMO*\*\_DEFAULT_VARIANTS exports |
| 481 | - Use `forwardRef` when wrapping DOM elements |
| 482 | - Set `displayName` for React DevTools |
| 483 | |
| 484 | 3. **Write demo files** in `packages/kumo-docs-astro/src/components/demos/{Name}Demo.tsx` |
| 485 | |
| 486 | 4. **Regenerate the component registry:** |
| 487 | ```bash |
| 488 | pnpm --filter @cloudflare/kumo codegen:registry |
| 489 | ``` |
| 490 | |
| 491 | ### What Gets Scaffolded |
| 492 | |
| 493 | - `src/components/{name}/{name}.tsx` - Component implementation |
| 494 | - `src/components/{name}/index.ts` - Re-exports |
| 495 | - Updates `src/index.ts` exports |
| 496 | - Updates `vite.config.ts` build entries |
| 497 | - Updates `package.json` exports |
| 498 | |
| 499 | ## Development & Tooling |
| 500 | |
| 501 | ### Package Management (`pnpm`) |
| 502 | |
| 503 | ```bash |
| 504 | pnpm install # Install all dependencies |
| 505 | pnpm --filter @cloudflare/kumo build # Build component library |
| 506 | pnpm --filter @cloudflare/kumo-docs dev # Run docs dev server |
| 507 | ``` |
| 508 | |
| 509 | ### Common Scripts |
| 510 | |
| 511 | ```bash |
| 512 | # From workspace root |
| 513 | pnpm dev # Start docs dev server |
| 514 | pnpm build # Build docs site |
| 515 | pnpm lint # Run linting |
| 516 | pnpm typecheck # Type check all packages |
| 517 | |
| 518 | # From packages/kumo |
| 519 | pnpm test # Run tests in watch mode |
| 520 | pnpm test:run # Run tests once |
| 521 | pnpm new-component # Scaffold new component |
| 522 | ``` |
| 523 | |
| 524 | ### Build & Test |
| 525 | |
| 526 | ```bash |
| 527 | # Testing |
| 528 | pnpm --filter @cloudflare/kumo test # Vitest watch mode |
| 529 | pnpm --filter @cloudflare/kumo test:run # Single run |
| 530 | pnpm --filter @cloudflare/kumo test:ui # UI mode |
| 531 | |
| 532 | # Linting (includes custom rules) |
| 533 | pnpm --filter @cloudflare/kumo lint # oxlint with: |
| 534 | # - no-primitive-colors (fails on bg-blue-500) |
| 535 | # - no-tailwind-dark-variant (fails on dark:) |
| 536 | |
| 537 | # Build |
| 538 | pnpm --filter @cloudflare/kumo build # Full build with CSS |
| 539 | pnpm --filter @cloudflare/kumo codegen:registry # Regenerate component-registry |
| 540 | ``` |
| 541 | |
| 542 | ### Linting (`oxlint`) |
| 543 | |
| 544 | The project uses `oxlint` with type-aware linting and custom rules: |
| 545 | |
| 546 | ```bash |
| 547 | pnpm --filter @cloudflare/kumo lint |
| 548 | ``` |
| 549 | |
| 550 | #### Custom Lint Rules |
| 551 | |
| 552 | 1. **`no-primitive-colors`** (`lint/no-primitive-colors.js`) |
| 553 | - Disallows Tailwind primitive colors (e.g., `bg-blue-500`, `text-gray-900`) |
| 554 | - Validates that semantic tokens exist in theme CSS files |
| 555 | - Enforces use of Kumo semantic tokens (e.g., `bg-kumo-base`, `text-kumo-subtle`) |
| 556 | |
| 557 | 2. **`no-tailwind-dark-variant`** (`lint/no-tailwind-dark-variant.js`) |
| 558 | - Disallows `dark:` variant in class names |
| 559 | - Dark mode is handled automatically by Kumo tokens |
| 560 | |
| 561 | 3. **`enforce-variant-standard`** (`lint/enforce-variant-standard.js`) |
| 562 | - Enforces the KUMO\_\*\_VARIANTS naming convention for component exports |
| 563 | - Only applies to files matching `src/components/{name}/{name}.tsx` |
| 564 | - Extracts component name from path (e.g., `button.tsx` → `BUTTON`, `clipboard-text.tsx` → `CLIPBOARD_TEXT`) |
| 565 | |
| 566 | 4. **`no-cross-package-imports`** (`lint/no-cross-package-imports.js`) |
| 567 | - Disallows relative imports that reach into sibling packages in the monorepo |
| 568 | - Catches patterns like `../../kumo/src/...` from other packages |
| 569 | - Enforces using proper package imports (e.g., `@cloudflare/kumo`) instead |
| 570 | |
| 571 | ```tsx |
| 572 | // ❌ WRONG - Cross-package relative import |
| 573 | import { Button } from "../../kumo/src/components/button"; |
| 574 | |
| 575 | // ✅ CORRECT - Use the package export |
| 576 | import { Button } from "@cloudflare/kumo"; |
| 577 | ``` |
| 578 | |
| 579 | **Required Exports:** |
| 580 | - `KUMO_{COMPONENT}_VARIANTS` - Variant configuration object |
| 581 | - `KUMO_{COMPONENT}_DEFAULT_VARIANTS` - Default variant values |
| 582 | |
| 583 | **Optional Exports:** |
| 584 | - `KUMO_{COMPONENT}_BASE_STYLES` - Base styles (must have `KUMO_` prefix if present) |
| 585 | |
| 586 | **Examples:** |
| 587 | |
| 588 | ```tsx |
| 589 | // ✅ CORRECT - Valid exports in button.tsx |
| 590 | export const KUMO_BUTTON_VARIANTS = { |
| 591 | variant: ["primary", "secondary", "ghost", "destructive"], |
| 592 | size: ["xs", "sm", "base", "lg"], |
| 593 | }; |
| 594 | |
| 595 | export const KUMO_BUTTON_DEFAULT_VARIANTS = { |
| 596 | variant: "secondary", |
| 597 | size: "base", |
| 598 | }; |
| 599 | |
| 600 | // Optional base styles |
| 601 | export const KUMO_BUTTON_BASE_STYLES = "flex items-center font-medium"; |
| 602 | ``` |
| 603 | |
| 604 | ```tsx |
| 605 | // ✅ CORRECT - Valid exports in clipboard-text.tsx (kebab-case → UPPER_SNAKE_CASE) |
| 606 | export const KUMO_CLIPBOARD_TEXT_VARIANTS = { |
| 607 | /* ... */ |
| 608 | }; |
| 609 | export const KUMO_CLIPBOARD_TEXT_DEFAULT_VARIANTS = { |
| 610 | /* ... */ |
| 611 | }; |
| 612 | ``` |
| 613 | |
| 614 | ```tsx |
| 615 | // ❌ WRONG - Missing KUMO_ prefix |
| 616 | export const BUTTON_VARIANTS = { |
| 617 | /* ... */ |
| 618 | }; |
| 619 | export const BUTTON_DEFAULT_VARIANTS = { |
| 620 | /* ... */ |
| 621 | }; |
| 622 | ``` |
| 623 | |
| 624 | ```tsx |
| 625 | // ❌ WRONG - Wrong component name |
| 626 | // In button.tsx: |
| 627 | export const KUMO_INPUT_VARIANTS = { |
| 628 | /* ... */ |
| 629 | }; |
| 630 | export const KUMO_INPUT_DEFAULT_VARIANTS = { |
| 631 | /* ... */ |
| 632 | }; |
| 633 | ``` |
| 634 | |
| 635 | ```tsx |
| 636 | // ❌ WRONG - BASE_STYLES without KUMO_ prefix |
| 637 | export const BUTTON_BASE_STYLES = "..."; // Should be KUMO_BUTTON_BASE_STYLES |
| 638 | ``` |
| 639 | |
| 640 | ```tsx |
| 641 | // ❌ WRONG - Missing required exports |
| 642 | // In button.tsx - missing KUMO_BUTTON_DEFAULT_VARIANTS |
| 643 | export const KUMO_BUTTON_VARIANTS = { |
| 644 | /* ... */ |
| 645 | }; |
| 646 | // Error: Component must export KUMO_BUTTON_DEFAULT_VARIANTS |
| 647 | ``` |
| 648 | |
| 649 | ### Testing (`vitest`) |
| 650 | |
| 651 | ```bash |
| 652 | pnpm --filter @cloudflare/kumo test # Watch mode |
| 653 | pnpm --filter @cloudflare/kumo test:run # Single run |
| 654 | pnpm --filter @cloudflare/kumo test:ui # UI mode |
| 655 | ``` |
| 656 | |
| 657 | ## Icon System |
| 658 | |
| 659 | Kumo uses [Phosphor Icons](https://phosphoricons.com/) directly via `@phosphor-icons/react`. This is a peer dependency that consumers must install. |
| 660 | |
| 661 | ### Installation |
| 662 | |
| 663 | ```bash |
| 664 | pnpm add @phosphor-icons/react |
| 665 | ``` |
| 666 | |
| 667 | ### Using Icons |
| 668 | |
| 669 | ```tsx |
| 670 | import { Check, ArrowRight, X } from "@phosphor-icons/react"; |
| 671 | |
| 672 | // Basic usage |
| 673 | <Check /> |
| 674 | <ArrowRight size={24} /> |
| 675 | |
| 676 | // With Kumo semantic colors |
| 677 | <X className="text-kumo-subtle" /> |
| 678 | <Check className="text-success" /> |
| 679 | |
| 680 | // Different weights |
| 681 | import { CheckBold, CheckLight } from "@phosphor-icons/react"; |
| 682 | ``` |
| 683 | |
| 684 | ### Size Guidelines |
| 685 | |
| 686 | | Context | Size | Phosphor prop | |
| 687 | | ---------------- | ---- | ------------- | |
| 688 | | Inline with text | 16px | `size={16}` | |
| 689 | | Buttons (sm) | 16px | `size={16}` | |
| 690 | | Buttons (base) | 20px | `size={20}` | |
| 691 | | Buttons (lg) | 24px | `size={24}` | |
| 692 | | Empty states | 48px | `size={48}` | |
| 693 | |
| 694 | ### Figma Plugin |
| 695 | |
| 696 | The Figma plugin embeds a subset of Phosphor icons needed for component generation. Icons are created on-demand using `figma.createNodeFromSvg()`. |
| 697 | |
| 698 | To add new icons to the Figma plugin, update `packages/kumo-figma/src/build-phosphor-icons.ts`. |
| 699 | |
| 700 | ## Changesets & Version Management |
| 701 | |
| 702 | Kumo uses [Changesets](https://github.com/changesets/changesets) for version management with automated validation and releases. |
| 703 | |
| 704 | ### ⚠️ IMPORTANT: AI Agents - Do NOT Version or Publish |
| 705 | |
| 706 | **AI agents should NEVER run these commands:** |
| 707 | |
| 708 | - ❌ `pnpm version` - Versions packages (human-only) |
| 709 | - ❌ `pnpm release` - Publishes to npm (human-only) |
| 710 | - ❌ `pnpm publish:beta` - Publishes beta versions (CI-only) |
| 711 | - ❌ `pnpm release:production` - Production release script (human-only) |
| 712 | |
| 713 | **AI agents SHOULD:** |
| 714 | |
| 715 | - ✅ Create changesets: `pnpm changeset` |
| 716 | - ✅ Validate changesets exist |
| 717 | - ✅ Build and test: `pnpm build`, `pnpm test` |
| 718 | |
| 719 | **Reasoning:** Versioning and publishing are sensitive operations that require human oversight and proper npm credentials. Beta releases are automated via CI. Production releases require manual verification. |
| 720 | |
| 721 | ### Creating Changesets |
| 722 | |
| 723 | **CRITICAL:** Changes to `packages/kumo/` **require** a changeset. Pre-push hooks enforce this locally, and CI validates in pull requests. |
| 724 | |
| 725 | ```bash |
| 726 | pnpm changeset |
| 727 | ``` |
| 728 | |
| 729 | 1. Select `@cloudflare/kumo` from the package list |
| 730 | 2. Choose change type: |
| 731 | - **patch** (`0.0.1`) - Bug fixes, small updates |
| 732 | - **minor** (`0.1.0`) - New components, backwards-compatible features |
| 733 | - **major** (`1.0.0`) - Breaking changes, removed components |
| 734 | 3. Write a clear description (appears in CHANGELOG.md) |
| 735 | 4. Commit the generated `.changeset/*.md` file |
| 736 | |
| 737 | ### Pre-Push Validation |
| 738 | |
| 739 | Lefthook enforces changeset validation before pushing: |
| 740 | |
| 741 | ```bash |
| 742 | # Push normally - validation runs automatically |
| 743 | git push |
| 744 | |
| 745 | # Skip validation if needed (use sparingly) |
| 746 | git push --no-verify |
| 747 | LEFTHOOK=0 git push |
| 748 | LEFTHOOK_EXCLUDE=validate-changeset git push |
| 749 | ``` |
| 750 | |
| 751 | **What it validates:** |
| 752 | |
| 753 | - Detects changes to `packages/kumo/` via `git merge-base origin/main HEAD` |
| 754 | - Ensures a **new** changeset exists targeting `@cloudflare/kumo` |
| 755 | - Blocks push with clear instructions if validation fails |
| 756 | |
| 757 | **Troubleshooting:** |
| 758 | |
| 759 | - **Missing origin/main**: Run `git fetch origin main` |
| 760 | - **GUI clients (Tower, SourceTree)**: Configure PATH in client settings |
| 761 | - **Hook not installed**: Run `pnpm lefthook install` |
| 762 | |
| 763 | ### Beta Releases (Automated) |
| 764 | |
| 765 | Beta versions are automatically published for pull requests: |
| 766 | |
| 767 | **Format:** `{version}-beta.{commit-hash}` (e.g., `0.1.0-beta.a1b2c3d`) |
| 768 | |
| 769 | **Process:** |
| 770 | |
| 771 | 1. Create PR with changeset |
| 772 | 2. CI validates changeset exists |
| 773 | 3. CI publishes beta version with `beta` tag |
| 774 | 4. PR receives comment with installation instructions |
| 775 | |
| 776 | **Install beta:** |
| 777 | |
| 778 | ```bash |
| 779 | pnpm add @cloudflare/kumo@0.1.0-beta.a1b2c3d |
| 780 | ``` |
| 781 | |
| 782 | ### Production Releases |
| 783 | |
| 784 | ```bash |
| 785 | # 1. Ensure on main with latest changes |
| 786 | git checkout main && git pull |
| 787 | |
| 788 | # 2. Version packages (consumes changesets) |
| 789 | pnpm version |
| 790 | |
| 791 | # 3. Build all packages |
| 792 | pnpm build:all |
| 793 | |
| 794 | # 4. Publish to npm |
| 795 | pnpm release |
| 796 | |
| 797 | # 5. Commit and push |
| 798 | git add . |
| 799 | git commit -m "chore: release @cloudflare/kumo@{version}" |
| 800 | git push --follow-tags |
| 801 | ``` |
| 802 | |
| 803 | **What happens:** |
| 804 | |
| 805 | - Updates `package.json` version |
| 806 | - Generates/updates `CHANGELOG.md` |
| 807 | - Removes consumed changeset files |
| 808 | - Publishes to npm registry |
| 809 | - Creates git tags |
| 810 | |
| 811 | ## Figma Token Sync |
| 812 | |
| 813 | The `kumo-figma` package provides scripts to sync semantic color tokens from CSS to Figma design variables, ensuring design tokens stay in sync between code and design. |
| 814 | |
| 815 | ### Purpose |
| 816 | |
| 817 | The Figma sync script automates synchronization of Kumo's semantic color tokens (defined in `packages/kumo/src/styles/theme-kumo.css`) to Figma variables: |
| 818 | |
| 819 | 1. Parses CSS tokens from `theme-kumo.css` |
| 820 | 2. Extracts light and dark mode values from `light-dark()` functions |
| 821 | 3. Resolves color values (oklch, hex, rgb) to Figma RGB format |
| 822 | 4. Pushes tokens to Figma via the Variables API |
| 823 | |
| 824 | This enables: |
| 825 | |
| 826 | - Designers to use semantic tokens in Figma |
| 827 | - Automatic updates when tokens change in code |
| 828 | - Single source of truth for color values |
| 829 | |
| 830 | ### Environment Setup |
| 831 | |
| 832 | 1. **Get a Figma personal access token:** |
| 833 | - Go to [Figma Settings > Personal Access Tokens](https://www.figma.com/developers/api#authentication) |
| 834 | - Create a new token with a descriptive name (e.g., "Kumo Token Sync") |
| 835 | - Copy the token (you won't see it again) |
| 836 | |
| 837 | 2. **Copy `.env.example` to `.env`:** |
| 838 | |
| 839 | ```bash |
| 840 | cp packages/kumo-figma/scripts/.env.example packages/kumo-figma/scripts/.env |
| 841 | ``` |
| 842 | |
| 843 | 3. **Add your token to `.env`:** |
| 844 | ```bash |
| 845 | FIGMA_TOKEN=your-token-here |
| 846 | FIGMA_FILE_KEY=sKKZc6pC6W1TtzWBLxDGSU |
| 847 | ``` |
| 848 | |
| 849 | ### Running the Sync |
| 850 | |
| 851 | ```bash |
| 852 | # With environment variable |
| 853 | FIGMA_TOKEN="your-token" pnpm --filter @cloudflare/kumo-figma figma:sync |
| 854 | |
| 855 | # Or load from .env |
| 856 | source packages/kumo-figma/scripts/.env |
| 857 | pnpm --filter @cloudflare/kumo-figma figma:sync |
| 858 | ``` |
| 859 | |
| 860 | ### Security Warning |
| 861 | |
| 862 | **⚠️ NEVER commit your Figma token to the repository.** |
| 863 | |
| 864 | - `.env` is gitignored by default |
| 865 | - Always use environment variables for tokens |
| 866 | - Rotate tokens if accidentally exposed |
| 867 | |
| 868 | For detailed documentation, see the Figma Plugin section below. |
| 869 | |
| 870 | ## Code Review Guidelines |
| 871 | |
| 872 | When reviewing code, focus on: |
| 873 | |
| 874 | ### Styling |
| 875 | |
| 876 | - **Verify Kumo tokens**: Ensure `bg-*`, `text-*`, `border-*` semantic classes are used (e.g., `bg-kumo-base`, `text-kumo-subtle`, `border-kumo-line`) |
| 877 | - **No raw Tailwind colors**: Flag any `bg-blue-500`, `text-gray-*`, etc. |
| 878 | - **No `dark:` variants**: Dark mode should be automatic via tokens |
| 879 | - **Use `cn()` utility**: For conditional class composition |
| 880 | |
| 881 | ### Component Quality |
| 882 | |
| 883 | - **Accessibility**: Components should use Base UI primitives for a11y |
| 884 | - **TypeScript**: Proper typing with exported types |
| 885 | - **forwardRef**: Components should forward refs when wrapping DOM elements |
| 886 | - **displayName**: Set for debugging in React DevTools |
| 887 | |
| 888 | ### Performance |
| 889 | |
| 890 | - **Tree-shaking**: Components should be individually importable |
| 891 | - **Bundle size**: Avoid unnecessary dependencies |
| 892 | - **Re-renders**: Check for unnecessary re-renders in complex components |
| 893 | |
| 894 | ### Testing |
| 895 | |
| 896 | - **Demo files**: Components should have demo files in the docs site for documentation |
| 897 | - **Edge cases**: Consider loading, error, empty, and disabled states |
| 898 | |
| 899 | ## Workflow Best Practices |
| 900 | |
| 901 | ### Before Writing Code |
| 902 | |
| 903 | 1. **Always check the component registry first** (`packages/kumo/ai/component-registry.{json,md}`) |
| 904 | - Use `jq` to query component props, variants, and examples |
| 905 | - Never guess component APIs - the registry is always current |
| 906 | - Example: `jq '.components.Button.props' packages/kumo/ai/component-registry.json` |
| 907 | |
| 908 | 2. **Read related components before modifying or creating similar ones** |
| 909 | - Examine existing implementations for patterns |
| 910 | - Maintain consistency with established conventions |
| 911 | |
| 912 | ### Tool Usage for AI Agents |
| 913 | |
| 914 | When working with this codebase as an AI agent: |
| 915 | |
| 916 | 1. **Read files** to examine component implementations before modifying them |
| 917 | 2. **Use `jq`** to query the component registry (`packages/kumo/ai/component-registry.json`) |
| 918 | 3. **Use search tools** for complex searches across the codebase (e.g., "find all components using bg-kumo-base") |
| 919 | 4. **Run commands** for scaffolding, build, test, and lint operations |
| 920 | |
| 921 | ### When Modifying Components |
| 922 | |
| 923 | 1. **Read the component first** |
| 924 | 2. **Verify semantic tokens** - Ensure no raw Tailwind colors exist |
| 925 | 3. **Run linting** - Custom rules will catch color and dark mode violations |
| 926 | 4. **Update demos** - Ensure demo files in docs site reflect changes |
| 927 | 5. **Regenerate registry** - Run `pnpm --filter @cloudflare/kumo codegen:registry` after changes |
| 928 | |
| 929 | ## Important Notes |
| 930 | |
| 931 | - The component registry (`packages/kumo/ai/component-registry.{json,md}`) is the source of truth for component APIs |
| 932 | - Always regenerate the registry (`pnpm --filter @cloudflare/kumo codegen:registry`) after modifying component props or variants |
| 933 | - Custom lint rules enforce semantic token usage and prevent `dark:` variants |
| 934 | |
| 935 | ### Common Mistakes to Avoid |
| 936 | |
| 937 | - Using raw Tailwind colors (`bg-blue-500`) instead of semantic tokens (`bg-kumo-base`) |
| 938 | - Using `dark:` variants instead of letting semantic tokens handle dark mode |
| 939 | - Forgetting to set `displayName` on `forwardRef` components |
| 940 | - Not checking the component registry before using a component |
| 941 | - Creating new components without running the scaffolding tool |
| 942 | - Forgetting to regenerate the component registry after changes |
| 943 | |
| 944 | ## CI/CD Pipeline |
| 945 | |
| 946 | ### Pipeline Stages |
| 947 | |
| 948 | | Stage | Purpose | |
| 949 | | -------------------- | ------------------------------------------ | |
| 950 | | `build` | Build packages | |
| 951 | | `checks` | Linting, typechecking, validation | |
| 952 | | `test` | Run tests | |
| 953 | | `review` | AI-powered code review | |
| 954 | | `beta-release` | Publish beta npm packages | |
| 955 | | `beta-preview` | Deploy docs previews | |
| 956 | | `mr-report` | Post consolidated MR comment | |
| 957 | | `production-release` | Deploy staging, manual production releases | |
| 958 | |
| 959 | ### Deployment Environments |
| 960 | |
| 961 | | Package | Staging | Production | |
| 962 | | --------------- | --------------------- | ------------- | |
| 963 | | kumo-docs-astro | `staging.kumo-ui.com` | `kumo-ui.com` | |
| 964 | |
| 965 | ### MR Reporter System |
| 966 | |
| 967 | The PR reporter collects artifacts from CI jobs and posts a consolidated comment: |
| 968 | |
| 969 | ``` |
| 970 | ci/ |
| 971 | ├── reporters/ # NPM, docs reporters |
| 972 | ├── scripts/ # post-pr-report.ts, write-*-report.ts |
| 973 | ├── utils/ # GitHub API, PR comment utilities |
| 974 | └── versioning/ # deploy-*.sh, publish-beta.sh |
| 975 | ``` |
| 976 | |
| 977 | ## Figma Plugin (`packages/kumo-figma`) |
| 978 | |
| 979 | ### Quick Start |
| 980 | |
| 981 | ```bash |
| 982 | # Build the plugin |
| 983 | pnpm --filter @cloudflare/kumo-figma build |
| 984 | |
| 985 | # Run in Figma: Plugins > Development > Import plugin from manifest... |
| 986 | # Select: packages/kumo-figma/src/manifest.json |
| 987 | ``` |
| 988 | |
| 989 | ### Workflow |
| 990 | |
| 991 | 1. **Sync tokens first**: `pnpm --filter @cloudflare/kumo-figma figma:sync` |
| 992 | 2. **Build plugin**: `pnpm --filter @cloudflare/kumo-figma build` |
| 993 | 3. **Run in Figma**: Plugins > Development > Kumo UI Kit Generator |
| 994 | |
| 995 | ### Adding New Component Generators |
| 996 | |
| 997 | 1. Create `generators/yourcomponent.ts` |
| 998 | 2. Register in `code.ts` GENERATORS array |
| 999 | 3. Run `pnpm --filter @cloudflare/kumo-figma validate` (drift detection) |
| 1000 | |
| 1001 | ### Drift Prevention |
| 1002 | |
| 1003 | - `drift-detection.test.ts` validates generators match `component-registry.json` |
| 1004 | - CI enforces on MRs touching `component-registry.json` or generators |
| 1005 | - Excluded components: Add to `EXCLUDED_COMPONENTS` in drift-detection.test.ts |
| 1006 | |
| 1007 | ## Resources |
| 1008 | |
| 1009 | - **Component Registry** - `packages/kumo/ai/component-registry.{json,md}` - Always-current component metadata |
| 1010 | - **Docs Site** - `pnpm dev` - Documentation at `http://localhost:4321` |
| 1011 | - **Source** - `packages/kumo/src/` - Component source code organized by type: |
| 1012 | - `components/` - UI primitives (Button, Input, Dialog) |
| 1013 | - `blocks/` - Composite components (Breadcrumbs, PageHeader, Empty) |
| 1014 | - `styles/` - CSS including `kumo-binding.css` (semantic token definitions) |
| 1015 | |