Skip to content

Latest commit

 

History

History
308 lines (228 loc) · 19.2 KB

File metadata and controls

308 lines (228 loc) · 19.2 KB

AGENTS.md

This file provides guidance to coding agents (Claude Code and others) when working with code in this repository.

Project Overview

This is the FluentUI React Native repository, a monorepo containing React Native components that implement Microsoft's Fluent Design System. The repository supports multiple platforms including iOS, Android, macOS, Windows, and Win32.

Repository Architecture

High-Level Structure

/apps/           - Demo and test applications
  /fluent-tester/ - Main test app for component development
  /E2E/          - End-to-end testing setup using Appium/WebDriverIO
  /win32/        - Win32-specific test app
  /component-generator/ - Tool to generate new components
/packages/       - Core library packages
  /components/   - UI component implementations (Button, Checkbox, Avatar, etc.)
  /native/       - Standalone native-backed component packages (Callout, etc.)
  /framework/    - Core theming and composition framework
    /composition/ - Component composition factory (current approach)
    /theme/      - Theme system
    /use-tokens/ - Token-based styling hooks
    /use-slots/  - Slot-based component composition
  /theming/      - Theme definitions for different platforms
    /android-theme/
    /apple-theme/
    /default-theme/
    /win32-theme/
  /experimental/ - Components under active development
  /deprecated/   - Old framework code (foundation-compose, foundation-composable)
  /utils/        - Shared utilities and tools
/scripts/        - Build and development scripts (fluentui-scripts CLI)
/docs/           - Component and theming documentation

Key Framework Concepts

Composition Framework: The repository uses @fluentui-react-native/composition (located at packages/framework/composition/) for building components. This is the current approach and is simpler than the older foundation-compose/foundation-composable frameworks in /deprecated/.

Slots: The slot pattern is used to compose higher-order components. A slot represents an inner component (actual entry in the render tree). For example, a Button might have slots for root, icon, and content. This allows advanced customization scenarios. Components wrapping a single native component typically have one slot.

Tokens: Design tokens handle styling and customization. Tokens are design-time values set via theme or component customization (e.g., "brandColor"). Tokens can also be props (specified via "TokensThatAreAlsoProps"). This system enables simpler customization and better memoization. For Flex token authoring, use packages/agentic/design/src/tokens/mappings/flex-token-map.yaml as the canonical mapping from generic CSS and Fluent token sources to grouped React Native Flex token paths. When mapping an agentic component to an existing V1 component, use the Win32, macOS, or Windows implementation as the canonical compatibility reference; do not use iOS as the cross-platform canonical value unless the work explicitly targets iOS.

Color utilities: Runtime-safe parsing, conversion, compositing, contrast, and interaction generation live behind the opt-in @fluentui-react-native/design/color-lib subpath. Keep its public exports intentionally narrow and add APIs only for demonstrated production needs. Color validation policy, semantic pairings, audit helpers, and conformance fixtures belong under packages/agentic/design/src/testing/ and are exported, when generally useful to tests, from @fluentui-react-native/design/testing. Production code must not depend on the testing subpath, and testing-only validation does not need bundle-size scenarios.

Theme-specific styles: Cache StyleSheet.create results that depend only on ThemeState with a module-scoped getter created by themedStyleSheetFactory. Treat them as immutable, and apply props, interaction state, and user styles separately so the cached sheet is safe to share between component instances.

Platform-Specific Files: Components use platform-specific files with extensions like .ios.ts, .android.ts, .win32.ts, .macos.ts for platform-specific implementations.

Legacy vs V1: Many components have both legacy and V1 implementations (e.g., Button and ButtonV1). The V1 versions use the newer composition framework and are preferred.

Build System & Commands

The repository builds as a single unified TypeScript project-references graph compiled by tsc (typescript). The root tsconfig.json lists every package's tsconfig.json under references, and yarn build runs tsc -b over that graph, building each package in dependency order. Lage orchestrates the non-build tasks (bundle, test, lint, clean), and Yarn 4 (Berry) in pnpm mode manages dependencies (configured in .yarnrc.yml).

Primary Commands (from the repo root)

yarn build         # Unified TypeScript build of all packages (tsc -b); emits each package's lib/
yarn build --clean # Remove build outputs (tsc -b --clean); follow with `yarn build` for a full rebuild
yarn lage test     # Build + run tests across packages
yarn lage lint     # Lint across packages
yarn lint-fix      # Lint with autofix (cross-env FURN_FIX_MODE=true lage lint)
yarn bundle:repo   # Bundle all packages (lage bundle)
yarn lage buildci  # CI aggregate graph: lint-repo, check-publishing, build, test, lint
yarn clean         # Clean build artifacts (lage clean)
yarn format        # Format code with oxfmt
yarn format:check  # Check formatting without writing

Repo-Level Checks & Housekeeping

yarn lint-repo        # Repo-wide structural lint (scripts/src/tasks/lintRepo.ts)
yarn lint-lockfile    # Validate the Yarn lockfile
yarn lage test-links # Validate links in repository Markdown files
yarn check-publishing # Validate package publishing configuration
yarn change           # Create a change file for the current branch
yarn change:check     # Verify required change files exist
yarn docs             # Start the documentation site

Lage Configuration

The task pipeline is defined in lage.config.mjs:

  • Tasks declare dependency ordering (e.g. test dependsOn build)
  • buildci is the aggregate CI alias (lint-repo, check-publishing, build, test, lint)
  • Lage caches task outputs; add --no-cache to bypass caching and --verbose for detailed output
  • After a major rework (e.g. moving packages, large refactors, or renaming exports), run yarn lage test --no-cache from the root once to force every test to re-run without relying on stale cached results. This also resets the Lage cache, so subsequent plain yarn lage test runs will work incrementally again.
  • Before checking in moved or renamed files, run yarn lage test-links and update any repository links that still reference the old paths.

Package-Level Commands

Each package's own build script is tsc -b (it builds that package together with its referenced dependencies). Other per-package tasks run through the fluentui-scripts CLI (in /scripts/):

  • yarn build - tsc project-references build (emits to lib/)
  • yarn lint - ESLint (fluentui-scripts lint)
  • yarn test - Jest tests where present (fluentui-scripts jest)
  • yarn depcheck - Unused-dependency check (fluentui-scripts depcheck)
  • yarn format / yarn format:fix - Check / fix formatting

fluentui-scripts also retains a build command that can emit dual ESM (lib/) and CommonJS (lib-commonjs/) output driven by per-package build config, but packages on this branch build with tsc -b directly.

Agent validation and investigation discipline

  • Run commands through the owning workspace's declared scripts. Inspect its package.json before invoking an underlying runner directly; do not assume scripts such as jest or check exist, and do not bypass project references with ad-hoc tsc file arguments.
  • Run working-directory-sensitive native tools from the owning app workspace. Flags such as CocoaPods' --project-directory select project files but do not necessarily change subprocess resolution roots.
  • During iteration, use the smallest relevant test and run package lint/build early after type-heavy edits. For final validation, run format, lint, package build, package tests, then the root build when public types, manifests, or project references changed.
  • After fixing a failed validation step, rerun that step and its downstream checks. Avoid repeatedly running the entire validation chain before the immediate failure is resolved.
  • Prefer committed *.types.test.ts coverage for compile-time contracts. Keep exploratory probes outside package source, or remove them before running package validation.
  • Review small, localized diffs directly. Use an independent review agent only after focused validation passes and only when the change still has a genuinely ambiguous or cross-cutting contract.
  • Keep sessions scoped to one reviewable outcome. After capturing a durable result, start unrelated work in a fresh session so stale context and generated output do not dominate later investigation.

TypeScript Configuration

The repo uses TypeScript 7.0.2 via tsc, which is automatically added as a dev dependency to every package that has a tsconfig.json through dynamic package extensions (scripts/dynamic.extensions.mts).

The whole repo compiles as one composite project-references graph: the root tsconfig.json lists every package under references, tsc -b builds them in dependency order, and each package caches incremental state in .cache/tsconfig.tsbuildinfo.

Key TypeScript Settings

  • Base config: scripts/configs/tsconfig/tsconfig.json → extends tsconfig.strict.json@rnx-kit/tsconfig/tsconfig.nodenext.json
  • target / lib: esnext
  • module: esnext, moduleResolution: bundler (ESM)
  • jsx: react-jsx
  • rewriteRelativeImportExtensions: true (ESM relative-import extension rewriting)
  • composite: true (required for project references), outDir: lib
  • Strict mode, relaxed for legacy compatibility (strictNullChecks, noImplicitAny, and strictBindCallApply are disabled in the base config)

Compatibility Notes

  • suppressImplicitAnyIndexErrors was removed in TS 5.8+
  • Platform.OS doesn't include 'win32' in the React Native types even though react-native-windows supports it at runtime — use Platform.OS === ('win32' as any)
  • The platform React Native forks (react-native, react-native-windows, react-native-macos, @office-iss/react-native-win32) have overlapping but divergent type definitions. Do not import more than one fork into a single program's type graph (it produces order-dependent type confusion under the unified build). Keep fork imports in .win32.ts / .windows.ts / .macos.ts files, or redeclare the needed shapes platform-neutrally.

Framework Type System

The composition framework uses precise types for better type safety:

  • SlotFn<TProps>: Slot functions return React.ReactElement | null (not ReactNode)
    • This reflects the actual behavior: slots always return elements via staged render or React.createElement
    • Provides better type inference when accessing slot props (e.g., Slots.root({}).props)
  • FinalRender<TProps>: Final render functions in staged components return JSX.Element | null
    • Used in composition framework's useRender functions
    • Ensures type compatibility between staged components and the composition system

Development Workflow

Setting Up Development Environment

  1. Clone repository
  2. Run yarn to install dependencies
  3. Run yarn build to build all packages
  4. Launch FluentUI Tester app for component testing (see /apps/fluent-tester/README.md)

Component Development

Component Location: Stable JavaScript components are in /packages/components/, standalone native-backed components are in /packages/native/, and components under development are in /packages/experimental/.

Component Structure: Each component typically has:

  • package.json - Package definition with workspace dependencies
  • src/index.ts - Main export file
  • src/<Component>.tsx - Component implementation (requires /** @jsxImportSource @fluentui-react-native/framework-base */ pragma)
  • src/<Component>.types.ts - TypeScript type definitions
  • src/<Component>.styling.ts - Styling and token definitions
  • src/<Component>.<platform>.ts - Platform-specific implementations
  • SPEC.md - Component specification and usage documentation
  • MIGRATION.md - Migration guide (for V1 components)
  • tsconfig.json, babel.config.js, jest.config.js, eslint.config.js

Using Composition Framework: Use @fluentui-react-native/composition for new components. For simpler components without slots/tokens, use the stagedComponent pattern from @fluentui-react-native/framework-base.

JSX Runtime: All components use the modern automatic JSX runtime:

  • Add /** @jsxImportSource @fluentui-react-native/framework-base */ at the top of .tsx files
  • The custom jsx-runtime intercepts JSX calls to optimize slot rendering
  • No need to import withSlots - it's handled automatically by the runtime
  • Components using React Fragments (<>...</>) work automatically (Fragment is re-exported from the jsx-runtime)
  • Packages using the jsx-runtime need @fluentui-react-native/framework-base in devDependencies

TypeScript Patterns:

  • Slot functions automatically return React.ReactElement, so you can access .props directly without type assertions
  • When checking for win32 platform: Platform.OS === ('win32' as any) - TypeScript doesn't recognize 'win32' but react-native-windows supports it
  • Final render functions should return FinalRender<TProps> with children as rest parameters: (props: TProps, ...children: React.ReactNode[])

Module Exports:

  • Do not use barrel exports (export * from '...') - wildcard re-exports break tree-shaking because bundlers cannot statically determine which symbols are used, so unused code is retained in consumers' bundles. Always use explicit named re-exports instead, e.g. export { Foo, Bar } from './module' and export type { Baz } from './module'.

Native Modules: Standalone native-backed component packages, such as Callout, live in /packages/native/. Components with native code (iOS/Android/Windows):

  • Typically have one root slot wrapping the native component
  • Use codegenNativeComponent for new architecture compatibility
  • May use constantsToExport for default values from native side
  • iOS/macOS: Include .podspec files
  • Must be added to FluentTester's Podfile (transitive dependencies aren't autolinked)

Creating a New Component

  1. Create the package under /packages/components/<ComponentName>, /packages/native/<ComponentName> for a standalone native-backed component, or /packages/experimental/<ComponentName> while it is under development
  2. Copy structure from existing component (e.g., Shimmer, Button)
  3. Update package.json with correct name and dependencies (use workspace:* for internal packages)
  4. Add the new package's tsconfig.json to the root tsconfig.json references so it joins the unified build
  5. Create source files in src/
  6. Add test page to FluentTester at /apps/fluent-tester/src/TestComponents/<ComponentName>/
  7. Register test page in testPages.tsx and platform-specific testPages.<platform>.tsx
  8. Add E2E tests (see E2E Testing section)
  9. Run yarn and yarn build from root
  10. For Apple platforms: run pod install in test app directories

Theming

Platform-specific themes are in /packages/theming/:

  • android-theme/ - Android theming
  • apple-theme/ - iOS and macOS theming
  • win32-theme/ - Win32 theming
  • default-theme/ - Cross-platform defaults
  • theme-tokens/ - Token definitions
  • theme-types/ - TypeScript types for themes

Components require ThemeProvider from @fluentui-react-native/design/theming to work properly.

Testing

Manual Testing: Use FluentUI Tester app (/apps/fluent-tester/) for interactive component testing. Test pages are in /apps/fluent-tester/src/TestComponents/.

E2E Testing: Required for all new components. Uses Appium + WebDriverIO.

  • E2E tests live in /apps/E2E/src/<ComponentName>/
  • Each component needs:
    • Page Object (<Component>PageObject.<platform>.ts) - Interface to interact with test page
    • Spec Document (<Component>Spec.<platform>.ts) - Jasmine test cases
    • Constants file in test component (/apps/fluent-tester/src/TestComponents/<Component>/consts.ts)
  • Test pages must include:
    • testID on first section matching page object's _pageName
    • Optional e2eSections prop for dedicated E2E test elements
  • Run E2E tests: yarn e2etest:<platform> from /apps/E2E/

Unit Tests: Component-specific Jest tests where present, typically in src/ directories. Run them with yarn test in a package or yarn lage test from the root. After a major rework, run yarn lage test --no-cache once at the root to ensure all tests re-run and pass without stale cache hits; this resets the Lage cache so a normal incremental yarn lage test works afterward.

Platform-Specific Development

iOS/macOS:

  • May wrap native controls from FluentUI Apple
  • Requires .podspec files for native modules
  • Run pod install after adding dependencies

Android:

  • Platform-specific styling and tokens
  • Uses accessibilityLabel for E2E selectors (other platforms use testID)

Win32:

  • Separate test app at /apps/win32/
  • Uses WinAppDriver for E2E testing

Windows (UWP):

  • Separate test app configuration
  • Legacy support

Version Management

Changesets: Used for change logs and versioning.

  • Run yarn change to create a change file when modifying packages
  • Change files are required before merging PRs (validated in CI via yarn change:check)
  • Changesets config in .changeset/config.json
  • Major versions are disallowed (validated in CI via .github/scripts/validate-changesets.mts)
  • Version bump PRs are created automatically by GitHub Actions; yarn changeset:version applies version bumps
  • Publishing happens in Azure Pipelines using .ado/azure-pipelines.publish.yml

Important Notes

  • This is an alpha-stage library under active development
  • Requires TypeScript 7.0.2 (typescript, compiled via tsc); the unified build runs tsc -b over the root project-references graph
  • Uses Yarn 4 in pnpm mode for dependency management (configured in .yarnrc.yml)
  • Uses modern automatic JSX runtime - all components should use @jsxImportSource @fluentui-react-native/framework-base
  • Dynamic package extensions: Common dev dependencies (TypeScript, Jest, ESLint, Prettier) are automatically added via scripts/dynamic.extensions.mts
  • New packages must be added to the root tsconfig.json references to join the unified build
  • Follow existing component patterns for consistency
  • Test components using FluentUI Tester app before submitting PRs
  • Platform differences should be documented in component SPEC.md files
  • Use the newer composition framework (@fluentui-react-native/composition) for new components, not the deprecated foundation frameworks
  • When importing V1 components, consider aliasing: import { ButtonV1 as Button }
  • Slot functions return React.ReactElement - you can safely access .props without type assertions
  • Avoid barrel exports (export * from '...'); use explicit named re-exports to preserve tree-shaking