diff --git a/.agents/skills/change/SKILL.md b/.agents/skills/change/SKILL.md new file mode 100644 index 00000000000000..1e8a0dcc65b0a7 --- /dev/null +++ b/.agents/skills/change/SKILL.md @@ -0,0 +1,56 @@ +--- +name: change +description: Create a beachball change file for the current changes. Determines change type (patch/minor) and generates a description from the diff. +disable-model-invocation: true +allowed-tools: Bash Read Grep Glob +--- + +# Create a Beachball Change File + +Generate a change file for the current branch's modifications using the repo's beachball setup. + +## Steps + +1. **Identify changed packages** by running: + + ```bash + git diff --name-only HEAD~1 + ``` + +2. **Determine the change type:** + + - `patch` — bug fixes, internal refactors, test-only changes + - `minor` — new features, new exports, new component variants + - `none` — changes that don't affect the published package (stories, docs, tests only) + - Never use `major` without explicit user approval + +3. **Generate a descriptive message** following the format: `fix(package-name): description` or `feat(package-name): description` + +4. **Run the repo's change script** to create the change file: + + ```bash + yarn change + ``` + + This runs `beachball change --no-commit` (configured in root `package.json`). + + For non-interactive usage with a specific type and message: + + ```bash + yarn beachball change --no-commit --type --message "" + ``` + +5. **Verify** the change file was created: + + ```bash + yarn check:change + ``` + +6. If multiple packages are affected, create separate change files for each. + +## Rules + +- Every published package change MUST have a change file +- Do not create change files for unpublished packages (stories packages, internal tools) +- Do not create change files for changes that only affect tests, stories, or docs within a package +- The message should describe the user-facing impact, not the implementation detail diff --git a/.agents/skills/lint-check/SKILL.md b/.agents/skills/lint-check/SKILL.md new file mode 100644 index 00000000000000..2f835bb1dfffdb --- /dev/null +++ b/.agents/skills/lint-check/SKILL.md @@ -0,0 +1,47 @@ +--- +name: lint-check +description: Run lint on affected packages, parse errors, and auto-fix common issues (design tokens, React.FC, SSR safety, import restrictions) +disable-model-invocation: true +argument-hint: '[package-name]' +allowed-tools: Bash Read Edit Grep Glob +--- + +# Lint Check and Auto-Fix + +Run linting and fix common issues for Fluent UI packages. + +## Steps + +1. **Determine scope.** If `$ARGUMENTS` is provided, lint that specific package. Otherwise, lint affected packages: + + ```bash + # Specific package + yarn nx run @fluentui/$ARGUMENTS:lint + + # Or affected packages + yarn nx affected -t lint + ``` + +2. **Parse the output** and categorize errors by the custom Fluent UI ESLint rules: + + | Rule | What it catches | How to fix | + | --------------------------------------- | ------------------------------------------- | ----------------------------------------------------- | + | `@fluentui/ban-context-export` | Context exported from wrong layer | Move to `react-shared-contexts` package | + | `@fluentui/ban-instanceof-html-element` | `instanceof HTMLElement` (breaks iframes) | Use element.tagName or feature detection | + | `@fluentui/no-global-react` | `React.FC`, `React.useState` etc. | Use named imports: `import { useState } from 'react'` | + | `@fluentui/no-restricted-imports` | Banned import paths | Use the allowed import path from the error message | + | `@fluentui/no-context-default-value` | Context created without `undefined` default | Use `createContext(undefined)` and add a guard hook | + +3. **Auto-fix** any issues found by editing the source files directly. For each fix: + + - Read the file + - Apply the fix + - Verify the fix by re-running lint on that specific file + +4. **Report** a summary of what was found and fixed. + +## References + +- ESLint plugin source: `packages/eslint-plugin/src/rules/` +- Design tokens guide: [docs/architecture/design-tokens.md](docs/architecture/design-tokens.md) +- Component patterns: [docs/architecture/component-patterns.md](docs/architecture/component-patterns.md) diff --git a/.agents/skills/package-info/SKILL.md b/.agents/skills/package-info/SKILL.md new file mode 100644 index 00000000000000..6354bc401d099f --- /dev/null +++ b/.agents/skills/package-info/SKILL.md @@ -0,0 +1,67 @@ +--- +name: package-info +description: Quick lookup for a Fluent UI package — path, dependencies, owner team, Nx project details, and relevant docs +argument-hint: +allowed-tools: Bash Read Grep Glob +--- + +# Package Info Lookup + +Get a comprehensive overview of the package **$ARGUMENTS**. + +## Steps + +1. **Resolve the package path.** Map the name to a filesystem path: + + - `react-button` or `@fluentui/react-button` → `packages/react-components/react-button/` + - Check both `library/` (source) and `stories/` (storybook) sub-packages + +2. **Get Nx project details:** + + ```bash + yarn nx show project @fluentui/$ARGUMENTS --json 2>/dev/null || yarn nx show project $ARGUMENTS --json 2>/dev/null + ``` + +3. **Read the package.json** for version, dependencies, and peer dependencies. + +4. **Check ownership** by searching CODEOWNERS: + + ```bash + grep -i "$ARGUMENTS" CODEOWNERS + ``` + +5. **Summarize the component structure** — list files under `library/src/components/`. + +6. **Check test coverage** — does it have: + + - Unit tests (`*.test.tsx`) + - Conformance tests (`testing/isConformant.ts`) + - Stories (`stories/` package) + +7. **Report** in this format: + + ``` + Package: @fluentui/ + Path: packages/react-components//library/ + Version: x.y.z + Tier: 3 (Component) | 2 (Foundation) | 1 (Core) + Owner: + Dependencies: + Components: + Tests: unit ✓/✗ | conformance ✓/✗ | stories ✓/✗ + ``` + +## Useful Commands for the Package + +```bash +yarn nx run :build # Build +yarn nx run :test # Unit tests +yarn nx run :lint # Lint +yarn nx run :type-check # Type check +yarn nx run :generate-api # Regenerate API docs (etc/*.api.md) +``` + +## References + +- Package layers: [docs/architecture/layers.md](../../../docs/architecture/layers.md) +- Team routing: [docs/team-routing.md](../../../docs/team-routing.md) diff --git a/.agents/skills/review-pr/SKILL.md b/.agents/skills/review-pr/SKILL.md new file mode 100644 index 00000000000000..7a25b4b2104c99 --- /dev/null +++ b/.agents/skills/review-pr/SKILL.md @@ -0,0 +1,211 @@ +--- +name: review-pr +description: Review a PR for correctness, pattern compliance, testing, accessibility, and safety. Produces a confidence score for merge readiness. +argument-hint: +allowed-tools: Bash Read Grep Glob +--- + +# Review a Pull Request + +Review PR **$ARGUMENTS** and produce a confidence score for merge readiness. + +## Phase 1: Gather PR Context + +```bash +# PR metadata +gh pr view $ARGUMENTS --json title,body,author,labels,files,additions,deletions,baseRefName,headRefName,state,isDraft,number + +# Changed files list +gh pr diff $ARGUMENTS --name-only + +# Full diff +gh pr diff $ARGUMENTS + +# CI status +gh pr checks $ARGUMENTS +``` + +## Phase 2: Classify PR Type + +Determine the PR type from changed files and metadata: + +| Type | Detection | Check scope | +| ---------------- | -------------------------------------------------------------------- | ---------------------------------------------- | +| **docs-only** | All files are `*.md`, `docs/**`, `**/stories/**`, `**/.storybook/**` | Change file only | +| **test-only** | All files are `*.test.*`, `*.spec.*`, `**/testing/**` | Change file + test quality | +| **bug-fix** | Branch starts with `fix/` or title contains "fix" | All checks, extra weight on tests | +| **feature** | Branch starts with `feat/` or adds new exports | All checks, extra weight on API + patterns | +| **refactor** | No new exports, restructures existing code | All checks, extra weight on no behavior change | +| **config/infra** | Changes to CI, configs, scripts only | Change file + no regressions | + +For **v8 packages** (`packages/react/`): skip V9 pattern checks — those are maintenance-only with different patterns. +For **web-components** (`packages/web-components/`): skip React-specific checks. + +## Phase 3: Run Checks + +Run each check category. For each finding, assign a severity: + +- **BLOCKER** — must fix before merge +- **WARNING** — should address +- **INFO** — consider + +### A. Beachball Change File + +Required if any published package source code changed (not just tests/stories/docs). + +- Check `change/` directory in the diff for new `.json` files +- Verify change type: `patch` for fixes, `minor` for features, never `major` without explicit approval +- Not required for changes that only affect tests, stories, docs, or snapshots + +**BLOCKER** if missing for published source changes. + +### B. V9 Component Pattern Compliance + +Only for files in `packages/react-components/react-*/library/src/`: + +| Check | Look for | Severity | +| ------------------- | --------------------------------------------------------------------------------------------------- | -------- | +| No `React.FC` | `React.FC`, `: FC<`, `React.FunctionComponent` in added lines | BLOCKER | +| No hardcoded styles | Hex colors `#[0-9a-fA-F]{3,8}`, hardcoded `px` values for spacing/radius/font in `.styles.ts` files | WARNING | +| Griffel usage | Style files must use `makeStyles` from `@griffel/react`, not inline styles | WARNING | +| mergeClasses order | User `className` must be the LAST argument in `mergeClasses()` | WARNING | +| Slot system | New components must use `slot.always`/`slot.optional` and `assertSlots` | WARNING | + +Reference: [docs/architecture/component-patterns.md](../../../docs/architecture/component-patterns.md) + +### C. Dependency Layer Violations + +For changes to `package.json` files or new imports in Tier 3 component packages: + +- **BLOCKER** if a Tier 3 package (`react-button`, `react-menu`, etc.) adds a dependency on another Tier 3 package +- Allowed Tier 2 deps: `react-utilities`, `react-theme`, `react-shared-contexts`, `react-tabster`, `react-positioning`, `react-portal` +- Allowed Tier 1 deps: `@griffel/react`, `@fluentui/tokens`, `@fluentui/react-jsx-runtime` + +Reference: [docs/architecture/layers.md](../../../docs/architecture/layers.md) + +### D. SSR Safety + +Grep added lines for unguarded browser API access: + +| Pattern | Severity | +| ------------------------------------------------------------- | -------- | +| `window.` without `canUseDOM` or `typeof window` guard nearby | BLOCKER | +| `document.` without guard | BLOCKER | +| `navigator.` without guard | BLOCKER | +| `localStorage` / `sessionStorage` without guard | BLOCKER | +| `instanceof HTMLElement` | WARNING | + +Check 3 lines above each match for a guard (`canUseDOM`, `typeof window !== 'undefined'`). + +### E. Testing + +| Check | Severity | +| --------------------------------------------------------------------------- | -------- | +| Source files changed but no corresponding `.test.tsx` changes | WARNING | +| New component missing `testing/isConformant.ts` | WARNING | +| Snapshot files need updating (render/style changes without `.snap` updates) | INFO | + +### F. API Surface + +| Check | Severity | +| ------------------------------------------------- | -------- | +| Public API changed but `etc/*.api.md` not updated | WARNING | +| Existing exports removed (breaking change) | BLOCKER | +| New exports added (flag for human review) | INFO | + +### G. Accessibility + +| Check | Severity | +| -------------------------------------------------- | -------- | +| Existing `aria-*` attributes removed | BLOCKER | +| `onClick` without `onKeyDown`/`onKeyUp` handler | WARNING | +| Interactive elements missing `role` or `aria-*` | WARNING | +| Images/icons without `aria-label` or `aria-hidden` | WARNING | + +### H. Security and Quality + +| Check | Severity | +| --------------------------------------------- | -------- | +| `eval()` or `new Function()` | BLOCKER | +| `dangerouslySetInnerHTML` | WARNING | +| `console.log` / `debugger` in production code | WARNING | +| `// @ts-ignore` without explanation | WARNING | +| `any` type in new code | INFO | + +## Phase 4: Calculate Confidence Score + +``` +Start at 100 + +For each BLOCKER: -25 points +For each WARNING: -5 points +For each INFO: -1 point + +Bonuses: + +5 if tests added/updated alongside source changes + +3 if change file present and well-described + +2 if PR description is thorough + +Floor at 0, cap at 100. +``` + +Score interpretation: + +- **90–100**: High confidence — safe to merge +- **70–89**: Moderate confidence — minor concerns +- **50–69**: Low confidence — needs attention +- **0–49**: Not safe to merge — blockers present + +## Phase 5: Produce Output + +Use this exact format: + +``` +## PR Review: # + +**Author:** <author> +**Type:** <detected PR type> +**Packages affected:** <list> +**CI Status:** <passing/failing/pending> + +### Confidence Score: <score>/100 + +<one-sentence summary> + +### Findings + +#### Blockers (must fix before merge) +- [ ] <finding with file:line reference> + +#### Warnings (should address) +- [ ] <finding with file:line reference> + +#### Info (consider) +- <finding> + +### Category Breakdown + +| Category | Status | Notes | +|----------|--------|-------| +| Change file | PASS/FAIL | ... | +| V9 patterns | PASS/WARN | ... | +| Dep layers | PASS/FAIL | ... | +| SSR safety | PASS/WARN | ... | +| Testing | PASS/WARN | ... | +| API surface | PASS/WARN | ... | +| Accessibility | PASS/WARN | ... | +| Security/Quality | PASS/WARN | ... | + +### Recommendation + +APPROVE / REQUEST_CHANGES / COMMENT + +<brief rationale> +``` + +## Notes + +- For large PRs (50+ files), prioritize: published source files > test files > config. Note reduced confidence due to review scope. +- Draft PRs: still review but note WIP status. +- Merge conflicts: flag as BLOCKER if detected. +- The `### Confidence Score: NN/100` line must always appear on its own line for machine parsing. diff --git a/.agents/skills/token-lookup/SKILL.md b/.agents/skills/token-lookup/SKILL.md new file mode 100644 index 00000000000000..fedc213f18028b --- /dev/null +++ b/.agents/skills/token-lookup/SKILL.md @@ -0,0 +1,56 @@ +--- +name: token-lookup +description: Find the matching Fluent UI design token for a hardcoded CSS value (color, spacing, font size, border radius, shadow) +argument-hint: <css-value> +allowed-tools: Read Grep Glob +--- + +# Design Token Lookup + +Find the correct `@fluentui/react-theme` design token for the hardcoded value **$ARGUMENTS**. + +## Steps + +1. **Identify the value category:** + + - Hex color / rgb / named color → color tokens + - px/rem spacing → spacing tokens + - px/rem font size → font size tokens + - px/rem border radius → border radius tokens + - Font weight (400, 600, bold) → font weight tokens + - Box shadow → shadow tokens + - Border width → stroke width tokens + - Duration (ms/s) → duration tokens + +2. **Search the theme source** for matching values: + + ``` + packages/react-components/react-theme/library/src/ + ``` + + Look in the relevant theme files (e.g., `colorPalette*.ts`, `global/`, `alias/`) to find tokens that map to the given value. + +3. **Return the token name and usage:** + + ```tsx + // Instead of: color: '#0078d4' + // Use: + import { tokens } from '@fluentui/react-theme'; + color: tokens.colorBrandBackground; + ``` + +4. **If no exact match exists**, suggest the closest semantic token and explain the difference. Refer to [docs/architecture/design-tokens.md](docs/architecture/design-tokens.md) for token categories. + +## Common Mappings + +| Hardcoded | Token | +| ------------------- | -------------------------------------------------------- | +| `#0078d4` | `tokens.colorBrandBackground` | +| `#323130` | `tokens.colorNeutralForeground1` | +| `#ffffff` | `tokens.colorNeutralBackground1` | +| `4px` border-radius | `tokens.borderRadiusMedium` | +| `8px` padding | `tokens.spacingHorizontalS` or `tokens.spacingVerticalS` | +| `14px` font-size | `tokens.fontSizeBase300` | +| `600` font-weight | `tokens.fontWeightSemibold` | + +These are approximate — always verify against the actual theme source. diff --git a/.agents/skills/v9-component/SKILL.md b/.agents/skills/v9-component/SKILL.md new file mode 100644 index 00000000000000..08f3a93fc1854d --- /dev/null +++ b/.agents/skills/v9-component/SKILL.md @@ -0,0 +1,76 @@ +--- +name: v9-component +description: Scaffold a new v9 component with all required files following Fluent UI patterns (hook, styles, render, types, tests, stories, conformance) +disable-model-invocation: true +argument-hint: <ComponentName> +allowed-tools: Read Write Bash Glob Grep +--- + +# Scaffold a V9 Component + +Create a new v9 component named **$ARGUMENTS** using the repo's Nx generators. + +## Steps + +### Adding a component to an existing package + +Use the `react-component` generator: + +```bash +yarn nx g @fluentui/workspace-plugin:react-component --name $ARGUMENTS --project <project-name> +``` + +Where `<project-name>` is the Nx project (e.g., `react-button`). This generates all required files: component, types, hook, styles, render, index barrel, and conformance test. + +### Creating a new package + component + +Use the `react-library` generator first, then add the component: + +```bash +# Create the package (will prompt for owner team) +yarn create-package + +# Or non-interactively: +yarn nx g @fluentui/workspace-plugin:react-library --name <package-name> --owner "<team>" + +# Then add the component inside it: +yarn nx g @fluentui/workspace-plugin:react-component --name $ARGUMENTS --project <package-name> +``` + +### After scaffolding + +1. **Review generated files** against [docs/architecture/component-patterns.md](../../../docs/architecture/component-patterns.md) and fill in component-specific logic. + +2. **Add styles** in `use${ARGUMENTS}Styles.styles.ts` using design tokens: + + ```tsx + import { makeStyles } from '@griffel/react'; + import { tokens } from '@fluentui/react-theme'; + ``` + +3. **Create a default story** at the appropriate stories package location if not generated. + +4. **Update API docs** after adding exports: + ```bash + yarn nx run <project>:generate-api + ``` + +## Critical Rules + +- Always use `ForwardRefComponent` with `React.forwardRef` — never `React.FC` +- Always use design tokens from `@fluentui/react-theme` — never hardcoded colors/spacing/typography +- Always preserve user `className` as the LAST argument in `mergeClasses()` +- Use `_unstable` suffix on exported hooks: `use$ARGUMENTS_unstable`, `use${ARGUMENTS}Styles_unstable`, `render${ARGUMENTS}_unstable` +- Guard any `window`/`document`/`navigator` access with `canUseDOM()` from `@fluentui/react-utilities` +- Do not add dependencies on other Tier 3 component packages (see [docs/architecture/layers.md](../../../docs/architecture/layers.md)) + +## Available Generators Reference + +| Generator | Command | Purpose | +| --------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------- | +| `react-component` | `yarn nx g @fluentui/workspace-plugin:react-component` | Add component to existing package | +| `react-library` | `yarn nx g @fluentui/workspace-plugin:react-library` | Create new v9 package | +| `recipe-generator` | `yarn nx g @fluentui/workspace-plugin:recipe-generator` | Create a v9 recipe | +| `prepare-initial-release` | `yarn nx g @fluentui/workspace-plugin:prepare-initial-release` | Prepare package for release (compat/preview/stable) | +| `bundle-size-configuration` | `yarn nx g @fluentui/workspace-plugin:bundle-size-configuration` | Setup bundle-size tracking | +| `cypress-component-configuration` | `yarn nx g @fluentui/workspace-plugin:cypress-component-configuration` | Setup Cypress component tests | diff --git a/.agents/skills/visual-test/SKILL.md b/.agents/skills/visual-test/SKILL.md new file mode 100644 index 00000000000000..76251702066083 --- /dev/null +++ b/.agents/skills/visual-test/SKILL.md @@ -0,0 +1,110 @@ +--- +name: visual-test +description: Visually verify a component by launching its Storybook story and taking a screenshot with playwright-cli. Use after making visual changes to a component. +disable-model-invocation: true +argument-hint: <ComponentName> [story-name] +allowed-tools: Bash Read Grep Glob +--- + +# Visual Test a Component + +Visually verify **$ARGUMENTS** by launching Storybook and capturing a screenshot with `playwright-cli`. + +## Prerequisites + +Ensure `playwright-cli` is installed globally: + +```bash +npm ls -g @playwright/cli 2>/dev/null || npm install -g @playwright/cli@latest +``` + +## Steps + +1. **Find the component's stories package.** Each v9 component has a dedicated stories package: + + ```bash + yarn nx show projects 2>/dev/null | grep "<lowercase-component-name>.*stories" + ``` + +2. **Start the component's Storybook dev server:** + + ```bash + # Use the stories package directly — much faster than the full VR tests app + yarn nx run react-<component>-stories:start & + ``` + + Wait for Storybook to be ready on port 6006. Check with: + + ```bash + curl -s -o /dev/null -w "%{http_code}" http://localhost:6006 2>/dev/null + ``` + +3. **Open the page with playwright-cli:** + + ```bash + playwright-cli open "http://localhost:6006" + ``` + +4. **Navigate to the specific story iframe** and capture a screenshot. + Use the iframe URL for a clean render without Storybook chrome: + + ```bash + playwright-cli goto "http://localhost:6006/iframe.html?id=components-<component>-<component>--default&viewMode=story" + playwright-cli screenshot --filename=/tmp/visual-test-$ARGUMENTS.png + ``` + +5. **View the screenshot** using the Read tool to visually inspect the rendered component. + +6. **Use `snapshot`** to get the accessibility tree and find interactive element refs: + + ```bash + playwright-cli snapshot + ``` + + Then interact with elements by ref (e.g., click, hover) before taking more screenshots. + +7. **If the component doesn't look right**, go back to the code, fix the issue, and repeat from step 4 (Storybook hot-reloads changes). + +8. **Clean up** when done: + ```bash + playwright-cli close + # Kill the storybook process + kill %1 2>/dev/null + ``` + +## Story ID Pattern + +Story IDs follow the pattern `<category>-<subcategory>-<component>--<story>`: + +``` +# Default story for Button +components-button-button--default + +# Appearance variant +components-button-button--appearance + +# Default story for Menu +components-menu-menu--default +``` + +To discover exact story IDs, open the Storybook sidebar and use `snapshot` to find navigation links, +or check the story file's `export default { title: '...' }` metadata. + +## Iframe URL Format + +``` +# Local storybook +http://localhost:6006/iframe.html?id=components-button-button--default&viewMode=story + +# Dark theme +http://localhost:6006/iframe.html?id=components-button-button--default&viewMode=story&globals=theme:webDarkTheme +``` + +The `/iframe.html` URL gives a clean render without Storybook chrome — always prefer this for screenshots. + +## Tips + +- Use `playwright-cli snapshot` to get an accessibility tree — useful for verifying ARIA attributes and finding interactive elements. +- Use `playwright-cli click <ref>` to interact with the component (test hover states, open menus, etc.) before taking a screenshot. +- Use `playwright-cli resize <width> <height>` to test responsive behavior. +- For multiple story variants, take a screenshot of each: Default, Appearance, Size, Disabled, etc. diff --git a/.claude/skills/change/SKILL.md b/.claude/skills/change/SKILL.md new file mode 100644 index 00000000000000..e2112f762b7e86 --- /dev/null +++ b/.claude/skills/change/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/change/SKILL.md diff --git a/.claude/skills/lint-check/SKILL.md b/.claude/skills/lint-check/SKILL.md new file mode 100644 index 00000000000000..c428ad2ed06c09 --- /dev/null +++ b/.claude/skills/lint-check/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/lint-check/SKILL.md diff --git a/.claude/skills/package-info/SKILL.md b/.claude/skills/package-info/SKILL.md new file mode 100644 index 00000000000000..42f1511a2282a9 --- /dev/null +++ b/.claude/skills/package-info/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/package-info/SKILL.md diff --git a/.claude/skills/review-pr/SKILL.md b/.claude/skills/review-pr/SKILL.md new file mode 100644 index 00000000000000..a0764b592bf4c3 --- /dev/null +++ b/.claude/skills/review-pr/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/review-pr/SKILL.md diff --git a/.claude/skills/token-lookup/SKILL.md b/.claude/skills/token-lookup/SKILL.md new file mode 100644 index 00000000000000..e3b5350bc8c204 --- /dev/null +++ b/.claude/skills/token-lookup/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/token-lookup/SKILL.md diff --git a/.claude/skills/v9-component/SKILL.md b/.claude/skills/v9-component/SKILL.md new file mode 100644 index 00000000000000..f1d9e355ab88f1 --- /dev/null +++ b/.claude/skills/v9-component/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/v9-component/SKILL.md diff --git a/.claude/skills/visual-test/SKILL.md b/.claude/skills/visual-test/SKILL.md new file mode 100644 index 00000000000000..3f3989564a5025 --- /dev/null +++ b/.claude/skills/visual-test/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/visual-test/SKILL.md diff --git a/.github/instructions/copilot.instructions.md b/.github/instructions/copilot.instructions.md index 8d55b58c90fbca..67bec9b63538a0 100644 --- a/.github/instructions/copilot.instructions.md +++ b/.github/instructions/copilot.instructions.md @@ -4,470 +4,54 @@ applyTo: '**' # Fluent UI Copilot Instructions -You are working in the Microsoft Fluent UI monorepo, a comprehensive design system and UI component library for web applications. This repository contains multiple versions and implementations of Fluent UI components serving millions of users across Microsoft's products. +Nx monorepo for Microsoft's Fluent UI design system. Yarn v1, Node 22, TypeScript strict. -## Repository Overview +## Quick Reference -This is a large Nx monorepo with the following key characteristics: +- **v9 components** (active): `packages/react-components/` — see [component patterns](../../docs/architecture/component-patterns.md) +- **v8 components** (maintenance only): `packages/react/` — critical fixes only +- **Web Components**: `packages/web-components/` +- **Charting**: `packages/charts/` -- **Package Manager**: Yarn v1 with strict dependency management -- **Build System**: Nx workspace with custom plugins (`tools/workspace-plugin/`) -- **Node.js Versions**: ^22.0.0 -- **Languages**: TypeScript (strict mode), React, Web Components -- **Testing**: Jest, Cypress (E2E), Storybook + StoryWright (Visual Regression), SSR testing (test-ssr) -- **Documentation**: Storybook sites, api.md files via API Extractor -- **Release**: Beachball for version management and change files - -## Project Structure - -1. **Fluent UI v9** (`@fluentui/react-components`) - **PRIORITIZE FOR NEW WORK** - - - Location: `packages/react-components/` - - Nx Project Tags: `vNext` - - Features: Current stable version, actively developed. Tree-shakeable, atomic CSS classes - -2. **Fluent UI v8** (`@fluentui/react`) - **MAINTENANCE ONLY** - - - Location: `packages/react/` - - Nx Project Tags: `v8` - - Status: Maintenance mode - - Features: Runtime styling, mature and stable - -3. **Fluent UI Charting** - - - Location: `packages/charts/` - - Nx Project Tags: `v8`, `vNext`,`charting` - - Features: Charting components compatible with both v8 and v9 - -4. **Web Components** (`@fluentui/web-components`) - Framework-agnostic - - Location: `packages/web-components/` - - Nx Project Tags: `web-components` - - Used by: Microsoft Edge - - Features: Platform-agnostic web standards - -More information about the projects and their status can be found through NX tags. - -## Development Guidelines - -### Essential Nx Workspace Commands - -This workspace uses **Nx** with custom workspace plugins. Key commands: +## Essential Commands ```bash -# Initial setup -yarn # Install dependencies and link packages -yarn clean # Clean all build artifacts - -# Development workflows -yarn start # Interactive prompt to choose project to run -yarn nx run <project>:build # Build specific project with dependencies -yarn nx run-many -t build # Build multiple projects -yarn nx run <project>:test # Run tests for specific project -yarn nx run <project>:test -u # Update Jest snapshots -yarn nx run <project>:start # Start Storybook for component - -# Component generation (v9 only) -yarn create-component # Interactive component generator - -# Release management -yarn change # Create beachball change file (required for PRs) - -# Show project targets -yarn nx show projects # List all projects -``` - -### Component Development Workflow - -**For v9 components** (preferred): - -1. Navigate to `packages/react-components/` -2. Use generator: `yarn create-component` or direct Nx command -3. Follow hook-based architecture pattern exactly -4. Add comprehensive tests and Storybook stories -5. Run `yarn nx run <project>:generate-api` to update API docs -6. Create change file with `yarn change` before PR - -**For v8 components** (maintenance only): - -- Work in `packages/react/` with extreme caution -- No new features, only critical bug fixes - -### Testing Architecture - -**Multi-layered testing strategy** ensures quality at scale: - -```bash -# Unit Tests - Jest + React Testing Library + SWC transforms -yarn nx run react-button:test -yarn nx run react-button:test -u # Update snapshots - -# Visual Regression - Storybook + StoryWright -yarn nx run vr-tests-react-components:test-vr # Creates Diff images only. no actual assertions are run - this is used only from CI - -# E2E Integration Tests - Cypress -yarn nx run react-components:e2e - -# SSR Compatibility Tests -yarn nx run ssr-tests-v9:test-ssr - -# Cross-version React compatibility -yarn nx run rit-tests-v9:test-rit - -# Cross-version React testing for react-text project -yarn nx run react-text:test-rit - -``` - -### Code Quality and Standards - -- **Linting**: ESLint with custom `@fluentui/eslint-plugin` rules -- **Formatting**: Prettier (automatic via lint-staged) -- **Type checking**: TypeScript strict mode required -- **Testing**: Jest for unit tests, minimum 80% coverage expected -- **Bundle analysis**: Automated bundle size tracking via build pipeline - -## Important Patterns and Conventions - -### File Organization Patterns - -**CRITICAL**: Every v9 component follows this exact architectural pattern: - -``` -# v9 Component Structure (EXACT pattern required) -packages/react-components/react-component-name/ -├── library/src/ # Implementation source -│ ├── index.ts # Re-exports everything -│ ├── ComponentName.tsx # Main component export -│ ├── components/ComponentName/ # Core implementation -│ │ ├── ComponentName.test.tsx # Unit tests (adjacent) -│ │ ├── ComponentName.tsx # ForwardRefComponent -│ │ ├── ComponentName.types.ts # Props, State, Slots -│ │ ├── index.ts # Local exports -│ │ ├── renderComponentName.tsx # JSX rendering -│ │ ├── useComponentName.ts # State management -│ │ └── useComponentNameStyles.styles.ts # Griffel styling -│ ├── testing/ # Test utilities -│ └── utils # Reusable utils (if needed) -└── stories/src/ # Storybook documentation - ├── ComponentName.stories.tsx # Component stories - └── ComponentNameDefault.stories.tsx # Default story -``` - -### Component Structure Pattern - -``` -react-component-name/ -├── library/src/ -│ ├── index.ts # Main package exports -│ ├── ComponentName.ts # Main export (calls hooks + render) -│ ├── components/ComponentName/ # Hook-based architecture -│ │ ├── ComponentName.tsx # Main component export -│ │ ├── ComponentName.types.ts # Props, State, Slots types -│ │ ├── useComponentName.ts # State management hook -│ │ ├── useComponentNameStyles.styles.ts # Griffel styling -│ │ └── renderComponentName.tsx # JSX rendering logic -│ └── index.ts # Package exports -└── stories/ # Storybook stories -``` - -### Hook-Based Architecture - -Components use three core hooks: - -1. **`useComponent_unstable()`** - Processes props, slots and main component logic into normalized state -2. **`useComponentStyles_unstable()`** - Creates Griffel CSS-in-JS styling -3. **`renderComponent_unstable()`** - Pure JSX rendering from state - -### Slot System - -**Critical Pattern**: All v9 components use slots for extensibility and consistent rendering: - -```tsx -// Define slots in types -type ButtonSlots = { - root: Slot<'button'>; - icon?: Slot<'span'>; -}; - -// Create slots in hook using slot.always() or slot.optional() -const state: ButtonState = { - root: slot.always(props.root, { elementType: 'button' }), - icon: slot.optional(props.icon, { elementType: 'span' }), -}; - -// Render slots with assertSlots for type safety -export const renderButton_unstable = (state: ButtonState) => { - assertSlots<ButtonSlots>(state); - return ( - <state.root> - {state.icon && <state.icon />} - {state.root.children} - </state.root> - ); -}; -``` - -### Build-Time CSS-in-JS with Atomic Classes - -**Critical**: v9 uses Griffel for compile-time CSS generation - styles are extracted into atomic CSS classes at build time, not runtime: - -```tsx -// useButtonStyles.styles.ts -import { makeStyles } from '@griffel/react'; -import { tokens } from '@fluentui/react-theme'; - -export const useButtonStyles = makeStyles({ - root: { - // Use design tokens, not hardcoded values - color: tokens.colorNeutralForeground1, - backgroundColor: tokens.colorNeutralBackground1, - padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`, - - // Pseudo-selectors and media queries supported - ':hover': { - backgroundColor: tokens.colorNeutralBackground1Hover, - }, - - ':focus-visible': { - outline: `${tokens.strokeWidthThick} solid ${tokens.colorStrokeFocus2}`, - }, - }, - - // Size variations - small: { padding: tokens.spacingVerticalXS }, - large: { padding: tokens.spacingVerticalL }, -}); - -// Apply in component hook -export const useButton_unstable = (props, ref) => { - const classes = useButtonStyles(); - const state = { - /* ... */ - }; - - state.root.className = mergeClasses( - classes.root, - props.size === 'small' && classes.small, - props.size === 'large' && classes.large, - state.root.className, // Always preserve user className - ); - - return state; -}; -``` - -### Design Tokens System - -**Always use design tokens** from `@fluentui/tokens` instead of hardcoded values: - -```tsx -// ✅ CORRECT - uses semantic design tokens -color: tokens.colorBrandForeground1; -padding: tokens.spacingVerticalM; -borderRadius: tokens.borderRadiusMedium; - -// ❌ AVOID - hardcoded values break theming -color: '#0078d4'; -padding: '8px'; -borderRadius: '4px'; -``` - -### Theme Architecture - -Themes define CSS custom properties consumed by components: - -```tsx -// FluentProvider injects CSS variables into DOM -<FluentProvider theme={webLightTheme}> - <App /> -</FluentProvider>; - -// Components reference tokens which resolve to CSS variables -makeStyles({ - root: { - color: tokens.colorNeutralForeground1, // becomes 'var(--colorNeutralForeground1)' - }, -}); -``` - -### TypeScript Patterns (v9) - -**Strict typing with consistent interfaces:** - -```tsx -// Component.types.ts - REQUIRED pattern -export type ComponentProps = ComponentPropsWithRef<'div'> & { - appearance?: 'primary' | 'secondary'; - size?: 'small' | 'medium' | 'large'; -}; - -export type ComponentState = Required<Pick<ComponentProps, 'appearance' | 'size'>> & { - components: ComponentSlots; - root: SlotProps<'div'>; -}; - -export type ComponentSlots = { - root: Slot<'div'>; - icon?: Slot<'span'>; -}; - -// Main component must use ForwardRefComponent -export const Component: ForwardRefComponent<ComponentProps> = React.forwardRef((props, ref) => { - // Hook pattern implementation -}); -``` - -### Testing Requirements - -**Comprehensive testing is mandatory** for all component changes: - -### Accessibility Standards - -**WCAG 2.1 compliance required** for all interactive components: - -- Provide proper ARIA labels and roles -- Support keyboard navigation patterns -- Test with screen readers (NVDA, JAWS, VoiceOver) -- High contrast mode compatibility required - -## Documentation and Resources - -- **Main docs**: <https://react.fluentui.dev/> -- **v8 docs**: <https://aka.ms/fluentui-react> -- **Web Components docs**: <https://aka.ms/fluentui-web-components> -- **Design specs**: Located in `specs/` directory -- **Storybook**: Run locally with appropriate build commands - -### Code Style - -- Use TypeScript strict mode -- Follow existing patterns in similar components -- Comprehensive prop interfaces with JSDoc comments -- Consistent naming conventions (PascalCase for components, camelCase for props) -- Use React hooks and modern patterns for v9 components -- Focus on the slot system, Griffel styling, and hook-based architecture for v9 - -### Testing Requirements - -- Unit tests for all public APIs -- Accessibility tests for interactive components -- Visual regression tests via Storybook -- Cross-browser compatibility considerations - -## Migration and Compatibility - -- v8 and v9 components can coexist in the same application -- Gradual migration is supported and encouraged -- Pay attention to design token usage for consistent theming -- Consider bundle size impact when mixing versions - -When working in this repository, always prioritize accessibility, design system consistency, and maintainability. The codebase serves millions of users across Microsoft's products, so quality and reliability are paramount. - -## GitHub Issue and Pull Request Labeling - -### Issue Labels - -The repository uses automated labeling based on issue templates and PR file changes. Understanding the labeling system helps with proper issue categorization and triage. - -#### Core Label Categories - -**Type Labels** (required for all issues): - -- `Type: Bug :bug:` - Bug reports across all platforms -- `Type: Feature` - Feature requests and enhancements -- `Area: Documentation` - Documentation issues and improvements - -**Product/Platform Labels** (auto-assigned by issue templates): - -- `Fluent UI react-components (v9)` - v9 React components -- `Fluent UI react (v8)` - v8/legacy React components -- `web-components` - Web Components implementation -- `Fluent UI WC (v3)` - Web Components v3 specific -- `Package: charting` - React Charting components - -**Triage Labels**: - -- `Needs: Triage :mag:` - Automatically applied to new issues, requires team review -- `Area: Build System` - Build tooling and infrastructure - -#### Issue Template Mapping - -**For React Components v9 bugs**: - -```yaml -labels: ['Type: Bug :bug:', 'Needs: Triage :mag:', 'Fluent UI react-components (v9)'] -``` - -**For React v8 bugs**: - -```yaml -labels: ['Type: Bug :bug:', 'Needs: Triage :mag:', 'Fluent UI react (v8)'] -``` - -**For Web Components bugs**: - -```yaml -labels: ['Type: Bug :bug:', 'Needs: Triage :mag:', 'web-components', 'Fluent UI WC (v3)'] -``` - -**For Feature Requests**: - -```yaml -labels: ['Type: Feature', 'Needs: Triage :mag:'] -``` - -**For Documentation Issues**: - -```yaml -labels: ['Area: Documentation', 'Needs: Triage :mag:'] -``` - -### Pull Request Labels - -PRs receive automatic labels based on file changes via GitHub Actions using `.github/labeler.yml`: - -**Workflow Labels**: - -- `Type: RFC` - Changes to RFC documentation (`docs/react-v9/contributing/rfcs/**`) -- `CI` - Changes to CI/CD pipelines (`.github/**`, `.devops/**`, `azure-pipelines*.yml`) - -**Nx Workspace Labels**: - -- `NX: core` - Core Nx configuration changes (`tools/workspace-plugin/**`, `nx.json`, `**/project.json`) -- `NX: workspace generators` - Nx generator changes (`tools/workspace-plugin/src/generators/*`) -- `NX: workspace executors` - Nx executor changes (`tools/workspace-plugin/src/executors/*`) -- `NX: workspace eslint-rules` - ESLint rule changes (`tools/eslint-rules/*`) - -### Automated Triage System - -The repository uses a triage bot that automatically assigns labels and assignees based on issue content: - -**Trigger Keywords** (from `.github/triage-bot.config.json`): - -- `(@fluentui/react-northstar)` → `["Fluent UI react-northstar (v0)", "Needs: Triage :mag:"]` -- `(@fluentui/react)` → `["Fluent UI react (v8)", "Needs: Triage :mag:"]` -- `(@fluentui/react-components)` → `["Fluent UI react-components (v9)", "Needs: Triage :mag:"]` -- `(@fluentui/web-components)` → `["web-components"]` - -### Best Practices for Contributors - -**When Creating Issues**: - -1. Use appropriate issue template - labels are automatically applied -2. Select correct component/package from dropdown - helps with routing -3. Include package version and relevant details -4. Don't manually add labels - let automation handle initial triage - -**When Creating Pull Requests**: - -1. Labels are automatically applied based on changed files -2. Focus on clear PR titles and descriptions -3. Reference related issues with proper syntax (`Fixes #123`) -4. Create change files for breaking changes (`yarn change`) - -**For Maintainers**: - -1. Review `Needs: Triage :mag:` label for new items -2. Add component-specific labels after triage if needed -3. Use consistent labeling for similar issues to help automation learning -4. Update triage bot configuration when adding new packages/areas +yarn nx run <project>:build # Build +yarn nx run <project>:test # Test +yarn nx run <project>:test -u # Update snapshots +yarn nx run <project>:lint # Lint +yarn nx run <project>:type-check # Type check +yarn nx run <project>:generate-api # Update API docs +yarn beachball change # Create change file (required for published packages) +``` + +## Key Rules + +1. Use [design tokens](../../docs/architecture/design-tokens.md), never hardcoded values +2. Follow [v9 component patterns](../../docs/architecture/component-patterns.md) exactly +3. Respect [package layer boundaries](../../docs/architecture/layers.md) +4. SSR-safe: no unguarded `window`/`document`/`navigator` access +5. Accessibility: proper ARIA attributes, keyboard navigation, WCAG 2.1 +6. Always use `yarn nx run` — never run tools directly +7. Create beachball change files for published package changes + +## Deep Dives + +| Topic | Doc | +| --------------------------------------------- | ---------------------------------------------------------------------------------------- | +| V9 component structure, hooks, slots, Griffel | [docs/architecture/component-patterns.md](../../docs/architecture/component-patterns.md) | +| Design tokens and theming | [docs/architecture/design-tokens.md](../../docs/architecture/design-tokens.md) | +| Package dependency layers | [docs/architecture/layers.md](../../docs/architecture/layers.md) | +| PR workflow, branch naming, checklist | [docs/workflows/contributing.md](../../docs/workflows/contributing.md) | +| Test types, SSR safety, conformance | [docs/workflows/testing.md](../../docs/workflows/testing.md) | +| Teams, labels, CODEOWNERS | [docs/team-routing.md](../../docs/team-routing.md) | +| Quality grades per package | [docs/quality-grades.md](../../docs/quality-grades.md) | +| Known tech debt | [docs/tech-debt-tracker.md](../../docs/tech-debt-tracker.md) | + +## Issue & PR Labels + +Issue templates auto-apply labels. PR labels are auto-applied based on changed files. +See [docs/team-routing.md](../../docs/team-routing.md) for the full taxonomy. + +Triage keywords: `@fluentui/react-components` → v9, `@fluentui/react` → v8, +`@fluentui/web-components` → WC. Config: `.github/triage-bot.config.json`. diff --git a/AGENTS.md b/AGENTS.md index 91e8232c569e30..07c235b0dc4a93 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,11 +3,111 @@ # General Guidelines for working with Nx -- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly -- You have access to the Nx MCP server and its tools, use them to help the user -- When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable. -- When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies -- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration -- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors +- Always run tasks through `nx` (`nx run`, `nx run-many`, `nx affected`), never tools directly +- Use `nx_workspace` tool to understand workspace architecture +- Use `nx_project_details` tool to analyze specific project structure and dependencies +- Use `nx_docs` tool for up-to-date Nx configuration guidance <!-- nx configuration end--> + +# Fluent UI — Agent Instructions + +**Instructions in this file are the source of truth, not existing code.** This repo contains +legacy patterns (especially in v8 packages) that predate current standards. Never copy patterns +from existing code without verifying they match these instructions. + +## Critical Rules (never violate) + +1. **Never hardcode colors, spacing, or typography values.** Always use design tokens from `@fluentui/react-theme`. See [docs/architecture/design-tokens.md](docs/architecture/design-tokens.md). +2. **Never use `React.FC`.** Always use `ForwardRefComponent` with `React.forwardRef`. +3. **Never access `window`, `document`, or `navigator` without SSR guards.** Use `canUseDOM()` from `@fluentui/react-utilities`. +4. **Never add dependencies between component packages.** `react-button` must not depend on `react-menu`. Shared logic goes in `react-utilities` or `react-shared-contexts`. See [docs/architecture/layers.md](docs/architecture/layers.md). +5. **Never skip beachball change files** for published package changes. Run `yarn beachball change`. + +## V9 Component Template (the correct pattern) + +```tsx +// ComponentName.tsx — always ForwardRefComponent, never React.FC +export const ComponentName: ForwardRefComponent<ComponentNameProps> = React.forwardRef((props, ref) => { + const state = useComponentName_unstable(props, ref); + useComponentNameStyles_unstable(state); + return renderComponentName_unstable(state); +}); + +// Styles — always use tokens, never hardcoded values +import { makeStyles } from '@griffel/react'; +import { tokens } from '@fluentui/react-theme'; + +export const useComponentNameStyles = makeStyles({ + root: { + color: tokens.colorNeutralForeground1, + padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`, + }, +}); + +// mergeClasses — always preserve user className LAST +state.root.className = mergeClasses( + classes.root, + state.root.className, // always last +); +``` + +## Legacy Anti-Patterns (never copy these) + +- **DO NOT copy patterns from `packages/react/` (v8).** That's maintenance-only legacy code using runtime styling, class components, and different APIs. +- **DO NOT use `@fluentui/react` imports for new v9 work.** Use `@fluentui/react-components`. +- **DO NOT use `mergeStyles` or `mergeStyleSets`.** Use Griffel `makeStyles` with design tokens. +- **DO NOT use `IStyle` or `IStyleFunctionOrObject`.** Use Griffel's `GriffelStyle` type. +- **DO NOT use `initializeIcons()`.** V9 uses `@fluentui/react-icons` with tree-shaking. + +## Exploration Guidance + +- `packages/react-components/` has 74+ packages — search by specific component name, never read the full directory. +- Use `yarn nx show project <project-name>` to understand a project's structure. +- Map package names to paths: `@fluentui/react-<name>` → `packages/react-components/react-<name>/library/src/`. + +## Architecture (deep dives) + +| Topic | Location | +| --------------------------------------------- | ---------------------------------------------------------------------------------- | +| V9 component patterns (hooks, slots, Griffel) | [docs/architecture/component-patterns.md](docs/architecture/component-patterns.md) | +| Design tokens and theming | [docs/architecture/design-tokens.md](docs/architecture/design-tokens.md) | +| Package dependency layers | [docs/architecture/layers.md](docs/architecture/layers.md) | + +## Workflows + +| Topic | Location | +| ------------------------------------ | ---------------------------------------------------------------- | +| PR checklist, change files, commands | [docs/workflows/contributing.md](docs/workflows/contributing.md) | +| Testing guide (unit, VRT, SSR, E2E) | [docs/workflows/testing.md](docs/workflows/testing.md) | +| Team routing and label taxonomy | [docs/team-routing.md](docs/team-routing.md) | + +## Quality Tracking + +| Topic | Location | +| -------------------------- | ------------------------------------------------------ | +| Per-package quality grades | [docs/quality-grades.md](docs/quality-grades.md) | +| Technical debt tracker | [docs/tech-debt-tracker.md](docs/tech-debt-tracker.md) | + +## Skills (Slash Commands) + +| Skill | Command | Purpose | +| -------------- | -------------------- | ---------------------------------------------------------- | +| `v9-component` | `/v9-component Name` | Scaffold a new v9 component with all required files | +| `change` | `/change` | Create beachball change file from current diff | +| `lint-check` | `/lint-check [pkg]` | Run lint, parse errors, and auto-fix common issues | +| `token-lookup` | `/token-lookup val` | Find the design token for a hardcoded CSS value | +| `package-info` | `/package-info pkg` | Quick lookup: path, deps, owner, tests, structure | +| `visual-test` | `/visual-test Name` | Visually verify a component via Storybook + playwright-cli | +| `review-pr` | `/review-pr #123` | Review a PR with confidence scoring and category checks | + +## Package Layout + +| Area | Path | Status | +| -------------- | ---------------------------- | ------------------ | +| V9 components | `packages/react-components/` | Active development | +| V8 components | `packages/react/` | Maintenance only | +| Web Components | `packages/web-components/` | Active | +| Charting | `packages/charts/` | Active | +| Build tooling | `tools/` | Active | +| ESLint plugin | `packages/eslint-plugin/` | Active | diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 00000000000000..47dc3e3d863cfb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/docs/architecture/component-patterns.md b/docs/architecture/component-patterns.md new file mode 100644 index 00000000000000..6d9ff3df0812f2 --- /dev/null +++ b/docs/architecture/component-patterns.md @@ -0,0 +1,126 @@ +# V9 Component Patterns + +## File Structure + +Every v9 component package follows this exact layout: + +``` +packages/react-components/react-<name>/library/src/ +├── components/<Name>/ +│ ├── <Name>.tsx # ForwardRefComponent +│ ├── <Name>.types.ts # Props, State, Slots types +│ ├── <Name>.test.tsx # Unit tests (adjacent) +│ ├── use<Name>.ts or .tsx # State management hook +│ ├── use<Name>Styles.styles.ts # Griffel styling +│ ├── render<Name>.tsx # JSX rendering +│ └── index.ts # Component barrel export +├── contexts/ # Optional: context definitions +├── utils/ # Optional: shared utilities +├── testing/ +│ └── isConformant.ts # Conformance tests +├── <Name>.ts # Root barrel per component +└── index.ts # Package export +``` + +## Hook-Based Architecture + +Components use three core hooks: + +1. **`use<Name>(props, ref)`** — Processes props and slots into normalized state. + Use `.ts` if pure logic, `.tsx` if the hook body contains JSX. + +2. **`use<Name>Styles(state)`** — Creates Griffel CSS-in-JS styling using design tokens. + Always ends in `.styles.ts`. + +3. **`render<Name>(state)`** — Pure JSX rendering from state. + Always `.tsx`. + +### Where to Fix Bugs + +| Bug type | Fix location | +| ---------------- | --------------------------- | +| State / behavior | `use<Name>.ts` | +| Styling | `use<Name>Styles.styles.ts` | +| Rendering / JSX | `render<Name>.tsx` | +| Types / props | `<Name>.types.ts` | + +## Slot System + +All v9 components use slots for extensibility: + +```tsx +// Types +type ButtonSlots = { + root: Slot<'button'>; + icon?: Slot<'span'>; +}; + +// Hook — create slots +const state: ButtonState = { + root: slot.always(props.root, { elementType: 'button' }), + icon: slot.optional(props.icon, { elementType: 'span' }), +}; + +// Render — use assertSlots for type safety +export const renderButton_unstable = (state: ButtonState) => { + assertSlots<ButtonSlots>(state); + return ( + <state.root> + {state.icon && <state.icon />} + {state.root.children} + </state.root> + ); +}; +``` + +## Griffel Styling + +Use `makeStyles` with design tokens — never hardcode values: + +```tsx +import { makeStyles } from '@griffel/react'; +import { tokens } from '@fluentui/react-theme'; + +export const useButtonStyles = makeStyles({ + root: { + color: tokens.colorNeutralForeground1, + backgroundColor: tokens.colorNeutralBackground1, + padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`, + ':hover': { + backgroundColor: tokens.colorNeutralBackground1Hover, + }, + }, +}); +``` + +Always use `mergeClasses()` and preserve user className as the **last** argument: + +```tsx +state.root.className = mergeClasses( + classes.root, + props.size === 'small' && classes.small, + state.root.className, // Always last +); +``` + +## TypeScript Patterns + +```tsx +// Component.types.ts +export type ComponentProps = ComponentPropsWithRef<'div'> & { + appearance?: 'primary' | 'secondary'; + size?: 'small' | 'medium' | 'large'; +}; + +export type ComponentState = Required<Pick<ComponentProps, 'appearance' | 'size'>> & { + components: ComponentSlots; + root: SlotProps<'div'>; +}; + +// Main component — always ForwardRefComponent, never React.FC +export const Component: ForwardRefComponent<ComponentProps> = React.forwardRef((props, ref) => { + const state = useComponent_unstable(props, ref); + useComponentStyles_unstable(state); + return renderComponent_unstable(state); +}); +``` diff --git a/docs/architecture/design-tokens.md b/docs/architecture/design-tokens.md new file mode 100644 index 00000000000000..b326ac6d599c40 --- /dev/null +++ b/docs/architecture/design-tokens.md @@ -0,0 +1,63 @@ +# Design Tokens + +## Rule + +**Always use design tokens** from `@fluentui/react-theme` instead of hardcoded values. +Hardcoded values break theming, high contrast mode, and dark mode. + +## Token Categories + +| Category | Example tokens | Use for | +| ------------- | --------------------------------------------------------------- | -------------------- | +| Color | `tokens.colorNeutralForeground1`, `tokens.colorBrandBackground` | All colors | +| Spacing | `tokens.spacingVerticalM`, `tokens.spacingHorizontalL` | Padding, margin, gap | +| Border radius | `tokens.borderRadiusMedium`, `tokens.borderRadiusLarge` | Border radius | +| Font | `tokens.fontSizeBase300`, `tokens.fontWeightSemibold` | Typography | +| Line height | `tokens.lineHeightBase300` | Line height | +| Stroke | `tokens.strokeWidthThin`, `tokens.strokeWidthThick` | Border width | +| Shadow | `tokens.shadow4`, `tokens.shadow16` | Box shadow | +| Duration | `tokens.durationNormal`, `tokens.durationFast` | Animations | +| Easing | `tokens.curveEasyEase` | Animation timing | + +## Examples + +```tsx +// CORRECT — uses semantic design tokens +color: tokens.colorBrandForeground1; +padding: tokens.spacingVerticalM; +borderRadius: tokens.borderRadiusMedium; +fontSize: tokens.fontSizeBase300; +boxShadow: tokens.shadow4; + +// WRONG — hardcoded values break theming +color: '#0078d4'; +padding: '8px'; +borderRadius: '4px'; +fontSize: '14px'; +boxShadow: '0 2px 4px rgba(0,0,0,0.1)'; +``` + +## Theme Architecture + +Themes define CSS custom properties consumed by components: + +```tsx +// FluentProvider injects CSS variables into DOM +<FluentProvider theme={webLightTheme}> + <App /> +</FluentProvider>; + +// Tokens resolve to CSS variables at build time +makeStyles({ + root: { + color: tokens.colorNeutralForeground1, + // becomes: 'var(--colorNeutralForeground1)' + }, +}); +``` + +## Available Themes + +- `webLightTheme` — Default light +- `webDarkTheme` — Default dark +- `teamsLightTheme` / `teamsDarkTheme` / `teamsHighContrastTheme` — Teams variants diff --git a/docs/architecture/layers.md b/docs/architecture/layers.md new file mode 100644 index 00000000000000..749a905cb884c5 --- /dev/null +++ b/docs/architecture/layers.md @@ -0,0 +1,51 @@ +# Package Dependency Layers + +## Layer Hierarchy + +Packages in this monorepo follow a layered dependency model. +Dependencies may only flow **downward** — never upward or sideways within the same tier. + +``` +┌─────────────────────────────────────┐ +│ Tier 4: Barrel Package │ @fluentui/react-components +│ (aggregates all v9 components) │ (depends on all component packages) +├─────────────────────────────────────┤ +│ Tier 3: Component Packages │ @fluentui/react-button, react-dialog, etc. +│ (individual UI components) │ (depend on utilities and theme) +├─────────────────────────────────────┤ +│ Tier 2: Foundation Packages │ @fluentui/react-utilities, react-theme, +│ (shared utilities, theme, context) │ react-shared-contexts, react-tabster, +│ │ react-positioning, react-portal +├─────────────────────────────────────┤ +│ Tier 1: Core Packages │ @griffel/react, @fluentui/tokens, +│ (tokens, CSS-in-JS engine) │ @fluentui/react-jsx-runtime +└─────────────────────────────────────┘ +``` + +## Rules + +1. **Component packages (Tier 3) must NOT depend on other component packages.** + If `react-button` needs something from `react-menu`, it should go through + a shared context or utility in Tier 2. + +2. **Foundation packages (Tier 2) must NOT depend on component packages (Tier 3).** + +3. **Stories packages may depend on anything** — they are leaf nodes. + +4. **Cross-cutting concerns** (contexts, portals, positioning) live in Tier 2 + and are shared through explicit imports, not peer dependencies. + +## Nx Tags + +Projects are tagged for identification: + +| Tag | Meaning | +| ---------------- | ------------------------------ | +| `vNext` | v9 packages | +| `v8` | v8 packages (maintenance only) | +| `platform:web` | Browser-targeted | +| `platform:node` | Node.js-targeted | +| `type:stories` | Storybook story packages | +| `web-components` | Web Components packages | +| `charting` | Charting packages | +| `tools` | Build tooling | diff --git a/docs/quality-grades.md b/docs/quality-grades.md new file mode 100644 index 00000000000000..8ed2276e44ea99 --- /dev/null +++ b/docs/quality-grades.md @@ -0,0 +1,33 @@ +# Package Quality Grades + +This file tracks the documentation and test coverage quality of v9 component packages. +Updated during periodic documentation audits. + +## Grading Criteria + +| Grade | Meaning | +| ----- | ---------------------------------------------------------------------------- | +| A | README + stories + tests + API docs all present and current | +| B | Missing one of: complete README, full story coverage, or up-to-date API docs | +| C | Missing two or more quality signals | +| D | Minimal or no documentation/testing | + +## Quality Signals + +- **README**: Has description, install command, usage example +- **Stories**: Has `stories/` directory with Default story and variant coverage +- **Tests**: Has unit tests with reasonable coverage +- **API docs**: `.api.md` file exists and matches current exports +- **Conformance**: `testing/isConformant.ts` exists and passes + +## Current Grades + +<!-- This table is updated by the docs-groomer agent. Do not edit manually. --> + +| Package | README | Stories | Tests | API Docs | Conformance | Grade | +| ----------------------------------------- | ------ | ------- | ----- | -------- | ----------- | ----- | +| _To be populated by first docs audit run_ | | | | | | | + +## How to Update + +This table is populated during periodic documentation audits. diff --git a/docs/team-routing.md b/docs/team-routing.md new file mode 100644 index 00000000000000..d0aaab955408da --- /dev/null +++ b/docs/team-routing.md @@ -0,0 +1,40 @@ +# Team Routing & Label Taxonomy + +## Team Routing + +The source of truth for code ownership is [`.github/CODEOWNERS`](../.github/CODEOWNERS). +The table below is a summary — when in doubt, check CODEOWNERS. + +| Area | Team | Packages | +| -------------- | ------------------------------- | ------------------------------------ | +| v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | +| v8 Components | @microsoft/cxe-red | `packages/react/*` | +| Web Components | @microsoft/fui-wc | `packages/web-components/*` | +| Charting | @microsoft/charting-team | `packages/charts/*` | +| Build/Tooling | @microsoft/fluentui-react-build | `tools/*`, `.github/*`, root configs | + +## Label Taxonomy + +### Product Labels + +- `Fluent UI react-components (v9)` — v9 React components +- `Fluent UI react (v8)` — v8 React components +- `web-components` / `Fluent UI WC (v3)` — Web Components +- `Package: charting` — Charting library + +### Type Labels + +- `Type: Bug :bug:` — Bug reports +- `Type: Feature` — Feature requests + +### Status Labels + +- `Needs: Triage :mag:` — Needs team review +- `Needs: Author Feedback` — Waiting on issue author +- `Needs: Attention` — Needs team attention + +### Resolution Labels + +- `Resolution: Duplicate` — Duplicate of another issue +- `Resolution: Not An Issue` — Not a valid issue +- `Resolution: By Design` — Working as intended diff --git a/docs/tech-debt-tracker.md b/docs/tech-debt-tracker.md new file mode 100644 index 00000000000000..2c4336c92cb705 --- /dev/null +++ b/docs/tech-debt-tracker.md @@ -0,0 +1,40 @@ +# Technical Debt Tracker + +This file tracks known technical debt items that agents and engineers should be aware of. +Items are added during code review, agent runs, and manual audits. + +## Active Debt Items + +<!-- Add items with: category, description, location, priority, and date added --> + +| ID | Category | Description | Location | Priority | Added | +| ------------------------------------------------- | -------- | ----------- | -------- | -------- | ----- | +| _To be populated by agent runs and manual audits_ | | | | | | + +## Categories + +- **pattern-violation** — Code that doesn't follow established patterns +- **missing-tests** — Insufficient test coverage +- **stale-docs** — Documentation that doesn't match code +- **deprecated-usage** — Use of deprecated APIs or patterns +- **accessibility** — Known a11y gaps +- **performance** — Known performance issues +- **ssr-safety** — SSR-unsafe code patterns + +## Priority Levels + +- **P0** — Blocking: breaks users or CI +- **P1** — High: should fix in next sprint +- **P2** — Medium: fix when touching the area +- **P3** — Low: nice to have, no urgency + +## How to Use + +### For agents + +When you find technical debt during a fix or review, add a row to the table above. +When you fix a debt item, remove its row. + +### For engineers + +Review this file when planning sprints. Use it to justify cleanup PRs. diff --git a/docs/workflows/contributing.md b/docs/workflows/contributing.md new file mode 100644 index 00000000000000..34494467790d51 --- /dev/null +++ b/docs/workflows/contributing.md @@ -0,0 +1,59 @@ +# Contributing Workflow + +## Development Commands + +```bash +# Setup +yarn # Install dependencies +yarn clean # Clean build artifacts + +# Development +yarn start # Interactive project selector +yarn nx run <project>:build # Build specific project +yarn nx run <project>:start # Start Storybook for component +yarn nx run <project>:test # Run unit tests +yarn nx run <project>:test -u # Update snapshots +yarn nx run <project>:lint # Lint +yarn nx run <project>:type-check # Type check +yarn nx run <project>:generate-api # Update API docs + +# Multi-project +yarn nx run-many -t build # Build multiple +yarn nx affected -t test # Test affected projects + +# Component generation (v9 only) +yarn create-component # Interactive generator +``` + +## PR Checklist + +1. **Change file** — Required for any published package change: + + ```bash + yarn beachball change --type patch --message "fix(react-button): description" + ``` + + Use `patch` for fixes, `minor` for features. Never `major` without approval. + +2. **Tests pass** — `yarn nx run <project>:test` + +3. **Lint passes** — `yarn nx run <project>:lint` + +4. **Types check** — `yarn nx run <project>:type-check` + +5. **API docs updated** — If public API changed: `yarn nx run <project>:generate-api` + +6. **Link issue** — Use `Fixes #<number>` in PR body + +## Branch Naming + +- Bug fixes: `fix/<issue>-<description>` +- Features: `feat/<issue>-<description>` +- Docs: `docs/<description>` + +## What NOT to Do + +- Don't refactor unrelated code alongside a bug fix +- Don't modify public API without explicit approval +- Don't skip beachball change files for published packages +- Don't add dependencies between component packages (Tier 3 → Tier 3) diff --git a/docs/workflows/testing.md b/docs/workflows/testing.md new file mode 100644 index 00000000000000..63e2df0050e155 --- /dev/null +++ b/docs/workflows/testing.md @@ -0,0 +1,70 @@ +# Testing Guide + +## Test Types + +| Type | Tool | Command | Purpose | +| ----------------- | ---------------------------- | ----------------------------------------------- | -------------------------------- | +| Unit | Jest + React Testing Library | `yarn nx run <project>:test` | Component behavior, hooks, utils | +| Visual Regression | Storybook + StoryWright | `yarn nx run vr-tests-react-components:test-vr` | Screenshot diffs (CI only) | +| E2E | Cypress | `yarn nx run react-components:e2e` | Integration flows | +| SSR | Custom | `yarn nx run ssr-tests-v9:test-ssr` | Server-side rendering safety | +| Cross-React | Custom | `yarn nx run rit-tests-v9:test-rit` | React version compatibility | +| Conformance | isConformant | Part of unit tests | Consistent component API | + +## Writing Unit Tests + +Tests live adjacent to the component they test: + +``` +components/Button/ +├── Button.tsx +├── Button.test.tsx ← here +└── ... +``` + +### What to Test + +- Default rendering (snapshot) +- All prop variants +- User interactions (click, keyboard, focus) +- Accessibility (ARIA attributes, roles, keyboard navigation) +- Controlled and uncontrolled patterns +- Edge cases (null children, empty arrays, etc.) + +### Updating Snapshots + +If your change intentionally alters rendered output: + +```bash +yarn nx run <project>:test -u +``` + +Review the snapshot diff to verify the change is correct before committing. + +## Conformance Tests + +Every component package has a `testing/isConformant.ts` file that validates: + +- Component renders without crashing +- Ref forwarding works +- className merging works +- `as` prop (if applicable) works +- Accessibility basics + +## SSR Safety + +Components must work in server-side rendering. Never access browser APIs without guards: + +```tsx +// WRONG — crashes on server +const width = window.innerWidth; + +// RIGHT — guarded access +const width = typeof window !== 'undefined' ? window.innerWidth : 0; + +// BETTER — use useIsSSR or check canUseDOM +import { canUseDOM } from '@fluentui/react-utilities'; +if (canUseDOM()) { + // safe to use window/document +} +``` diff --git a/packages/eslint-plugin/src/rules/ban-context-export/index.js b/packages/eslint-plugin/src/rules/ban-context-export/index.js index 5e263ccc37a757..58158a2ed33356 100644 --- a/packages/eslint-plugin/src/rules/ban-context-export/index.js +++ b/packages/eslint-plugin/src/rules/ban-context-export/index.js @@ -40,8 +40,10 @@ module.exports = createRule({ description: 'Ban export of React context or context selector objects', }, messages: { - nativeContext: '{{exportName}} should not be exported directly', - contextSelector: '{{exportName}} should not be exported directly', + nativeContext: + '{{exportName}} should not be exported directly. Export a Provider component and a use*Context hook instead. See docs/architecture/component-patterns.md', + contextSelector: + '{{exportName}} should not be exported directly. Export a Provider component and a use*Context hook instead. See docs/architecture/component-patterns.md', }, }, create(context) { diff --git a/packages/eslint-plugin/src/rules/ban-instanceof-html-element/index.js b/packages/eslint-plugin/src/rules/ban-instanceof-html-element/index.js index aedade9629173a..6af7bd2a8d7015 100644 --- a/packages/eslint-plugin/src/rules/ban-instanceof-html-element/index.js +++ b/packages/eslint-plugin/src/rules/ban-instanceof-html-element/index.js @@ -14,7 +14,8 @@ module.exports = createRule({ description: 'Ban usage of instanceof HTMLElement comparison', }, messages: { - invalidBinaryExpression: 'instanceof {{right}} should be avoided, use isHTMLElement instead.', + invalidBinaryExpression: + 'instanceof {{right}} should be avoided. Use isHTMLElement() from @fluentui/react-utilities instead for SSR safety. See docs/workflows/testing.md', }, fixable: 'code', schema: [], diff --git a/packages/eslint-plugin/src/rules/no-context-default-value/index.js b/packages/eslint-plugin/src/rules/no-context-default-value/index.js index aec94028ff35e5..c39ccf95c7599d 100644 --- a/packages/eslint-plugin/src/rules/no-context-default-value/index.js +++ b/packages/eslint-plugin/src/rules/no-context-default-value/index.js @@ -20,7 +20,8 @@ module.exports = createRule({ description: 'Restricts usage of default values on React context creation', }, messages: { - invalidDefaultValue: 'Invalid default value for context declaration, default value should be undefined', + invalidDefaultValue: + 'Invalid default value for context declaration, default value should be undefined. Use createContext(undefined) and handle missing context in the consumer hook. See docs/architecture/component-patterns.md', }, fixable: 'code', schema: [ diff --git a/packages/eslint-plugin/src/rules/no-global-react.js b/packages/eslint-plugin/src/rules/no-global-react.js index 9fcec153bed0a2..27a581a3898a82 100644 --- a/packages/eslint-plugin/src/rules/no-global-react.js +++ b/packages/eslint-plugin/src/rules/no-global-react.js @@ -10,7 +10,8 @@ module.exports = createRule({ description: 'Prevent accidental references to the global React namespace', }, messages: { - missingImport: 'You must explicitly import React to reference it', + missingImport: + "You must explicitly import React to reference it. Add: import * as React from 'react'; See docs/architecture/component-patterns.md", }, schema: [], }, diff --git a/packages/eslint-plugin/src/rules/no-restricted-imports/index.js b/packages/eslint-plugin/src/rules/no-restricted-imports/index.js index 4a60b504a58ecb..2237cf6994eb12 100644 --- a/packages/eslint-plugin/src/rules/no-restricted-imports/index.js +++ b/packages/eslint-plugin/src/rules/no-restricted-imports/index.js @@ -26,7 +26,8 @@ module.exports = createRule({ description: 'Restricts imports of certain packages', }, messages: { - restrictedImport: 'Import from {{ packageName }} detected which is not allowed.', + restrictedImport: + 'Import from {{ packageName }} detected which is not allowed. Use the barrel export @fluentui/react-components instead. See docs/architecture/layers.md', }, fixable: 'code', schema: [