diff --git a/.github/skills/fluentui-cli/SKILL.md b/.github/skills/fluentui-cli/SKILL.md new file mode 100644 index 00000000000000..a5e52373e89766 --- /dev/null +++ b/.github/skills/fluentui-cli/SKILL.md @@ -0,0 +1,116 @@ +--- +name: fluentui-cli +description: 'Guides working with @fluentui/cli — the internal Fluent UI command-line tool. Use when asked to add a new CLI command, modify an existing command, understand CLI architecture, debug CLI issues, or work with the CLI build/test workflow. Covers: architecture overview, yargs conventions, lazy-loading pattern, testing, and available Nx generators.' +--- + +# `@fluentui/cli` + +The `@fluentui/cli` package (`tools/cli/`) is the internal Fluent UI command-line tool built with **yargs**. It uses a modular, lazy-loading architecture where each command lives in its own directory and is only loaded at runtime when invoked. + +## Architecture + +### File Structure + +``` +tools/cli/ +├── bin/fluentui-cli.js # Node entry point (requires compiled output) +├── src/ +│ ├── cli.ts # Main yargs setup, registers all commands +│ ├── index.ts # Public API re-exports +│ ├── utils/ +│ │ ├── index.ts # Barrel exports +│ │ └── types.ts # Shared CommandHandler type +│ └── commands/ +│ ├── migrate/ # Command module +│ │ ├── index.ts # CommandModule definition (eagerly loaded) +│ │ ├── handler.ts # Handler implementation (lazy-loaded) +│ │ └── handler.spec.ts # Tests +│ └── report/ # Command module +│ ├── index.ts +│ ├── handler.ts +│ └── handler.spec.ts +``` + +### Lazy Loading Pattern + +Command definitions (name, description, options builder) are eagerly imported — they are lightweight. The actual handler logic is **lazy-loaded via dynamic `import()`** only when the command is invoked: + +```typescript +// index.ts — always loaded (lightweight) +handler: async argv => { + // handler.ts only loaded when this specific command runs + const { handler } = await import('./handler'); + return handler(argv); +}, +``` + +This means running `fluentui-cli migrate` will never load the code for `report` or any other command. + +### CommandHandler Type + +All handlers use the shared `CommandHandler` type from `src/utils/types.ts`: + +```typescript +import type { ArgumentsCamelCase } from 'yargs'; + +export type CommandHandler = (argv: ArgumentsCamelCase) => Promise; +``` + +### Command Registration in cli.ts + +Each command is imported and registered in `tools/cli/src/cli.ts`: + +```typescript +import yargs from 'yargs'; +import migrateCommand from './commands/migrate'; +import reportCommand from './commands/report'; + +export async function main(argv: string[]): Promise { + await yargs(argv) + .scriptName('fluentui-cli') + .usage('$0 [options]') + .command(migrateCommand) + .command(reportCommand) + .demandCommand(1, 'You need to specify a command to run.') + .help() + .strict() + .parse(); +} +``` + +## Build & Test + +```sh +# Build the CLI +yarn nx run cli:build + +# Run tests +yarn nx run cli:test + +# Test --help output +node tools/cli/bin/fluentui-cli.js --help +node tools/cli/bin/fluentui-cli.js --help +``` + +## Conventions + +- **Always use the Nx generator** to scaffold new commands — do not create command files manually. See the [adding commands](references/adding-commands.md) reference. +- Place shared utilities in `tools/cli/src/utils/` and export through the barrel file. +- Every command must support `--help` (handled by yargs `.help()` in the builder). +- Handler files must export a named `handler` constant typed with `CommandHandler`. +- Tests live adjacent to handlers as `handler.spec.ts`. + +## Common Patterns + +### Subcommands (nested commands) + +If a command needs subcommands, use yargs nested command pattern in the builder: + +```typescript +builder: yargs => + yargs + .command('run', 'Run migrations', subBuilder => subBuilder, subHandler) + .command('list', 'List available migrations', subBuilder => subBuilder, subHandler) + .demandCommand(1) + .help(), +``` diff --git a/.github/skills/fluentui-cli/references/adding-commands.md b/.github/skills/fluentui-cli/references/adding-commands.md new file mode 100644 index 00000000000000..ab6f3aac928c18 --- /dev/null +++ b/.github/skills/fluentui-cli/references/adding-commands.md @@ -0,0 +1,135 @@ +# Adding a New CLI Command + +## Step 1 — Scaffold the Command + +Run the `cli-command` Nx generator: + +```sh +yarn nx g @fluentui/workspace-plugin:cli-command --description "" +``` + +### Example + +```sh +yarn nx g @fluentui/workspace-plugin:cli-command analyze --description "Analyze bundle sizes" +``` + +### What Gets Generated + +``` +tools/cli/src/commands// +├── index.ts # Yargs CommandModule definition (lightweight, eagerly loaded) +├── handler.ts # Handler implementation (lazy-loaded via dynamic import) +└── handler.spec.ts # Jest unit tests for the handler +``` + +The generator also **automatically registers** the new command in `tools/cli/src/cli.ts` by: + +1. Adding an import statement for the command module +2. Adding a `.command()` registration call + +Preview what will be generated without writing to disk: + +```sh +yarn nx g @fluentui/workspace-plugin:cli-command --dry-run +``` + +## Step 2 — Implement the Handler + +Open `tools/cli/src/commands//handler.ts` and implement the command logic: + +```typescript +import type { CommandHandler } from '../../utils/types'; + +// Define the shape of your command's arguments +interface AnalyzeArgs { + project?: string; + verbose?: boolean; +} + +export const handler: CommandHandler = async argv => { + const { project, verbose } = argv; + + // Your implementation here + console.log(`Analyzing${project ? ` project: ${project}` : ''}...`); +}; +``` + +## Step 3 — Add Options and Arguments + +Edit `tools/cli/src/commands//index.ts` to add yargs options in the `builder`: + +```typescript +import type { CommandModule } from 'yargs'; + +const command: CommandModule = { + command: 'analyze', + describe: 'Analyze bundle sizes', + builder: yargs => + yargs + .option('project', { + alias: 'p', + type: 'string', + describe: 'Project name to analyze', + }) + .option('verbose', { + type: 'boolean', + default: false, + describe: 'Show detailed output', + }) + .version(false) + .help(), + handler: async argv => { + const { handler } = await import('./handler'); + return handler(argv); + }, +}; + +export default command; +``` + +## Step 4 — Write Tests + +Update `tools/cli/src/commands//handler.spec.ts` with meaningful tests: + +```typescript +import { handler } from './handler'; + +describe('analyze handler', () => { + let logSpy: jest.SpyInstance; + + beforeEach(() => { + logSpy = jest.spyOn(console, 'log').mockImplementation(); + }); + + afterEach(() => { + logSpy.mockRestore(); + }); + + it('should analyze all projects when no project specified', async () => { + await handler({ _: ['analyze'], $0: 'fluentui-cli' }); + + expect(logSpy).toHaveBeenCalledWith('Analyzing...'); + }); + + it('should analyze specific project when specified', async () => { + await handler({ _: ['analyze'], $0: 'fluentui-cli', project: 'react-button' }); + + expect(logSpy).toHaveBeenCalledWith('Analyzing project: react-button...'); + }); +}); +``` + +## Step 5 — Verify + +```sh +# Build the CLI +yarn nx run cli:build + +# Run tests +yarn nx run cli:test + +# Test --help output +node tools/cli/bin/fluentui-cli.js --help +node tools/cli/bin/fluentui-cli.js --help +``` diff --git a/docs/plans/fluent-cli.md b/docs/plans/fluent-cli.md new file mode 100644 index 00000000000000..6daf71ef2396d5 --- /dev/null +++ b/docs/plans/fluent-cli.md @@ -0,0 +1,439 @@ +# Fluent UI v9 Agentic Migration — Project Spec + +## 1) Objective + +Build an **agentic migration solution** for Fluent UI React v9 that consists of exactly two products: + +1. **Agent Skills** — deterministic instructions/workflows AI coding agents can follow for complex migrations. +2. **CLI** — executable migration engine that performs Nx-style migrations and AST transformations. + +The skills are the **primary source of migration logic and sequencing**; the CLI is a **supportive execution utility** for codemods, reporting, and deterministic automation steps. + +--- + +## 2) Problem Statement + +Current migrations (v8 → v9 and v9 minor upgrades) are inconsistent and largely manual: + +- no official migration skill system that encodes migration strategy, +- no stable bridge between agent workflow decisions and codemod execution, +- no repeatable agent workflows tied to deterministic transform tooling. + +Result: high migration cost, inconsistent outcomes, and low confidence for large codebases. + +--- + +## 3) Scope + +### In scope (MVP) + +- Define and version **agent skills** for v8 → v9 migration. +- Publish a CLI package (`@fluentui/cli`) as a support tool with: + - codemod execution (`migrate`) for skill-selected steps, + - AST-based transforms (jscodeshift initially), + - migration diagnostics/reporting (`report`), + - dry-run support and machine-readable output for agents. +- Ensure skills invoke CLI only for deterministic/mechanical edits and analysis. +- Implement and ship the two required MVP codemods: `deprecated-packages`, `deprecated-props`. + +### Out of scope (MVP) + +- Full auto-migration for every v8 component. +- IDE plugin UI. +- New runtime shim framework. +- Cross-framework migrations beyond React/TS. +- Migration recommendation hints in `report` output (Phase 2). + +--- + +## 4) Success Criteria + +- Agent can execute end-to-end migration workflow on a sample app with no custom prompts. +- Skills own migration planning decisions; CLI only executes requested codemods reliably. +- CLI provides deterministic output (`dry-run` + applied mode) and idempotent transforms. +- For MVP migrations, at least 80% of targeted mechanical edits are automated. +- Type-check/test failure count after migration is lower than baseline manual approach. + +--- + +## 5) Architecture + +### 5.1 Agent Skills Layer + +Skills are the migration control plane and contain: + +- decision trees (when to run which migration), +- sequencing rules (report → dry-run → apply → verify), +- stop conditions (unsafe diff size, parser failures, unresolved imports), +- escalation rules (manual review checkpoints). + +Skills select migration steps, call CLI commands, and evaluate outcomes before proceeding. + +### 5.2 CLI Layer + +CLI is an execution/data plane and contains: + +- transform runner, +- reporting/analysis commands, +- structured result output for agent consumption. + +Primary transform engine for MVP: **jscodeshift** (TS/TSX parser defaults). CLI does not own migration strategy. + +--- + +## 6) CLI Spec + +CLI command behavior is intentionally minimal and execution-focused; migration strategy remains in skills. + +### 6.1 Commands + +#### `migrate` + +Run codemods selected by skills (from `migrations.json` or by migration name). + +```sh +npx @fluentui/cli migrate [migration-name] [path] [options] + +# Registry-driven run (Nx-style): +npx @fluentui/cli migrate --run-migrations=migrations.json src/ + +# Direct migration run: +npx @fluentui/cli migrate deprecated-packages src/ + +# Semver-filtered run (typically skill-driven): +npx @fluentui/cli migrate src/ --from 8 --to 9 +``` + +Options: + +- `--run-migrations ` — registry file path +- `--from ` — lower bound +- `--to ` — upper bound +- `-d, --dry-run` — preview without writing +- `-l, --list` — list known migrations +- `--cpus ` — worker count +- `--parser ` — default: `tsx` +- `--json` — emit machine-readable result summary to stdout + +#### `report` + +Produce diagnostics data for agents to decide migration steps. Writes structured JSON to a file by default (output can be large). + +```sh +npx @fluentui/cli report [path] [options] +``` + +Options: + +- `--output ` — default: `./fluent-report.json` +- `--print` — print compact summary to stdout instead of writing file +- `--json` — emit full JSON to stdout (for agent piping) + +Output sections: + +1. **Environment** — Node, OS, npm versions (always included). +2. **Installed packages** — all `@fluentui/*` and `@fluentui-contrib/*` versions (always included). +3. **Usage inventory** — imports + JSX props per component across scanned path (only when `[path]` is provided). + +Terminal summary always printed after file write: + +```sh +Report written to fluent-report.json + +Node: 22.21.1 | OS: darwin-arm64 | npm: 10.9.4 +Installed: @fluentui/react-components@9.73.1 @fluentui/react@8.125.5 (+1 more) +Usage: 312 usages across 14 components in src/ +``` + +JSON schema (`fluent-report.json`): + +```json +{ + "generatedAt": "2026-03-02T10:00:00.000Z", + "env": { "node": "22.21.1", "os": "darwin-arm64", "npm": "10.9.4" }, + "packages": { + "@fluentui/react-components": "9.73.1", + "@fluentui/react": "8.125.5" + }, + "usage": { + "scannedPath": "src/", + "totalUsages": 312, + "components": [ + { "name": "Button", "package": "@fluentui/react-components", "usages": 142, "props": ["appearance", "icon"] }, + { "name": "DefaultButton", "package": "@fluentui/react", "usages": 37, "props": ["text", "onClick"] } + ] + } +} +``` + +`usage` is `null` when no path is provided. + +Migration recommendation hints are **Phase 2** — MVP `report` is data-only. + +--- + +### 6.2 Migration Registry Contract (`migrations.json`) + +`migrations.json` is an execution catalog consumed by CLI; it is not the migration strategy source. + +```json +{ + "version": "1", + "migrations": [ + { + "name": "deprecated-packages", + "version": "9.0.0", + "fromVersion": "8.0.0", + "description": "Replace deprecated @fluentui package imports with supported equivalents", + "package": "@fluentui/react-components", + "kind": "ast", + "implementation": "./dist/transforms/v9/deprecated-packages.js", + "idempotent": true + }, + { + "name": "deprecated-props", + "version": "9.0.0", + "fromVersion": "8.0.0", + "description": "Replace @deprecated props across v9 components with their supported replacements", + "package": "@fluentui/react-components", + "kind": "ast", + "implementation": "./dist/transforms/v9/deprecated-props.js", + "idempotent": true + } + ] +} +``` + +Fields: + +- `kind`: `ast` | `nx-generator` | `composite` +- `implementation`: path to compiled entrypoint, resolved from package root +- `idempotent`: required declaration — transforms that cannot guarantee idempotency are not permitted in MVP + +--- + +### 6.3 Migration Types + +1. **AST transform** (default): codemods for imports, props, JSX shape. Implemented as standard jscodeshift transforms. +2. **Nx-style migration wrapper**: invokes migration-style scripts through the unified CLI contract. +3. **Composite migration**: ordered list of atomic migrations with shared context. + +--- + +## 7) Agent Skills Spec + +Skills are the authoritative migration source for this project. + +### 7.1 Location + +`skills//SKILL.md` at the **repo root**. Versioned with the codebase. Distributed via the `npx skills add` tooling (external). + +### 7.2 Skill Inventory (MVP) + +| Skill file | Purpose | +| ------------------------------------- | ------------------------------------- | +| `skills/fluentui-migrate-v8-to-v9.md` | End-to-end v8 → v9 migration workflow | + +### 7.3 Skill Workflow Contract + +Every skill must follow this baseline sequence: + +1. Run `@fluentui/cli report [path] --json` and parse the JSON output. +2. Determine applicable migration plan from skill rules. +3. Resolve needed codemod steps against CLI migration list (`migrate --list --json`). +4. Run `migrate --dry-run --json` and evaluate risk signals against safety thresholds. +5. Run `migrate --json` (apply step) if safe; confirm with user if large-diff gate triggers. +6. Run verification (`npx nx run-many -t type-check` on touched projects). +7. Produce final summary: migrations run, files changed, remaining manual follow-ups. + +### 7.4 Skill Safety Thresholds + +Defined as named constants in the skill file (hardcoded defaults, documented for review): + +| Constant | Default | Meaning | +| ------------------------- | ------- | ----------------------------------------------------- | +| `PARSE_ERROR_RATE_LIMIT` | `0.05` | Stop if >5% of files fail to parse | +| `LARGE_DIFF_FILE_COUNT` | `100` | Require human confirmation if >100 files changed | +| `UNRESOLVED_IMPORT_LIMIT` | `0` | Stop if any unresolved imports remain after transform | + +### 7.5 Safety Rules + +- Never run apply step before dry-run succeeds. +- Stop if parser error rate exceeds `PARSE_ERROR_RATE_LIMIT`. +- Stop if transform claims success but outputs unresolved imports. +- Require human confirmation when changed file count exceeds `LARGE_DIFF_FILE_COUNT`. + +--- + +## 8) Required Codemods (MVP) + +### `deprecated-packages` + +Rewrites imports from deprecated `@fluentui` packages to their current equivalents. + +Deprecated packages (source: `packages/react-components/deprecated/`): + +| Deprecated import | Replacement | Note | +| ----------------------------- | ------------------------------------- | ------------------------------------------------------------------- | +| `@fluentui/react-alert` | `@fluentui/react-toast` | Insert `// TODO: also consider @fluentui/react-message-bar` comment | +| `@fluentui/react-infobutton` | `@fluentui/react-infolabel` | Rename component `InfoButton` → `InfoLabel` | +| `@fluentui/react-virtualizer` | `@fluentui-contrib/react-virtualizer` | Package moved to contrib | + +**Source of truth**: read `packages/react-components/deprecated/*/README.md` before implementing. + +### `deprecated-props` + +Renames (or removes) deprecated component props to their supported replacements. + +Known deprecated props (source: `@deprecated` JSDoc tags in `packages/react-components/`): + +| Component | Deprecated prop | Replacement | +| -------------------- | --------------------- | ------------------------------------ | +| `TableSelectionCell` | `hidden` | `invisible` | +| `Popover` | `legacyTrapFocus` | `inertTrapFocus` | +| `Calendar` (compat) | `overlayedWithButton` | `overlaidWithButton` | +| `PositioningOptions` | `positionFixed` | `strategy="fixed"` (value transform) | +| `TagPickerList` | `disableAutoFocus` | _(remove — prop has no effect)_ | + +**Source of truth**: search `@deprecated` across `packages/react-components/` before implementing to catch any additions. + +### Transform conventions + +```typescript +import type { Transform, API, FileInfo } from 'jscodeshift'; +const transform: Transform = (file: FileInfo, api: API): string | void => { ... }; +export default transform; +// Return undefined → nochange (no write). Must be idempotent. +``` + +--- + +## 9) Package Layout + +```sh +packages/cli/ +├── bin/fluentui.js # #!/usr/bin/env node shebang +├── migrations.json # execution catalog (ships in npm package) +├── src/ +│ ├── index.ts # yargs root — registers migrate + report +│ ├── types.ts # MigrationEntry, RunnerOptions, ReportOutput, ... +│ ├── commands/ +│ │ ├── migrate.ts +│ │ └── report.ts +│ ├── runner/ +│ │ ├── MigrationRunner.ts # loads registry, filters, invokes jscodeshift Runner API +│ │ └── semverFilter.ts # normaliseVersion(), filterMigrationsByRange() +│ ├── report/ +│ │ ├── packageScanner.ts # globs node_modules for @fluentui/* versions +│ │ └── UsageCollector.ts # jscodeshift read-only visitor for imports + JSX props +│ ├── transforms/ +│ │ └── v9/ +│ │ ├── deprecated-packages.ts +│ │ └── deprecated-props.ts +│ └── __tests__/ +│ ├── semverFilter.test.ts +│ ├── MigrationRunner.test.ts +│ ├── report/packageScanner.test.ts +│ └── transforms/ +│ ├── deprecated-packages/ # golden input/output fixtures +│ └── deprecated-props/ # golden input/output fixtures +├── package.json +├── project.json # @nx/js:swc build target, platform:node tag +├── .swcrc # copy from tools/visual-regression-assert/.swcrc +├── tsconfig.json +├── tsconfig.lib.json +├── tsconfig.spec.json +├── jest.config.ts +└── eslint.config.js +``` + +Skills (repo root, versioned with code): + +```sh +skills/ +└── fluentui-migrate-v8-to-v9.md +``` + +--- + +## 10) Build Tooling + +Mirrors `tools/visual-regression-assert/` — the canonical modern Node CLI package in the repo. + +| Concern | Tool | +| -------------- | ----------------------------------------------------------------------------- | +| Build executor | `@nx/js:swc` — output to `packages/cli/dist/` | +| SWC config | `.swcrc` — copy from `tools/visual-regression-assert/.swcrc` | +| Tests | `@swc/jest` reading `.swcrc`, `testEnvironment: node` | +| TypeScript | Solution config: `tsconfig.json` → `tsconfig.lib.json` + `tsconfig.spec.json` | +| Lint | ESLint flat config, `@fluentui/eslint-plugin` `flat/node` base | + +Workspace changes required: + +| File | Change | +| -------------------- | ----------------------------------------------------------- | +| `nx.json` | Add `"packages/cli/**"` to workspace plugin `include` array | +| `tsconfig.base.json` | Add `"@fluentui/cli": ["packages/cli/src/index.ts"]` | + +--- + +## 11) Implementation Phases + +### Phase 0 — Scaffold + +- Create `packages/cli/` with all config files (build, test, lint, TypeScript). +- Update `nx.json` + `tsconfig.base.json`. +- Verify `nx build cli` and `nx test cli` pass on empty stubs. + +### Phase 1 — Core Engine + +- Implement `types.ts`, `semverFilter.ts`, `MigrationRunner.ts` with tests. +- Implement `migrate` command shell + `--list`, `--dry-run`, `--json` modes. +- Create `migrations.json` with two entries (stubs, implementations not yet wired). +- Verify CLI responds correctly to all flags against stub transforms. + +### Phase 2 — Codemods + +- Implement `deprecated-packages.ts` with golden tests + idempotency test. +- Implement `deprecated-props.ts` with golden tests + idempotency test. +- Wire transforms into `migrations.json`. + +### Phase 3 — Report Command + +- Implement `packageScanner.ts` + tests. +- Implement `UsageCollector.ts` + tests. +- Implement `report.ts` command (file write, `--print`, `--json`). + +### Phase 4 — Agent Skill + +- Author `skills/fluentui-migrate-v8-to-v9/SKILL.md` with safety thresholds, 7-step workflow, CLI invocations. +- Validate workflow against real CLI output on a sample v8 codebase. + +--- + +## 12) Verification Strategy + +- **Unit tests**: runner, semver filter, registry parsing. +- **Golden tests**: each transform has `*.input.tsx` / `*.output.tsx` fixture pairs. +- **Idempotency test**: run each transform twice — second run must return `undefined` (no change). +- **Integration test**: sample v8 app → CLI migrate → typecheck passes. +- **Skill E2E**: simulated agent execution path using real CLI `--json` outputs. + +--- + +## 13) Risks and Mitigations + +- **API drift**: registry versioning + semver gates. +- **False-positive transforms**: conservative AST matching + dry-run diff review. +- **Agent overreach**: enforce skills-first policy; CLI is execution-only. +- **Large monorepo runtime cost**: path scoping + `--cpus` worker control + incremental runs. + +--- + +## 14) Open Decisions — Resolved + +| Decision | Resolution | +| ------------------------------ | ------------------------------------------------------------------------------------------------------ | +| Skills packaging location | `skills/.md` at repo root. `react-components-agent-skills/` placeholder not used. | +| Report migration hints in MVP | Phase 2 only. MVP `report` is data-only. | +| Safety gate threshold defaults | Hardcoded named constants in skill file: parse error 5%, diff gate 100 files, zero unresolved imports. | diff --git a/skills/fluentui-migrate-v8-to-v9/SKILL.md b/skills/fluentui-migrate-v8-to-v9/SKILL.md new file mode 100644 index 00000000000000..bdfed67332f77d --- /dev/null +++ b/skills/fluentui-migrate-v8-to-v9/SKILL.md @@ -0,0 +1,683 @@ +--- +name: fluentui-migrate-v8-to-v9 +description: 'Guides migration from Fluent UI React v8 (@fluentui/react) to v9 (@fluentui/react-components). Use when asked to migrate a component or codebase, explain what replaces a v8 component, compare v8 and v9 APIs, or troubleshoot visual/styling issues after migration. Covers: component mapping, prop changes, architectural shifts (FluentProvider, Griffel makeStyles, slots, declarative children), and common troubleshooting.' +--- + +# Fluent UI v8 → v9 Migration + +## Agent Output Template + +After completing a migration session, report using this structure: + +``` +### Migration Report + +**Scope:** 12 files, 4 component types migrated +**Assumptions logged:** 2 (see inline `// MIGRATION ASSUMPTION:` comments) +**Unresolved deltas:** none (e.g. "GroupedList in DataView.tsx — awaiting user direction") +**Shims still in place:** none (e.g. "PrimaryButtonShim → src/Header.tsx") +**Shim removal plan:** n/a (e.g. "replace with Button appearance=primary once v8 deps removed") +**Reference precedence used:** none (e.g. "Dialog: used references/dialog.md over SKILL.md table") +**Validation evidence:** + - TypeScript check (yarn tsc --noEmit): ✅ (e.g. ❌ 3 errors — baseline was 1) + - lint: ✅ (e.g. ⏭️ skipped — no linter found) + - tests: ✅ (e.g. ❌ 2 failing) + - No remaining @fluentui/react imports: ✅ (e.g. ❌ 3 remaining) +**Final status:** ✅ Complete (e.g. ⚠️ Partial — see unresolved deltas | ❌ Blocked: tsc errors) +``` + +--- + +## Migration Workflow + +### Step 1 — Assess + +**Determine project root and source directory:** + +1. Locate `package.json` to identify the **project root** — it is the source of truth. If multiple `package.json` files exist (monorepo), ask: _"Which package should I migrate? (e.g., `packages/my-app`)"_ — do not proceed until confirmed. +2. Once the project root is confirmed, check `tsconfig.json` for `include` or `rootDir` to narrow down the **source directory** (e.g., `src/`, `app/`, `lib/`). +3. If no `tsconfig.json` exists, treat the project root as the source root. + +Use `` for the package root and `` for the narrowed source directory in the commands below. + +**Check for existing migration annotations:** + +First check for `.fluent-migrate/metadata.json` — written by the CLI after annotating, it lists annotated files deterministically. The CLI writes it to the same directory passed as `--path`, so look in ``: + +```sh +cat /.fluent-migrate/metadata.json +``` + +- If `metadata.json` exists, use the `annotatedFiles` list as your work queue. **Paths are relative to the directory that contains `.fluent-migrate/`** (i.e. relative to ``). Resolve each path against `` before opening. Skip the grep scan. +- If it does not exist, scan for annotations: + +```sh +grep -rl "@fluent-migrate:" --include="*.ts" --include="*.tsx" +``` + +- If annotations are found → proceed to Step 3 (annotations are the work queue). +- If nothing is found → tell the user: + + > "No migration annotations found. Please run `npx @fluentui/cli migrate v8-to-v9 --path ` first, then re-invoke the skill." + > + > Tip: add `--dry-run` to preview which files would be annotated without writing any changes. + + Stop until the user confirms the CLI has been run. + +### Setup Boundary + +**Agents do not run installs.** Read `package.json` and report what's missing; the user installs. + +Ready-to-proceed conditions (verify before starting Step 3): + +- `@fluentui/react-components` is present in `package.json` dependencies +- A `FluentProvider` wrapper exists somewhere in the codebase (or the user has confirmed they will add one) + +If either condition is unmet, report what's needed and stop until the user confirms setup is complete. + +### Step 2 — User Setup (reference only) + +> **Agent note:** do not execute these commands. Report which packages are missing from `package.json` and wait for the user to confirm installation is complete before proceeding to Step 3. + +```sh +npm install @fluentui/react-components @fluentui/react-icons +# If running v8 + v9 side-by-side: +npm install @fluentui/react-portal-compat +# Shim package for incremental migration (optional — see below): +npm install @fluentui/react-migration-v8-v9 +``` + +Wrap the app root (or each subtree using v9 components): + +```tsx +import { FluentProvider, webLightTheme } from '@fluentui/react-components'; +// If v9 inside v8 Callout/Panel: +import { PortalCompatProvider } from '@fluentui/react-portal-compat'; + + + + {' '} + {/* only if needed */} + + +; +``` + +### Step 3 — Migrate (annotation-driven) + +**Before starting:** run the repo's TypeScript check command (`tsc --noEmit` or the `package.json` scripts equivalent) and record the error count as your baseline. After migration, TypeScript is ✅ if error count ≤ baseline — do not block on pre-existing errors. + +**Behavior-preserving default:** when uncertain, preserve existing behavior. Never silently drop functionality. + +**Stop and escalate when:** + +- TypeScript errors remain after processing annotations that you cannot resolve +- A `no-equivalent` annotation has no clear workaround from the component mapping table +- More than 2 unresolved `manual` annotations in the same file + +In those cases: commit what's done, fill in the Output Template with status ⚠️ or ❌, list the blockers, and wait for user input. + +#### Annotation format + +Every annotation written by the CLI has this structure: + +``` +// @fluent-migrate: | | | +``` + +Inside JSX (where a line comment is a syntax error): + +```tsx +{ + /* @fluent-migrate: | | | */ +} +``` + +Fields: + +- `action` — `auto`, `scaffold`, `manual`, or `no-equivalent` +- `codemod` — identifies the transformation rule (see Codemod → Reference Index) +- `payload` — machine-readable hint (e.g. `Toggle → Switch`, `@fluentui/react → @fluentui/react-components`) +- `note` — optional human-readable context; may reference a file (e.g. `see references/dialog.md`) + +The annotation always sits on the line **above** the node it describes. + +--- + +Process the `@fluent-migrate:` annotations as a work queue. If `.fluent-migrate/metadata.json` exists, open each file it lists directly. Otherwise, get all annotations with: + +```sh +grep -rn "@fluent-migrate:" --include="*.ts" --include="*.tsx" +``` + +Work through them in this order, removing each annotation comment after applying the change: + +#### 1. `auto` annotations — apply mechanically, no questions + +```sh +grep -rn "@fluent-migrate:auto" --include="*.ts" --include="*.tsx" +``` + +Each annotation encodes exactly what to do in its payload. Apply the transformation on the line below the comment and remove the annotation. No user questions needed. + +#### 2. `scaffold` annotations — generate boilerplate with TODO placeholders + +```sh +grep -rn "@fluent-migrate:scaffold" --include="*.ts" --include="*.tsx" +``` + +The annotation `payload` specifies the target structure (e.g. `TextField.label → `). Apply the transformation mechanically using the payload as the template. Add `// TODO:` placeholders only for values you cannot infer from surrounding context (e.g. specific style token values). Remove the annotation on completion. + +#### 3. `manual` annotations — read context, apply or ask + +```sh +grep -rn "@fluent-migrate:manual" --include="*.ts" --include="*.tsx" +``` + +Read the surrounding code context to determine intent. The annotation's `note` field lists choices. Apply if confident (>80%), using the component mapping table and per-component references below as reference. If ambiguous, log an assumption comment or ask the user before applying. Remove the annotation on completion. + +#### 4. `no-equivalent` annotations — surface to user + +```sh +grep -rn "@fluent-migrate:no-equivalent" --include="*.ts" --include="*.tsx" +``` + +Do not attempt migration. Report each one to the user with the recommended alternative. Wait for user direction before proceeding. Remove the annotation only after the user provides a resolution. + +All `no-equivalent` annotations use `codemod: no-equivalent`. The `note` field always contains the specific guidance. Two common sub-cases: + +- **Component-level** (`payload` is a component name, e.g. `ActivityItem`, `Stack` in JSX) → the `note` is the deprecation table entry; the deprecation table below lists recommended alternatives. +- **Prop- or specifier-level** (`payload` is a prop name or import specifier name, e.g. `onText`, `offText`) → follow the `note` directly (e.g. "remove the prop or build a custom wrapper", "remove this import specifier"). + +#### Verify zero annotations remain + +```sh +grep -r "@fluent-migrate:" --include="*.ts" --include="*.tsx" +``` + +Should return nothing. Any remaining annotations are blockers — include them in the Output Template as unresolved deltas, then proceed to Step 4. + +#### Cross-check remaining v8 imports against the component mapping table + +The CLI annotates JSX usages it knows about, but does not annotate every component. After clearing all annotations, scan for any `@fluentui/react` imports that survived the import-path change: + +```sh +grep -rn "from '@fluentui/react'" --include="*.ts" --include="*.tsx" +``` + +For each surviving named import, look it up in the **Component Mapping** table below. If the v9 equivalent has a different name or a structural API change, apply that migration now before moving to Step 4. + +### Codemod → Reference Index + +When processing an annotation, use the `codemod` field to load the right reference before applying the change. For codemods marked "— see inline" below, follow the inline instructions in this section. + +| `codemod` | Reference | +| -------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `button-variants` | [references/button.md](references/button.md) | +| `component-rename` | Component mapping table (this file); for `manual` annotations also load the ref named in the annotation's note field | +| `dialog-props` | [references/dialog.md](references/dialog.md) | +| `enum-to-string` | — see inline | +| `get-id` | — see inline | +| `icon-props` | [references/icons.md](references/icons.md) | +| `import-paths` | — see inline | +| `keycodes` | — see inline | +| `label-extraction` | [references/input.md](references/input.md), [references/label.md](references/label.md) | +| `no-equivalent` | Deprecation table (this file) — surface to user, do not migrate | +| `progress-bar-props` | [references/progressbar.md](references/progressbar.md) | +| `prop-rename` | — see inline | +| `remove-theme-prop` | [references/theme.md](references/theme.md) | +| `styles-prop` | [references/theme.md](references/theme.md) | +| `toggle-props` | [references/switch.md](references/switch.md) | +| `use-boolean` | — see inline | + +#### `enum-to-string` + +Replace the enum member access with the string literal from the payload. Remove the migrated enum from the `@fluentui/react` import. + +```tsx +// annotation payload: MessageBarType.error → "error" +intent={MessageBarType.error} → intent="error" +``` + +#### `get-id` + +Replace `getId(prefix?)` with the `useId(prefix?)` hook from `@fluentui/react-components`, called at the top level of the function component. + +```tsx +// before +const id = getId('field'); + +// after +import { useId } from '@fluentui/react-components'; +const id = useId('field'); +``` + +For `manual` annotations (outside a function component), restructure the class before converting. + +#### `import-paths` + +- `payload: @fluentui/react → @fluentui/react-components` (action: `auto`) — change the module specifier. Keep named imports that have v9 equivalents; specifiers with a `no-equivalent` annotation are handled separately. +- `payload: @fluentui/react → remove (side-effect import)` (action: `auto`) — delete the entire import statement. +- `payload: ` (action: `manual`) — the named import must move to a separate compat package. Remove the specifier from the `@fluentui/react` import and add a new import using the exact compat export name: `CalendarCompat` from `@fluentui/react-calendar-compat`, `DatePickerCompat` from `@fluentui/react-datepicker-compat`, `TimePickerCompat` from `@fluentui/react-timepicker-compat`. If `@fluentui/react-components` is also needed, keep both import lines. + +#### `keycodes` + +Replace `KeyCodes.X` with the string literal from the payload. Rewrite surrounding key-event checks from `event.which`/`event.keyCode` to `event.key`. Remove `KeyCodes` from the `@fluentui/react` import. + +```tsx +// before +if (event.which === KeyCodes.enter) +// after +if (event.key === 'Enter') +``` + +#### `prop-rename` + +Rename the JSX prop as specified in the payload (`oldProp → newProp`). Common cases: `ariaLabel → aria-label`, `componentRef → ref`. + +```tsx +// annotation payload: componentRef → ref + setIsOpen(true); +const closeDialog = () => setIsOpen(false); +const toggleDialog = () => setIsOpen(prev => !prev); +``` + +Remove `useBoolean` from the `@fluentui/react` import; add `useState` from `react`. For `manual` annotations (outside a function component), restructure before converting. + +--- + +### Step 4 — Validate + +Run the host repo's own commands. Do not assume `npx tsc` or `npm test` — check `package.json` scripts first. + +**Checklist (all must pass before reporting ✅ Complete):** + +- [ ] **TypeScript** — TypeScript check command from `package.json` scripts (or `tsc --noEmit` if none) exits with error count ≤ baseline +- [ ] **Lint** — repo linter (eslint/biome/etc.) exits 0; if no linter found, mark ⏭️ skipped +- [ ] **Tests** — repo test command passes; if no test command found, mark ⏭️ skipped +- [ ] **No remaining v8 imports** — `grep -r "from '@fluentui/react'" --include="*.tsx" --include="*.ts"` returns nothing (only `@fluentui/react-components` imports remain) +- [ ] **Shims tracked** — every `*Shim` import still in the codebase is listed in the Output Template +- [ ] **Accessibility parity** — icon-only buttons have `aria-label`; all form controls have labels via `` or `